diff --git a/NouVeL/NVL.cpp b/NouVeL/NVL.cpp index 06e525b..bd9e62c 100644 --- a/NouVeL/NVL.cpp +++ b/NouVeL/NVL.cpp @@ -19,6 +19,20 @@ namespace { std::cout << "\t"; } } + 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; + } // facilities for scopers const std::map SCOPER_MAP = { @@ -86,13 +100,10 @@ namespace { } } - // cannot match all the copes and has read to the end of the file + // cannot match all the scopes and has read to the end of the file if (stream.eof()) - { - std::cerr << "Can't cope with scope!" << std::endl; - throw; - } - + throw std::runtime_error("Can't cope with scope at line " + std::to_string(query_ifstream_line_number(stream))); + } final = stream.tellg(); @@ -106,6 +117,26 @@ namespace { { stream >> std::ws; } + + 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; + } std::string read_sequence_name(std::ifstream& nvl) { @@ -125,7 +156,8 @@ namespace { return token; } - void skip_comment_ouside_sequence(std::ifstream& nvl, char& c) + + void skip_comment(std::ifstream& nvl, char& c) { if (nvl.peek() == ';') { @@ -137,7 +169,7 @@ namespace { skip_ws(nvl); - skip_comment_ouside_sequence(nvl, c); + skip_comment(nvl, c); skip_ws(nvl); } @@ -146,16 +178,73 @@ namespace { namespace NVL { - template - void parse_Object(Context parent_context, std::ifstream& nvl) + void parse_Dialogue(Context old_context, std::ifstream& nvl) { - Object this_object; - Context this_context = parent_context; - this_context.Scope_Hierarchy.push_back(&this_object); + if (std::get(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(old_context.Scope_Hierarchy.back())->Objects.front(); // Copy speaker object + + Sequence* parent_sequence = std::get(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); - std::streampos end; // unused if object is not scoped + 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(1)); + + nvl.get(c); // get > + + skip_ws(nvl); + return; + } + + template + void parse_Object(Context parent_context, std::ifstream& nvl) + { + // chevron dialogue is now handled in a separate function + Object this_object; + Context this_context = parent_context; + this_context.Scope_Hierarchy.push_back(&this_object); std::variant> content = ""; // init to empty str char c{}; @@ -171,8 +260,9 @@ namespace NVL switch (nvl.peek()) { case '[': // List + { nvl.get(c); - end = scope_cope('[', nvl); + std::streampos end = scope_cope('[', nvl); while (nvl.tellg() < end - static_cast(1)) { parse_Object(this_context, nvl); @@ -180,18 +270,17 @@ namespace NVL } nvl.get(c); // skip ending scoper (']') break; - case '<': // Dialogue, not yet implemented - end = scope_cope('<', nvl); - break; + } case '\"': // String + { nvl.get(c); - end = scope_cope('\"', nvl); + std::streampos end = scope_cope('\"', nvl); while (nvl.tellg() < end - static_cast(1)) { nvl.get(c); // do not concat escaping '\' to content - if (c == '\\' && (nvl.peek() == '\'' || nvl.peek() == '\"')) + if (c == '\\' && (nvl.peek() == '\'' || nvl.peek() == '\"')) continue; content = std::get(content) + c; @@ -200,17 +289,18 @@ namespace NVL this_object.Value = std::get(content); break; + } default: - + { nvl.get(c); while (c != ' ' && c != '\n' && c != '}' && c != ']' && c != ',') { - content = std::get(content) + c; + content = std::get(content) + c; nvl.get(c); } // ']' handled in next object parse, '}' handled in sequence parse, '\n' will be skipped somewhere with skip_ws(), where? idk - if (c == ']' || c == '}' || c == '\n') + if (c == ']' || c == '}' || c == '\n') { nvl.putback(c); } @@ -223,7 +313,7 @@ namespace NVL } catch (std::exception) { - if (std::get(content) == "true") + if (std::get(content) == "true") { this_object.Value = true; } @@ -233,7 +323,7 @@ namespace NVL else { // early return if string is empty, this should only happen if calling from an object (not a call) - if (str_test_every_char_not(isspace, std::get(content))) + if (str_test_every_char_not(&isspace, std::get(content))) return; // default case if content does not match keywords @@ -242,6 +332,7 @@ namespace NVL } break; } + } this_object.Is_Symbol = is_symbol; @@ -254,6 +345,7 @@ namespace NVL ).push_back(this_object); else // parent is not yet vector, initialize as (change from nil to) vector std::get(parent_context.Scope_Hierarchy.back())->Value = std::vector { this_object }; + // the case for chevron dialogues are handled in the switch, there is an early return in the chevron case } void parse_Call(Context parent_context, std::ifstream& nvl) @@ -263,12 +355,24 @@ namespace NVL Context this_context = parent_context; this_context.Scope_Hierarchy.push_back(&this_call); - // parse the symbol (first obj in call) - parse_Object(this_context, nvl); + 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; - // parse the rest while (!(nvl.peek() == '\n' || nvl.peek() == '}')) { + skip_ws(nvl); + if (nvl.peek() == '<') // dialogue! abandon current call, parse_Dialogue deals with pushing calls + { + parse_Dialogue(this_context, nvl); + return; + } + parse_Object(this_context, nvl); } @@ -284,7 +388,7 @@ namespace NVL skip_ws(nvl); char c{}; - skip_comment_ouside_sequence(nvl, c); + skip_comment(nvl, c); this_sequence = Sequence(read_sequence_name(nvl)); @@ -293,10 +397,7 @@ namespace NVL nvl.get(c); // get { if (c != '{') - { - std::cerr << "Sequence parse failed!" << std::endl; - throw; - } + throw std::runtime_error("Sequence parse failed at line " + std::to_string(query_ifstream_line_number(nvl))); std::streampos end_pos = scope_cope(c, nvl); @@ -314,28 +415,29 @@ namespace NVL void parse_NVL(Tree& root, std::string path) { std::ifstream nvl; + std::cout << "Reading file " << path << "..." << std::endl; nvl.open(path); if (nvl.is_open()) { Context current_context; current_context.Scope_Hierarchy.push_back(&root); - + + skip_ws(nvl); // just in case the file is completely empty + while (!nvl.eof()) { // parse_Sequence() already takes care of comments before a sequence parse_Sequence(current_context, nvl); skip_ws(nvl); char c{}; - skip_comment_ouside_sequence(nvl, c); + skip_comment(nvl, c); skip_ws(nvl); } nvl.close(); - } else { - std::cerr << "Unable to read file " << path << std::endl; - throw; - } + } else + throw std::runtime_error("Unable to read file " + path); } @@ -352,16 +454,16 @@ namespace NVL std::cout << "Nil" << std::endl; break; case 1: - std::cout << "float: " << std::get(Value) << std::endl; + std::cout << "Float: " << std::get(Value) << std::endl; break; case 2: if (Is_Symbol) - std::cout << "symbol: " << std::get(Value) << std::endl; + std::cout << "Symbol: " << std::get(Value) << std::endl; else - std::cout << "string: " << std::get(Value) << std::endl; + std::cout << "String: " << std::get(Value) << std::endl; break; case 3: - std::cout << "bool: " << std::boolalpha << std::get(Value) << std::endl; + std::cout << "Bool: " << std::boolalpha << std::get(Value) << std::endl; break; case 4: std::cout << "List:" << std::endl; diff --git a/NouVeL/NVL.h b/NouVeL/NVL.h index 4977d05..2ef3620 100644 --- a/NouVeL/NVL.h +++ b/NouVeL/NVL.h @@ -26,6 +26,12 @@ namespace NVL bool Is_Symbol = false; void Print(int indent); + + Object() + {} + + Object(std::string n) : Value(n) + {} }; struct Call diff --git a/test_j.nvl b/test_j.nvl index a6c4318..1e6a0c9 100644 --- a/test_j.nvl +++ b/test_j.nvl @@ -1,22 +1,8 @@ -; dasjdh -; dasjdh - mmawesome { - uahsdfidaf asjd - [][][][[[][] hi]] ; yes -} ; dasjdh + ; testing chevron syntax + "not mmaker" < + I just realized that -; dasjdh -; dasjdh -saddasdaesome {} ; dasjdh - -; dasjdh -; dasjdh - -; dasjdh -sadkoajsdo { - kifsdj [] [] [] [][ ] [[[[][[]]]]] asd 3.223.232 .2323 34.34 true false ;dsad -}; dasjdh - -; dasjdh -; dasjdh + I get this for free with scope coping + > +}