From 084acbba1e4041bed678195d3af40c56eede71cd Mon Sep 17 00:00:00 2001 From: lachrymaL Date: Sun, 12 Dec 2021 22:20:28 -0500 Subject: [PATCH] ITS WORKING HOLY SHIT --- CMakeSettings.json | 2 +- NouVeL/CMakeLists.txt | 2 +- NouVeL/Environment.cpp | 51 +++++++ NouVeL/Environment.h | 33 ++++ NouVeL/NouVeL.cpp | 10 +- NouVeL/{NVL.cpp => Parser.cpp} | 266 +++++++++++++++++---------------- NouVeL/{NVL.h => Parser.h} | 0 NouVeL/spec.nvl | 2 +- NouVeL/test.nvl | 2 +- 9 files changed, 237 insertions(+), 131 deletions(-) create mode 100644 NouVeL/Environment.cpp create mode 100644 NouVeL/Environment.h rename NouVeL/{NVL.cpp => Parser.cpp} (57%) rename NouVeL/{NVL.h => Parser.h} (100%) diff --git a/CMakeSettings.json b/CMakeSettings.json index 8979b5a..03984cb 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -4,7 +4,7 @@ "name": "x64-Debug", "generator": "Ninja", "configurationType": "Debug", - "inheritEnvironments": [ "clang_cl_x64_x64" ], + "inheritEnvironments": [ "msvc_x64_x64" ], "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}", "cmakeCommandArgs": "", diff --git a/NouVeL/CMakeLists.txt b/NouVeL/CMakeLists.txt index 37818e7..07828e9 100644 --- a/NouVeL/CMakeLists.txt +++ b/NouVeL/CMakeLists.txt @@ -4,6 +4,6 @@ cmake_minimum_required (VERSION 3.8) # Add source to this project's executable. -add_executable (NouVeL "NouVeL.cpp" "NVL.cpp") +add_executable (NouVeL "NouVeL.cpp" "Parser.cpp" "Environment.cpp" "Environment.h") # TODO: Add tests and install targets if needed. diff --git a/NouVeL/Environment.cpp b/NouVeL/Environment.cpp new file mode 100644 index 0000000..156eb76 --- /dev/null +++ b/NouVeL/Environment.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include +#include "Environment.h" + +namespace NVL { + const unsigned LOG_SIZE = 50; + + bool Variable::operator==(const Variable& other) const { + if (other.type != type) + return false; + switch (type) { + case Type::Number: + return std::get(other.value) == std::get(value); + case Type::String: + return std::get(other.value) == std::get(value); + default: + return false; + } + } + Variable::Variable() {} + Variable::Variable(const Number& v) : type(Type::Number), value(v), length(1) {} + Variable::Variable(const std::string& v) : type(Type::String), value(v), length(1) {} + Variable::Variable(const std::vector& v) : type(Type::Array), value(v), length(v.size()) {} + Variable::Variable(void* v, int l) : type(Type::Procedure), value(v), length(l) {} + + Environment ENVIRONMENT; + + void Environment::enter(const std::string& name, Variable p) { + if (env.find(name) != env.end()) + throw std::runtime_error("Redefinition of symbol in environment"); + env[name] = p; + } + + void Environment::set(const std::string& name, Variable p) { + if (env.find(name) == env.end()) + throw std::runtime_error("Attempting to set undefined variable"); + env[name] = p; + } + + Variable Environment::get(const std::string& name) { + return env.at(name); + } + + bool Environment::exists(const std::string& name) { + return env.find(name) != env.end(); + } + +} \ No newline at end of file diff --git a/NouVeL/Environment.h b/NouVeL/Environment.h new file mode 100644 index 0000000..14983cd --- /dev/null +++ b/NouVeL/Environment.h @@ -0,0 +1,33 @@ +#pragma once +#include +#include +#include +#include + +namespace NVL { + using Number = float; + enum class Type { Procedure, Number, String, Array }; + + struct Variable { + Type type; + std::variant, void*> value; + int length; + bool operator==(const Variable& other) const; + Variable(); + Variable(const Number& v); + Variable(const std::string& v); + Variable(const std::vector& v); + Variable(void* v, int l); + }; + + class Environment { + private: + std::unordered_map env{}; + public: + void enter(const std::string& name, Variable p); + void set(const std::string& name, Variable p); + Variable get(const std::string& name); + bool exists(const std::string& name); + }; + extern Environment ENVIRONMENT; +} \ No newline at end of file diff --git a/NouVeL/NouVeL.cpp b/NouVeL/NouVeL.cpp index b694f54..6a6aeb7 100644 --- a/NouVeL/NouVeL.cpp +++ b/NouVeL/NouVeL.cpp @@ -1,8 +1,16 @@ -#include "NVL.h" +#include "Parser.h" +#include "Environment.h" +#include +#include #include #include int main() { + auto f = [](std::string& what) -> std::any { return true; }; + NVL::Variable v(&f, 3); + + NVL::ENVIRONMENT.enter("hello", v); + const std::string PJ_DIR = "E:\\Archive\\Projects\\NouVeL\\NouVeL\\"; NVL::ParseFile(PJ_DIR + "test.nvl"); return 0; diff --git a/NouVeL/NVL.cpp b/NouVeL/Parser.cpp similarity index 57% rename from NouVeL/NVL.cpp rename to NouVeL/Parser.cpp index 5698a75..f3db886 100644 --- a/NouVeL/NVL.cpp +++ b/NouVeL/Parser.cpp @@ -1,23 +1,26 @@ #include #include #include -#include #include #include #include -#include +#include -#include "SymbolConfig.h" - - -const unsigned LOG_SIZE = 50; +#include "Environment.h" namespace { struct ParseGroup { std::string accept; + + operator std::string() const { + return accept; + } + constexpr bool operator== (const std::string& other) const { + return accept == other; + } }; struct Match { @@ -48,13 +51,19 @@ namespace { const ParseGroup SYMBOL = { ALPHA.accept + NUMERIC.accept + "_-" }; const ParseGroup WS = { " \t\v\f\r\n" }; const ParseGroup SEPARATOR = { - WS.accept + char(ARRAY_OPEN) + char(ARRAY_CLOSE) + char(GROUP_OPEN) + char(GROUP_CLOSE) + WS.accept + + char(ARRAY_OPEN) + + char(ARRAY_CLOSE) + + char(GROUP_OPEN) + + char(GROUP_CLOSE) + + char(ARRAY_DELIM) + + char(COMMENT_BEGIN) }; const Match NEWLINE = { "\n" }; const Match ESCAPE = { "\\" }; const ParseGroup ESCAPED = { "\\\"" }; - enum class Type { Symbol, Number, String, Array }; + enum class ParseType { Symbol, Number, String, Array, Subexpression }; std::string read_file_to_string(const std::string& path) { std::ifstream f(path); @@ -67,8 +76,7 @@ namespace { std::vector lines; int pos = 0; int prev = 0; - while ((pos = str.find(NEWLINE.accept[0], prev)) != std::string::npos) - { + while ((pos = str.find(NEWLINE, prev)) != std::string::npos) { lines.push_back(str.substr(prev, pos - prev)); prev = pos + 1; } @@ -94,56 +102,28 @@ namespace { return true; } - enum class ParseState { Root, Scene, Command, Dialogue, String, Array }; - - using NVL_Number = float; - - struct Environment { - private: - std::unordered_map env; - public: - void enter(const std::string& name, void* p) { - if (env.find(name) != env.end()) - throw std::runtime_error("Redefinition of symbol in environment"); - env[name] = p; - } - - void set(const std::string& name, void* p) { - if (env.find(name) == env.end()) - throw std::runtime_error("Attempting to set undefined variable"); - env[name] = p; - } - - auto get(const std::string& name) { - return env.at(name); - } - - bool exists(const std::string& name) { - return env.find(name) != env.end(); - } - } ENV; - + /*struct Command { + Object procedure; + std::vector args{}; + };*/ class Object { public: - Type type; + ParseType type; std::variant< - NVL_Number, + NVL::Number, std::string, std::vector > value; }; + using Command = std::vector; - struct Command { - Object procedure; - std::vector args{}; - }; class Scene { std::string name; std::vector commands{}; public: Scene(const std::string& name) : name(name) { - ENV.enter(name, this); + //NVL::ENVIRONMENT.enter(name, this); } void append(const Command& c) { commands.push_back(c); @@ -169,6 +149,11 @@ namespace { } } + void SkipOverFirstChar(const std::string& f, size_t& pos) { + SkipWS(f, pos); + pos++; + } + std::string GetToken(const std::string& f, size_t& pos) { SkipWS(f, pos); auto start = pos; @@ -189,7 +174,7 @@ namespace { return f.substr(start, pos - start); } - bool IsSymbol(const std::string& token) { + bool IsLegalSymbolName(const std::string& token) { if (ALPHA.accept.find(token[0]) == std::string::npos) return false; for (auto& i : token) @@ -197,47 +182,41 @@ namespace { return false; return true; } - void ParseArray(ParseState& state, const std::string& f, size_t& pos, Command& command) { - SkipWS(f, pos); - /* - // Read until array end OR end of line, state wont change if array end was not found - auto start = pos; - while (pos < line.length()) { - if (ARRAY_OPEN.accept[0] == line[pos]) - parse.layer++; - else if (ARRAY_CLOSE.accept[0] == line[pos]) - break; - pos++; - } - parse.parse = line.substr(start, pos++ - start); - if (parse.parse.back() == ARRAY_CLOSE.accept[0]) { - parse.layer--; - if (parse.layer == 0) { + Object ParseExpression(const std::string& f, size_t& pos); + Object ParseArray(const std::string& f, size_t& pos, int layer) { + SkipComments(f, pos); - } + std::vector array{}; + + array.push_back(ParseExpression(f, pos)); + while (PeekToken(f, pos)[0] != ARRAY_CLOSE) { + if (PeekToken(f, pos)[0] == ARRAY_DELIM) + SkipOverFirstChar(f, pos); + else + throw std::runtime_error("Invalid array member"); + array.push_back(ParseExpression(f, pos)); } - return; - */ + return { ParseType::Array, array }; } - std::string ParseString(ParseState& state, const std::string& f, size_t& pos) { - SkipWS(f, pos); + std::string ParseString(const std::string& f, size_t& pos) { + SkipComments(f, pos); std::vector discards{}; auto start = ++pos; // skip opening quote do { - if (f[pos] == char(QUOTE)) { + if (f[pos] == QUOTE) { break; } - else if (f[pos] == char(ESCAPE)) { + else if (f[pos] == ESCAPE) { if (ESCAPED.accept.find(f[pos]) != std::string::npos) { discards.push_back(pos++); } else throw std::runtime_error("Unrecognized escape sequence"); } - else if (f[pos] == char(NEWLINE)) { + else if (f[pos] == NEWLINE) { throw std::runtime_error("Unclosed String"); } } while (pos++); @@ -247,74 +226,110 @@ namespace { } return str; } - - // Handles dialogue as well - void ParseCommand(ParseState& state, const std::string& f, size_t& pos, Scene& scene) { - int initial = pos; // TEMP - - int exp_layer = 0; - bool exp_complete = false; - - auto proc = GetToken(f, pos); - if (!IsSymbol(proc)) throw std::runtime_error("Illegal Procedure name"); - - Command c{ { Type::Symbol, proc }, {} }; - while (!exp_complete) { - SkipComments(f, pos); - if (pos > f.find('\n', initial)) // TEMP - exp_complete = true; // TEMP - auto t = PeekToken(f, pos); - if (t[0] == ARRAY_OPEN) { - state = ParseState::Array; - ParseArray(state, f, pos, c); - break; - } - else if (t[0] == GROUP_OPEN) { - - } - else if (t[0] == GROUP_CLOSE) { - - } - else if (t[0] == QUOTE) { - state = ParseState::String; - c.args.push_back(Object{ Type::String, ParseString(state, f, pos) }); - } - else if (t[0] == ARRAY_CLOSE) - throw std::runtime_error("Cannot match array"); - else { - auto token = GetToken(f, pos); - if (IsNumeric(token)) { - c.args.push_back(Object{ Type::Number, std::stof(token) }); - } - else if (IsSymbol(token)) { - c.args.push_back(Object{ Type::Symbol, token }); - } - else - throw std::runtime_error("Illegal symbol"); - } - } - - scene.append(c); + unsigned GetProcedureArity(const std::string& key) { + return NVL::ENVIRONMENT.get(key).length; } - void ParseScene(ParseState& state, const std::string& f, size_t& pos, std::vector& scenes) { + Command ParseCommand(const std::string& f, size_t& pos) { + SkipComments(f, pos); + + auto proc = GetToken(f, pos); + + if (!IsLegalSymbolName(proc)) throw std::runtime_error("Illegal Procedure name"); + Command c{ Object{ ParseType::Symbol, proc } }; + for (int i = 0; i < GetProcedureArity(proc); i++) { + c.push_back(ParseExpression(f, pos)); + }; + + return c; + } + + Object ParseExpression(const std::string& f, size_t& pos) { + SkipComments(f, pos); + + auto t = PeekToken(f, pos); + if (t[0] == ARRAY_OPEN) { + SkipOverFirstChar(f, pos); + auto c = ParseArray(f, pos, 0); + if (PeekToken(f, pos)[0] != ARRAY_CLOSE) + throw std::runtime_error("Cannot match closing Array"); + else + SkipOverFirstChar(f, pos); + return c; + } + else if (t[0] == GROUP_OPEN) { + SkipOverFirstChar(f, pos); + auto c = ParseCommand(f, pos); + if (PeekToken(f, pos)[0] != GROUP_CLOSE) + throw std::runtime_error("Cannot match closing subexpression"); + else + SkipOverFirstChar(f, pos); + return Object(ParseType::Subexpression, c); + } + else if (t[0] == GROUP_CLOSE) + throw std::runtime_error("Cannot match closing subexpression, likely too few arguments"); + else if (t[0] == QUOTE) + return { ParseType::String, ParseString(f, pos) }; + else if (t[0] == ARRAY_CLOSE) + throw std::runtime_error("Cannot match closing array"); + else { + auto token = GetToken(f, pos); + if (IsNumeric(token)) + return { ParseType::Number, std::stof(token) }; + else if (IsLegalSymbolName(token)) + return { ParseType::Symbol, token }; + else + throw std::runtime_error("Illegal symbol"); + } + + } + + Command ParseDialogue(const std::string& s) { + return { { ParseType::Symbol, "Say" }, { ParseType::String, s } }; + } + + Scene ParseScene(const std::string& f, size_t& pos) { SkipComments(f, pos); if (!(GetToken(f, pos) == BEGIN.accept)) throw std::runtime_error("Could not match accept at root"); auto scene_name = GetToken(f, pos); - if (!IsSymbol(scene_name)) throw std::runtime_error("Illegal Scene name"); + if (!IsLegalSymbolName(scene_name)) throw std::runtime_error("Illegal Scene name"); Scene s{ scene_name }; - state = ParseState::Command; + bool dialogue_mode = false; + while (PeekToken(f, pos) != END.accept) { - ParseCommand(state, f, pos, s); + if (!dialogue_mode) + if (PeekToken(f, pos) == DIALOGUE_OPEN.accept) { + dialogue_mode = true; + GetToken(f, pos); // skip DIALOGUE_OPEN + SkipComments(f, pos); + } + else + s.append(ParseCommand(f, pos)); + else { + auto end = f.find(NEWLINE.accept + DIALOGUE_CLOSE.accept, pos); + if (end == std::string::npos) + throw std::runtime_error("Dialogue does not terminate"); + auto lines = split_string_by_lines(f.substr(pos, end - pos)); + for (auto& l : lines) { + s.append(ParseDialogue(l)); + } + dialogue_mode = false; + pos = end; + GetToken(f, pos); // skip DIALOGUE_CLOSE + SkipComments(f, pos); + } } + if (dialogue_mode) + throw std::runtime_error("Illegal Scene end"); + GetToken(f, pos); // skip END - scenes.push_back(s); + return s; } /*Scene EvalScene(const std::vector>& parses) { // TODO THIS IS TOTALLY FUCKED @@ -352,11 +367,10 @@ namespace { namespace NVL { void ParseFile(const std::string& path) { std::string f = read_file_to_string(path); - ParseState state = ParseState::Root; std::vector list {}; // Vector of scenes which each contain a vector of Parses for (size_t i = 0; i < f.length(); i++) { - ParseScene(state, f, i, list); + list.push_back(ParseScene(f, i)); } } diff --git a/NouVeL/NVL.h b/NouVeL/Parser.h similarity index 100% rename from NouVeL/NVL.h rename to NouVeL/Parser.h diff --git a/NouVeL/spec.nvl b/NouVeL/spec.nvl index 5c51870..a2bdf88 100644 --- a/NouVeL/spec.nvl +++ b/NouVeL/spec.nvl @@ -61,6 +61,6 @@ Choice var4 ["Apple", "Orange"] // to have ways to terminate a scene other than END, for instance JUMP can be an alternative like this: // JUMP Scene2 // we can equally do something like this -// JUMP (switch var4 ("Apple" RouteA) ("Orange" RouteB) (default RouteC)) +// JUMP (switch var4 [["Apple", RouteA], ["Orange", RouteB], [default, RouteC]]) END diff --git a/NouVeL/test.nvl b/NouVeL/test.nvl index bb46019..4fc0f9a 100644 --- a/NouVeL/test.nvl +++ b/NouVeL/test.nvl @@ -1,3 +1,3 @@ BEGIN Scene1 -hello "String \\ sdsd \" \\ \" sds" +hello "String \\ sdsd \" \\ \" sds" 873482384 .2342342 END