Mon, 16 Feb 2015 12:58:31 +0200
* if/elseif/else now compiles into VM code, probably speeding execution by
2 orders of magnitute
--- a/Source/Scripting/Compilers.h Fri Feb 13 11:02:15 2015 +0200 +++ b/Source/Scripting/Compilers.h Mon Feb 16 12:58:31 2015 +0200 @@ -6,6 +6,7 @@ #include "Data.h" #include "Instruction.h" +#include "Compilers/AST.h" #include "../Types.h" namespace Gorgon { namespace Scripting { @@ -60,14 +61,17 @@ /// Programming dialect compiler class Programming : public Base { public: - + Programming() : compiler(List) { } + virtual unsigned Compile(const std::string &input) override; virtual void Finalize() override; private: + ASTCompiler compiler; std::string left; std::vector<unsigned> linestarts; + int waiting=0; }; /// Disassembles the given instruction
--- a/Source/Scripting/Compilers/AST.cpp Fri Feb 13 11:02:15 2015 +0200 +++ b/Source/Scripting/Compilers/AST.cpp Mon Feb 16 12:58:31 2015 +0200 @@ -361,12 +361,13 @@ Utils::ASSERT_FALSE("Unknown AST Node"); } - //This function compiles a given AST tree - unsigned CompileAST(ASTNode *tree, std::vector<Instruction> &list) { + unsigned ASTCompiler::Compile(ASTNode *tree) { auto sz=list.size(); //temporaries start from 1 Byte tempind=1; + + std::vector<Instruction> writebacks; //Assignment operation if(tree->Type==ASTNode::Assignment) { @@ -374,7 +375,6 @@ inst.Type=InstructionType::Assignment; inst.RHS=compilevalue(tree->Leaves[1], list, tempind); - std::vector<Instruction> writebacks; if(tree->Leaves[0].Type==ASTNode::Identifier || tree->Leaves[0].Type==ASTNode::Variable) { inst.Name.Type=ValueType::Variable; @@ -420,10 +420,92 @@ //Keyword call else if(tree->Type==ASTNode::Keyword) { -// if(tree->Text=="if") { -// -// } -// else { + //fully compile if keyword + if(String::ToLower(tree->Text)=="if") { + if(tree->Leaves.GetCount()==0) { + throw MissingParameterException("condition", 0, "Bool", "Condition for If keyword is not specified"); + } + else if(tree->Leaves.GetCount()>1) { + throw TooManyParametersException(tree->Leaves.GetCount(), 1, "If keyword requires only a single condition"); + } + + Instruction jf; + jf.Type=InstructionType::JumpFalse; + jf.RHS=compilevalue(tree->Leaves[0], list, tempind); + + scopes.push_back(scope{scope::ifkeyword, (int)list.size()}); + + list.push_back(jf); + waitingcount++; + } + else if(String::ToLower(tree->Text)=="else") { + if(scopes.size()==0 || scopes.back().type!=scope::ifkeyword) { + throw FlowException("Else without If"); + } + if(scopes.back().elsepassed) { + throw FlowException("Multiple Else for a single If"); + } + + Instruction ja; + ja.Type=InstructionType::Jump; + list.push_back(ja); + + list[scopes.back().indices.back()].JumpOffset=list.size()-scopes.back().indices.back(); + scopes.back().indices.back()=list.size()-1; + scopes.back().elsepassed=true; + } + else if(String::ToLower(tree->Text)=="elseif") { + if(scopes.size()==0 || scopes.back().type!=scope::ifkeyword) { + throw FlowException("ElseIf without If"); + } + if(scopes.back().elsepassed) { + throw FlowException("ElseIf statements must be before Else statement"); + } + if(tree->Leaves.GetCount()==0) { + throw MissingParameterException("condition", 0, "Bool", "Condition for ElseIf keyword is not specified"); + } + else if(tree->Leaves.GetCount()>1) { + throw TooManyParametersException(tree->Leaves.GetCount(), 1, "ElseIf keyword requires only a single condition"); + } + + Instruction ja; + ja.Type=InstructionType::Jump; + list.push_back(ja); + + list[scopes.back().indices.back()].JumpOffset=list.size()-scopes.back().indices.back(); + scopes.back().indices.back()=list.size()-1; + + Instruction jf; + jf.Type=InstructionType::JumpFalse; + jf.RHS=compilevalue(tree->Leaves[0], list, tempind); + + scopes.back().indices.push_back(list.size()); + list.push_back(jf); + } + else if(String::ToLower(tree->Text)=="end" && (scopes.size()==0 || scopes.back().type!=scope::unknown) ) { + if(scopes.size()==0) { + throw FlowException("end without keyword"); + } + + switch(scopes.back().type) { + case scope::ifkeyword: + for(auto &index : scopes.back().indices) { + list[index].JumpOffset=list.size()-index; + } + break; + } + + scopes.pop_back(); + waitingcount--; + } + else { + if(String::ToLower(tree->Text)=="end") { + scopes.pop_back(); + } + else if(Keywords.Functions[tree->Text].IsScoped()) { + scopes.push_back(scope{scope::unknown, (int)list.size()}); + } + Instruction instmark, inst; //Mark the start of the keyword @@ -443,12 +525,21 @@ inst.Parameters.push_back(compilevalue(p, list, tempind)); } list.push_back(inst); -// } + } } else { ASSERT(false, "Unknown top level AST Node"); } + //release used temporaries + for(int i=1;i<tempind;i++) { + Instruction inst; + inst.Type=InstructionType::RemoveTemp; + inst.Store=i; + + list.push_back(inst); + } + return list.size()-sz; }
--- a/Source/Scripting/Compilers/AST.h Fri Feb 13 11:02:15 2015 +0200 +++ b/Source/Scripting/Compilers/AST.h Mon Feb 16 12:58:31 2015 +0200 @@ -100,11 +100,47 @@ }; - /// This function compiles given abstract syntax tree, returns the number of instructions generated. - ///@param tree is the AST to be compiled - ///@param list is the list where the instructions will be placed. Instructions will be added - /// at the end of the list - unsigned CompileAST(ASTNode *tree, std::vector<Instruction> &list); + /** + * ASTCompiler stores states for AST compiler. This class requires the list of instructions to be saved. + * + */ + class ASTCompiler { + public: + + /// AST compiler requires a vector of instructions. The compiler appends elements to the end of the + /// list. However, it is important not to modify the list while IsReady function returns false. + ASTCompiler(std::vector<Instruction> &list) : list(list) { } + + /// This function compiles given abstract syntax tree, returns the number of instructions generated. + /// You should check IsReady function before using instructions. + ///@param tree is the AST to be compiled + unsigned Compile(ASTNode *tree); + + /// If this function returns true, it is ok to use instructions from the list. A return value of + /// false means not all instructions are fully completed. In these cases, more ASTs should be + /// supplied to the compiler + bool IsReady() const { return waitingcount==0; } + + private: + + struct scope { + enum scopetype { + unknown, + ifkeyword, + } type; + + scope(scopetype type, int index) : type(type) { indices.push_back(index); } + + bool elsepassed=0; + std::vector<int> indices; + }; + + std::vector<scope> scopes; + + int waitingcount = 0; + std::vector<Instruction> &list; + + }; /// Converts given AST to an SVG file. This function requires GraphViz dot to be in path. The SVG will be saved /// as temp.dot.svg in the current directory.
--- a/Source/Scripting/Compilers/PD.cpp Fri Feb 13 11:02:15 2015 +0200 +++ b/Source/Scripting/Compilers/PD.cpp Mon Feb 16 12:58:31 2015 +0200 @@ -945,7 +945,8 @@ left.append(input); - int elements=0; + int elements=waiting; + waiting=0; while(left.length()) { //parse until we run out of data std::string process; @@ -956,7 +957,7 @@ if(ret) { try { - elements+=CompileAST(ret, List); + elements+=compiler.Compile(ret); } catch(...) { delete ret; @@ -965,15 +966,21 @@ else { break; } - - std::vector<std::string> strlines; - for(auto it=List.end()-elements;it!=List.end();++it) { - strlines.push_back(Disassemble(&(*it))); + + if(showsvg__) { + std::vector<std::string> strlines; + for(auto it=List.end()-elements;it!=List.end();++it) { + strlines.push_back(Disassemble(&(*it))); + } + + ASTToSVG(input, *ret, strlines, true); } - if(showsvg__) - ASTToSVG(input, *ret, strlines, true); - + } + + if(!compiler.IsReady()) { + waiting=elements; + elements=0; } return elements;
--- a/Source/Scripting/Scripting.cpp Fri Feb 13 11:02:15 2015 +0200 +++ b/Source/Scripting/Scripting.cpp Mon Feb 16 12:58:31 2015 +0200 @@ -22,8 +22,9 @@ } pline++; + int compiled=0; try { - parser->Compile(str); + compiled=parser->Compile(str); } catch(ParseError &err) { if(err.GetLine()<0) @@ -32,10 +33,11 @@ throw err; } - for(auto &inst : parser->List) - lines.push_back({inst, pline}); + for(int i=parser->List.size()-compiled;i<parser->List.size();i++) { + lines.push_back({parser->List[i], pline}); + } - parser->List.clear(); + parser->List.erase(parser->List.end()-compiled, parser->List.end()); } return &lines[line].instruction;