DMS/DMS/LineParserMatchProcess.cpp
2020-09-14 00:03:21 -04:00

807 lines
25 KiB
C++

#include "LineParser.h"
using namespace dms::tokens;
using namespace dms::utils;
namespace dms {
bool LineParser::match_process_standard(tokenstream* stream, value* v) {
if (match_process_expression(stream,v)) {
return true; // Nothing todo
}
else if (match_process_function(stream, v)) {
return true; // Nothing todo
}
else if (match_process_list(stream, v)) {
return true;
}
else if (stream->match(tokens::True)) {
v->set(buildBool(true));
stream->next();
return true;
}
else if (stream->match(tokens::False)) {
v->set(buildBool(false));
stream->next();
return true;
}
else if (stream->match(tokens::number)) {
v->set(buildNumber(std::stod(stream->next().name)));
return true;
}
else if (stream->match(tokens::string)) {
v->set(buildString(stream->next().name));
return true;
}
else if (stream->match(tokens::name)) {
v->set(buildString(stream->next().name));
v->type = datatypes::variable;
return true;
}
else if (stream->match(tokens::nil)) {
stream->next();
v->set();
return true;
}
else if (stream->match(tokens::bracketo, tokens::name, tokens::bracketc)) {
// We are assigning a block as a variable
stream->next();
v->set(buildString(stream->next().name));
v->type = datatypes::block;
stream->next();
return true;
}
else if (stream->match(tokens::newline)) {
return false;
}
return false;
}
bool LineParser::match_process_list(tokenstream* stream, value* v) {
if (stream->match(tokens::cbracketo)) {
token start = stream->peek();
std::vector<token> t = stream->next(tokens::cbracketo, tokens::cbracketc);
tokenstream tempstream;
tempstream.init(&t);
value* ref = buildVariable();
value* length = new 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);
while (tempstream.peek().type != tokens::none) {
print(tempstream.peek());
if (tempstream.match(tokens::cbracketc)) {
c = new cmd;
c->opcode = codes::INST;
c->args.push(v);
c->args.push(ref);
current_chunk->addCmd(c);
break;
}
// This will modify the ref!!!
else if (match_process_standard(&tempstream, ref)) {
count++;
print(">>",tempstream.peek());
// TODO: make sure each statement is properly seperated by a comma n such
/*if (tempstream.match(tokens::seperator) || tempstream.match(tokens::newline,tokens::seperator)) {
print("Good!");
}*/
/*if (!(tempstream.match(tokens::seperator) || tempstream.match(tokens::cbracketc) || tempstream.match(tokens::newline))) {
state->push_error(errors::error{ errors::badtoken,concat("Unexpected symbol '",tempstream.next().toString(),"' Expected '}' to close list at ",start.line_num),true,tempstream.peek().line_num,current_chunk });
return false;
}*/
}
else if (tempstream.match(tokens::newline)) {
tempstream.next(); // this is fine
}
else if (tempstream.match(tokens::seperator)) {
// Handle the next piece
c = new cmd;
c->opcode = codes::INST;
c->args.push(v);
c->args.push(ref);
current_chunk->addCmd(c);
// Build new value
ref = buildVariable();
tempstream.next();
}
else if (tempstream.match(tokens::cbracketo)) {
tempstream.next();
}
else {
badSymbol(&tempstream);
}
}
length->set(buildNumber(count)); // the second argument is the length of the list! This should be modified if lists are changed at runtime!
return true;
}
return false;
}
bool LineParser::match_process_disp(tokenstream* stream) {
/*
DISP, "msg"
DISP, "msg" speaker
Compound DISP
*/
//lastCall.push("MP_disp");
if ((isBlock(bt_block) || isBlock(bt_method)) && stream->match(tokens::newline, tokens::string, tokens::newline)) {
stream->next(); // Standard consumption
std::string msg = stream->next().name;
cmd* c = new cmd;
c->opcode = codes::DISP;
//c->args.push(buildValue());
c->args.push(buildValue(msg));
current_chunk->addCmd(c); // Add the cmd to the current chunk
//lastCall.pop();
return true;
}
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(buildVariable(name));
current_chunk->addCmd(c);
c = new cmd;
c->opcode = codes::DISP;
//c->args.push(buildValue());
c->args.push(buildValue(msg));
current_chunk->addCmd(c); // Add the cmd to the current chunk
// We might have to consume a newline... Depends on what's next
//lastCall.pop();
return true;
}
else if ((isBlock(bt_block) || isBlock(bt_method)) && stream->match(tokens::name,tokens::colon,tokens::cbracketo)) {
std::string name = stream->next().name;
cmd* c = new cmd;
c->opcode = codes::SSPK;
c->args.push(buildVariable(name));
current_chunk->addCmd(c);
// Reset the display for the new speaker. Append can be used!
//c = new cmd;
//c->opcode = codes::DISP;
//c->args.push(buildValue());
//c->args.push(buildValue(std::string("")));
//current_chunk->addCmd(c);
// Command to set the speaker
stream->next();
stream->next();
while (stream->peek().type != tokens::cbracketc) {
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(buildValue(mode));
current_chunk->addCmd(c);
// Now build the apnd msg cmd
c = new cmd;
c->opcode = codes::APND;
c->args.push(buildValue(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(buildValue(stream->next().name));
current_chunk->addCmd(c);
}
else if (stream->match(tokens::newline)) {
stream->next();
}
else {
badSymbol(stream);
return false;
}
}
stream->next();
return true;
}
// emotion: "path"
// looks like a simple disp command
else if (isBlock(bt_character) && stream->match(tokens::tab, tokens::name, tokens::colon, tokens::string, tokens::newline)) {
//lastCall.pop();
return true;
}
// TODO: We still have to implement the compound disp
//lastCall.pop();
return false;
}
bool LineParser::match_process_assignment(tokenstream* stream) {
// something equals something else lets go
//lastCall.push("MP_assignment");
if (stream->match(tokens::name,tokens::equal)) {
value* var = buildVariable(stream->next().name); // 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);
if (match_process_list(stream, var)) {
return true;
}
else if (match_process_expression(stream, var)) {
// Expressions can internally set variables
// We actually need to clean up our cmds
return true;
}
else if (match_process_function(stream, var)) {
// Functions can internally set variables
// We actually need to clean up our cmds
return true;
}
else if (stream->match(tokens::True)) {
stream->next();
c->args.push(buildValue(true));
current_chunk->addCmd(c);
return true;
}
else if (stream->match(tokens::False)) {
stream->next();
c->args.push(buildValue(false));
current_chunk->addCmd(c);
return true;
}
else if (stream->match(tokens::string)) {
c->args.push(buildValue(stream->next().name));
current_chunk->addCmd(c);
return true;
}
else if (stream->match(tokens::nil)) {
stream->next();
c->args.push(buildValue());
current_chunk->addCmd(c);
return true;
}
else if (stream->match(tokens::number)) {
c->args.push(buildValue(std::stod(stream->next().name)));
current_chunk->addCmd(c);
return true;
}
else if (stream->match(tokens::bracketo, tokens::name, tokens::bracketc)) {
// We are assigning a block as a variable
stream->next();
c->args.push(buildBlock(stream->next().name));
current_chunk->addCmd(c);
stream->next();
return true;
}
else if (stream->match(tokens::name)) {
c->args.push(buildVariable(stream->next().name));
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(buildValue(stream->next().name));
}
else {
c->args.push(buildVariable(stream->next().name));
}
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) {
//lastCall.push("MP_choice");
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
std::string choicelabel = concat("$CHOI_END_", stream->peek().line_num);
c->opcode = codes::CHOI;
c->args.push(buildValue(prompt));
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!" "Choice 1" "Choice 2" "Choice 3" "Choice 4"
1 FUNC print "You picked 1!"
2 GOTO $CHOI_END_1
3 FUNC print "You picked 2!"
4 GOTO $CHOI_END_1
5 JUMP park
6 NOOP
7 GOTO mylabel
8 LABL $CHOI_END_1
The CHOI code tells the vm that we need to process user input. The input we get in a number 0-3
I know we have 4 choices
If the user provides us with a 0 then we need to move to off 1
If the user provides us with a 1 then we need to move to off 3
If the user provides us with a 2 then we need to move to off 5
If the user provides us with a 3 then we need to move to off 7
I'm sure you see the pattern here. 1 (+2) 3 (+2) 5... We only need to jump once then let the vm continue like normal.
The math for this is: [current_pos] + (n*2+1)
n*2+1 (n = 0) = 1
n*2+1 (n = 1) = 3
n*2+1 (n = 2) = 5
n*2+1 (n = 3) = 7
Which is why you see NOOP for the JUMP code. If GOTO wasn't the last choice possible to make there would be a NOOP after that as well.
The NOOP ensures the pattern stays.
If we are provided with a number greater than 3 then we can push an execption.
*/
std::string str = concat("$",stream->peek().line_num);
while (!stream->match(tokens::cbracketc)) {
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(buildValue(name)); // We append the choice to the first part of the CHOI cmd
// We consumed the option now lets do some matching, note that all of these are one liners in the bytecode!
if (match_process_function(stream,nullptr,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(str);
}
else if (match_process_goto(stream)) {
buildNoop(); // Add noop to post-goto command
}
else if (match_process_jump(stream)) {
buildNoop(); // Add noop to post-jump command
}
else if (match_process_exit(stream)) {
buildNoop(); // Add noop to post-exit command
}
}
// Last Match
else if (stream->match(tokens::newline)) {
stream->next(); // Consume
}
else {
badSymbol(stream);
}
}
cmd* cc = current_chunk->cmds.back(); // Delete last element
current_chunk->cmds.pop_back();
delete cc;
if (hasfunc)
buildLabel(str);
//lastCall.pop();
return true;
}
//lastCall.pop();
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) {
//lastCall.push("MP_function");
/*
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)) {
cmd* c = new cmd;
c->opcode = codes::FUNC;
std::string n = stream->next().name;
c->args.push(buildVariable(n)); // Set the func identifier as the first variable
// Let's set the target
if (v != nullptr) {
c->args.push(v); // Push the supplied variable
}
else {
c->args.push(buildValue()); // Creates a nil value
}
// Already we have built: FUNC name val
// Next we add arguments this is where things get interesting
tokenstream tempstream;
// This is a balanced consuming method (()(()))
std::vector<token> t = stream->next(tokens::parao, tokens::parac); // Consume and get tokens
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;
// 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
print("> ", tempstream.peek());
tempval = buildVariable();
tok = tempstream.peek();
if (tempstream.match(tokens::seperator)) {
// We have a seperator for function arguments
tempstream.next(); // Consume it
cleanup(tempval); // We don't need it
}
else if (match_process_list(&tempstream, tempval)) {
c->args.push(tempval);
}
else if (match_process_expression(&tempstream, tempval)) {
c->args.push(tempval);
}
else if (tempstream.match(tokens::string)) {
cleanup(tempval);
tempval = buildValue(tempstream.next().name); // Get the string
c->args.push(tempval); // add this argument to the function opcodes
}
else if (tempstream.match(tokens::number)) {
cleanup(tempval);
std::string str = tempstream.next().name;
tempval = buildValue(std::stod(str)); // Get the number and convert it to a double then build a value
c->args.push(tempval); // add this argument to the function opcodes
}
else if (tempstream.match(tokens::True)) {
cleanup(tempval);
tempval = buildValue(true);
c->args.push(tempval);
tempstream.next();
}
else if (tempstream.match(tokens::False)) {
cleanup(tempval);
tempval = buildValue(false);
c->args.push(tempval);
tempstream.next();
} else if (tempstream.match(tokens::nil)){
cleanup(tempval);
tempval = buildValue();
c->args.push(tempval);
tempstream.next();
}
// Final match this could be a function it might also be an expression
else if (match_process_function(&tempstream, tempval)) {
if (!nested) {
state->push_error(errors::error{ errors::nested_function,"Nested functions are not allowed in this context!",true, tempstream.peek().line_num });
}
c->args.push(tempval);
}
else if (tempstream.match(tokens::name)) {
cleanup(tempval);
tempval = buildVariable(tempstream.next().name);
c->args.push(tempval);
}
else if (tempstream.match(tokens::newline)) {
tempstream.next();
}
else if (tempstream.match(tokens::parac)) {
cleanup(tempval);
tempstream.next();
current_chunk->addCmd(c); // We push this onto the chunk after all dependants if any have been handled
//lastCall.pop();
return true;
}
else {
cleanup(tempval); // Cleanup
badSymbol(&tempstream);
}
}
}
//lastCall.pop();
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_jump(tokenstream* stream) {
if (stream->match(tokens::jump, tokens::name) || tokens::jump, tokens::string) {
cmd* c = new cmd;
c->opcode = codes::JUMP;
stream->next(); // consume jump
if (stream->match(tokens::name)) {
c->args.push(buildVariable(stream->next().name));
}
else {
c->args.push(buildValue(stream->next().name));
}
current_chunk->addCmd(c);
return true;
}
return false;
}
bool LineParser::match_process_exit(tokenstream* stream) {
if (stream->match(tokens::exit)) {
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(buildValue(std::stod(stream->next().name)));
}
else {
c->args.push(buildVariable(stream->next().name));
}
}
current_chunk->addCmd(c);
return true;
}
return false;
}
bool LineParser::match_process_IFFF(tokenstream* stream) {
return false; // TODO finish this
}
void reset(value*& l,codes::op& op, value*& r) {
l = nullptr;
op = codes::NOOP;
r = nullptr;
}
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;
/*if(!current_chunk->cmds.empty())
lastcmd = current_chunk->cmds.back();*/
// 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)) {
// 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;
/*
* We have a few built-in methods we will have to handle
* ADD val v1 v2
* SUB val v1 v2
* MUL val v1 v2
* DIV val v1 v2
* POW val v1 v2
* MOD val v1 v2
*/
value* wv = nullptr;
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) {
if (stream->match(tokens::parao)) {
tokenstream temp;
temp.init(&(stream->next(tokens::parao, tokens::parac))); // Balanced match!
value* tmpvalue = buildVariable();
if (match_process_expression(&temp, tmpvalue)) {
if (left == nullptr)
left = tmpvalue;
else if (right == nullptr)
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 == nullptr) {
left = buildValue(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 = buildVariable();
if (match_process_function(stream,tmpvalue)) {
if (left == nullptr)
left = tmpvalue;
else if (right == nullptr)
right = tmpvalue;
else
badSymbol(stream);
}
else {
badSymbol(stream);
}
}
else if (stream->match(tokens::number)) {
double num = std::stod(stream->next().name);
if (left == nullptr)
left = buildValue(num);
else if (right == nullptr)
right = buildValue(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 == nullptr)
left = buildVariable(stream->next().name);
else if (right == nullptr)
right = buildVariable(stream->next().name);
else
badSymbol(stream);
}
else if (/*stream->match(tokens::newline) || */stream->match(tokens::parac) || stream->match(tokens::seperator)) {
if (wv == nullptr)
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);
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 != nullptr && right != nullptr && op != codes::NOOP) {
cmd* c = new cmd;
c->opcode = op;
if (wv == nullptr) {
value* temp = buildVariable();
c->args.push(temp);
wv = temp;
}
else {
wv = buildVariable();
c->args.push(wv);
}
c->args.push(left);
c->args.push(right);
current_chunk->addCmd(c);
reset(left, op, right);
left = wv;
}
}
}
else {
return stream->restore(lastcmd, current_chunk); // Always return false and restores the position in stream!
}
}
}