1144 lines
34 KiB
C++
1144 lines
34 KiB
C++
#include "LineParser.h"
|
|
using namespace dms::tokens;
|
|
using namespace dms::utils;
|
|
// TODO: process if elseif else statements, for loops and while loops
|
|
namespace dms {
|
|
bool LineParser::match_process_standard(tokenstream* stream, value& v) {
|
|
//utils::debug(stream->peek());
|
|
if (stream->peek().type == tokens::none) {
|
|
return false;
|
|
}
|
|
if (stream->match(tokens::parao)) {
|
|
std::vector<tokens::token> toks = stream->next(tokens::parao, tokens::parac);
|
|
toks.pop_back(); // Remove the ')'
|
|
toks.push_back(tokens::token{tokens::newline,codes::NOOP,"",stream->peek().line_num});
|
|
tokenstream tempstream(&toks);
|
|
value var(datatypes::variable);
|
|
if (match_process_standard(&tempstream, var)) {
|
|
v.set(var.s);
|
|
v.type = datatypes::variable;
|
|
return true;
|
|
}
|
|
else {
|
|
badSymbol(stream);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
if (match_process_andor(stream, v)) {
|
|
match_process_condition(stream, v);
|
|
return true;
|
|
}
|
|
if (match_process_expression(stream, v)) {
|
|
match_process_condition(stream,v);
|
|
return true;
|
|
}
|
|
else if (match_process_function(stream, v)) {
|
|
match_process_condition(stream, v);
|
|
return true;
|
|
}
|
|
else if (match_process_list(stream, v)) {
|
|
match_process_condition(stream, v);
|
|
return true;
|
|
}
|
|
else if (match_process_index(stream, v)) {
|
|
match_process_condition(stream, v);
|
|
return true;
|
|
}
|
|
else if (stream->match(tokens::True)) {
|
|
v.set(true);
|
|
stream->next();
|
|
match_process_condition(stream, v);
|
|
return true;
|
|
}
|
|
else if (stream->match(tokens::False)) {
|
|
v.set(false);
|
|
stream->next();
|
|
match_process_condition(stream, v);
|
|
return true;
|
|
}
|
|
else if (stream->match(tokens::number)) {
|
|
v.set(std::stod(stream->next().name));
|
|
match_process_condition(stream, v);
|
|
return true;
|
|
}
|
|
else if (stream->match(tokens::string)) {
|
|
v.set(stream->next().name);
|
|
match_process_condition(stream, v);
|
|
return true;
|
|
}
|
|
else if (stream->match(tokens::name)) {
|
|
v.set(stream->next().name);
|
|
v.type = datatypes::variable;
|
|
match_process_condition(stream, v);
|
|
return true;
|
|
}
|
|
else if (stream->match(tokens::nil)) {
|
|
stream->next();
|
|
v.set();
|
|
match_process_condition(stream, v);
|
|
return true;
|
|
}
|
|
else if (stream->match(tokens::bracketo, tokens::name, tokens::bracketc)) {
|
|
// We are assigning a block as a variable
|
|
stream->next();
|
|
v.set(stream->next().name);
|
|
v.type = datatypes::block;
|
|
stream->next();
|
|
match_process_condition(stream, v);
|
|
return true;
|
|
}
|
|
else if (stream->match(tokens::newline)) {
|
|
stream->next();
|
|
return match_process_standard(stream,v);
|
|
}
|
|
return false;
|
|
}
|
|
bool LineParser::match_process_andor(tokenstream* stream, value& v) {
|
|
codes::op code = codes::MUL;
|
|
if (stream->match(tokens::Or)) {
|
|
code = codes::ADD;
|
|
}
|
|
else if (!stream->match(tokens::And)){
|
|
return false;
|
|
}
|
|
stream->next();
|
|
value right = value(datatypes::variable);
|
|
value left = v;
|
|
value var = value(datatypes::variable);
|
|
// We have some work to do here
|
|
if (match_process_standard(stream, right)) {
|
|
v.set(var.s);
|
|
v.type = datatypes::variable;
|
|
cmd* c = new cmd;
|
|
c->opcode = code;
|
|
c->args.push(v);
|
|
c->args.push(left);
|
|
c->args.push(right);
|
|
current_chunk->addCmd(c);
|
|
return true;
|
|
}
|
|
else {
|
|
badSymbol(stream);
|
|
return false;
|
|
}
|
|
}
|
|
bool LineParser::match_process_condition(tokenstream* stream, value& v) {
|
|
// This has taken way too long, but there exists only a few places where this needs to be interjected
|
|
// The reference to v needs some work if we have a comparison!
|
|
// First we need to get a copy of v store it somewhere do the comparision and convert v into a variable that points to the output of the comparison
|
|
comp cmp;
|
|
// We only need to see if one of these conditions are true
|
|
//==
|
|
if (stream->match(tokens::equal,tokens::equal)) {
|
|
cmp = comp::eq;
|
|
stream->next();
|
|
stream->next();
|
|
}
|
|
//<=
|
|
else if (stream->match(tokens::anglebracketO, tokens::equal)) {
|
|
cmp = comp::lteq;
|
|
stream->next();
|
|
stream->next();
|
|
}
|
|
//>=
|
|
else if (stream->match(tokens::anglebracketC, tokens::equal)) {
|
|
cmp = comp::gteq;
|
|
stream->next();
|
|
stream->next();
|
|
}
|
|
//!=
|
|
else if (stream->match(tokens::exclamation, tokens::equal)) {
|
|
cmp = comp::nteq;
|
|
stream->next();
|
|
stream->next();
|
|
}
|
|
//<
|
|
else if (stream->match(tokens::anglebracketO)) {
|
|
cmp = comp::lt;
|
|
stream->next();
|
|
}
|
|
//>
|
|
else if (stream->match(tokens::anglebracketC)) {
|
|
cmp = comp::gt;
|
|
stream->next();
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
// So if all is good we continue here
|
|
value right = value(datatypes::variable);
|
|
value left = v;
|
|
value var = value(datatypes::variable);
|
|
// COMP cmp out v1 v2
|
|
if (match_process_standard(stream,right)) {
|
|
v.set(var.s);
|
|
v.type = datatypes::variable;
|
|
cmd* c = new cmd;
|
|
c->opcode = codes::COMP;
|
|
c->args.push(value((double)cmp));
|
|
c->args.push(var);
|
|
c->args.push(left);
|
|
c->args.push(right);
|
|
current_chunk->addCmd(c);
|
|
if (match_process_andor(stream, v)) {
|
|
match_process_standard(stream, v);
|
|
}
|
|
return true;
|
|
}
|
|
else {
|
|
badSymbol(stream);
|
|
return false;
|
|
}
|
|
}
|
|
//Covers if, elseif, else
|
|
bool LineParser::match_process_list(tokenstream* stream, value& v) {
|
|
if (stream->match(tokens::cbracketo)) {
|
|
token start = stream->peek();
|
|
token ancor = start;
|
|
std::vector<token> t = stream->next(tokens::cbracketo, tokens::cbracketc);
|
|
tokenstream tempstream(&t);
|
|
value ref = value(datatypes::variable);
|
|
value length = value();
|
|
value dict = value();
|
|
size_t count = 0;
|
|
cmd* c = new cmd;
|
|
c->opcode = codes::LIST;
|
|
c->args.push(v);
|
|
c->args.push(length);
|
|
current_chunk->addCmd(c);
|
|
bool ready = true;
|
|
while (tempstream.peek().type != tokens::none) {
|
|
debugInvoker(stream);
|
|
if (tempstream.match(tokens::cbracketc)) {
|
|
ready = true;
|
|
c = new cmd;
|
|
c->opcode = codes::INST;
|
|
c->args.push(v);
|
|
c->args.push(ref);
|
|
if (dict != nullptr) {
|
|
c->args.push(dict);
|
|
dict = nullptr;
|
|
}
|
|
current_chunk->addCmd(c);
|
|
break;
|
|
}
|
|
// Match Dict
|
|
else if (tempstream.match(tokens::name,tokens::colon)) {
|
|
dict = value(tempstream.next().name,datatypes::variable);
|
|
tempstream.next();
|
|
if (!match_process_standard(&tempstream,ref)) {
|
|
badSymbol();
|
|
}
|
|
}
|
|
// This will modify the ref!!!
|
|
else if (match_process_standard(&tempstream, ref)) {
|
|
count++;
|
|
if (ready) {
|
|
ancor = tempstream.last();
|
|
ready = false;
|
|
}
|
|
else
|
|
state->push_error(errors::error{ errors::badtoken,concat("Unexpected symbol '",ancor.toString(),"' Expected '}' to close list (line: ",start.line_num,") Did you forget a comma?"),true,ancor.line_num,current_chunk });
|
|
}
|
|
else if (tempstream.match(tokens::newline)) {
|
|
tempstream.next(); // this is fine
|
|
}
|
|
else if (tempstream.match(tokens::seperator)) {
|
|
// Handle the next piece
|
|
ready = true;
|
|
c = new cmd;
|
|
c->opcode = codes::INST;
|
|
c->args.push(v);
|
|
c->args.push(ref);
|
|
if (dict != nullptr) {
|
|
c->args.push(dict);
|
|
dict = nullptr;
|
|
}
|
|
current_chunk->addCmd(c);
|
|
// Build new value
|
|
ref = value(datatypes::variable);
|
|
tempstream.next();
|
|
}
|
|
else if (tempstream.match(tokens::cbracketo)) {
|
|
tempstream.next();
|
|
}
|
|
else {
|
|
badSymbol(&tempstream);
|
|
}
|
|
}
|
|
length.set((double)count); // the second argument is the length of the list! This should be modified if lists are changed at runtime!
|
|
c = new cmd;
|
|
c->opcode = codes::INST;
|
|
c->args.push(v);
|
|
c->args.push(value());
|
|
if (dict != nullptr) {
|
|
c->args.push(dict);
|
|
dict = nullptr;
|
|
}
|
|
current_chunk->addCmd(c);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
bool LineParser::match_process_disp(tokenstream* stream) {
|
|
if ((isBlock(bt_block) || isBlock(bt_method)) && stream->match(tokens::newline, tokens::string, tokens::newline)) {
|
|
stream->next(); // Standard consumption
|
|
cmd* c = new cmd;
|
|
c->opcode = codes::DISP;
|
|
c->args.push(value(stream->next().name));
|
|
current_chunk->addCmd(c); // Add the cmd to the current chunk
|
|
current_chunk->addCmd(new cmd{ codes::HALT });
|
|
return true;
|
|
}
|
|
else if ((isBlock(bt_block) || isBlock(bt_method)) && stream->match(tokens::newline,tokens::pipe ,tokens::string, tokens::newline)) {
|
|
stream->next(); // Standard consumption
|
|
stream->next(); // pipe consumption
|
|
if (current_chunk->cmds.size() && current_chunk->cmds.back()->opcode == codes::HALT) {
|
|
current_chunk->cmds.pop_back();
|
|
}
|
|
std::string msg = stream->next().name;
|
|
cmd* c = new cmd;
|
|
c->opcode = codes::APND;
|
|
c->args.push(value(concat(" ", msg)));
|
|
current_chunk->addCmd(c); // Add the cmd to the current chunk
|
|
current_chunk->addCmd(new cmd{ codes::HALT });
|
|
return true;
|
|
}
|
|
else if (isBlock(bt_character) && stream->match(tokens::newline, tokens::name, tokens::colon, tokens::string, tokens::newline)) {
|
|
stream->next(); // Standard consumption
|
|
std::string name = stream->next().name;
|
|
stream->next(); // That colon
|
|
std::string msg = stream->next().name;
|
|
cmd* c = new cmd;
|
|
c->opcode = codes::STAT;
|
|
c->args.push(value(name,datatypes::variable));
|
|
c->args.push(value(msg));
|
|
current_chunk->addCmd(c); // Add the cmd to the current chunk
|
|
}
|
|
else if ((isBlock(bt_block) || isBlock(bt_method)) && stream->match(tokens::newline, tokens::name, tokens::colon, tokens::string, tokens::newline)) {
|
|
// We might have to handle scope here
|
|
// Here we match 'Ryan: "This guy said this!"' Note the colon is needed!
|
|
stream->next(); // Standard consumption
|
|
std::string name = stream->next().name;
|
|
stream->next(); // That colon
|
|
std::string msg = stream->next().name;
|
|
cmd* c = new cmd;
|
|
c->opcode = codes::SSPK;
|
|
c->args.push(value(name, datatypes::variable));
|
|
current_chunk->addCmd(c);
|
|
c = new cmd;
|
|
c->opcode = codes::DISP;
|
|
c->args.push(value(msg));
|
|
current_chunk->addCmd(c); // Add the cmd to the current chunk
|
|
current_chunk->addCmd(new cmd{ codes::HALT });
|
|
return true;
|
|
}
|
|
else if ((isBlock(bt_block) || isBlock(bt_method)) && stream->match(tokens::name,tokens::colon,tokens::cbracketo)) {
|
|
std::string name = stream->next().name;
|
|
// Command to set the speaker
|
|
cmd* c = new cmd;
|
|
c->opcode = codes::SSPK;
|
|
c->args.push(value(name,datatypes::variable));
|
|
current_chunk->addCmd(c);
|
|
stream->next();
|
|
stream->next();
|
|
while (stream->peek().type != tokens::cbracketc) {
|
|
debugInvoker(stream);
|
|
if (stream->match(tokens::name)) {
|
|
std::string mode = stream->next().name;
|
|
if (mode == "speed") {
|
|
if (stream->match(tokens::number)) {
|
|
buildSpeed(std::stod(stream->next().name));
|
|
}
|
|
else {
|
|
badSymbol(stream);
|
|
return false;
|
|
}
|
|
}
|
|
else if (mode == "wait") {
|
|
if (stream->match(tokens::number)) {
|
|
buildWait(std::stod(stream->next().name));
|
|
}
|
|
else {
|
|
badSymbol(errors::disp_unknown, stream);
|
|
}
|
|
}
|
|
else {
|
|
// Assume we have a dact
|
|
if (stream->match(tokens::string) || stream->match(tokens::colon,tokens::string)) {
|
|
if (stream->match(tokens::colon)) {
|
|
stream->next();
|
|
}
|
|
cmd* c = new cmd;
|
|
c->opcode = codes::DACT;
|
|
c->args.push(value(mode));
|
|
current_chunk->addCmd(c);
|
|
// Now build the apnd msg cmd
|
|
c = new cmd;
|
|
c->opcode = codes::APND;
|
|
c->args.push(value(stream->next().name));
|
|
current_chunk->addCmd(c);
|
|
}
|
|
else {
|
|
badSymbol(stream);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else if (stream->match(tokens::string)) {
|
|
cmd* c = new cmd;
|
|
c->opcode = codes::APND;
|
|
c->args.push(value(stream->next().name));
|
|
current_chunk->addCmd(c);
|
|
}
|
|
else if (stream->match(tokens::newline)) {
|
|
stream->next();
|
|
}
|
|
else {
|
|
badSymbol(stream);
|
|
return false;
|
|
}
|
|
}
|
|
stream->next();
|
|
current_chunk->addCmd(new cmd{ codes::HALT });
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
bool LineParser::match_process_return(tokenstream* stream) {
|
|
// Only handle this inside of a function block!
|
|
if (current_chunk->type == blocktype::bt_method) {
|
|
if (stream->match(tokens::ret)) {
|
|
cmd* c = new cmd;
|
|
c->opcode = codes::RETN;
|
|
value ref = value(datatypes::variable);
|
|
stream->next();
|
|
if (match_process_standard(stream, ref)) {
|
|
c->args.push(ref);
|
|
current_chunk->addCmd(c);
|
|
return true;
|
|
}
|
|
else {
|
|
badSymbol();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else if(stream->match(tokens::ret)) {
|
|
stream->next();
|
|
badSymbol();
|
|
}
|
|
return false;
|
|
}
|
|
bool LineParser::match_process_assignment(tokenstream* stream) {
|
|
value v = value();
|
|
if (match_process_index(stream, v, true)) {
|
|
cmd* c = current_chunk->cmds.back();
|
|
value ref = value(datatypes::variable);
|
|
if (stream->peek().type == tokens::equal) {
|
|
stream->next();
|
|
}
|
|
else {
|
|
badSymbol(stream);
|
|
return false;
|
|
}
|
|
if (match_process_standard(stream, ref)) {
|
|
c->args.push(ref);
|
|
return true;
|
|
}
|
|
else if (stream->match(tokens::newline)) {
|
|
stream->next();
|
|
}
|
|
}
|
|
else if (stream->match(tokens::name,tokens::equal)) {
|
|
value var = value(stream->next().name,datatypes::variable); // The variable that we will be setting stuff to
|
|
stream->next(); // Consume the equal
|
|
cmd* c = new cmd;
|
|
c->opcode = codes::ASGN;
|
|
c->args.push(var);
|
|
value ref = value(datatypes::variable);
|
|
if (match_process_standard(stream,ref)) {
|
|
c->args.push(ref);
|
|
current_chunk->addCmd(c);
|
|
return true;
|
|
}
|
|
else if (stream->match(tokens::newline)) {
|
|
stream->next();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
bool LineParser::match_process_debug(tokenstream* stream) {
|
|
if (stream-> match(tokens::newline, tokens::debug, tokens::string) || stream->match(tokens::newline, tokens::debug, tokens::name)){
|
|
stream->next();
|
|
stream->next();
|
|
if (state->isEnabled("debugging")) {
|
|
cmd* c = new cmd;
|
|
c->opcode = codes::DEBG;
|
|
if (stream->match(tokens::string)) {
|
|
c->args.push(value(stream->next().name));
|
|
}
|
|
else {
|
|
c->args.push(value(stream->next().name,datatypes::variable));
|
|
}
|
|
current_chunk->addCmd(c);
|
|
return true;
|
|
}
|
|
else {
|
|
stream->next(); // Consume the third element anyway
|
|
return true; // This is a debugging match, but debugging is disabled. It's good!
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
bool LineParser::match_process_choice(tokenstream* stream) {
|
|
token temp = stream->peek();
|
|
if (temp.raw == codes::CHOI && stream->match(tokens::control,tokens::string)) {
|
|
// Let's parse choice blocks.
|
|
stream->next();
|
|
std::string prompt = stream->next().name;
|
|
bool good = true;
|
|
std::string option;
|
|
cmd* c = new cmd;
|
|
// Create a unique label name by using the line number
|
|
int CHOI_ID = 0;
|
|
std::string choi_str = concat("CHOI_", fn);
|
|
std::string choicelabel = concat("$CHOI_END_", choi_str,"_", stream->peek().line_num);
|
|
c->opcode = codes::CHOI;
|
|
c->args.push(prompt);
|
|
c->args.push(fn);
|
|
current_chunk->addCmd(c); // We will keep a reference to this and add to it as we go through the list
|
|
bool start = false;
|
|
bool hasfunc = false;
|
|
/*
|
|
What's going on here might be tough to understand just by looking at the code
|
|
The bytecode generated by this code might look something like this:
|
|
|
|
off op opcodes
|
|
0 CHOI "Pick one!" CHOI_filename_index "Choice 1" "Choice 2" "Choice 3" "Choice 4"
|
|
1 LABL CHOI_filename_1
|
|
2 FUNC print "You picked 1!"
|
|
3 GOTO $CHOI_END_1
|
|
4 LABL CHOI_filename_2
|
|
5 FUNC print "You picked 2!"
|
|
6 GOTO $CHOI_END_1
|
|
7 LABL CHOI_filename_3
|
|
8 JUMP park
|
|
9 LABL CHOI_filename_4
|
|
10 GOTO mylabel
|
|
11 LABL $CHOI_END_1
|
|
*/
|
|
while (!stream->match(tokens::cbracketc)) {
|
|
debugInvoker(stream);
|
|
if (start && !stream->match(tokens::newline)) {
|
|
buildLabel(concat(choi_str,"_", CHOI_ID++));
|
|
}
|
|
if (stream->match(tokens::cbracketo) && !start) {
|
|
start = true;
|
|
stream->next();
|
|
}
|
|
else if (stream->match(tokens::cbracketo) && start) {
|
|
badSymbol(stream);
|
|
}
|
|
else if (stream->match(tokens::string)) {
|
|
std::string name = stream->next().name;
|
|
c->args.push(value(name)); // We append the choice to the first part of the CHOI cmd
|
|
|
|
value val = value();
|
|
if (match_process_function(stream,val,false)) { // No returns and also no nesting of functions!
|
|
// We cannot have a nested function here, but if we dont have that then we add our goto
|
|
hasfunc = true;
|
|
buildGoto(choicelabel);
|
|
}
|
|
else if (match_process_goto(stream)) {}
|
|
else if (match_process_jump(stream)) {}
|
|
else if (match_process_exit(stream)) {}
|
|
}
|
|
// Last Match
|
|
else if (stream->match(tokens::newline)) {
|
|
stream->next(); // Consume
|
|
}
|
|
else {
|
|
badSymbol(stream);
|
|
}
|
|
}
|
|
buildGoto(choicelabel);
|
|
cmd* cc = current_chunk->cmds.back(); // Delete last element
|
|
current_chunk->cmds.pop_back();
|
|
delete cc;
|
|
if (hasfunc)
|
|
buildLabel(choicelabel);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void cleanup(value* v) {
|
|
v->nuke(); // Make sure we clean up the data
|
|
delete v; // We didn't need it, lets clean it up!
|
|
}
|
|
bool LineParser::match_process_function(tokenstream* stream, value& v, bool nested) {
|
|
/*
|
|
Functions should be able to handle function calls as arguments,
|
|
HOWEVER functions cannot be passed as values since they aren't values like they are in other languages!
|
|
|
|
The bytecode here is a little tricky, a bit of moving parts and work that the vm has to do.
|
|
|
|
If a function has a return that is expected to be consumed, v will not be nullptr and will be pointing to something
|
|
The type of v should be variable and if not throw an error!
|
|
|
|
The bytecode should look something like this for a given method:
|
|
testfunc("Hello!")
|
|
off op opcode
|
|
0 FUNC testfunc <nil> "Hello"
|
|
|
|
The bytecode should look something like this for a given method:
|
|
val = testfunc2(2,2)
|
|
off op opcode
|
|
0 FUNC testfunc2 val 2 2
|
|
|
|
Notice how we have nil for the first function with no return, this tells the interperter that we do not need to store a value at all
|
|
The second method has a varaible to store val so we store that in val
|
|
|
|
Given a function:
|
|
val = test3(testfunc2(4,4),"Test function as an argument!")
|
|
off op opcode
|
|
0 FUNC testfunc2 $A 4 4
|
|
1 FUNC test3 val $A "Test function as an argument!"
|
|
|
|
Not too scary, in fact it's rathar stright forward
|
|
Last example:
|
|
test4("Testing",test3(1,testfunc2(1,2)),testfunc2(10,100))
|
|
off op opcode
|
|
0 FUNC testfunc2 $A 1 2
|
|
1 FUNC test3 $B 1 $A
|
|
2 FUNC testfunc2 $C 10 100
|
|
3 FUNC test4 nil "Testing" $B $C
|
|
|
|
A bit more involved, but I'm sure it wasn't too much to follow, especially if you looked at the examples in order
|
|
|
|
That's all there is to functions within the bytecode side of things, the vm is where things are a little more involved
|
|
*/
|
|
if (stream->match(tokens::name, tokens::parao) || stream->match(tokens::gotoo, tokens::parao) || stream->match(tokens::name, tokens::jump)) {
|
|
cmd* c = new cmd;
|
|
c->opcode = codes::FUNC;
|
|
token tokn = stream->peek();
|
|
std::string n = stream->next().name;
|
|
c->args.push(value(n,datatypes::variable)); // Set the func identifier as the first variable
|
|
// Let's set the target
|
|
if (!v.isNil()) {
|
|
c->args.push(v); // Push the supplied variable
|
|
}
|
|
else {
|
|
c->args.push(value()); // Creates a nil value
|
|
}
|
|
// Already we have built: FUNC name val
|
|
// Next we add arguments this is where things get interesting
|
|
// This is a balanced consuming method (()(()))
|
|
std::vector<token> t = stream->next(tokens::parao, tokens::parac); // Consume and get tokens
|
|
//t.pop_back();
|
|
tokenstream tempstream(&t);
|
|
if (t.size() == 1) { // No arg function!
|
|
current_chunk->addCmd(c);
|
|
return true;
|
|
}
|
|
token end = t.back();
|
|
t.pop_back();
|
|
t.push_back(token{ tokens::seperator,codes::NOOP,"",t[0].line_num });
|
|
t.push_back(token{ tokens::nil,codes::NOOP,"",t[0].line_num });
|
|
t.push_back(end);
|
|
tempstream.init(&t); // Turn tokens we consumed into a tokenstream
|
|
if (tokn.type==tokens::gotoo) {
|
|
if (tempstream.match(tokens::string)) {
|
|
buildGoto(tempstream.next().name);
|
|
delete c;
|
|
return true;
|
|
}
|
|
else if (tempstream.match(tokens::name)) {
|
|
buildGoto(tempstream.next().name,true);
|
|
delete c;
|
|
return true;
|
|
}
|
|
else {
|
|
badSymbol(&tempstream);
|
|
return false;
|
|
}
|
|
}
|
|
else if (tokn.type == tokens::jump) {
|
|
if (tempstream.match(tokens::string)) {
|
|
buildJump(tempstream.next().name);
|
|
delete c;
|
|
return true;
|
|
}
|
|
else if (tempstream.match(tokens::name)) {
|
|
buildJump(tempstream.next().name, true);
|
|
delete c;
|
|
return true;
|
|
}
|
|
else {
|
|
badSymbol(&tempstream);
|
|
return false;
|
|
}
|
|
}
|
|
value tempval;
|
|
value ref = value(datatypes::variable);
|
|
// This part we add values to the opcodes for the bytecode FUNC val a1 a2 a3 ... an
|
|
while (tempstream.peek().type != tokens::none) { // End of stream
|
|
debugInvoker(stream);
|
|
tempval = value(datatypes::variable);
|
|
if (tempstream.match(tokens::seperator)) {
|
|
// We have a seperator for function arguments
|
|
tempstream.next(); // Consume it
|
|
}
|
|
else if (match_process_standard(&tempstream, tempval)) {
|
|
c->args.push(tempval);
|
|
}
|
|
else if (tempstream.match(tokens::newline)) {
|
|
tempstream.next();
|
|
}
|
|
else if (tempstream.match(tokens::parac)) {
|
|
tempstream.next();
|
|
current_chunk->addCmd(c); // We push this onto the chunk after all dependants if any have been handled
|
|
return true;
|
|
}
|
|
else {
|
|
utils::debug(tempstream.peek());
|
|
badSymbol(&tempstream);
|
|
}
|
|
}
|
|
|
|
}
|
|
else if (stream->match(tokens::name,tokens::dot,tokens::name,tokens::parao)) {
|
|
cmd* c = new cmd;
|
|
c->opcode = codes::OFUN;
|
|
// OFUN obj fname target args
|
|
c->args.push(value(stream->next().name,datatypes::variable)); // push the obj
|
|
stream->next(); // consume the dot
|
|
c->args.push(value(stream->next().name,datatypes::variable)); // push the fname
|
|
// Let's set the target
|
|
if (!v.isNil()) {
|
|
c->args.push(v); // Push the supplied variable
|
|
}
|
|
else {
|
|
c->args.push(value()); // Creates a nil value
|
|
}
|
|
// Already we have built: FUNC name val
|
|
// Next we add arguments this is where things get interesting
|
|
// This is a balanced consuming method (()(()))
|
|
std::vector<token> t = stream->next(tokens::parao, tokens::parac); // Consume and get tokens
|
|
t.pop_back();
|
|
tokenstream tempstream(&t);
|
|
if (t.size() == 1) { // No arg function!
|
|
current_chunk->addCmd(c);
|
|
return true;
|
|
}
|
|
token end = t.back();
|
|
t.pop_back();
|
|
t.push_back(token{ tokens::seperator,codes::NOOP,"",t[0].line_num });
|
|
t.push_back(token{ tokens::nil,codes::NOOP,"",t[0].line_num });
|
|
t.push_back(end);
|
|
tempstream.init(&t); // Turn tokens we consumed into a tokenstream
|
|
value tempval;
|
|
token tok;
|
|
value ref = value(datatypes::variable);
|
|
// This part we add values to the opcodes for the bytecode FUNC val a1 a2 a3 ... an
|
|
while (tempstream.peek().type != tokens::none) { // End of stream
|
|
debugInvoker(stream);
|
|
tempval = value(datatypes::variable);
|
|
tok = tempstream.peek();
|
|
if (tempstream.match(tokens::seperator)) {
|
|
// We have a seperator for function arguments
|
|
tempstream.next(); // Consume it
|
|
}
|
|
else if (match_process_standard(&tempstream, tempval)) {
|
|
c->args.push(tempval);
|
|
}
|
|
else if (tempstream.match(tokens::newline)) {
|
|
tempstream.next();
|
|
}
|
|
else {
|
|
badSymbol(&tempstream);
|
|
}
|
|
}
|
|
tempstream.next();
|
|
current_chunk->addCmd(c); // We push this onto the chunk after all dependants if any have been handled
|
|
//lastCall.pop();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
bool LineParser::match_process_goto(tokenstream* stream) {
|
|
if (stream->match(tokens::gotoo,tokens::name) || stream->match(tokens::gotoo,tokens::string)) {
|
|
stream->next(); // consume gotoo
|
|
if (stream->match(tokens::name)) {
|
|
buildGoto(stream->next().name,true);
|
|
}
|
|
else {
|
|
buildGoto(stream->next().name);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
bool LineParser::match_process_index(tokenstream* stream, value& v, bool leftside) {
|
|
if (stream->match(tokens::name,tokens::bracketo)) {
|
|
std::string name = stream->next().name;
|
|
std::vector<token> toks = stream->next(tokens::bracketo, tokens::bracketc);
|
|
toks.pop_back(); // Remove the last element since its a ']'
|
|
toks.push_back(token{ tokens::newline,codes::NOOP,"",toks[0].line_num });
|
|
tokenstream tempstream(&toks); // As usual the tokens are balanced match to [...] where the contents of tok = ...
|
|
value tempval = value(datatypes::variable);
|
|
cmd* c = new cmd;
|
|
if (leftside) {
|
|
c->opcode = codes::ASID;
|
|
}
|
|
else {
|
|
c->opcode = codes::INDX;
|
|
}
|
|
int nlcount = 0;
|
|
while (tempstream.peek().type != tokens::none) { // Keep going until we hit the end
|
|
if (match_process_standard(&tempstream, tempval)) {
|
|
c->args.push(v);
|
|
c->args.push(value(name,datatypes::block));
|
|
c->args.push(tempval);
|
|
}
|
|
else if (nlcount) {
|
|
state->push_error(errors::error{ errors::badtoken,concat("Unexpected symbol '",tempstream.last().toString(),"' Expected ']' to close list (line: ",tempstream.last().line_num,") Indexing must be done on one line?"),true,tempstream.last().line_num,current_chunk });
|
|
return false;
|
|
}
|
|
else if (tempstream.match(tokens::newline)) {
|
|
nlcount++;
|
|
tempstream.next();
|
|
}
|
|
else {
|
|
badSymbol(&tempstream);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
current_chunk->addCmd(c);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
bool LineParser::match_process_jump(tokenstream* stream) {
|
|
if (current_chunk->type == blocktype::bt_character || current_chunk->type == blocktype::bt_env)
|
|
return false;
|
|
if (stream->match(tokens::jump, tokens::name) || stream->match(tokens::jump, tokens::string)) {
|
|
cmd* c = new cmd;
|
|
c->opcode = codes::JUMP;
|
|
stream->next(); // consume jump
|
|
if (stream->match(tokens::name)) {
|
|
c->args.push(value(stream->next().name,datatypes::variable));
|
|
}
|
|
else {
|
|
c->args.push(value(stream->next().name));
|
|
}
|
|
current_chunk->addCmd(c);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
bool LineParser::match_process_exit(tokenstream* stream) {
|
|
if (stream->match(tokens::exit)) {
|
|
stream->next();
|
|
cmd* c = new cmd;
|
|
c->opcode = codes::EXIT;
|
|
if (stream->match(tokens::number) || stream->match(tokens::name)) {
|
|
if(stream->match(tokens::number)){
|
|
c->args.push(value(std::stod(stream->next().name)));
|
|
}
|
|
else {
|
|
c->args.push(value(stream->next().name,datatypes::variable));
|
|
}
|
|
}
|
|
current_chunk->addCmd(c);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
bool LineParser::match_process_IFFF(tokenstream* stream) {
|
|
/*if(this) {
|
|
* then()
|
|
* }
|
|
* else if(that){
|
|
* doThis()
|
|
* } else {
|
|
* this()
|
|
* }
|
|
*/
|
|
|
|
// We match a condition, or anything that is non nil/false
|
|
// God controls are from a time past... I could refactor, but I'm lazy and would have to change a lot of old code... So we will deal with controls
|
|
if (stream->match(tokens::name,tokens::parao) && stream->peek().name == "if") {
|
|
stream->next();
|
|
std::vector<token> ts = stream->next(tokens::parao, tokens::parac);
|
|
ts.pop_back();
|
|
tokenstream tmpstream(&ts);
|
|
value cmp(datatypes::variable);
|
|
if (match_process_standard(&tmpstream,cmp)) {
|
|
std::string ifend = std::string("IFE_") + random_string(4);
|
|
std::string next = std::string("IFF_") + random_string(4);
|
|
if (stream->match(tokens::cbracketo)) {
|
|
std::vector<token> toks = stream->next(tokens::cbracketo, tokens::cbracketc);
|
|
toks.pop_back();
|
|
tokenstream tempstream(&toks);
|
|
cmd* c = new cmd;
|
|
c->opcode = codes::IFFF;
|
|
c->args.push(cmp);
|
|
c->args.push(value(next));
|
|
current_chunk->addCmd(c);
|
|
ParseLoop(&tempstream);
|
|
buildGoto(ifend);
|
|
buildLabel(next);
|
|
if (match_process_ELIF(stream,ifend)) {
|
|
utils::debug("here");
|
|
}
|
|
else if (match_process_ELSE(stream,ifend)) {
|
|
utils::debug("here");
|
|
}
|
|
buildLabel(ifend);
|
|
// We keep trying to match else if/else until nothing is left
|
|
return true;
|
|
}
|
|
}
|
|
else {
|
|
badSymbol(stream);
|
|
}
|
|
}
|
|
return false; // TODO finish this
|
|
}
|
|
bool LineParser::match_process_ELSE(tokenstream* stream, std::string ifend) {
|
|
if (stream->match(tokens::name, tokens::cbracketo) && stream->peek().name == "else") {
|
|
stream->next();
|
|
std::vector<token> ts = stream->next(tokens::cbracketo, tokens::cbracketc);
|
|
ts.pop_back();
|
|
tokenstream tempstream(&ts);
|
|
ParseLoop(&tempstream);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
bool LineParser::match_process_ELIF(tokenstream* stream, std::string ifend) {
|
|
if (stream->match(tokens::name, tokens::parao) && stream->peek().name == "elseif") {
|
|
stream->next();
|
|
std::vector<token> ts = stream->next(tokens::parao, tokens::parac);
|
|
ts.pop_back();
|
|
tokenstream tmpstream(&ts);
|
|
value cmp(datatypes::variable);
|
|
//buildLabel(iff);
|
|
if (match_process_standard(&tmpstream, cmp)) {
|
|
std::string next = std::string("IFF_") + random_string(4);
|
|
if (stream->match(tokens::cbracketo)) {
|
|
std::vector<token> toks = stream->next(tokens::cbracketo, tokens::cbracketc);
|
|
toks.pop_back();
|
|
tokenstream tempstream(&toks);
|
|
cmd* c = new cmd;
|
|
c->opcode = codes::IFFF;
|
|
c->args.push(cmp);
|
|
c->args.push(value(next));
|
|
current_chunk->addCmd(c);
|
|
ParseLoop(&tempstream);
|
|
buildGoto(ifend);
|
|
buildLabel(next);
|
|
if (match_process_ELIF(stream, ifend)) {
|
|
utils::debug("here");
|
|
}
|
|
else if (match_process_ELSE(stream, ifend)) {
|
|
utils::debug("here");
|
|
}
|
|
// We keep trying to match else if/else until nothing is left
|
|
return true;
|
|
}
|
|
}
|
|
else {
|
|
badSymbol(stream);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool LineParser::match_process_wait(tokenstream* stream) {
|
|
if (stream->match(tokens::name, tokens::number)) {
|
|
if (stream->peek().name == "wait") {
|
|
stream->next();
|
|
buildWait(std::stod(stream->next().name));
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void reset(value& l, codes::op& op, value& r) {
|
|
l = value();
|
|
op = codes::NOOP;
|
|
r = value();
|
|
}
|
|
|
|
bool LineParser::match_process_expression(tokenstream* stream, value& v) {
|
|
// I will have to consume for this to work so we need to keep track of what was incase we return false!
|
|
stream->store(current_chunk);
|
|
cmd* lastcmd = nullptr;
|
|
// It has to start with one of these 3 to even be considered an expression
|
|
if ((stream->match(tokens::number) || stream->match(tokens::name) || stream->match(tokens::parao)) && stream->tokens.size()>=3) {
|
|
// What do we know, math expressions can only be on a single line. We know where to stop looking if we have to
|
|
cmd* c = new cmd;
|
|
value wv;
|
|
value left; // lefthand
|
|
codes::op op; // opperator
|
|
value right; // righthand
|
|
reset(left, op, right);
|
|
size_t loops = 0;
|
|
bool hasOP = false;
|
|
while (stream->peek().type != tokens::none) {
|
|
debugInvoker(stream);
|
|
if (stream->match(tokens::parao)) {
|
|
auto ts = stream->next(tokens::parao, tokens::parac);
|
|
tokenstream temp(&ts);
|
|
value tmpvalue = value(datatypes::variable);
|
|
if (match_process_expression(&temp, tmpvalue)) {
|
|
if (left.isNil())
|
|
left = tmpvalue;
|
|
else if (right.isNil())
|
|
right = tmpvalue;
|
|
else
|
|
badSymbol(stream);
|
|
}
|
|
else {
|
|
badSymbol(stream);
|
|
}
|
|
// Take that temp value and set it to left or right TODO finish this
|
|
}
|
|
else if (stream->match(tokens::plus)) {
|
|
hasOP = true;
|
|
if (op == codes::NOOP)
|
|
op = codes::ADD;
|
|
else
|
|
badSymbol(stream);
|
|
stream->next();
|
|
}
|
|
else if (stream->match(tokens::minus)) {
|
|
hasOP = true;
|
|
if (left.isNil()) {
|
|
left = value(0); // -5 is the same as 0-5 right? Also -(5+5) is the same as 0-(5+5) So we good
|
|
}
|
|
if (op == codes::NOOP)
|
|
op = codes::SUB;
|
|
else
|
|
badSymbol(stream);
|
|
stream->next();
|
|
}
|
|
else if (stream->match(tokens::multiply)) {
|
|
hasOP = true;
|
|
if (op == codes::NOOP)
|
|
op = codes::MUL;
|
|
else
|
|
badSymbol(stream);
|
|
stream->next();
|
|
}
|
|
else if (stream->match(tokens::divide)) {
|
|
hasOP = true;
|
|
if (op == codes::NOOP)
|
|
op = codes::DIV;
|
|
else
|
|
badSymbol(stream);
|
|
stream->next();
|
|
}
|
|
else if (stream->match(tokens::percent)) {
|
|
hasOP = true;
|
|
if (op == codes::NOOP)
|
|
op = codes::MOD;
|
|
else
|
|
badSymbol(stream);
|
|
stream->next();
|
|
}
|
|
else if (stream->match(tokens::caret)) {
|
|
hasOP = true;
|
|
if (op == codes::NOOP)
|
|
op = codes::POW;
|
|
else
|
|
badSymbol(stream);
|
|
stream->next();
|
|
}
|
|
else if (stream->match(tokens::name,tokens::parao)) {
|
|
//Function template ^^^ If we encounter an issue the method should push an error, incase it misses something we will!
|
|
value tmpvalue = value(datatypes::variable);
|
|
if (match_process_function(stream,tmpvalue)) {
|
|
if (left.isNil())
|
|
left = tmpvalue;
|
|
else if (right.isNil())
|
|
right = tmpvalue;
|
|
else
|
|
badSymbol(stream);
|
|
}
|
|
else {
|
|
badSymbol(stream);
|
|
}
|
|
}
|
|
else if (stream->match(tokens::number)) {
|
|
double num = std::stod(stream->next().name);
|
|
if (left.isNil())
|
|
left = value(num);
|
|
else if (right.isNil())
|
|
right = value(num);
|
|
else
|
|
badSymbol(stream);
|
|
}
|
|
else if (stream->match(tokens::name)) {
|
|
// We tested functions already! So if that fails and we have a name then... we have a variable lets handle this!
|
|
if (left.isNil())
|
|
left = value(stream->next().name,datatypes::variable);
|
|
else if (right.isNil())
|
|
right = value(stream->next().name,datatypes::variable);
|
|
else
|
|
badSymbol(stream);
|
|
}
|
|
else if (stream->match(tokens::newline) || stream->match(tokens::parac) || stream->match(tokens::seperator)) {
|
|
if (wv.isNil())
|
|
return stream->restore(lastcmd, current_chunk); // Always return false and restores the position in stream!
|
|
cmd* cc = new cmd;
|
|
cc->opcode = codes::ASGN;
|
|
cc->args.push(v);
|
|
cc->args.push(wv);
|
|
current_chunk->addCmd(cc);
|
|
if(stream->match(tokens::parac))
|
|
stream->next();
|
|
// We done!
|
|
int t=0;
|
|
return true;
|
|
}
|
|
else if (stream->match(tokens::newline)) {
|
|
stream->next();
|
|
}
|
|
else {
|
|
return stream->restore(lastcmd, current_chunk);
|
|
}
|
|
if (!left.isNil() && !right.isNil() && op != codes::NOOP) {
|
|
cmd* c = new cmd;
|
|
c->opcode = op;
|
|
if (wv.isNil()) {
|
|
value temp = value(datatypes::variable);
|
|
c->args.push(temp);
|
|
wv = temp;
|
|
}
|
|
else {
|
|
wv = value(datatypes::variable);
|
|
c->args.push(wv);
|
|
}
|
|
c->args.push(left);
|
|
c->args.push(right);
|
|
current_chunk->addCmd(c);
|
|
reset(left, op, right);
|
|
left = wv;
|
|
}
|
|
}
|
|
return stream->restore(lastcmd, current_chunk); // Always return false and restores the position in stream!
|
|
}
|
|
else {
|
|
return stream->restore(lastcmd, current_chunk); // Always return false and restores the position in stream!
|
|
}
|
|
}
|
|
} |