diff options
author | John Hodge <tpg@mutabah.net> | 2018-03-18 10:48:26 +0800 |
---|---|---|
committer | John Hodge <tpg@mutabah.net> | 2018-03-18 10:48:26 +0800 |
commit | 5b0450395af81ceba0d0ac27fc73b16f966bd7d3 (patch) | |
tree | e3c753b83a562be78cdbd74b8164dab785bf07a6 /tools/common | |
parent | 363e6fa172f787e970c8abc8f631b6d60d571248 (diff) | |
download | mrust-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/Makefile | 36 | ||||
-rw-r--r-- | tools/common/debug.cpp | 80 | ||||
-rw-r--r-- | tools/common/debug.h | 68 | ||||
-rw-r--r-- | tools/common/helpers.h | 53 | ||||
-rw-r--r-- | tools/common/path.cpp | 194 | ||||
-rw-r--r-- | tools/common/path.h | 181 | ||||
-rw-r--r-- | tools/common/toml.cpp | 414 | ||||
-rw-r--r-- | tools/common/toml.h | 163 |
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; + } +}; |