summaryrefslogtreecommitdiff
path: root/tools/common
diff options
context:
space:
mode:
authorJohn Hodge <tpg@mutabah.net>2018-03-18 10:48:26 +0800
committerJohn Hodge <tpg@mutabah.net>2018-03-18 10:48:26 +0800
commit5b0450395af81ceba0d0ac27fc73b16f966bd7d3 (patch)
treee3c753b83a562be78cdbd74b8164dab785bf07a6 /tools/common
parent363e6fa172f787e970c8abc8f631b6d60d571248 (diff)
downloadmrust-5b0450395af81ceba0d0ac27fc73b16f966bd7d3.tar.gz
All - Move toml parser and path header to a common library, start on custom target specs.
Diffstat (limited to 'tools/common')
-rw-r--r--tools/common/Makefile36
-rw-r--r--tools/common/debug.cpp80
-rw-r--r--tools/common/debug.h68
-rw-r--r--tools/common/helpers.h53
-rw-r--r--tools/common/path.cpp194
-rw-r--r--tools/common/path.h181
-rw-r--r--tools/common/toml.cpp414
-rw-r--r--tools/common/toml.h163
8 files changed, 1189 insertions, 0 deletions
diff --git a/tools/common/Makefile b/tools/common/Makefile
new file mode 100644
index 00000000..36251680
--- /dev/null
+++ b/tools/common/Makefile
@@ -0,0 +1,36 @@
+#
+# Mini version of cargo
+# - Interprets Cargo.toml files and emits makefiles
+# - Supports overriding build script output
+#
+
+V ?= @
+
+OBJDIR := .obj/
+
+BIN := ../bin/common_lib.a
+OBJS = toml.o path.o debug.o
+
+CXXFLAGS := -Wall -std=c++14 -g -O2
+
+OBJS := $(OBJS:%=$(OBJDIR)%)
+
+.PHONY: all clean
+
+all: $(BIN)
+
+clean:
+ rm $(BIN) $(OBJS)
+
+$(BIN): $(OBJS)
+ @mkdir -p $(dir $@)
+ @echo [AR] rcu $@
+ $V$(AR) rcu $@ $(OBJS)
+
+$(OBJDIR)%.o: %.cpp
+ @mkdir -p $(dir $@)
+ @echo [CXX] $<
+ $V$(CXX) -o $@ -c $< $(CXXFLAGS) -MMD -MP -MF $@.dep
+
+-include $(OBJS:%.o=%.o.dep)
+
diff --git a/tools/common/debug.cpp b/tools/common/debug.cpp
new file mode 100644
index 00000000..a3fb9956
--- /dev/null
+++ b/tools/common/debug.cpp
@@ -0,0 +1,80 @@
+/*
+ * MiniCargo - mrustc's minimal clone of cargo
+ * - By John Hodge (Mutabah/thePowersGang)
+ *
+ * debug.cpp
+ * - Debugging helpers
+ */
+#if defined(__MINGW32__)
+# define DISABLE_MULTITHREAD // Mingw32 doesn't have c++11 threads
+#endif
+#include <set>
+#include <iostream>
+#include "debug.h"
+#include <mutex>
+
+static int giIndentLevel = 0;
+static const char* gsDebugPhase = "";
+static ::std::set<::std::string> gmDisabledDebug;
+#ifndef DISABLE_MULTITHREAD
+static ::std::mutex gDebugLock;
+#endif
+
+void Debug_SetPhase(const char* phase_name)
+{
+ gsDebugPhase = phase_name;
+}
+bool Debug_IsEnabled()
+{
+ if( gmDisabledDebug.find(gsDebugPhase) != gmDisabledDebug.end() )
+ return false;
+ return true;
+}
+void Debug_DisablePhase(const char* phase_name)
+{
+ gmDisabledDebug.insert( ::std::string(phase_name) );
+}
+void Debug_Print(::std::function<void(::std::ostream& os)> cb)
+{
+ if( !Debug_IsEnabled() )
+ return ;
+#ifndef DISABLE_MULTITHREAD
+ ::std::unique_lock<::std::mutex> _lh { gDebugLock };
+#endif
+
+ ::std::cout << gsDebugPhase << "- ";
+ for(auto i = giIndentLevel; i --; )
+ ::std::cout << " ";
+ cb(::std::cout);
+ ::std::cout << ::std::endl;
+}
+void Debug_EnterScope(const char* name, dbg_cb_t cb)
+{
+ if( !Debug_IsEnabled() )
+ return ;
+#ifndef DISABLE_MULTITHREAD
+ ::std::unique_lock<::std::mutex> _lh { gDebugLock };
+#endif
+
+ ::std::cout << gsDebugPhase << "- ";
+ for(auto i = giIndentLevel; i --; )
+ ::std::cout << " ";
+ ::std::cout << ">>> " << name << "(";
+ cb(::std::cout);
+ ::std::cout << ")" << ::std::endl;
+ giIndentLevel ++;
+}
+void Debug_LeaveScope(const char* name, dbg_cb_t cb)
+{
+ if( !Debug_IsEnabled() )
+ return ;
+#ifndef DISABLE_MULTITHREAD
+ ::std::unique_lock<::std::mutex> _lh { gDebugLock };
+#endif
+
+ ::std::cout << gsDebugPhase << "- ";
+ giIndentLevel --;
+ for(auto i = giIndentLevel; i --; )
+ ::std::cout << " ";
+ ::std::cout << "<<< " << name << ::std::endl;
+}
diff --git a/tools/common/debug.h b/tools/common/debug.h
new file mode 100644
index 00000000..ace00876
--- /dev/null
+++ b/tools/common/debug.h
@@ -0,0 +1,68 @@
+#pragma once
+
+#include <functional>
+#include <vector>
+#include <sstream>
+
+typedef ::std::function<void(::std::ostream& os)> dbg_cb_t;
+extern void Debug_SetPhase(const char* phase_name);
+extern void Debug_DisablePhase(const char* phase_name);
+extern bool Debug_IsEnabled();
+extern void Debug_EnterScope(const char* name, dbg_cb_t );
+extern void Debug_LeaveScope(const char* name, dbg_cb_t );
+extern void Debug_Print(dbg_cb_t cb);
+
+#if defined(NOLOG)
+# define DEBUG(fmt) do { } while(0)
+# define TRACE_FUNCTION_F(fmt) do{}while(0)
+#else
+# define DEBUG(fmt) do { Debug_Print([&](auto& os){ os << "DEBUG: " << fmt; }); } while(0)
+# define TRACE_FUNCTION_F(fmt) DebugFunctionScope trace_function_hdr { __FUNCTION__, [&](auto& os){ os << fmt; } }
+#endif
+#define TODO(fmt) do { ::std::cerr << "TODO: " << fmt << ::std::endl; abort(); } while(0)
+
+namespace {
+ static inline void format_to_stream(::std::ostream& os) {
+ }
+ template<typename T, typename... A>
+ static inline void format_to_stream(::std::ostream& os, const T& v, const A&... a) {
+ os << v;
+ format_to_stream(os, a...);
+ }
+}
+
+struct DebugFunctionScope {
+ const char* m_name;
+ DebugFunctionScope(const char* name, dbg_cb_t cb):
+ m_name(name)
+ {
+ Debug_EnterScope(m_name, cb);
+ }
+ ~DebugFunctionScope()
+ {
+ Debug_LeaveScope(m_name, [](auto& ){});
+ }
+};
+
+template<typename ...T>
+::std::string format(const T&... v)
+{
+ ::std::stringstream ss;
+ format_to_stream(ss, v...);
+ return ss.str();
+}
+
+template<typename T>
+::std::ostream& operator<<(::std::ostream& os, const ::std::vector<T>& v)
+{
+ bool first = true;
+ for(const auto& e : v)
+ {
+ if(!first)
+ os << ",";
+ os << e;
+ first = false;
+ }
+ return os;
+}
+
diff --git a/tools/common/helpers.h b/tools/common/helpers.h
new file mode 100644
index 00000000..8111483a
--- /dev/null
+++ b/tools/common/helpers.h
@@ -0,0 +1,53 @@
+#pragma once
+
+#include <string>
+#include <cstring>
+#include <iostream>
+
+namespace helpers {
+
+class string_view
+{
+ const char* m_start;
+ const size_t m_len;
+public:
+ string_view(const char* s, size_t n):
+ m_start(s), m_len(n)
+ {
+ }
+
+ bool operator==(const ::std::string& s) const {
+ return *this == s.c_str();
+ }
+ bool operator==(const char* s) const {
+ if(::std::strncmp(m_start, s, m_len) != 0)
+ return false;
+ return s[m_len] == '\0';
+ }
+
+ char operator[](size_t n) const {
+ return m_start[n];
+ }
+
+ operator ::std::string() const {
+ return ::std::string { m_start, m_start + m_len };
+ }
+ friend ::std::string& operator+=(::std::string& x, const string_view& sv) {
+ x.append(sv.m_start, sv.m_start+sv.m_len);
+ return x;
+ }
+ friend ::std::ostream& operator<<(::std::ostream& os, const string_view& sv) {
+ os.write(sv.m_start, sv.m_len);
+ return os;
+ }
+
+ const char* begin() const {
+ return m_start;
+ }
+ const char* end() const {
+ return m_start+m_len;
+ }
+};
+
+
+} // namespace helpers
diff --git a/tools/common/path.cpp b/tools/common/path.cpp
new file mode 100644
index 00000000..12e505bb
--- /dev/null
+++ b/tools/common/path.cpp
@@ -0,0 +1,194 @@
+/*
+ */
+#include "path.h"
+#if _WIN32
+# include <Windows.h>
+#else
+# include <unistd.h> // getcwd/chdir
+#endif
+
+helpers::path::path(const char* s):
+ m_str(s)
+{
+ // 1. Normalise path separators to the system specified separator
+ for(size_t i = 0; i < m_str.size(); i ++)
+ {
+ if( m_str[i] == '/' || m_str[i] == '\\' )
+ m_str[i] = SEP;
+ }
+
+ // 2. Remove any trailing separators
+ if( !m_str.empty() )
+ {
+ while(!m_str.empty() && m_str.back() == SEP )
+ m_str.pop_back();
+ if(m_str.empty())
+ {
+ m_str.push_back(SEP);
+ }
+ }
+ else
+ {
+ throw ::std::runtime_error("Empty path being constructed");
+ }
+}
+
+helpers::path helpers::path::to_absolute() const
+{
+ if(!this->is_valid())
+ throw ::std::runtime_error("Calling to_absolute() on an invalid path");
+
+ if(this->m_str[0] == SEP)
+ return *this;
+
+ #if _WIN32
+ char cwd[1024];
+ GetCurrentDirectoryA(sizeof(cwd), cwd);
+ #else
+ char cwd[1024];
+ if( !getcwd(cwd, sizeof(cwd)) )
+ throw ::std::runtime_error("Calling getcwd() failed in path::to_absolute()");
+ #endif
+ auto rv = path(cwd);
+ for(auto comp : *this)
+ {
+ if(comp == ".")
+ ;
+ else if( comp == ".." )
+ rv.pop_component();
+ else
+ rv /= comp;
+ }
+ #if _WIN32
+ #else
+ #endif
+ return rv;
+}
+
+helpers::path helpers::path::normalise() const
+{
+ path rv;
+ rv.m_str.reserve( m_str.size()+1 );
+
+ for(auto comp : *this)
+ {
+ if( comp == "." ) {
+ // Ignore.
+ }
+ else if( comp == ".." )
+ {
+ // If the path is empty, OR the last element is a "..", push the element
+ if( rv.m_str.empty()
+ || (rv.m_str.size() == 3 && rv.m_str[0] == '.' && rv.m_str[1] == '.' && rv.m_str[2] == SEP)
+ || (rv.m_str.size() > 4 && *(rv.m_str.end()-4) == SEP && *(rv.m_str.end()-3) == '.' && *(rv.m_str.end()-2) == '.' && *(rv.m_str.end()-1) == SEP )
+ )
+ {
+ // Push
+ rv.m_str += comp;
+ rv.m_str += SEP;
+ }
+ else
+ {
+ rv.m_str.pop_back();
+ auto pos = rv.m_str.find_last_of(SEP);
+ if(pos == ::std::string::npos)
+ {
+ rv.m_str.resize(0);
+ }
+ else if( pos == 0 )
+ {
+ // Keep.
+ }
+ else
+ {
+ rv.m_str.resize(pos+1);
+ }
+ }
+ }
+ else
+ {
+ rv.m_str += comp;
+ rv.m_str += SEP;
+ }
+ }
+ rv.m_str.pop_back();
+ return rv;
+}
+
+#if 0
+void helpers::path::normalise_in_place()
+{
+ size_t insert_point = 0;
+
+ for(size_t read_pos = 0; read_pos < m_str.size(); read_pos ++)
+ {
+ auto pos = m_str.find_first_of(SEP, read_pos);
+ if(pos == ::std::string::npos)
+ pos = m_str.size();
+ auto comp = string_view(m_str.c_str() + read_pos, pos - read_pos);
+
+ bool append;
+ if(comp == ".")
+ {
+ // Advance read without touching insert
+ append = false;
+ }
+ else if( comp == ".." )
+ {
+ // Consume parent (if not a relative component already)
+ // Move insertion point back to the previous separator
+ auto pos = m_str.find_last_of(SEP, insert_point);
+ if(pos == ::std::string::npos)
+ {
+ // Only one component currently (or empty)
+ append = true;
+ }
+ else if(string_view(m_str.c_str() + pos+1, insert_point - pos-1) == "..")
+ {
+ // Last component is ".." - keep adding
+ append = true;
+ }
+ else
+ {
+ insert_point = pos;
+ append = false;
+ }
+ }
+ else
+ {
+ append = true;
+ }
+
+ if(append)
+ {
+ if( read_pos != insert_point )
+ {
+ //assert(read_pos > insert_point);
+ while(read_pos < pos)
+ {
+ m_str[insert_point++] = m_str[read_pos++];
+ }
+ }
+ }
+ else
+ {
+ read_pos = pos;
+ }
+ }
+}
+#endif
+
+void helpers::path::ComponentsIter::operator++()
+{
+ if(end == p.m_str.size())
+ {
+ pos = end;
+ }
+ else
+ {
+ pos = end+1;
+ end = p.m_str.find(SEP, pos);
+ if(end == ::std::string::npos)
+ end = p.m_str.size();
+ }
+}
diff --git a/tools/common/path.h b/tools/common/path.h
new file mode 100644
index 00000000..dd97f9be
--- /dev/null
+++ b/tools/common/path.h
@@ -0,0 +1,181 @@
+#pragma once
+
+#include <string>
+#include <stdexcept>
+#include "helpers.h"
+
+namespace helpers {
+
+/// Path helper class (because I don't want to include boost)
+class path
+{
+#ifdef _WIN32
+ static const char SEP = '\\';
+#else
+ static const char SEP = '/';
+#endif
+
+ ::std::string m_str;
+
+public:
+ path()
+ {
+ }
+ path(const ::std::string& s):
+ path(s.c_str())
+ {
+ }
+ path(const char* s);
+
+ bool is_valid() const {
+ return m_str != "";
+ }
+
+ path& operator/=(const path& p)
+ {
+ if(!p.is_valid())
+ throw ::std::runtime_error("Appending from an invalid path");
+
+ return *this /= p.m_str.c_str();
+ }
+ path& operator/=(const char* o)
+ {
+ if(!this->is_valid())
+ throw ::std::runtime_error("Appending to an invalid path");
+ if(o[0] == '/')
+ throw ::std::runtime_error("Appending an absolute path to another path");
+ this->m_str.push_back(SEP);
+ this->m_str.append(o);
+ return *this;
+ }
+ path& operator/=(const string_view& o)
+ {
+ if(!this->is_valid())
+ throw ::std::runtime_error("Appending to an invalid path");
+ if(o[0] == '/')
+ throw ::std::runtime_error("Appending an absolute path to another path");
+ this->m_str.push_back(SEP);
+ this->m_str += o;
+ return *this;
+ }
+
+ path operator/(const path& p) const
+ {
+ auto rv = *this;
+ rv /= p;
+ return rv;
+ }
+ /// Append a relative path
+ path operator/(const char* o) const
+ {
+ auto rv = *this;
+ rv /= o;
+ return rv;
+ }
+ /// Add an arbitary string to the final component
+ path operator+(const char* o) const
+ {
+ if(!this->is_valid())
+ throw ::std::runtime_error("Appending a string to an invalid path");
+ if( ::std::strchr(o, SEP) != nullptr )
+ throw ::std::runtime_error("Appending a string containing the path separator (with operator+)");
+ auto rv = *this;
+ rv.m_str.append(o);
+ return rv;
+ }
+
+ bool pop_component()
+ {
+ if(!this->is_valid())
+ throw ::std::runtime_error("Calling pop_component() on an invalid path");
+ auto pos = m_str.find_last_of(SEP);
+ if(pos == ::std::string::npos || pos == 0)
+ {
+ return false;
+ }
+ else
+ {
+ this->m_str.resize(pos);
+ return true;
+ }
+ }
+ path parent() const
+ {
+ if(!this->is_valid())
+ throw ::std::runtime_error("Calling parent() on an invalid path");
+ auto pos = m_str.find_last_of(SEP);
+ if(pos == ::std::string::npos)
+ {
+ return *this;
+ }
+ else
+ {
+ path rv;
+ rv.m_str = m_str.substr(0, pos);
+ return rv;
+ }
+ }
+ path to_absolute() const;
+ ::std::string basename() const
+ {
+ if(!this->is_valid())
+ throw ::std::runtime_error("Calling basename() on an invalid path");
+
+ auto pos = m_str.find_last_of(SEP);
+ if(pos == ::std::string::npos)
+ {
+ return m_str;
+ }
+ else
+ {
+ return m_str.substr(pos+1);
+ }
+ }
+
+ const ::std::string& str() const
+ {
+ return m_str;
+ }
+ operator ::std::string() const
+ {
+ return m_str;
+ }
+
+ class ComponentsIter
+ {
+ const path& p;
+ size_t pos;
+ size_t end;
+
+ friend class path;
+ ComponentsIter(const path& p, size_t i): p(p), pos(i) {
+ end = p.m_str.find(SEP, pos);
+ if(end == ::std::string::npos)
+ end = p.m_str.size();
+ }
+ public:
+ string_view operator*() const {
+ return string_view(p.m_str.c_str() + pos, end - pos);
+ }
+ void operator++();
+ bool operator!=(const ComponentsIter& x) const {
+ return pos != x.pos;
+ }
+ };
+ ComponentsIter begin() const {
+ return ComponentsIter(*this, 0);
+ }
+ ComponentsIter end() const {
+ return ComponentsIter(*this, m_str.size());
+ }
+
+ path normalise() const;
+ //void normalise_in_place();
+
+ friend ::std::ostream& operator<<(::std::ostream& os, const path& p)
+ {
+ return os << p.m_str;
+ }
+};
+
+}
diff --git a/tools/common/toml.cpp b/tools/common/toml.cpp
new file mode 100644
index 00000000..9fad0ec4
--- /dev/null
+++ b/tools/common/toml.cpp
@@ -0,0 +1,414 @@
+/*
+ * A very bad streaming TOML parser
+ */
+#define NOLOG
+#include "toml.h"
+#include "debug.h"
+#include <cassert>
+#include <string>
+
+
+struct Token
+{
+ enum class Type
+ {
+ Eof,
+ SquareOpen,
+ SquareClose,
+ BraceOpen,
+ BraceClose,
+ Assign,
+ Newline,
+ Comma,
+ Dot,
+
+ Ident,
+ String,
+ Integer,
+ };
+
+ Type m_type;
+ ::std::string m_data;
+ int64_t m_intval = 0;
+
+ Token(Type ty):
+ m_type(ty)
+ {
+ }
+ Token(Type ty, ::std::string s):
+ m_type(ty),
+ m_data(s)
+ {
+ }
+ Token(Type ty, int64_t i):
+ m_type(ty),
+ m_intval(i)
+ {
+ }
+
+
+ static Token lex_from(::std::ifstream& is);
+ static Token lex_from_inner(::std::ifstream& is);
+
+ const ::std::string& as_string() const {
+ assert(m_type == Type::Ident || m_type == Type::String);
+ return m_data;
+ }
+
+ friend ::std::ostream& operator<<(::std::ostream& os, const Token& x) {
+ switch(x.m_type)
+ {
+ case Type::Eof: os << "Eof"; break;
+ case Type::SquareOpen: os << "SquareOpen"; break;
+ case Type::SquareClose: os << "SquareClose"; break;
+ case Type::BraceOpen: os << "BraceOpen"; break;
+ case Type::BraceClose: os << "BraceClose"; break;
+ case Type::Assign: os << "Assign"; break;
+ case Type::Newline: os << "Newline"; break;
+ case Type::Comma: os << "Comma"; break;
+ case Type::Dot: os << "Dot"; break;
+ case Type::Ident: os << "Ident(" << x.m_data << ")"; break;
+ case Type::String: os << "String(" << x.m_data << ")"; break;
+ case Type::Integer: os << "Integer(" << x.m_intval << ")"; break;
+ }
+ return os;
+ }
+};
+
+TomlFile::TomlFile(const ::std::string& filename):
+ m_if(filename)
+{
+ if( !m_if.is_open() ) {
+ throw ::std::runtime_error("Unable to open file '" + filename + "'");
+ }
+}
+TomlFileIter TomlFile::begin()
+{
+ TomlFileIter rv { *this };
+ ++rv;
+ return rv;
+}
+TomlFileIter TomlFile::end()
+{
+ return TomlFileIter { *this };
+}
+
+TomlKeyValue TomlFile::get_next_value()
+{
+ auto t = Token::lex_from(m_if);
+
+ if(m_current_composite.empty())
+ {
+ while( t.m_type == Token::Type::Newline )
+ t = Token::lex_from(m_if);
+
+ // Expect '[', a string, or an identifier
+ switch(t.m_type)
+ {
+ case Token::Type::Eof:
+ // Empty return indicates the end of the list
+ return TomlKeyValue {};
+ case Token::Type::SquareOpen:
+ m_current_block.clear();
+ do
+ {
+ t = Token::lex_from(m_if);
+ bool is_array = false;
+ if(t.m_type == Token::Type::SquareOpen)
+ {
+ is_array = true;
+ t = Token::lex_from(m_if);
+ }
+ assert(t.m_type == Token::Type::Ident || t.m_type == Token::Type::String);
+ m_current_block.push_back(t.as_string());
+ if(is_array)
+ {
+ m_current_block.push_back(::format(m_array_counts[t.as_string()]++));
+ t = Token::lex_from(m_if);
+ assert(t.m_type == Token::Type::SquareClose);
+ }
+
+ t = Token::lex_from(m_if);
+ } while(t.m_type == Token::Type::Dot);
+ if( t.m_type != Token::Type::SquareClose )
+ {
+ throw ::std::runtime_error(::format("Unexpected token in block header - ", t));
+ }
+ t = Token::lex_from(m_if);
+ if (t.m_type != Token::Type::Newline)
+ {
+ throw ::std::runtime_error(::format("Unexpected token after block block - ", t));
+ }
+ DEBUG("Start block " << m_current_block);
+ // Recurse!
+ return get_next_value();
+ default:
+ break;
+ }
+ }
+ else
+ {
+ // Expect a string or an identifier
+ if( t.m_type == Token::Type::Eof )
+ {
+ // EOF isn't allowed here
+ throw ::std::runtime_error(::format("Unexpected EOF in composite"));
+ }
+ }
+ switch (t.m_type)
+ {
+ case Token::Type::String:
+ case Token::Type::Ident:
+ break;
+ default:
+ throw ::std::runtime_error(::format("Unexpected token for key - ", t));
+ }
+ ::std::string key_name = t.as_string();
+ t = Token::lex_from(m_if);
+
+ if(t.m_type != Token::Type::Assign)
+ throw ::std::runtime_error(::format("Unexpected token after key - ", t));
+ t = Token::lex_from(m_if);
+
+ TomlKeyValue rv;
+ switch(t.m_type)
+ {
+ case Token::Type::String:
+ rv.path = m_current_block;
+ rv.path.insert(rv.path.end(), m_current_composite.begin(), m_current_composite.end());
+ rv.path.push_back(key_name);
+
+ rv.value = TomlValue { t.m_data };
+ break;
+ case Token::Type::SquareOpen:
+ rv.path = m_current_block;
+ rv.path.insert(rv.path.end(), m_current_composite.begin(), m_current_composite.end());
+ rv.path.push_back(key_name);
+
+ rv.value.m_type = TomlValue::Type::List;
+ while( (t = Token::lex_from(m_if)).m_type != Token::Type::SquareClose )
+ {
+ while( t.m_type == Token::Type::Newline )
+ t = Token::lex_from(m_if);
+ if( t.m_type == Token::Type::SquareClose )
+ break;
+
+ // TODO: Recurse parse a value
+ switch(t.m_type)
+ {
+ case Token::Type::String:
+ rv.value.m_sub_values.push_back(TomlValue { t.as_string() });
+ break;
+ default:
+ throw ::std::runtime_error(::format("Unexpected token in array value position - ", t));
+ }
+
+ t = Token::lex_from(m_if);
+ if(t.m_type != Token::Type::Comma)
+ break;
+ }
+ if(t.m_type != Token::Type::SquareClose)
+ throw ::std::runtime_error(::format("Unexpected token after array - ", t));
+ break;
+ case Token::Type::BraceOpen:
+ m_current_composite.push_back(key_name);
+ DEBUG("Enter composite block " << m_current_block << ", " << m_current_composite);
+ // Recurse to restart parse
+ return get_next_value();
+ case Token::Type::Integer:
+ rv.path = m_current_block;
+ rv.path.insert(rv.path.end(), m_current_composite.begin(), m_current_composite.end());
+ rv.path.push_back(key_name);
+ rv.value = TomlValue { t.m_intval };
+ return rv;
+ case Token::Type::Ident:
+ if( t.m_data == "true" )
+ {
+ rv.path = m_current_block;
+ rv.path.insert(rv.path.end(), m_current_composite.begin(), m_current_composite.end());
+ rv.path.push_back(key_name);
+ rv.value = TomlValue { true };
+ }
+ else if( t.m_data == "false" )
+ {
+ rv.path = m_current_block;
+ rv.path.insert(rv.path.end(), m_current_composite.begin(), m_current_composite.end());
+ rv.path.push_back(key_name);
+
+ rv.value = TomlValue { false };
+ }
+ else
+ {
+ throw ::std::runtime_error(::format("Unexpected identifier in value position - ", t));
+ }
+ break;
+ default:
+ throw ::std::runtime_error(::format("Unexpected token in value position - ", t));
+ }
+
+ t = Token::lex_from(m_if);
+ while (!m_current_composite.empty() && t.m_type == Token::Type::BraceClose)
+ {
+ DEBUG("Leave composite block " << m_current_block << ", " << m_current_composite);
+ m_current_composite.pop_back();
+ t = Token::lex_from(m_if);
+ }
+ if( m_current_composite.empty() )
+ {
+ // TODO: Allow EOF?
+ if(t.m_type != Token::Type::Newline)
+ throw ::std::runtime_error(::format("Unexpected token in TOML file after entry - ", t));
+ }
+ else
+ {
+ if( t.m_type != Token::Type::Comma )
+ throw ::std::runtime_error(::format("Unexpected token in TOML file after composite entry - ", t));
+ }
+ return rv;
+}
+
+Token Token::lex_from(::std::ifstream& is)
+{
+ auto rv = Token::lex_from_inner(is);
+ //DEBUG("lex_from: " << rv);
+ return rv;
+}
+Token Token::lex_from_inner(::std::ifstream& is)
+{
+ int c;
+ do
+ {
+ c = is.get();
+ } while( c != EOF && c != '\n' && isspace(c) );
+
+ ::std::string str;
+ switch(c)
+ {
+ case EOF: return Token { Type::Eof };
+ case '[': return Token { Type::SquareOpen };
+ case ']': return Token { Type::SquareClose };
+ case '{': return Token { Type::BraceOpen };
+ case '}': return Token { Type::BraceClose };
+ case ',': return Token { Type::Comma };
+ case '.': return Token { Type::Dot };
+ case '=': return Token { Type::Assign };
+ case '\n': return Token { Type::Newline };
+ case '#':
+ while(c != '\n')
+ {
+ c = is.get();
+ if(c == EOF)
+ return Token { Type::Eof };
+ }
+ return Token { Type::Newline };
+ case '\'':
+ c = is.get();
+ while (c != '\'')
+ {
+ if (c == EOF)
+ throw ::std::runtime_error("Unexpected EOF in single-quoted string");
+ if (c == '\\')
+ {
+ // TODO: Escaped strings
+ throw ::std::runtime_error("TODO: Escaped sequences in strings (single)");
+ }
+ str += (char)c;
+ c = is.get();
+ }
+ return Token { Type::String, str };
+ case '"':
+ c = is.get();
+ if(c == '"')
+ {
+ c = is.get();
+ if( c != '"' )
+ {
+ is.putback(c);
+ return Token { Type::String, "" };
+ }
+ else
+ {
+ // Keep reading until """
+ for(;;)
+ {
+ c = is.get();
+ if(c == '"')
+ {
+ c = is.get();
+ if(c == '"')
+ {
+ c = is.get();
+ if(c == '"')
+ {
+ break;
+ }
+ str += '"';
+ }
+ str += '"';
+ }
+ if( c == EOF )
+ throw ::std::runtime_error("Unexpected EOF in triple-quoted string");
+ if(c == '\\')
+ {
+ // TODO: Escaped strings
+ throw ::std::runtime_error("TODO: Escaped sequences in strings (triple)");
+ }
+ str += (char)c;
+ }
+ }
+ }
+ else
+ {
+ while(c != '"')
+ {
+ if (c == EOF)
+ throw ::std::runtime_error("Unexpected EOF in double-quoted string");
+ if (c == '\\')
+ {
+ // TODO: Escaped strings
+ c = is.get();
+ switch(c)
+ {
+ case '"': str += '"'; break;
+ case 'n': str += '\n'; break;
+ default:
+ throw ::std::runtime_error("TODO: Escaped sequences in strings");
+ }
+ c = is.get();
+ continue ;
+ }
+ str += (char)c;
+ c = is.get();
+ }
+ }
+ return Token { Type::String, str };
+ default:
+ if(isalpha(c))
+ {
+ // Identifier
+ while(isalnum(c) || c == '-' || c == '_')
+ {
+ str += (char)c;
+ c = is.get();
+ }
+ is.putback(c);
+ return Token { Type::Ident, str };
+ }
+ else if( isdigit(c) )
+ {
+ int64_t val = 0;
+ while(isdigit(c))
+ {
+ val *= 10;
+ val += c - '0';
+ c = is.get();
+ }
+ is.putback(c);
+ return Token { Type::Integer, val };
+ }
+ else
+ {
+ throw ::std::runtime_error(::format("Unexpected chracter '", (char)c, "' in file"));
+ }
+ }
+}
diff --git a/tools/common/toml.h b/tools/common/toml.h
new file mode 100644
index 00000000..e57c28ae
--- /dev/null
+++ b/tools/common/toml.h
@@ -0,0 +1,163 @@
+#pragma once
+
+#include <fstream>
+#include <vector>
+#include <string>
+#include <unordered_map>
+
+class TomlFileIter;
+struct TomlKeyValue;
+
+class TomlFile
+{
+ /// Input file stream
+ ::std::ifstream m_if;
+
+ /// Name of the current `[]` block
+ ::std::vector<::std::string> m_current_block;
+
+ /// Path suffix of the current composite (none if empty)
+ ::std::vector<::std::string> m_current_composite;
+
+ /// Index of the next array field (if zero, not parsing an array)
+ unsigned int m_next_array_index;
+
+ /// Next indexes if top-level defined arrays (e.g. `[[foo]]`)
+ ::std::unordered_map<::std::string,unsigned> m_array_counts;
+
+public:
+ TomlFile(const ::std::string& filename);
+
+ TomlFileIter begin();
+ TomlFileIter end();
+
+ TomlKeyValue get_next_value();
+};
+
+struct TomlValue
+{
+ enum class Type
+ {
+ Boolean,
+ String,
+ Integer,
+ List,
+ };
+ struct TypeError:
+ public ::std::exception
+ {
+ Type have;
+ Type exp;
+
+ TypeError(Type h, Type e):
+ have(h),
+ exp(e)
+ {
+ }
+
+ const char* what() const noexcept override {
+ return "toml type error";
+ }
+ };
+
+ Type m_type;
+ uint64_t m_int_value;
+ ::std::string m_str_value;
+ ::std::vector<TomlValue> m_sub_values;
+
+ TomlValue():
+ m_type(Type::String)
+ {
+ }
+ TomlValue(::std::string s):
+ m_type( Type::String ),
+ m_str_value(::std::move(s))
+ {
+ }
+ TomlValue(int64_t v):
+ m_type(Type::Integer),
+ m_int_value(v)
+ {
+ }
+ TomlValue(bool v) :
+ m_type(Type::Boolean),
+ m_int_value(v ? 1 : 0)
+ {
+ }
+
+ const ::std::string& as_string() const {
+ if( m_type != Type::String ) {
+ throw TypeError { m_type, Type::String };
+ }
+ return m_str_value;
+ }
+ bool as_bool() const {
+ if(m_type != Type::Boolean) {
+ throw TypeError { m_type, Type::Boolean };
+ }
+ return m_int_value != 0;
+ }
+ uint64_t as_int() const {
+ if(m_type != Type::Integer) {
+ throw TypeError { m_type, Type::Integer };
+ }
+ return m_int_value;
+ }
+ const ::std::vector<TomlValue>& as_list() const {
+ if(m_type != Type::List) {
+ throw TypeError { m_type, Type::List };
+ }
+ return m_sub_values;
+ }
+
+ friend ::std::ostream& operator<<(::std::ostream& os, const TomlValue& x) {
+ switch(x.m_type)
+ {
+ case Type::Boolean: os << (x.m_int_value != 0 ? "true" : "false"); break;
+ case Type::Integer: os << x.m_int_value; break;
+ case Type::List:
+ os << "[";
+ for(auto& e : x.m_sub_values)
+ os << e << ",";
+ os << "]";
+ break;
+ case Type::String:
+ os << "\"" << x.m_str_value << "\"";
+ break;
+ }
+ return os;
+ }
+};
+
+struct TomlKeyValue
+{
+ ::std::vector<::std::string> path;
+ TomlValue value;
+};
+
+class TomlFileIter
+{
+ friend class TomlFile;
+ TomlFile& m_reader;
+ TomlKeyValue m_cur_value;
+
+ TomlFileIter(TomlFile& tf):
+ m_reader(tf)
+ {
+
+ }
+
+public:
+ TomlKeyValue operator*() const
+ {
+ return m_cur_value;
+ }
+ void operator++()
+ {
+ m_cur_value = m_reader.get_next_value();
+ }
+ bool operator!=(const TomlFileIter& x) const
+ {
+ return m_cur_value.path != x.m_cur_value.path;
+ }
+};