From 57c69e2db42c8f25676114cb23e0d0b98d27ebc3 Mon Sep 17 00:00:00 2001 From: lachrymaL Date: Fri, 17 Dec 2021 01:05:38 -0500 Subject: [PATCH] evaluator ig --- NouVeL/CMakeLists.txt | 2 +- NouVeL/Common.h | 5 ++ NouVeL/Environment.cpp | 68 ++++++++++----- NouVeL/Environment.h | 16 ++-- NouVeL/Markup.cpp | 36 ++++++++ NouVeL/NouVeL.cpp | 21 ++++- NouVeL/Parser.cpp | 184 +++++++++++++++-------------------------- NouVeL/Parser.h | 37 ++++++++- NouVeL/spec.nvl | 4 +- NouVeL/test.nvl | 56 ++++++++++++- 10 files changed, 280 insertions(+), 149 deletions(-) create mode 100644 NouVeL/Common.h create mode 100644 NouVeL/Markup.cpp diff --git a/NouVeL/CMakeLists.txt b/NouVeL/CMakeLists.txt index 07828e9..7dd0c82 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" "Parser.cpp" "Environment.cpp" "Environment.h") +add_executable (NouVeL "NouVeL.cpp" "Parser.cpp" "Environment.cpp" "Environment.h" "Markup.cpp" "Common.h") # TODO: Add tests and install targets if needed. diff --git a/NouVeL/Common.h b/NouVeL/Common.h new file mode 100644 index 0000000..2b16f42 --- /dev/null +++ b/NouVeL/Common.h @@ -0,0 +1,5 @@ +#pragma once + +namespace NVL { + using Number = float; +} diff --git a/NouVeL/Environment.cpp b/NouVeL/Environment.cpp index ae6a963..c90587b 100644 --- a/NouVeL/Environment.cpp +++ b/NouVeL/Environment.cpp @@ -1,12 +1,20 @@ -#include -#include -#include -#include -#include #include "Environment.h" +#include "Parser.h" -namespace NVL { - const unsigned LOG_SIZE = 50; +#include + +namespace NVL::Environment { + Environment ENVIRONMENT; + + Variable::Variable() {} + Variable::Variable(Type t) : type(t) { + if (t != Type::Nil) + throw std::runtime_error("Cannot create non-nil object with type alone"); + } + 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(const std::function < Variable(std::vector)>& v, int l) : type(Type::Procedure), value(v), length(l) {} bool Variable::operator==(const Variable& other) const { if (other.type != type) @@ -20,17 +28,6 @@ namespace NVL { return false; } } - Variable::Variable() {} - Variable::Variable(Type t) : type(t) { - if (t != Type::Nil) - throw std::runtime_error("Cannot create non-nil object with type alone"); - } - 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(const std::function < Variable(std::vector)>& 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()) @@ -44,7 +41,7 @@ namespace NVL { env[name] = p; } - Variable Environment::get(const std::string& name) { + Variable& Environment::get(const std::string& name) { return env.at(name); } @@ -52,4 +49,37 @@ namespace NVL { return env.find(name) != env.end(); } + Variable Eval(const Parse::Object& obj) { + switch (obj.type) { + case Parse::Type::Symbol: + return std::get(obj.value); + case Parse::Type::Number: + return Variable(std::get(obj.value)); + case Parse::Type::String: + return Variable(std::get(obj.value)); + case Parse::Type::Array: + { + std::vector v{}; + for (const auto& x : std::get>(obj.value)) + v.push_back(Eval(x)); + return Variable(v); + } + case Parse::Type::Subexpression: + { + return Apply(std::get>(obj.value)); + } + } + } + + Variable Apply(const Parse::Command& c) { + std::vector args{}; + for (int i = 1; i < c.size(); i++) + args.push_back(Eval(c[i])); + return std::get)>>(ENVIRONMENT.get(std::get(c[0].value)).value)(args); + } + + void EvalScene(const Parse::Scene& s) { + for (const auto& c : s.get()) + Apply(c); + } } diff --git a/NouVeL/Environment.h b/NouVeL/Environment.h index 029a7d7..f81d78a 100644 --- a/NouVeL/Environment.h +++ b/NouVeL/Environment.h @@ -1,16 +1,17 @@ #pragma once #include #include -#include #include +#include "Parser.h" +#include "Common.h" -namespace NVL { - using Number = float; +namespace NVL::Environment { enum class Type { Procedure, Number, String, Array, Nil }; struct Variable { Type type; std::variant, std::function)>> value; + // Most things will have length 1 (including string); this is mostly for function arity and array length int length; bool operator==(const Variable& other) const; Variable(); @@ -18,7 +19,7 @@ namespace NVL { Variable(const Number& v); Variable(const std::string& v); Variable(const std::vector& v); - Variable(const std::function < Variable(std::vector)>& v, int l); + Variable(const std::function)>& v, int l); }; class Environment { @@ -27,8 +28,13 @@ namespace NVL { public: void enter(const std::string& name, Variable p); void set(const std::string& name, Variable p); - Variable get(const std::string& name); + Variable& get(const std::string& name); bool exists(const std::string& name); }; + extern Environment ENVIRONMENT; + + Variable Eval(const Parse::Object& obj); + Variable Apply(const Parse::Command& c); + void EvalScene(const Parse::Scene & s); } diff --git a/NouVeL/Markup.cpp b/NouVeL/Markup.cpp new file mode 100644 index 0000000..8bdf7e0 --- /dev/null +++ b/NouVeL/Markup.cpp @@ -0,0 +1,36 @@ +#if 0 + +struct Markup { + //... TODO +}; + +struct MarkupInstance { + std::pair range; + std::string effect; //TEMP +}; + + +std::vector discards; +for (int i = 0; i < s.length(); i++) { + if (s[i] == ESCAPE) { + if (DIALOGUE_ESCAPED_SINGLE.accept.find(s[i]) != std::string::npos) { + discards.push_back(i++); + } + else + throw std::runtime_error("Unrecognized escape sequence"); + } +} +std::string new_s{ s }; +for (int i = 0; i < discards.size(); i++) { + new_s.erase(discards[i] - i, 1); + discards[i] -= i; // New indices become the positions of the escaped characters +} +// TODO +for (int i = 0; i < s.length(); i++) { + if (!discards.empty() && discards[0] < i) { + discards.erase(discards.begin()); + } + +} +#endif + diff --git a/NouVeL/NouVeL.cpp b/NouVeL/NouVeL.cpp index 79012f0..1cb0924 100644 --- a/NouVeL/NouVeL.cpp +++ b/NouVeL/NouVeL.cpp @@ -2,13 +2,26 @@ #include "Environment.h" #include #include +#include int main() { - NVL::Variable v([](NVL::Variable) { return NVL::Variable(NVL::Type::Nil); }, 1); - - NVL::ENVIRONMENT.enter("hello", v); + auto f = [](NVL::Environment::Variable x) { return NVL::Environment::Variable(NVL::Environment::Type::Nil); }; + NVL::Environment::ENVIRONMENT.enter("Hello", { f, 1 }); + NVL::Environment::ENVIRONMENT.enter("Command", { f, 1 }); + NVL::Environment::ENVIRONMENT.enter("Do", { f, 2 }); + NVL::Environment::ENVIRONMENT.enter("Set", { f, 2 }); + NVL::Environment::ENVIRONMENT.enter("This", { f, 1 }); + NVL::Environment::ENVIRONMENT.enter("ClearDialog", { f, 0 }); + NVL::Environment::ENVIRONMENT.enter("Choice", { f, 2 }); + NVL::Environment::ENVIRONMENT.enter("=?", { f, 2 }); + NVL::Environment::ENVIRONMENT.enter("+", { f, 2 }); + NVL::Environment::ENVIRONMENT.enter("switch", { f, 2 }); + NVL::Environment::ENVIRONMENT.enter("SwitchSpeaker", { f, 1 }); + NVL::Environment::ENVIRONMENT.enter("Say", { f, 1 }); const std::string PJ_DIR = "E:\\Archive\\Projects\\NouVeL\\NouVeL\\"; - NVL::ParseFile(PJ_DIR + "test.nvl"); + for (auto& s : NVL::Parse::ParseFile(PJ_DIR + "test.nvl")) { + NVL::Environment::EvalScene(s); + } return 0; } diff --git a/NouVeL/Parser.cpp b/NouVeL/Parser.cpp index a178af6..6d03c95 100644 --- a/NouVeL/Parser.cpp +++ b/NouVeL/Parser.cpp @@ -1,6 +1,4 @@ -#include -#include -#include +#include "Parser.h" #include #include @@ -25,9 +23,14 @@ namespace { struct Match { std::string accept; - + operator char() const { - return accept[0]; + if (accept.length() == 1) + return accept[0]; + else { + std::cerr << "Cannot demote Match " << accept << " to char" << std::endl; + return '\0'; + } } bool operator== (const std::string& other) const { return accept == other; @@ -36,6 +39,7 @@ namespace { const ParseGroup NUMERIC = { "1234567890" }; const Match DECIMAL_DOT = { "." }; + const Match NEGATIVE = { "-" }; const ParseGroup ALPHA = { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" }; const Match ARRAY_OPEN = { "[" }; const Match ARRAY_CLOSE = { "]" }; @@ -48,7 +52,18 @@ namespace { const Match DIALOGUE_CLOSE = { "->>" }; const Match BEGIN = { "BEGIN" }; const Match END = { "END" }; - const ParseGroup SYMBOL = { ALPHA.accept + NUMERIC.accept + "_-" }; + const ParseGroup SYMBOL = { ALPHA.accept + NUMERIC.accept + "_"}; + const Match SPECIAL_SYMBOLS[] = { + { "+" }, + { "-" }, + { "*" }, + { "/" }, + { "=?" }, + { ">?" }, + { "=?" } + }; const ParseGroup WS = { " \t\v\f\r\n" }; const ParseGroup SEPARATOR = { WS.accept + @@ -57,7 +72,7 @@ namespace { char(GROUP_OPEN) + char(GROUP_CLOSE) + char(ARRAY_DELIM) + - char(COMMENT_BEGIN) + COMMENT_BEGIN.accept[0] }; const Match NEWLINE = { "\n" }; const ParseGroup ESCAPED = { "\\\"" }; @@ -89,8 +104,6 @@ namespace { // char(TEMPLATE_CLOSE) }; - enum class ParseType { Symbol, Number, String, Array, Subexpression }; - std::string read_file_to_string(const std::string& path) { std::ifstream f(path); { // Some apps on Windows adds this signature in front of UTF-8 files when saving @@ -120,8 +133,10 @@ namespace { } inline bool IsNumeric(const std::string& str) { + bool negative = str[0] == NEGATIVE; bool had_dot = false; - for (auto& c : str) { + + for (auto& c : negative ? str.substr(1) : str) { if (NUMERIC.accept.find(c) == std::string::npos) { if (c == DECIMAL_DOT.accept[0]) { if (had_dot) @@ -133,38 +148,20 @@ namespace { return false; } } + + if (had_dot + negative == str.length()) + return false; + return true; } - class Object { - public: - ParseType type; - std::variant< - NVL::Number, - std::string, - std::vector - > value; - }; - using Command = std::vector; - - class Scene { - std::string name; - std::vector commands{}; - public: - Scene(const std::string& name) : name(name) {} - void append(const Command& c) { - commands.push_back(c); + inline bool ContainsOnlyWS(const std::string& s) { + for (auto& c : s) { + if (WS.accept.find(c) == std::string::npos) + return false; } - }; - - struct Markup { - //... TODO - }; - - struct MarkupInstance { - std::pair range; - std::string effect; //TEMP - }; + return true; + } void SkipWS(const std::string& f, size_t& pos) { while (WS.accept.find(f[pos]) != std::string::npos) @@ -196,7 +193,7 @@ namespace { while (++pos) { if (SEPARATOR.accept.find(f[pos]) != std::string::npos) break; - }; + } return f.substr(start, pos - start); } @@ -206,11 +203,15 @@ namespace { while (++pos) { if (SEPARATOR.accept.find(f[pos]) != std::string::npos) break; - }; + } return f.substr(start, pos - start); } bool IsLegalSymbolName(const std::string& token) { + for (const auto& x: SPECIAL_SYMBOLS) { + if (token == x.accept) + return true; + } if (ALPHA.accept.find(token[0]) == std::string::npos) return false; for (auto& i : token) @@ -219,11 +220,11 @@ namespace { return true; } - Object ParseExpression(const std::string& f, size_t& pos); - Object ParseArray(const std::string& f, size_t& pos, int layer) { + NVL::Parse::Object ParseExpression(const std::string& f, size_t& pos); + NVL::Parse::Object ParseArray(const std::string& f, size_t& pos, int layer) { SkipComments(f, pos); - std::vector array{}; + std::vector array{}; array.push_back(ParseExpression(f, pos)); while (PeekToken(f, pos)[0] != ARRAY_CLOSE) { @@ -234,7 +235,7 @@ namespace { array.push_back(ParseExpression(f, pos)); } - return { ParseType::Array, array }; + return { NVL::Parse::Type::Array, array }; } std::string ParseString(const std::string& f, size_t& pos) { @@ -263,16 +264,16 @@ namespace { return str; } unsigned GetProcedureArity(const std::string& key) { - return NVL::ENVIRONMENT.get(key).length; + return NVL::Environment::ENVIRONMENT.get(key).length; } - Command ParseCommand(const std::string& f, size_t& pos) { + NVL::Parse::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 } }; + NVL::Parse::Command c{ NVL::Parse::Object{ NVL::Parse::Type::Symbol, proc } }; for (int i = 0; i < GetProcedureArity(proc); i++) { c.push_back(ParseExpression(f, pos)); }; @@ -281,7 +282,7 @@ namespace { return c; } - Object ParseExpression(const std::string& f, size_t& pos) { + NVL::Parse::Object ParseExpression(const std::string& f, size_t& pos) { SkipComments(f, pos); auto t = PeekToken(f, pos); @@ -301,70 +302,49 @@ namespace { throw std::runtime_error("Cannot match closing subexpression"); else SkipOverFirstChar(f, pos); - return Object{ ParseType::Subexpression, c }; + return NVL::Parse::Object{ NVL::Parse::Type::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) }; + return { NVL::Parse::Type::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) }; + return { NVL::Parse::Type::Number, std::stof(token) }; else if (IsLegalSymbolName(token)) - return { ParseType::Symbol, token }; + return { NVL::Parse::Type::Symbol, token }; else throw std::runtime_error("Illegal symbol"); } } - Command ParseDialogue(const std::string& s) { + NVL::Parse::Command ParseDialogue(const std::string& s) { if (s.substr(0, 2) == COMMAND_ESCAPE.accept) { size_t dummy = 0; - return ParseCommand(s.substr(2), dummy); // TODO string[] will throw on this 100%, maybe do ParseDialogueCommand + // Pad a space towards the end, the helpers do not expect strings to immediately terminate + return ParseCommand(s.substr(2) + " ", dummy); } + // assume SwitchSpeaker and Say are unary for now + if (s.back() == SPEAKER_CLOSE) { if (s.front() == SPEAKER_OPEN) { auto name = s.substr(1, s.length() - 2); if (IsLegalSymbolName(name)) - return { { ParseType::Symbol, "SwitchSpeaker" }, { ParseType::String, name } }; + return { { NVL::Parse::Type::Symbol, "SwitchSpeaker" }, { NVL::Parse::Type::String, name } }; } else throw std::runtime_error("Malformed speaker command"); } - std::vector discards; - for (int i = 0; i < s.length(); i++) { - if (s[i] == ESCAPE) { - if (DIALOGUE_ESCAPED_SINGLE.accept.find(s[i]) != std::string::npos) { - discards.push_back(i++); - } - else - throw std::runtime_error("Unrecognized escape sequence"); - } - } - std::string new_s{s}; - for (int i = 0; i < discards.size(); i++) { - new_s.erase(discards[i] - i, 1); - discards[i] -= i; // New indices become the positions of the escaped characters - } - // TODO - for (int i = 0; i < s.length(); i++) { - if (!discards.empty() && discards[0] < i) { - discards.erase(discards.begin()); - } - - } - - std::cout << new_s << std::endl; - return { { ParseType::Symbol, "Say" }, { ParseType::String, new_s } }; + return { { NVL::Parse::Type::Symbol, "Say" }, { NVL::Parse::Type::String, s } }; } - Scene ParseScene(const std::string& f, size_t& pos) { + NVL::Parse::Scene ParseScene(const std::string& f, size_t& pos) { SkipComments(f, pos); if (!(GetToken(f, pos) == BEGIN.accept)) @@ -372,7 +352,7 @@ namespace { auto scene_name = GetToken(f, pos); if (!IsLegalSymbolName(scene_name)) throw std::runtime_error("Illegal Scene name"); - Scene s{ scene_name }; + NVL::Parse::Scene s{ scene_name }; bool dialogue_mode = false; @@ -391,7 +371,8 @@ namespace { 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)); + if (!l.empty() && !ContainsOnlyWS(l)) + s.append(ParseDialogue(l)); } dialogue_mode = false; pos = end; @@ -405,43 +386,13 @@ namespace { GetToken(f, pos); // skip END + SkipComments(f, pos); return s; } - - /*Scene EvalScene(const std::vector>& parses) { // TODO THIS IS TOTALLY FUCKED - if (parses[0].size() != 2) - throw std::runtime_error("Too many tokens in Scene BEGIN"); - - Scene r(parses[0][1].parse); - for (int i = 1; i < parses.size() - 1; i++) { // Skip first and last lines, presumed BEGIN and END - if (!ENV.exists(parses[i][0].parse)) - throw std::runtime_error("Symbol not defined in environment"); - Command call = { { Type::Symbol, parses[i][0].parse }, {} }; - for (int token_i = 1; token_i < parses[i].size();) { // skip first token - switch (parses[i][token_i].type) { - case Type::Symbol: - { - - } - case Type::Number: - call.args.push_back(Object{ Type::Number, std::stof(parses[i][token_i].parse) }); - break; - case Type::String: - call.args.push_back(Object{ Type::String, parses[i][token_i].parse }); - break; - case Type::Array: - { - - } - } - } - - } -*/ } -namespace NVL { - void ParseFile(const std::string& path) { +namespace NVL::Parse { + std::vector ParseFile(const std::string& path) { std::string f = read_file_to_string(path); std::vector list {}; // Vector of scenes which each contain a vector of Parses @@ -449,6 +400,7 @@ namespace NVL { list.push_back(ParseScene(f, i)); } + return list; } } diff --git a/NouVeL/Parser.h b/NouVeL/Parser.h index b217980..f2313dc 100644 --- a/NouVeL/Parser.h +++ b/NouVeL/Parser.h @@ -1,6 +1,39 @@ #pragma once +#include +#include #include +#include "Common.h" -namespace NVL { - void ParseFile(const std::string& path); +namespace NVL::Parse { + enum class Type { Symbol, Number, String, Array, Subexpression }; + + class Object { + public: + Type type; + std::variant< + Number, + std::string, + std::vector + > value; + }; + + using Command = std::vector; + + class Scene { + std::string name; + std::vector commands{}; + public: + Scene(const std::string& name) : name(name) {} + void append(const Command& c) { + commands.push_back(c); + } + void append(Command&& c) { + commands.push_back(std::move(c)); + } + const auto& get() const { + return commands; + } + }; + + std::vector ParseFile(const std::string& path); } diff --git a/NouVeL/spec.nvl b/NouVeL/spec.nvl index a10c2d2..051628e 100644 --- a/NouVeL/spec.nvl +++ b/NouVeL/spec.nvl @@ -18,6 +18,7 @@ Set var3 (+ var var2) // Enter and exit dialogue mode with <<- and ->> <<- +// Comments are only legal at the beginning like this in Dialogue mode // IDEA: once NVL is parsed, set up indices for each line // Make another program to match audio [Alex] @@ -44,7 +45,8 @@ To grab a value from the environment, do it like this: ${var}. This is also the syntax to evaluate a command from dialogue mode. If the return is void it will say "undefined" or something like that. -*! This "is a command in dialogue mode." // Dialogue mode commands have to be one liners +// Dialogue mode commands have to be one liners an cannot have trailing comments (arbitrary) +*! This "is a command in dialogue mode." *! Set var (+ var 1) [NARRATION] diff --git a/NouVeL/test.nvl b/NouVeL/test.nvl index 05af1a1..56aff27 100644 --- a/NouVeL/test.nvl +++ b/NouVeL/test.nvl @@ -1,13 +1,67 @@ -BEGIN Scene1 +// This new syntax should be easier to parse although parsing wasn't really the issue with the last syntax +// I really want the syntax to feel a lot more like the actual script for a play or something of the sort + +// THINK ABOUT THIS Newlines are the end of a statement in this syntax, I think this mirrors text formatting pretty well + +// I find that the "BEGIN" things in some older languages are actually quite fitting +BEGIN Scene1 + +Command "test" +Do 5 [2, 3, 6] + +Set var 2 + +// Make the actual commands like Lisp, not the entire language +// strip the outermost brackets since this syntax will be parsed line by line +Set var2 (=? var 1) +Set var3 (+ var var2) + +// Enter and exit dialogue mode with <<- and ->> <<- +// Comments are only legal at the beginning like this in Dialogue mode +// IDEA: once NVL is parsed, set up indices for each line +// Make another program to match audio [Alex] Hello. Welcome to dialogue mode. Every new line is a new "click". I discarded the "pause" idea. + [Bailey] Je ne suis plus Alex. Ça ne m'arrête pas de vous démontrer notre support multilingue noblement distingué. + [Catherine] CJKテスト! 夏はマシンガン。 + +[Dick] +I present my idea for markup syntax here. +It is something like [b,i]{this}. +It's kind of like KP's TyperTags, only that you can specify more than one effect in the square brackets in front. +If the effect is parametrized in some way invoke it like [wiggle_y(5)]{this}. +Then naturally we would have ruby like [rb("that")]{this} (important feature, trust me). + +[Elliot] +To grab a value from the environment, do it like this: ${var}. +This is also the syntax to evaluate a command from dialogue mode. +If the return is void it will say "undefined" or something like that. + +*! This "is a command in dialogue mode." +*! Set var (+ var 1) + +[NARRATION] +Thanks! + +*! ClearDialog + ->> + +// something like Choice can load the index of the choice the user selects into a variable +Choice var4 ["Apple", "Orange"] + +// After thinking about this for a while I think it would be a good idea +// 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 [[0, RouteA], [1, RouteB], [default, RouteC]]) + END