chevron dialogue parsing
This commit is contained in:
parent
93d6673226
commit
ecad7b3817
3 changed files with 157 additions and 63 deletions
188
NouVeL/NVL.cpp
188
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<char, char> 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 <bool is_parent_call, bool is_symbol>
|
||||
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<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);
|
||||
|
||||
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<std::streampos>(1));
|
||||
|
||||
nvl.get(c); // get >
|
||||
|
||||
skip_ws(nvl);
|
||||
return;
|
||||
}
|
||||
|
||||
template <bool is_parent_call, bool is_symbol>
|
||||
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<std::string, std::vector<Object>> 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<std::streampos>(1))
|
||||
{
|
||||
parse_Object<false, false>(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<std::streampos>(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<std::string>(content) + c;
|
||||
|
@ -200,17 +289,18 @@ namespace NVL
|
|||
|
||||
this_object.Value = std::get<std::string>(content);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
{
|
||||
nvl.get(c);
|
||||
while (c != ' ' && c != '\n' && c != '}' && c != ']' && c != ',')
|
||||
{
|
||||
content = std::get<std::string>(content) + c;
|
||||
content = std::get<std::string>(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<std::string>(content) == "true")
|
||||
if (std::get<std::string>(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<std::string>(content)))
|
||||
if (str_test_every_char_not(&isspace, std::get<std::string>(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<Object*>(parent_context.Scope_Hierarchy.back())->Value = std::vector<Object> { 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<true, true>(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<true, false>(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<float>(Value) << std::endl;
|
||||
std::cout << "Float: " << std::get<float>(Value) << std::endl;
|
||||
break;
|
||||
case 2:
|
||||
if (Is_Symbol)
|
||||
std::cout << "symbol: " << std::get<std::string>(Value) << std::endl;
|
||||
std::cout << "Symbol: " << std::get<std::string>(Value) << std::endl;
|
||||
else
|
||||
std::cout << "string: " << std::get<std::string>(Value) << std::endl;
|
||||
std::cout << "String: " << std::get<std::string>(Value) << std::endl;
|
||||
break;
|
||||
case 3:
|
||||
std::cout << "bool: " << std::boolalpha << std::get<bool>(Value) << std::endl;
|
||||
std::cout << "Bool: " << std::boolalpha << std::get<bool>(Value) << std::endl;
|
||||
break;
|
||||
case 4:
|
||||
std::cout << "List:" << std::endl;
|
||||
|
|
|
@ -26,6 +26,12 @@ namespace NVL
|
|||
bool Is_Symbol = false;
|
||||
|
||||
void Print(int indent);
|
||||
|
||||
Object()
|
||||
{}
|
||||
|
||||
Object(std::string n) : Value(n)
|
||||
{}
|
||||
};
|
||||
|
||||
struct Call
|
||||
|
|
26
test_j.nvl
26
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
|
||||
>
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue