summaryrefslogtreecommitdiff
path: root/runtime.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'runtime.cpp')
-rw-r--r--runtime.cpp285
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);
+}