nuked the old one :))))))
This commit is contained in:
parent
f7cddaf6dc
commit
e182ffc14a
12 changed files with 433 additions and 512 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
.vs
|
||||
out
|
||||
documentation
|
|
@ -3,7 +3,7 @@
|
|||
#
|
||||
cmake_minimum_required (VERSION 3.8)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "",
|
||||
"ctestCommandArgs": ""
|
||||
"ctestCommandArgs": "",
|
||||
"intelliSenseMode": "windows-clang-x64"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -4,6 +4,6 @@
|
|||
cmake_minimum_required (VERSION 3.8)
|
||||
|
||||
# Add source to this project's executable.
|
||||
add_executable (NouVeL "NouVeL.cpp" "NVL.cpp" "NVL.h" "SymbolConfig.h")
|
||||
add_executable (NouVeL "NouVeL.cpp" "NVL.cpp")
|
||||
|
||||
# TODO: Add tests and install targets if needed.
|
||||
|
|
678
NouVeL/NVL.cpp
678
NouVeL/NVL.cpp
|
@ -1,330 +1,364 @@
|
|||
#include "NVL.h"
|
||||
#include "SymbolConfig.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <variant>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "SymbolConfig.h"
|
||||
|
||||
|
||||
const unsigned LOG_SIZE = 50;
|
||||
|
||||
namespace {
|
||||
struct ParseGroup {
|
||||
std::string accept;
|
||||
};
|
||||
|
||||
struct Match {
|
||||
std::string accept;
|
||||
|
||||
constexpr operator char() const {
|
||||
return accept[0];
|
||||
}
|
||||
constexpr bool operator== (const std::string& other) const {
|
||||
return accept == other;
|
||||
}
|
||||
};
|
||||
|
||||
const ParseGroup NUMERIC = { "1234567890" };
|
||||
const Match DECIMAL_DOT = { "." };
|
||||
const ParseGroup ALPHA = { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrst" };
|
||||
const Match ARRAY_OPEN = { "[" };
|
||||
const Match ARRAY_CLOSE = { "]" };
|
||||
const Match ARRAY_DELIM = { "," };
|
||||
const Match GROUP_OPEN = { "(" };
|
||||
const Match GROUP_CLOSE = { ")" };
|
||||
const Match QUOTE = { "\"" };
|
||||
const Match COMMENT_BEGIN = { "//" };
|
||||
const Match DIALOGUE_OPEN = { "<<-" };
|
||||
const Match DIALOGUE_CLOSE = { "->>" };
|
||||
const Match BEGIN = { "BEGIN" };
|
||||
const Match END = { "END" };
|
||||
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)
|
||||
};
|
||||
const Match NEWLINE = { "\n" };
|
||||
const Match ESCAPE = { "\\" };
|
||||
const ParseGroup ESCAPED = { "\\\"" };
|
||||
|
||||
enum class Type { Symbol, Number, String, Array };
|
||||
|
||||
std::string read_file_to_string(const std::string& path) {
|
||||
std::ifstream f(path);
|
||||
std::stringstream buffer;
|
||||
buffer << f.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
std::vector<std::string> split_string_by_lines(const std::string& str) {
|
||||
std::vector<std::string> lines;
|
||||
int pos = 0;
|
||||
int prev = 0;
|
||||
while ((pos = str.find(NEWLINE.accept[0], prev)) != std::string::npos)
|
||||
{
|
||||
lines.push_back(str.substr(prev, pos - prev));
|
||||
prev = pos + 1;
|
||||
}
|
||||
|
||||
lines.push_back(str.substr(prev));
|
||||
return lines;
|
||||
}
|
||||
|
||||
inline bool IsNumeric(const std::string& str) {
|
||||
bool had_dot = false;
|
||||
for (auto& c : str) {
|
||||
if (NUMERIC.accept.find(c) == std::string::npos) {
|
||||
if (c == DECIMAL_DOT.accept[0]) {
|
||||
if (had_dot)
|
||||
return false;
|
||||
else
|
||||
had_dot = true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
enum class ParseState { Root, Scene, Command, Dialogue, String, Array };
|
||||
|
||||
using NVL_Number = float;
|
||||
|
||||
struct Environment {
|
||||
private:
|
||||
std::unordered_map<std::string, void*> 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;
|
||||
|
||||
class Object {
|
||||
public:
|
||||
Type type;
|
||||
std::variant<
|
||||
NVL_Number,
|
||||
std::string,
|
||||
std::vector<Object>
|
||||
> value;
|
||||
};
|
||||
|
||||
struct Command {
|
||||
Object procedure;
|
||||
std::vector<Object> args{};
|
||||
};
|
||||
|
||||
class Scene {
|
||||
std::string name;
|
||||
std::vector<Command> commands{};
|
||||
public:
|
||||
Scene(const std::string& name) : name(name) {
|
||||
ENV.enter(name, this);
|
||||
}
|
||||
void append(const Command& c) {
|
||||
commands.push_back(c);
|
||||
}
|
||||
};
|
||||
|
||||
void SkipWS(const std::string& f, size_t& pos) {
|
||||
while (WS.accept.find(f[pos]) != std::string::npos)
|
||||
pos++;
|
||||
}
|
||||
|
||||
void SkipToNextLine(const std::string& f, size_t& pos) {
|
||||
while (f[pos] != NEWLINE.accept[0])
|
||||
pos++;
|
||||
pos++;
|
||||
}
|
||||
|
||||
void SkipComments(const std::string& f, size_t& pos) {
|
||||
SkipWS(f, pos);
|
||||
while (f.substr(pos, 2) == COMMENT_BEGIN.accept) {
|
||||
SkipToNextLine(f, pos);
|
||||
SkipWS(f, pos);
|
||||
}
|
||||
}
|
||||
|
||||
std::string GetToken(const std::string& f, size_t& pos) {
|
||||
SkipWS(f, pos);
|
||||
auto start = pos;
|
||||
while (++pos) {
|
||||
if (SEPARATOR.accept.find(f[pos]) != std::string::npos)
|
||||
break;
|
||||
};
|
||||
return f.substr(start, pos - start);
|
||||
}
|
||||
|
||||
std::string PeekToken(const std::string& f, size_t pos) {
|
||||
SkipWS(f, pos);
|
||||
auto start = pos;
|
||||
while (++pos) {
|
||||
if (SEPARATOR.accept.find(f[pos]) != std::string::npos)
|
||||
break;
|
||||
};
|
||||
return f.substr(start, pos - start);
|
||||
}
|
||||
|
||||
bool IsSymbol(const std::string& token) {
|
||||
if (ALPHA.accept.find(token[0]) == std::string::npos)
|
||||
return false;
|
||||
for (auto& i : token)
|
||||
if (SYMBOL.accept.find(i) == std::string::npos)
|
||||
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) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
*/
|
||||
}
|
||||
|
||||
std::string ParseString(ParseState& state, const std::string& f, size_t& pos) {
|
||||
SkipWS(f, pos);
|
||||
std::vector<int> discards{};
|
||||
auto start = ++pos; // skip opening quote
|
||||
do {
|
||||
if (f[pos] == char(QUOTE)) {
|
||||
break;
|
||||
}
|
||||
else if (f[pos] == char(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)) {
|
||||
throw std::runtime_error("Unclosed String");
|
||||
}
|
||||
} while (pos++);
|
||||
auto str = f.substr(start, pos++ - start);
|
||||
for (int i = 0; i < discards.size(); i++) {
|
||||
str.erase(discards[i] - start - i, 1);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
void ParseScene(ParseState& state, const std::string& f, size_t& pos, std::vector<Scene>& scenes) {
|
||||
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");
|
||||
Scene s{ scene_name };
|
||||
|
||||
state = ParseState::Command;
|
||||
while (PeekToken(f, pos) != END.accept) {
|
||||
ParseCommand(state, f, pos, s);
|
||||
}
|
||||
|
||||
GetToken(f, pos); // skip END
|
||||
|
||||
scenes.push_back(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 {
|
||||
std::unordered_map<char, Char_Type> intialize_map() {
|
||||
std::unordered_map<char, Char_Type> Char_Map;
|
||||
for (char c : "abcdefghijklmnopqrstuvwxyz") {
|
||||
Char_Map[c] = Char_Type::LOWERCASE;
|
||||
}
|
||||
for (char c : "ABCDEFGHIJKLMNOPQRSTUVWXYZ") {
|
||||
Char_Map[c] = Char_Type::UPPERCASE;
|
||||
}
|
||||
for (char c : "1234567890") {
|
||||
Char_Map[c] = Char_Type::NUMERIC;
|
||||
}
|
||||
for (char c : "<{([") {
|
||||
Char_Map[c] = Char_Type::DELIM_BEG;
|
||||
}
|
||||
for (char c : ">})]") {
|
||||
Char_Map[c] = Char_Type::DELIM_END;
|
||||
}
|
||||
for (char c : " \t") {
|
||||
Char_Map[c] = Char_Type::WHITESPACE;
|
||||
}
|
||||
for (char c : "\'\"") {
|
||||
Char_Map[c] = Char_Type::QUOTE;
|
||||
}
|
||||
Char_Map['\n'] = Char_Type::RETURN;
|
||||
Char_Map['.'] = Char_Type::DOT;
|
||||
Char_Map['_'] = Char_Type::UNDERSCORE;
|
||||
return Char_Map;
|
||||
}
|
||||
void ParseFile(const std::string& path) {
|
||||
std::string f = read_file_to_string(path);
|
||||
ParseState state = ParseState::Root;
|
||||
|
||||
const auto Char_Map = intialize_map();
|
||||
|
||||
Char_Type determine_type(char c) {
|
||||
return Char_Map.at(c);
|
||||
}
|
||||
|
||||
Char_Type determine_type_escaped(char c) {
|
||||
if (Char_Map.find(c) != Char_Map.end())
|
||||
return Char_Map.at(c);
|
||||
else
|
||||
return Char_Type::LOWERCASE;
|
||||
}
|
||||
|
||||
Token::Token(char c, Char_Type type, int line) {
|
||||
value = std::string{ c };
|
||||
this->line = line;
|
||||
switch (type) {
|
||||
case Char_Type::DELIM_BEG:
|
||||
switch (c) {
|
||||
case '<':
|
||||
this->type = Token_Type::DIALOGUE_BEG;
|
||||
break;
|
||||
case '{':
|
||||
this->type = Token_Type::SEQUENCE_BEG;
|
||||
break;
|
||||
case '(':
|
||||
this->type = Token_Type::BRACKET_BEG;
|
||||
break;
|
||||
case '[':
|
||||
this->type = Token_Type::LIST_BEG;
|
||||
break;
|
||||
default:
|
||||
std::cout << "Failed to construct token: " << c << '\n'; // error, cannot do error codes in constructor..
|
||||
}
|
||||
break;
|
||||
case Char_Type::DELIM_END:
|
||||
switch (c) {
|
||||
case '>':
|
||||
this->type = Token_Type::DIALOGUE_END;
|
||||
break;
|
||||
case '}':
|
||||
this->type = Token_Type::SEQUENCE_END;
|
||||
break;
|
||||
case ')':
|
||||
this->type = Token_Type::BRACKET_END;
|
||||
break;
|
||||
case ']':
|
||||
this->type = Token_Type::LIST_END;
|
||||
break;
|
||||
default:
|
||||
std::cout << "Failed to construct token: " << c << '\n'; // error
|
||||
}
|
||||
break;
|
||||
default:
|
||||
std::cout << "Failed to construct token: " << c << '\n'; // error
|
||||
}
|
||||
}
|
||||
|
||||
NVLError tokenize(std::string file, std::vector<Token>& tokens) {
|
||||
Token_Type state = Token_Type::STANDBY;
|
||||
|
||||
std::string current;
|
||||
|
||||
int current_line = 1;
|
||||
|
||||
for (int i = 0; i < file.length(); i++ ) {
|
||||
switch (state) {
|
||||
case Token_Type::STANDBY:
|
||||
current = "";
|
||||
|
||||
switch (determine_type(file[i])) {
|
||||
case Char_Type::LOWERCASE:
|
||||
case Char_Type::UPPERCASE:
|
||||
case Char_Type::UNDERSCORE:
|
||||
state = Token_Type::ID;
|
||||
current += file[i];
|
||||
break;
|
||||
case Char_Type::NUMERIC:
|
||||
state = Token_Type::INT;
|
||||
current += file[i];
|
||||
break;
|
||||
case Char_Type::DELIM_BEG:
|
||||
tokens.push_back(Token(file[i], Char_Type::DELIM_BEG, current_line));
|
||||
break;
|
||||
case Char_Type::DELIM_END:
|
||||
tokens.push_back(Token(file[i], Char_Type::DELIM_END, current_line));
|
||||
break;
|
||||
case Char_Type::RETURN:
|
||||
tokens.push_back(Token('\n', Token_Type::RETURN, current_line));
|
||||
current_line++;
|
||||
break;
|
||||
case Char_Type::WHITESPACE:
|
||||
break;
|
||||
case Char_Type::SEMICOLON:
|
||||
state = Token_Type::COMMENT;
|
||||
break;
|
||||
case Char_Type::QUOTE:
|
||||
state = Token_Type::STRING;
|
||||
tokens.push_back(Token(file[i], Token_Type::QUOTE, current_line));
|
||||
break;
|
||||
case Char_Type::DOT:
|
||||
state = Token_Type::FLOAT;
|
||||
current += file[i];
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Token_Type::ID:
|
||||
switch (determine_type(file[i])) {
|
||||
case Char_Type::LOWERCASE:
|
||||
case Char_Type::UPPERCASE:
|
||||
case Char_Type::NUMERIC:
|
||||
case Char_Type::UNDERSCORE:
|
||||
current += file[i];
|
||||
break;
|
||||
case Char_Type::DELIM_BEG:
|
||||
return { ErrCode::PARSE_ERROR, current_line };
|
||||
case Char_Type::DELIM_END:
|
||||
tokens.push_back(Token(current, state, current_line));
|
||||
tokens.push_back(Token(file[i], Char_Type::DELIM_END, current_line));
|
||||
state = Token_Type::STANDBY;
|
||||
break;
|
||||
case Char_Type::RETURN:
|
||||
tokens.push_back(Token(current, state, current_line));
|
||||
tokens.push_back(Token('\n', Token_Type::RETURN, current_line));
|
||||
current_line++;
|
||||
state = Token_Type::STANDBY;
|
||||
break;
|
||||
case Char_Type::WHITESPACE:
|
||||
tokens.push_back(Token(current, state, current_line));
|
||||
state = Token_Type::STANDBY;
|
||||
break;
|
||||
case Char_Type::SEMICOLON:
|
||||
case Char_Type::QUOTE:
|
||||
case Char_Type::DOT:
|
||||
return { ErrCode::PARSE_ERROR, current_line };
|
||||
}
|
||||
break;
|
||||
case Token_Type::INT:
|
||||
switch (determine_type(file[i])) {
|
||||
case Char_Type::LOWERCASE:
|
||||
case Char_Type::UPPERCASE:
|
||||
case Char_Type::UNDERSCORE:
|
||||
case Char_Type::DELIM_BEG:
|
||||
case Char_Type::SEMICOLON:
|
||||
case Char_Type::QUOTE:
|
||||
return { ErrCode::PARSE_ERROR, current_line };
|
||||
case Char_Type::NUMERIC:
|
||||
current += file[i];
|
||||
break;
|
||||
case Char_Type::DELIM_END:
|
||||
tokens.push_back(Token(current, state, current_line));
|
||||
tokens.push_back(Token(file[i], Char_Type::DELIM_END, current_line));
|
||||
state = Token_Type::STANDBY;
|
||||
break;
|
||||
case Char_Type::RETURN:
|
||||
tokens.push_back(Token(current, state, current_line));
|
||||
tokens.push_back(Token('\n', Token_Type::RETURN, current_line));
|
||||
current_line++;
|
||||
state = Token_Type::STANDBY;
|
||||
break;
|
||||
case Char_Type::WHITESPACE:
|
||||
tokens.push_back(Token(current, state, current_line));
|
||||
state = Token_Type::STANDBY;
|
||||
break;
|
||||
case Char_Type::DOT:
|
||||
state = Token_Type::FLOAT;
|
||||
current += file[i];
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Token_Type::FLOAT:
|
||||
switch (determine_type(file[i])) {
|
||||
case Char_Type::LOWERCASE:
|
||||
case Char_Type::UPPERCASE:
|
||||
case Char_Type::UNDERSCORE:
|
||||
case Char_Type::SEMICOLON:
|
||||
case Char_Type::QUOTE:
|
||||
case Char_Type::DOT:
|
||||
return { ErrCode::PARSE_ERROR, current_line };
|
||||
case Char_Type::NUMERIC:
|
||||
current += file[i];
|
||||
break;
|
||||
case Char_Type::DELIM_BEG:
|
||||
return { ErrCode::PARSE_ERROR, current_line };
|
||||
case Char_Type::DELIM_END:
|
||||
tokens.push_back(Token(current, state, current_line));
|
||||
tokens.push_back(Token(file[i], Char_Type::DELIM_END, current_line));
|
||||
state = Token_Type::STANDBY;
|
||||
break;
|
||||
case Char_Type::RETURN:
|
||||
tokens.push_back(Token(current, state, current_line));
|
||||
tokens.push_back(Token('\n', Token_Type::RETURN, current_line));
|
||||
current_line++;
|
||||
state = Token_Type::STANDBY;
|
||||
break;
|
||||
case Char_Type::WHITESPACE:
|
||||
tokens.push_back(Token(current, state, current_line));
|
||||
state = Token_Type::STANDBY;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Token_Type::STRING:
|
||||
if (determine_type_escaped(file[i]) == Char_Type::RETURN)
|
||||
return { ErrCode::PARSE_ERROR, current_line };
|
||||
if (determine_type_escaped(file[i]) == Char_Type::QUOTE && current.back() != '\\') {
|
||||
tokens.push_back(Token(current, state, current_line));
|
||||
tokens.push_back(Token('\"', Token_Type::QUOTE, current_line));
|
||||
state = Token_Type::STANDBY;
|
||||
}
|
||||
else {
|
||||
current += file[i];
|
||||
}
|
||||
break;
|
||||
case Token_Type::DIALOGUE:
|
||||
// if (determine_type(file[i]) == Char_Type::)
|
||||
;
|
||||
break;
|
||||
case Token_Type::COMMENT:
|
||||
if (determine_type_escaped(file[i]) == Char_Type::RETURN) {
|
||||
tokens.push_back(Token('\n', Token_Type::RETURN, current_line));
|
||||
current_line++;
|
||||
}
|
||||
state = Token_Type::STANDBY;
|
||||
break;
|
||||
default:
|
||||
return { ErrCode::PARSE_ERROR, current_line };
|
||||
}
|
||||
}
|
||||
return { ErrCode::SUCCESS, -1 };
|
||||
}
|
||||
|
||||
namespace Parse {
|
||||
NVLError Symbol(Token** token, Node& root) {
|
||||
NVLError status = { ErrCode::PARSE_ERROR, (*token)->line };
|
||||
|
||||
return status;
|
||||
std::vector<Scene> 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);
|
||||
}
|
||||
|
||||
NVLError Dialogue(Token** token, Node& root) {
|
||||
NVLError status = { ErrCode::SYNTAX_ERROR, (*token)->line };
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
NVLError Line(Token** token, Node& root) {
|
||||
NVLError status = { ErrCode::SYNTAX_ERROR, (*token)->line };
|
||||
|
||||
while ((*token)->type != Token_Type::RETURN) {
|
||||
if ((*token)->type == Token_Type::LIST_END) {
|
||||
(*token)++;
|
||||
break;
|
||||
}
|
||||
|
||||
if ((*token)->type == Token_Type::LIST_BEG) {
|
||||
(*token)++;
|
||||
status = Parse::Line(token, root);
|
||||
}
|
||||
else if ((*token)->type == Token_Type::DIALOGUE_BEG) {
|
||||
(*token)++;
|
||||
status = Parse::Dialogue(token, root);
|
||||
}
|
||||
else {
|
||||
status = Parse::Symbol(token, root);
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
NVLError SequeneceName(Token** token, Node& root) {
|
||||
NVLError status = { ErrCode::SYNTAX_ERROR, (*token)->line };
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
NVLError Sequenece(Token** token, Node& root) {
|
||||
NVLError status = { ErrCode::SYNTAX_ERROR, (*token)->line };
|
||||
|
||||
SequeneceName(token, root);
|
||||
|
||||
while ((*token)->type != Token_Type::SEQUENCE_END)
|
||||
status = Parse::Line(token, root);
|
||||
|
||||
(*token)++;
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
NVLError append_tree(std::vector<Token> tokens, Node& tree) {
|
||||
NVLError status;
|
||||
for (Token* i = &(tokens.front()); i < &(tokens.back()); i++)
|
||||
status = Parse::Sequenece(&i, tree);
|
||||
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
95
NouVeL/NVL.h
95
NouVeL/NVL.h
|
@ -1,99 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <variant>
|
||||
#include <deque>
|
||||
|
||||
namespace NVL {
|
||||
enum struct Char_Type {
|
||||
LOWERCASE,
|
||||
UPPERCASE,
|
||||
NUMERIC,
|
||||
DELIM_BEG,
|
||||
DELIM_END,
|
||||
RETURN,
|
||||
WHITESPACE,
|
||||
SEMICOLON,
|
||||
QUOTE,
|
||||
DOT,
|
||||
UNDERSCORE
|
||||
};
|
||||
|
||||
enum struct ErrCode {
|
||||
SUCCESS,
|
||||
PARSE_ERROR,
|
||||
SYNTAX_ERROR,
|
||||
EVAL_ERROR
|
||||
};
|
||||
|
||||
struct NVLError {
|
||||
ErrCode code;
|
||||
int line;
|
||||
|
||||
};
|
||||
|
||||
enum struct Token_Type {
|
||||
STANDBY, // Only used in the tokenizer, initial state
|
||||
WS,
|
||||
RETURN,
|
||||
ID,
|
||||
INT,
|
||||
FLOAT,
|
||||
BOOL,
|
||||
STRING,
|
||||
SEQUENCE_BEG,
|
||||
SEQUENCE_END,
|
||||
DIALOGUE_BEG,
|
||||
DIALOGUE_END,
|
||||
LIST_BEG,
|
||||
LIST_END,
|
||||
BRACKET_BEG,
|
||||
BRACKET_END,
|
||||
QUOTE,
|
||||
DIALOGUE, // Do not want to deal with characters in all languages separately, essentially just strings
|
||||
COMMENT
|
||||
};
|
||||
|
||||
struct Token {
|
||||
std::string value;
|
||||
Token_Type type;
|
||||
int line;
|
||||
|
||||
Token(std::string str, Token_Type type, int line) : value(str), type(type), line(line) {};
|
||||
Token(char c, Token_Type type, int line) : value(std::string{ c }), type(type), line(line) {};
|
||||
Token(char c, Char_Type type, int line);
|
||||
};
|
||||
|
||||
NVLError tokenize(std::string file, std::vector<Token>& tokens);
|
||||
|
||||
enum struct Node_Type {
|
||||
ROOT,
|
||||
SEQUENCE,
|
||||
CALL,
|
||||
LIST,
|
||||
ID,
|
||||
LITERAL
|
||||
};
|
||||
|
||||
struct Node {
|
||||
std::vector<Node> children;
|
||||
Node_Type type;
|
||||
std::variant<
|
||||
std::string,
|
||||
int,
|
||||
float,
|
||||
bool
|
||||
> Value;
|
||||
};
|
||||
|
||||
NVLError append_tree(std::vector<Token> tokens, Node& tree);
|
||||
|
||||
namespace Parse {
|
||||
NVLError Sequenece(Token** token, Node& root);
|
||||
NVLError Line(Token** token, Node& root);
|
||||
NVLError Dialogue(Token** token, Node& root);
|
||||
NVLError Symbol(Token** token, Node& root);
|
||||
}
|
||||
void ParseFile(const std::string& path);
|
||||
}
|
|
@ -1,20 +1,9 @@
|
|||
#include "NVL.h"
|
||||
#include <fstream>
|
||||
|
||||
int main()
|
||||
{
|
||||
const std::string PJ_DIR = "E:\\Archive\\Projects\\NouVeL\\";
|
||||
|
||||
std::ifstream fs(PJ_DIR + "test_utf8.nvl");
|
||||
std::string file((std::istreambuf_iterator<char>(fs)), std::istreambuf_iterator<char>());
|
||||
|
||||
std::vector<NVL::Token> tokens;
|
||||
|
||||
NVL::tokenize(file, tokens);
|
||||
|
||||
for (auto& c : tokens) {
|
||||
std::cout << c.value << ": " << (int) c.type << "\n";
|
||||
}
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
const std::string PJ_DIR = "E:\\Archive\\Projects\\NouVeL\\NouVeL\\";
|
||||
NVL::ParseFile(PJ_DIR + "test.nvl");
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1 +1,8 @@
|
|||
#pragma once
|
||||
/*enum Symbol {
|
||||
BeginScene,
|
||||
EndScene,
|
||||
SetVar,
|
||||
Say,
|
||||
SwitchSpeaker
|
||||
};*/
|
66
NouVeL/spec.nvl
Normal file
66
NouVeL/spec.nvl
Normal file
|
@ -0,0 +1,66 @@
|
|||
// 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 ->>
|
||||
<<-
|
||||
// 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 me 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 ("Apple" RouteA) ("Orange" RouteB) (default RouteC))
|
||||
|
||||
END
|
3
NouVeL/test.nvl
Normal file
3
NouVeL/test.nvl
Normal file
|
@ -0,0 +1,3 @@
|
|||
BEGIN Scene1
|
||||
hello "String \\ sdsd \" \\ \" sds"
|
||||
END
|
|
@ -1,4 +0,0 @@
|
|||
# Implementation
|
||||
It is certainly not a good idea to parse and interpret NVL files on the go. To reduce this overhead, I'm still thinking what I should do.
|
||||
|
||||
Candidate: compile NVL files into another format (perhaps binary?) that is fast to use on the go
|
|
@ -1,83 +0,0 @@
|
|||
# NVL Syntax
|
||||
|
||||
Every sequence is wrapped in blocks, and they can be contained in different files. All NVL files will be supplied to the parser, make sure to avoid name conflicts even in different files because of this. All definitions at the highest level will be sequences.
|
||||
```
|
||||
Intro { // "Intro" is automatically the name of this sequence
|
||||
FOO
|
||||
BAR
|
||||
}
|
||||
```
|
||||
|
||||
## Dialogue
|
||||
Dialogue is common enough, so we provide special syntax for them, there is nothing special about this special syntax, it is defined the same way as other commands as we will see. `[]` is used to specify who is speaking.
|
||||
|
||||
```
|
||||
Intro {
|
||||
[Alice]
|
||||
> Wow! I hope this project goes somewhere!
|
||||
> Pick a project so difficult that nobody believes you can do it
|
||||
[Bob]
|
||||
> Channel your "I'll fucking show them" energy.
|
||||
}
|
||||
```
|
||||
|
||||
## Implication of pauses
|
||||
As mentioned in the last section this is important.
|
||||
|
||||
It is recommended that `[]` (and other commands) does not do anything more than update the "who is talking" information and `>` be the only command that provide a pause.
|
||||
```
|
||||
Intro {
|
||||
[Alice] // Changes the speaker and continues to read the next line
|
||||
> Wow! I hope this project goes somewhere! // Changes the current text (or text position, depending on how you want to define your parser) and also PAUSE
|
||||
> Pick a project so difficult that nobody believes you can do it // ditto
|
||||
[Bob] // Changes the speaker and continues to read the next line
|
||||
> Channel your "I'll fucking show them" energy. // Change text and pause
|
||||
}
|
||||
```
|
||||
The comment syntax is the same as in C.
|
||||
|
||||
## Commands
|
||||
Aside from who is the speaker and what are they speaking, the frontend still needs more information, such as what happens on the screen, what animation sequence to play, what background should the scene have, what sprites should show and how should they show. Only the strictly narrative sections of the game (i.e. things that have to do with storytelling) should be defined as commands in NVL, everything else ought to be a part of the frontend, such as system logic, UI, etc.
|
||||
|
||||
How any of these actions would be handled is done through commands.
|
||||
A command definition file defines the commands that are used in the NVL file and what should happen when they are called.
|
||||
```
|
||||
Intro {
|
||||
SET_COMPOSITE_TRACK 0 Source BG_ROOM01
|
||||
SET_AUDIO_TRACK 0 Source Easygoing Loop
|
||||
|
||||
SET_COMPOSITE_TRACK 1 Source Alice_N_01
|
||||
SET_AUDIO_TRACK 1 Source Alice_Voice_00 Once
|
||||
[Alice]
|
||||
> Wow! I hope this project goes somewhere!
|
||||
> Pick a project so difficult that nobody believes you can do it
|
||||
|
||||
SET_COMPOSITE_TRACK 2 Source Bob_N_01
|
||||
SET_AUDIO_TRACK 1 Source Bob_Voice_00 Once
|
||||
[Bob]
|
||||
> Channel your "I'll fucking show them" energy.
|
||||
}
|
||||
```
|
||||
As a matter of style I have made commands upper-case, this is not required. Note that commands are really just function calls but the syntax says that arguments are not called in parentheses. There are also no semicolons at the end of each line, this implies that line breaks are part of the syntax.
|
||||
|
||||
`SET_COMPOSITE_TRACK` and `SET_AUDIO_TRACK` are facilities used in the ADVect engine, they each correspond to the audio mixing tracks and video composite tracks.
|
||||
|
||||
It is important to reiterate that the `>` or the `[]` syntax is just another command whose behavior can be defined as the developer pleases, for example one could choose to raise the hierarchy of the sprite of the character speaking in the composite when a new block spoken by that character is entered, this wouldn't work with the current model of ADVect but it is something that can be done if the frontend has such an association between the speaker information and the sprites.
|
||||
|
||||
```c
|
||||
void SET_COMPOSITE_TRACK(int track_index, string track_property, string value) {
|
||||
// track property of S means source, other properties can be V for volume etc..
|
||||
...
|
||||
}
|
||||
void SET_AUDIO_TRACK(int track_index, string track_property, string value, optional<string> Behavior = "") {
|
||||
...
|
||||
}
|
||||
void SET_LINE() {
|
||||
// defined somewhere to be the alias of the > syntax
|
||||
...
|
||||
}
|
||||
void SET_SPEAKER() {
|
||||
// defined somewhere to be the alias of the [] syntax
|
||||
...
|
||||
}
|
||||
```
|
Loading…
Add table
Reference in a new issue