NouVeL/NouVeL/NVL.cpp

515 lines
12 KiB
C++
Raw Normal View History

2021-05-14 16:51:04 -04:00
#include <map>
2021-05-14 11:47:54 -04:00
#include "NVL.h"
2021-05-14 16:51:04 -04:00
namespace {
2021-05-16 12:59:18 -04:00
// general helpers, may move these into a different translation unit
2021-05-16 13:28:40 -04:00
bool str_test_every_char_not(int (*test)(int), std::string str)
2021-05-15 17:48:37 -04:00
{
2021-05-16 12:59:18 -04:00
for (auto& x : str)
{
2021-05-16 13:28:40 -04:00
if (!(*test)(x))
2021-05-16 12:59:18 -04:00
return false;
}
return true;
}
void indent_loop(int indent)
{
for (int i = 0; i < indent; i++)
{
std::cout << "\t";
}
2021-05-15 17:48:37 -04:00
}
2021-05-19 17:02:57 -04:00
int query_ifstream_line_number(std::ifstream& stream)
{
std::streampos pos = stream.tellg();
stream.seekg(0); // we should not be eof
int i = 0;
char c{};
while (stream.tellg() < pos)
{
if (c == '\n')
i++;
stream.get(c);
}
return i;
}
2021-05-15 17:48:37 -04:00
2021-05-16 12:59:18 -04:00
// facilities for scopers
2021-05-14 16:51:04 -04:00
const std::map<char, char> SCOPER_MAP = {
{'{', '}' }, // CURLY
{'<', '>'}, // ANGLED
{'(', ')'}, // BRACKET
{'[', ']'}, // SQUARE
{'\'', '\''}, // SQUOTE
{'\"', '\"'} // DQUOTE
};
bool need_escape(char c)
{
2021-05-15 17:48:37 -04:00
// todo: refactor, too cryptic
2021-05-14 16:51:04 -04:00
return (c == '\'' || c == '\"');
}
struct ScopeContext {
std::vector<char> Scope;
};
2021-05-15 17:48:37 -04:00
std::streampos scope_cope(char scope_char, std::ifstream& stream)
2021-05-14 11:47:54 -04:00
{
2021-05-15 17:48:37 -04:00
// important
// scope_cope() expects the scope opener to already have been fetched and stored in scope_char
// i.o.w. it wont be able to cope properly because there will be one extra level of scope that it cannot match
2021-05-14 16:51:04 -04:00
std::streampos initial = stream.tellg();
std::streampos final;
char c{};
ScopeContext current_scope;
current_scope.Scope.push_back(scope_char);
while (!current_scope.Scope.empty()) {
stream.get(c);
2021-05-14 23:17:55 -04:00
2021-05-15 17:48:37 -04:00
// push scope if opening scoper is found
2021-05-15 01:51:02 -04:00
bool set_scope_on_this_iter = false;
2021-05-15 17:54:24 -04:00
// there cannot be any more scope inside of quoted strings
if (current_scope.Scope.back() != '\'' && current_scope.Scope.back() != '\"') {
2021-05-14 23:17:55 -04:00
for (auto const& x : SCOPER_MAP)
{
if (c == x.first) {
2021-05-15 01:51:02 -04:00
current_scope.Scope.push_back(c);
set_scope_on_this_iter = true;
2021-05-14 23:17:55 -04:00
break;
}
2021-05-14 16:51:04 -04:00
}
}
2021-05-15 17:48:37 -04:00
// pop last scope if ending scoper is found
// set_scope_on_this_iter to prevent immediately popping scopers that have the same closers and openers
2021-05-15 01:51:02 -04:00
if (!set_scope_on_this_iter && c == SCOPER_MAP.at(current_scope.Scope.back()))
current_scope.Scope.pop_back();
2021-05-14 16:51:04 -04:00
if (c == '\\') // encounters an escaped sequence
{
char cc = stream.peek();
if (need_escape(cc)) // see if the escaped char happens to be a scoper
{
stream.get(c); // skip it, since whoever wrote the script escaped it
}
}
2021-05-19 17:02:57 -04:00
// cannot match all the scopes and has read to the end of the file
2021-05-14 16:51:04 -04:00
if (stream.eof())
2021-05-19 17:02:57 -04:00
throw std::runtime_error("Can't cope with scope at line " + std::to_string(query_ifstream_line_number(stream)));
2021-05-14 16:51:04 -04:00
}
2021-05-14 11:47:54 -04:00
2021-05-14 16:51:04 -04:00
final = stream.tellg();
stream.seekg(initial);
2021-05-15 01:51:02 -04:00
2021-05-14 16:51:04 -04:00
return final;
2021-05-14 11:47:54 -04:00
}
2021-05-16 12:59:18 -04:00
// facilities for parsing
2021-05-16 13:28:40 -04:00
void skip_ws(std::ifstream& stream)
{
stream >> std::ws;
}
2021-05-19 17:02:57 -04:00
bool only_ws_before_next_newline(std::ifstream& stream) {
if (!isspace(stream.peek()))
return false;
std::streampos initial = stream.tellg();
char c{};
stream.get(c);
while (c != '\n')
{
stream.get(c);
if (!isspace(c)) {
stream.seekg(initial);
return false;
}
}
stream.seekg(initial);
return true;
}
2021-05-16 13:28:40 -04:00
2021-05-14 20:44:09 -04:00
std::string read_sequence_name(std::ifstream& nvl)
2021-05-14 11:47:54 -04:00
{
2021-05-15 17:48:37 -04:00
// this function will move the reading head of nvl, this is hard to keep track of but simplifies the program
skip_ws(nvl);
2021-05-14 16:51:04 -04:00
std::string token;
2021-05-15 17:48:37 -04:00
char c{};
nvl.get(c);
while (c != ' ' && c != '{' && c != '\n')
2021-05-14 16:51:04 -04:00
{
2021-05-15 17:48:37 -04:00
token += c;
nvl.get(c);
2021-05-14 16:51:04 -04:00
}
2021-05-15 17:48:37 -04:00
nvl.putback(c);
2021-05-14 16:51:04 -04:00
return token;
}
2021-05-15 13:38:10 -04:00
2021-05-19 17:02:57 -04:00
void skip_comment(std::ifstream& nvl, char& c)
2021-05-15 13:38:10 -04:00
{
if (nvl.peek() == ';')
{
nvl.get(c);
2021-05-15 17:48:37 -04:00
while (c != '\n')
{
nvl.get(c);
}
skip_ws(nvl);
2021-05-19 17:02:57 -04:00
skip_comment(nvl, c);
2021-05-15 17:48:37 -04:00
skip_ws(nvl);
}
}
2021-05-14 16:51:04 -04:00
}
namespace NVL
{
2021-05-19 17:02:57 -04:00
void parse_Dialogue(Context old_context, std::ifstream& nvl)
{
if (std::get<Call*>(old_context.Scope_Hierarchy.back())->Objects.size() != 1)
throw std::runtime_error("Failed to parse dialogue at line " + std::to_string(query_ifstream_line_number(nvl)));
const Object say_command = Object("Say");
Object speaker = std::get<Call*>(old_context.Scope_Hierarchy.back())->Objects.front(); // Copy speaker object
Sequence* parent_sequence = std::get<Sequence*>(old_context.Scope_Hierarchy.rbegin()[1]);
// old call has never been pushed, we need to exit parse_Call right away when we finish parsing the dialogue, because this already deals with calls
char c{};
nvl.get(c); // get <
std::streampos end = scope_cope('<', nvl);
skip_ws(nvl);
do
{
parent_sequence->Calls.push_back(Call());
parent_sequence->Calls.back().Objects.push_back(say_command);
parent_sequence->Calls.back().Objects.push_back(speaker);
std::string text;
if (nvl.peek() == '>')
break;
// im not sure if the execution order here will be problematic, seems to work ok
while (!( (c == '\n' && only_ws_before_next_newline(nvl)) || nvl.peek() == '>'))
{
nvl.get(c);
text += c;
}
while (isspace(text.back()))
text.erase(text.size()-1, 1);
for (size_t i = text.find('\n'); i != std::string::npos; i = text.find('\n'))
{
int e = 0;
std::cout << text.size();
while (isspace(text.at(i + e)))
e++;
text.erase(i, e);
text.insert(i, " ");
}
parent_sequence->Calls.back().Objects.push_back(Object(text));
skip_ws(nvl);
} while (nvl.tellg() < end - static_cast<std::streampos>(1));
nvl.get(c); // get >
skip_ws(nvl);
return;
}
2021-05-14 23:17:55 -04:00
template <bool is_parent_call, bool is_symbol>
2021-05-14 20:44:09 -04:00
void parse_Object(Context parent_context, std::ifstream& nvl)
2021-05-14 16:51:04 -04:00
{
2021-05-19 17:02:57 -04:00
// chevron dialogue is now handled in a separate function
2021-05-14 20:44:09 -04:00
Object this_object;
Context this_context = parent_context;
2021-05-17 15:16:02 -04:00
this_context.Scope_Hierarchy.push_back(&this_object);
2021-05-14 23:17:55 -04:00
2021-05-15 17:48:37 -04:00
std::variant<std::string, std::vector<Object>> content = ""; // init to empty str
char c{};
2021-05-15 17:48:37 -04:00
// early exit for comments
if (nvl.peek() == ';' && is_parent_call)
{
while (nvl.peek() != '\n')
nvl.get(c);
return;
}
2021-05-15 17:48:37 -04:00
2021-05-14 23:17:55 -04:00
switch (nvl.peek())
2021-05-14 16:51:04 -04:00
{
2021-05-15 01:51:02 -04:00
case '[': // List
2021-05-19 17:02:57 -04:00
{
2021-05-15 17:48:37 -04:00
nvl.get(c);
2021-05-19 17:02:57 -04:00
std::streampos end = scope_cope('[', nvl);
2021-05-14 23:17:55 -04:00
while (nvl.tellg() < end - static_cast<std::streampos>(1))
2021-05-14 20:44:09 -04:00
{
parse_Object<false, false>(this_context, nvl);
2021-05-15 17:48:37 -04:00
skip_ws(nvl);
2021-05-14 20:44:09 -04:00
}
2021-05-15 17:48:37 -04:00
nvl.get(c); // skip ending scoper (']')
2021-05-14 20:44:09 -04:00
break;
2021-05-19 17:02:57 -04:00
}
2021-05-14 23:17:55 -04:00
case '\"': // String
2021-05-19 17:02:57 -04:00
{
2021-05-14 23:17:55 -04:00
nvl.get(c);
2021-05-19 17:02:57 -04:00
std::streampos end = scope_cope('\"', nvl);
2021-05-14 23:17:55 -04:00
while (nvl.tellg() < end - static_cast<std::streampos>(1))
2021-05-14 20:44:09 -04:00
{
nvl.get(c);
2021-05-15 17:48:37 -04:00
// do not concat escaping '\' to content
2021-05-19 17:02:57 -04:00
if (c == '\\' && (nvl.peek() == '\'' || nvl.peek() == '\"'))
2021-05-15 01:51:02 -04:00
continue;
2021-05-15 17:48:37 -04:00
2021-05-14 20:44:09 -04:00
content = std::get<std::string>(content) + c;
}
2021-05-15 17:48:37 -04:00
nvl.get(c); // skip ending scoper
2021-05-14 23:17:55 -04:00
2021-05-14 20:44:09 -04:00
this_object.Value = std::get<std::string>(content);
break;
2021-05-19 17:02:57 -04:00
}
2021-05-14 20:44:09 -04:00
default:
2021-05-19 17:02:57 -04:00
{
2021-05-15 17:48:37 -04:00
nvl.get(c);
while (c != ' ' && c != '\n' && c != '}' && c != ']' && c != ',')
2021-05-14 20:44:09 -04:00
{
2021-05-19 17:02:57 -04:00
content = std::get<std::string>(content) + c;
2021-05-15 17:48:37 -04:00
nvl.get(c);
2021-05-14 20:44:09 -04:00
}
2021-05-15 17:48:37 -04:00
// ']' handled in next object parse, '}' handled in sequence parse, '\n' will be skipped somewhere with skip_ws(), where? idk
2021-05-19 17:02:57 -04:00
if (c == ']' || c == '}' || c == '\n')
{
2021-05-14 20:44:09 -04:00
nvl.putback(c);
}
2021-05-14 23:17:55 -04:00
try
{
2021-05-15 17:48:37 -04:00
// try number
2021-05-14 23:17:55 -04:00
this_object.Value = std::stof(std::get<std::string>(content));
}
catch (std::exception)
{
2021-05-19 17:02:57 -04:00
if (std::get<std::string>(content) == "true")
2021-05-14 23:17:55 -04:00
{
this_object.Value = true;
}
else if (std::get<std::string>(content) == "false") {
this_object.Value = false;
}
else
{
2021-05-15 17:48:37 -04:00
// early return if string is empty, this should only happen if calling from an object (not a call)
2021-05-19 17:02:57 -04:00
if (str_test_every_char_not(&isspace, std::get<std::string>(content)))
2021-05-16 12:36:44 -04:00
return;
2021-05-15 17:48:37 -04:00
// default case if content does not match keywords
2021-05-14 23:17:55 -04:00
this_object.Value = std::get<std::string>(content);
}
}
2021-05-14 20:44:09 -04:00
break;
2021-05-14 23:17:55 -04:00
}
2021-05-19 17:02:57 -04:00
}
2021-05-14 16:51:04 -04:00
2021-05-14 23:17:55 -04:00
this_object.Is_Symbol = is_symbol;
2021-05-14 20:44:09 -04:00
if (is_parent_call)
2021-05-17 15:16:02 -04:00
std::get<Call*>(parent_context.Scope_Hierarchy.back())->Objects.push_back(this_object);
else if (std::get<Object*>(parent_context.Scope_Hierarchy.back())->Value.index() == 4) // 4 for list, indicates that parent is an object that is already a vector of objects
std::get<std::vector<Object>>(
std::get<Object*>(parent_context.Scope_Hierarchy.back())->Value
).push_back(this_object);
2021-05-15 17:48:37 -04:00
else // parent is not yet vector, initialize as (change from nil to) vector
2021-05-17 15:16:02 -04:00
std::get<Object*>(parent_context.Scope_Hierarchy.back())->Value = std::vector<Object> { this_object };
2021-05-19 17:02:57 -04:00
// the case for chevron dialogues are handled in the switch, there is an early return in the chevron case
2021-05-14 20:44:09 -04:00
}
void parse_Call(Context parent_context, std::ifstream& nvl)
{
Call this_call;
Context this_context = parent_context;
2021-05-17 15:16:02 -04:00
this_context.Scope_Hierarchy.push_back(&this_call);
2021-05-14 23:17:55 -04:00
2021-05-19 17:02:57 -04:00
skip_ws(nvl);
// do not push anything if line starts with a comment
char c{};
skip_comment(nvl, c);
// early exit if sequence is empty
if (nvl.peek() == '}')
return;
2021-05-14 20:44:09 -04:00
while (!(nvl.peek() == '\n' || nvl.peek() == '}'))
2021-05-14 20:44:09 -04:00
{
2021-05-19 17:02:57 -04:00
skip_ws(nvl);
if (nvl.peek() == '<') // dialogue! abandon current call, parse_Dialogue deals with pushing calls
{
parse_Dialogue(this_context, nvl);
return;
}
2021-05-14 20:44:09 -04:00
parse_Object<true, false>(this_context, nvl);
}
2021-05-17 15:16:02 -04:00
std::get<Sequence*>(parent_context.Scope_Hierarchy.back())->Calls.push_back(this_call);
2021-05-14 11:47:54 -04:00
}
2021-05-14 16:51:04 -04:00
void parse_Sequence(Context parent_context, std::ifstream& nvl)
2021-05-14 11:47:54 -04:00
{
2021-05-15 13:38:10 -04:00
Sequence this_sequence;
2021-05-14 16:51:04 -04:00
Context this_context = parent_context;
2021-05-17 15:16:02 -04:00
this_context.Scope_Hierarchy.push_back(&this_sequence);
2021-05-14 11:47:54 -04:00
2021-05-15 17:48:37 -04:00
skip_ws(nvl);
2021-05-14 16:51:04 -04:00
2021-05-15 13:38:10 -04:00
char c{};
2021-05-19 17:02:57 -04:00
skip_comment(nvl, c);
2021-05-15 13:38:10 -04:00
this_sequence = Sequence(read_sequence_name(nvl));
2021-05-15 17:48:37 -04:00
skip_ws(nvl);
2021-05-14 20:44:09 -04:00
nvl.get(c); // get {
2021-05-15 13:38:10 -04:00
if (c != '{')
2021-05-19 17:02:57 -04:00
throw std::runtime_error("Sequence parse failed at line " + std::to_string(query_ifstream_line_number(nvl)));
2021-05-14 16:51:04 -04:00
std::streampos end_pos = scope_cope(c, nvl);
2021-05-14 20:44:09 -04:00
while (nvl.tellg() < end_pos - static_cast<std::streampos>(1))
2021-05-14 16:51:04 -04:00
{
2021-05-14 20:44:09 -04:00
parse_Call(this_context, nvl);
2021-05-15 17:48:37 -04:00
skip_ws(nvl);
2021-05-14 16:51:04 -04:00
}
2021-05-14 20:44:09 -04:00
nvl.get(c); // get }
2021-05-17 15:16:02 -04:00
std::get<Tree*>(parent_context.Scope_Hierarchy.back())->Sequences.push_back(this_sequence);
2021-05-14 11:47:54 -04:00
}
2021-05-14 16:51:04 -04:00
void parse_NVL(Tree& root, std::string path)
2021-05-14 11:47:54 -04:00
{
std::ifstream nvl;
2021-05-19 17:02:57 -04:00
std::cout << "Reading file " << path << "..." << std::endl;
2021-05-14 11:47:54 -04:00
nvl.open(path);
if (nvl.is_open()) {
2021-05-14 16:51:04 -04:00
Context current_context;
2021-05-17 15:16:02 -04:00
current_context.Scope_Hierarchy.push_back(&root);
2021-05-19 17:02:57 -04:00
skip_ws(nvl); // just in case the file is completely empty
2021-05-14 17:21:10 -04:00
while (!nvl.eof()) {
2021-05-15 17:48:37 -04:00
// parse_Sequence() already takes care of comments before a sequence
2021-05-14 16:51:04 -04:00
parse_Sequence(current_context, nvl);
2021-05-15 17:48:37 -04:00
skip_ws(nvl);
char c{};
2021-05-19 17:02:57 -04:00
skip_comment(nvl, c);
2021-05-15 17:48:37 -04:00
skip_ws(nvl);
2021-05-14 17:21:10 -04:00
}
2021-05-14 11:47:54 -04:00
nvl.close();
2021-05-19 17:02:57 -04:00
} else
throw std::runtime_error("Unable to read file " + path);
2021-05-14 16:51:04 -04:00
}
2021-05-14 20:44:09 -04:00
2021-05-14 16:51:04 -04:00
void Object::Print(int indent)
{
indent_loop(indent);
std::cout << "Object: ";
2021-05-15 17:48:37 -04:00
2021-05-14 16:51:04 -04:00
switch (Value.index())
{
case 0:
2021-05-14 23:17:55 -04:00
std::cout << "Nil" << std::endl;
2021-05-14 16:51:04 -04:00
break;
2021-05-14 20:44:09 -04:00
case 1:
2021-05-19 17:02:57 -04:00
std::cout << "Float: " << std::get<float>(Value) << std::endl;
2021-05-14 16:51:04 -04:00
break;
2021-05-14 20:44:09 -04:00
case 2:
2021-05-14 23:17:55 -04:00
if (Is_Symbol)
2021-05-19 17:02:57 -04:00
std::cout << "Symbol: " << std::get<std::string>(Value) << std::endl;
2021-05-14 23:17:55 -04:00
else
2021-05-19 17:02:57 -04:00
std::cout << "String: " << std::get<std::string>(Value) << std::endl;
2021-05-14 16:51:04 -04:00
break;
2021-05-14 20:44:09 -04:00
case 3:
2021-05-19 17:02:57 -04:00
std::cout << "Bool: " << std::boolalpha << std::get<bool>(Value) << std::endl;
2021-05-14 23:17:55 -04:00
break;
case 4:
2021-05-14 16:51:04 -04:00
std::cout << "List:" << std::endl;
2021-05-15 17:48:37 -04:00
2021-05-14 16:51:04 -04:00
for (auto& x : std::get<std::vector<Object>>(Value))
{
x.Print(indent + 1);
2021-05-14 23:17:55 -04:00
}
break;
2021-05-14 16:51:04 -04:00
}
}
2021-05-15 17:48:37 -04:00
2021-05-14 16:51:04 -04:00
void Call::Print(int indent)
{
indent_loop(indent);
std::cout << "Call:" << std::endl;
2021-05-15 17:48:37 -04:00
2021-05-14 16:51:04 -04:00
for (auto& x : Objects)
{
x.Print(indent + 1);
}
}
2021-05-15 17:48:37 -04:00
2021-05-14 16:51:04 -04:00
void Sequence::Print(int indent)
{
indent_loop(indent);
std::cout << "Sequence " << Name << ":" << std::endl;
2021-05-15 17:48:37 -04:00
2021-05-14 16:51:04 -04:00
for (auto& x : Calls)
{
x.Print(indent + 1);
}
2021-05-15 17:48:37 -04:00
2021-05-14 20:44:09 -04:00
indent_loop(indent);
2021-05-15 17:48:37 -04:00
std::cout << "-----------------------------------------------" << std::endl;
2021-05-14 16:51:04 -04:00
}
2021-05-15 17:48:37 -04:00
2021-05-14 16:51:04 -04:00
void Tree::Print(int indent)
{
indent_loop(indent);
std::cout << "Tree:" << std::endl;
2021-05-15 17:48:37 -04:00
2021-05-14 16:51:04 -04:00
for (auto& x : Sequences)
{
x.Print(indent + 1);
2021-05-14 11:47:54 -04:00
}
}
2021-05-14 17:21:10 -04:00
}