diff options
Diffstat (limited to 'runtime.cpp')
-rw-r--r-- | runtime.cpp | 285 |
1 files changed, 285 insertions, 0 deletions
diff --git a/runtime.cpp b/runtime.cpp new file mode 100644 index 0000000..63e0b52 --- /dev/null +++ b/runtime.cpp @@ -0,0 +1,285 @@ +#include <iostream> +#include <vector> +#include <string> +#include <unordered_map> +#include <functional> +#include <cstring> + +#include "runtime.h" + +using namespace std; + +inline bool isword(char c){return isalpha(c)||c=='_'||c=='@'||c=='$';} +inline bool isextword(char c){return isword(c)||isdigit(c);} +inline bool isoperator(char c){return (bool)strchr("+-*/=><!%^{}",c);} + + +Stackitem::Stackitem(void):isstr(false),intval(0){} +Stackitem::Stackitem(int _i):isstr(false),intval(_i){} +Stackitem::Stackitem(const string &_s):isstr(true),strval(_s),intval(0){} +Stackitem::Stackitem(const Stackitem&)=default; +Stackitem::Stackitem(Stackitem &&other):isstr(other.isstr),strval(move(other.strval)),intval(other.intval){ + other.isstr=false; +} +Stackitem& Stackitem::operator=(const Stackitem&)=default; +Stackitem& Stackitem::operator=(Stackitem &&other){ + isstr=other.isstr; + strval=move(other.strval); + intval=other.intval; + other.isstr=false; + return *this; +} +Stackitem::operator bool(void) const{return (isstr&&strval.size())||(!isstr&&intval);} +bool Stackitem::operator==(const Stackitem &other) const{return isstr==other.isstr&&(isstr?strval==other.strval:intval==other.intval);} + + +extern const unordered_map<string,function<void(vector<Stackitem>&,unordered_map<string,Stackitem>&)>> builtins; + + +vector<string> tokenise(istream &stream){ + vector<string> tokens; + string token; + char c,stringmode; + bool gotminus=false; + while(true){ + c=stream.get(); + if(!stream)break; + while(c=='#'){ + while((c=stream.get())!='\n'&&stream); + c=stream.get(); + if(!stream)break; + } + if(isdigit(c)){ + if(token.size()==0){ + token="#"; + if(gotminus)token+="-"; + gotminus=false; + } + token+=c; + } else if(isword(c)||(token.size()>0&&isextword(c))){ + token+=c; + } else { + if(token.size())tokens.push_back(move(token)); + if(isoperator(c)){ + if(c=='-'&&isdigit(stream.peek()))gotminus=true; + else tokens.push_back(string(1,c)); + } else if(c=='"'||c=='\''){ + stringmode=c; + while(true){ + c=stream.get(); + if(!stream)throw string("Non-terminated string at end of file"); + if(c==stringmode){ + token="'"+token; + tokens.push_back(move(token)); + break; + } + if(c=='\\'){ + c=stream.get(); + if(!stream)throw string("Non-terminated escape sequence in string at end of file"); + switch(c){ + case 'n':c='\n';break; + case 'r':c='\r';break; + case 't':c='\t';break; + case '0':c='\0';break; + case '"':c='"';break; + case '\'':c='\'';break; + case 'x':{ + int res; + c=stream.get(); + if(!stream)throw string("Non-terminated hexadecimal escape sequence in string at end of file"); + res=c>='0'&&c<='9'?c-'0':(c>='a'&&c<='f')||(c>='A'&&c<='F')?(c&~32)-'A'+10:-1; + if(res==-1)throw "Invalid character '"+string(1,c)+"'' in hexadecimal escape sequence in string"; + res*=16; + c=stream.get(); + if(!stream)throw string("Non-terminated hexadecimal escape sequence in string at end of file"); + res+=c>='0'&&c<='9'?c-'0':(c>='a'&&c<='f')||(c>='A'&&c<='F')?(c&~32)-'A'+10:-241; + if(res<0)throw "Invalid character '"+string(1,c)+"'' in hexadecimal escape sequence in string"; + c=(char)res; + break; + } + } + } + token+=c; + } + } else if(isspace(c)); + else throw string("Invalid character found in source: '")+c+'\''; + } + } + if(token.size())tokens.push_back(move(token)); + return tokens; +} + +void runpasseval(const vector<string> &T, + vector<Stackitem> &S, + const unordered_map<string,pair<vector<string>,unordered_map<unsigned int,unsigned int>>> &functions, + unordered_map<string,Stackitem> &variables, + const unordered_map<unsigned int,unsigned int> &jumpmap){ + unsigned int cursor; + for(cursor=0;cursor<T.size();cursor++){ + const string &word=T[cursor]; + //cerr<<"Executing word <"<<word<<'>'<<endl; + if(word[0]=='#'){ + S.emplace_back((int)strtol(word.data()+1,NULL,10)); + } else if(word[0]=='\''){ + S.push_back(word.substr(1)); + } else { + auto it=variables.find(word); + if(it!=variables.end()){ + S.push_back(it->second); + } else { + auto it=functions.find(word); + if(it!=functions.end()){ + runpasseval(it->second.first,S,functions,variables,it->second.second); + } else { + if(word=="while"){ + if(S.size()<1)throw string("Keyword 'while' needs 1 stack item"); + Stackitem v=S.back(); S.pop_back(); + if(!v){ + auto it=jumpmap.find(cursor); + if(it==jumpmap.end()){ + cerr<<"NO JUMPMAP ENTRY FOR "<<word<<" AT INDEX "<<cursor<<endl; + } + cursor=jumpmap.find(cursor)->second-1; //jump to corresponding end + } + continue; + } else if(word=="if"){ + if(S.size()<1)throw string("Keyword 'if' needs 1 stack item"); + Stackitem v=S.back(); S.pop_back(); + if(!v){ + auto it=jumpmap.find(cursor); + if(it==jumpmap.end()){ + cerr<<"NO JUMPMAP ENTRY FOR "<<word<<" AT INDEX "<<cursor<<endl; + } + cursor=jumpmap.find(cursor)->second-1; //jump to corresponding else/end + } + continue; + } else if(word=="else"||word=="end"){ + auto it=jumpmap.find(cursor); + if(it==jumpmap.end()){ + cerr<<"NO JUMPMAP ENTRY FOR "<<word<<" AT INDEX "<<cursor<<endl; + } + cursor=jumpmap.find(cursor)->second-1; + continue; + } + + auto it=builtins.find(word); + if(it==builtins.end())throw "Unknown variable or function '"+word+"'"; + it->second(S,variables); + } + } + } + } +} + +void populatejumpmap(const vector<string> &T,unordered_map<unsigned int,unsigned int> &jumpmap){ + //first, index flow control keywords + vector<unsigned int> flowindexes; + for(unsigned int cursor=0;cursor<T.size();cursor++){ + const string &word=T[cursor]; + if(word=="end"||word=="while"||word=="if"||word=="else")flowindexes.push_back(cursor); + } + + //then, index jump targets in jumpmap + for(unsigned int i=0;i<flowindexes.size();i++){ + const string &word=T[flowindexes[i]]; + if(word=="while"){ + if(i==flowindexes.size()-1)throw string("No 'end' after 'while' in source"); + int depth=1; + unsigned int end; + for(end=i+1;end<flowindexes.size();end++){ + if(T[flowindexes[end]]=="end")depth--; + else if(T[flowindexes[end]]=="while"||T[flowindexes[end]]=="if")depth++; + if(depth==0)break; + } + if(end==flowindexes.size())throw string("No 'end' after 'while' in source"); + jumpmap[flowindexes[i]]=flowindexes[end]+1; + jumpmap[flowindexes[end]]=flowindexes[i]; + } else if(word=="if"){ + if(i==flowindexes.size()-1)throw string("No 'end' after 'if' in source"); + int depth=1; + unsigned int els,end; + for(els=i+1;els<flowindexes.size();els++){ + if(depth==1&&T[flowindexes[els]]=="else")break; + else if(T[flowindexes[els]]=="end")depth--; + else if(T[flowindexes[els]]=="while"||T[flowindexes[els]]=="if")depth++; + if(depth==0)break; + } + if(els==flowindexes.size())throw string("No 'else' or 'end' after 'if' in source"); + if(depth!=0){ //we found an else + for(end=els+1;end<flowindexes.size();end++){ + if(T[flowindexes[end]]=="end")depth--; + else if(T[flowindexes[end]]=="while"||T[flowindexes[end]]=="if")depth++; + if(depth==0)break; + } + if(end==flowindexes.size())throw string("No 'end' after 'if' in source"); + jumpmap[flowindexes[i]]=flowindexes[els]+1; + jumpmap[flowindexes[els]]=flowindexes[end]+1; + jumpmap[flowindexes[end]]=flowindexes[end]+1; + } else { //we found an end in the first place + //cerr<<"coupling if-end indexes "<<flowindexes[i]<<" and "<<flowindexes[els]<<endl; + jumpmap[flowindexes[i]]=flowindexes[els]+1; + jumpmap[flowindexes[els]]=flowindexes[els]+1; + } + } + } +} + +void run(vector<string> T){ + unordered_map<string,pair<vector<string>,unordered_map<unsigned int,unsigned int>>> functions; + //functions is a pair of tokens and jumpmap + unordered_map<string,Stackitem> variables; + unordered_map<unsigned int,unsigned int> jumpmap; + vector<Stackitem> S; + unsigned int cursor=0; + //first pass, handle functions and includes + while(cursor<T.size()){ + const string &word=T[cursor]; + if(word=="@defun"){ + if(cursor+3>=T.size())throw string("Unterminated @defun statement at end of file"); + string name=T[cursor+1]; + if(name[0]!='\'')throw string("@defun expected a string as name, but got '")+name+"'"; + name.erase(0,1); + if(T[cursor+2]!="{")throw string("Invalid @defun statement of function ")+name; + unsigned int start=cursor+3; + unsigned int end; + int depth=1; + for(end=start;end<T.size();end++){ + if(T[end]=="{")depth++; + else if(T[end]=="}")depth--; + else if(T[end]=="\""){ + while(++end<T.size()&&T[end]!="\"")if(T[end]=="\\")end++; + } else if(T[end]=="'"){ + while(++end<T.size()&&T[end]!="'")if(T[end]=="\\")end++; + } + if(depth==0)break; + } + if(depth!=0)throw string("Non-terminated @defun statement at end of file"); + vector<string> &functoks=functions[name].first; + functoks.resize(end-start); + for(unsigned int i=start;i<end;i++)functoks[i-start]=move(T[i]); + T.erase(T.begin()+cursor,T.begin()+end+1); + } else if(word=="@include"){ + if(cursor+1>=T.size())throw string("Unterminated @include statement at end of file"); + string name=T[cursor+1]; + if(name[0]!='\'')throw string("@include expected a string as file, but got '")+name+"'"; + name.erase(0,1); + ifstream file(name); + if(!file)throw string("Could not open file '")+name+"' specified by @include statement"; + vector<string> included=tokenise(file); + file.close(); + T.erase(T.begin()+cursor,T.begin()+cursor+2); + T.insert(T.begin()+cursor,included.begin(),included.end()); + } else { + cursor++; + } + } + + //second pass, populate jumpmaps + populatejumpmap(T,jumpmap); + for(auto &p : functions){ + populatejumpmap(p.second.first,p.second.second); + } + + //third pass, evaluate code + runpasseval(T,S,functions,variables,jumpmap); +} |