evaluator ig

This commit is contained in:
lachrymaL 2021-12-17 01:05:38 -05:00
parent 87b9471735
commit 57c69e2db4
No known key found for this signature in database
GPG key ID: F3640ACFA174B1C1
10 changed files with 280 additions and 149 deletions

View file

@ -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.

5
NouVeL/Common.h Normal file
View file

@ -0,0 +1,5 @@
#pragma once
namespace NVL {
using Number = float;
}

View file

@ -1,12 +1,20 @@
#include <unordered_map>
#include <functional>
#include <any>
#include <variant>
#include <stdexcept>
#include "Environment.h"
#include "Parser.h"
namespace NVL {
const unsigned LOG_SIZE = 50;
#include <stdexcept>
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<Variable>& v) : type(Type::Array), value(v), length(v.size()) {}
Variable::Variable(const std::function < Variable(std::vector<Variable>)>& 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<Variable>& v) : type(Type::Array), value(v), length(v.size()) {}
Variable::Variable(const std::function < Variable(std::vector<Variable>)>& 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<std::string>(obj.value);
case Parse::Type::Number:
return Variable(std::get<Number>(obj.value));
case Parse::Type::String:
return Variable(std::get<std::string>(obj.value));
case Parse::Type::Array:
{
std::vector<Variable> v{};
for (const auto& x : std::get<std::vector<Parse::Object>>(obj.value))
v.push_back(Eval(x));
return Variable(v);
}
case Parse::Type::Subexpression:
{
return Apply(std::get<std::vector<Parse::Object>>(obj.value));
}
}
}
Variable Apply(const Parse::Command& c) {
std::vector<Variable> args{};
for (int i = 1; i < c.size(); i++)
args.push_back(Eval(c[i]));
return std::get<std::function<Variable(std::vector<Variable>)>>(ENVIRONMENT.get(std::get<std::string>(c[0].value)).value)(args);
}
void EvalScene(const Parse::Scene& s) {
for (const auto& c : s.get())
Apply(c);
}
}

View file

@ -1,16 +1,17 @@
#pragma once
#include <unordered_map>
#include <functional>
#include <any>
#include <variant>
#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<Number, std::string, std::vector<Variable>, std::function<Variable(std::vector<Variable>)>> 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<Variable>& v);
Variable(const std::function < Variable(std::vector<Variable>)>& v, int l);
Variable(const std::function<Variable(std::vector<Variable>)>& 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);
}

36
NouVeL/Markup.cpp Normal file
View file

@ -0,0 +1,36 @@
#if 0
struct Markup {
//... TODO
};
struct MarkupInstance {
std::pair<unsigned, unsigned> range;
std::string effect; //TEMP
};
std::vector<int> 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

View file

@ -2,13 +2,26 @@
#include "Environment.h"
#include <string>
#include <iostream>
#include <sstream>
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;
}

View file

@ -1,6 +1,4 @@
#include <string>
#include <vector>
#include <variant>
#include "Parser.h"
#include <fstream>
#include <sstream>
@ -27,7 +25,12 @@ namespace {
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<Object>
> value;
};
using Command = std::vector<Object>;
class Scene {
std::string name;
std::vector<Command> 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<unsigned, unsigned> 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<Object> array{};
std::vector<NVL::Parse::Object> 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<int> 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<std::vector<Parse>>& 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<Scene> ParseFile(const std::string& path) {
std::string f = read_file_to_string(path);
std::vector<Scene> 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;
}
}

View file

@ -1,6 +1,39 @@
#pragma once
#include <vector>
#include <variant>
#include <string>
#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<Object>
> value;
};
using Command = std::vector<Object>;
class Scene {
std::string name;
std::vector<Command> 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<Scene> ParseFile(const std::string& path);
}

View file

@ -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]

View file

@ -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