diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | postrun.cpp | 222 | ||||
-rw-r--r-- | test.prn | 2 |
3 files changed, 226 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..babfeab --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +postrun +*.dSYM diff --git a/postrun.cpp b/postrun.cpp new file mode 100644 index 0000000..4de856b --- /dev/null +++ b/postrun.cpp @@ -0,0 +1,222 @@ +#include <iostream> +#include <fstream> +#include <sstream> +#include <vector> +#include <unordered_map> +#include <stack> +#include <functional> +#include <cstdlib> +#include <cstring> + +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);} + +vector<string> tokenise(istream &stream){ + vector<string> tokens; + string token; + char c,stringmode; + while(true){ + c=stream.get(); + if(!stream)break; + if(isword(c)||(token.size()>0&&isextword(c))){ + token+=c; + } else if(isdigit(c)){ + if(token.size()==0)token="#"; + token+=c; + } else { + if(token.size())tokens.push_back(move(token)); + else if(isoperator(c))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 'x':{ + int res; + c=stream.get(); + cerr<<"Parsed "<<c; + 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(); + cerr<<c<<" into "; + 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; + cerr<<(int)c<<endl; + break; + } + } + } + token+=c; + } + } else if(isspace(c)); + else throw string("Invalid character found in source: '")+c+'\''; + } + } + return tokens; +} + +string readfile(ifstream stream){ + stream.seekg(0,ios::end); + size_t len=stream.tellg(); + stream.seekg(0,ios::beg); + string contents; + contents.resize(len); + stream.read(&*contents.begin(),len); + return contents; +} + +struct Stackitem{ + bool isstr; + string strval; + int intval; +}; + +const unordered_map<string,function<void(stack<Stackitem>&,unordered_map<string,Stackitem>&)>> builtins={ + {"+",[](stack<Stackitem> &S,unordered_map<string,Stackitem> &variables){ + if(S.size()<2)throw string("Builtin '+' needs 2 stack items"); + Stackitem a,b,c; + b=S.top(); S.pop(); + a=S.top(); S.pop(); + if(!a.isstr&&!b.isstr){ + S.push({false,"",a.intval+b.intval}); + return; + } + if(!a.isstr){ + a.strval=to_string(a.intval); + a.isstr=true; + } + if(!b.isstr){ + b.strval=to_string(b.intval); + b.isstr=true; + } + S.push({true,a.strval+b.strval,0}); + }}, + {"-",[](stack<Stackitem> &S,unordered_map<string,Stackitem> &variables){ + if(S.size()<2)throw string("Builtin '-' needs 2 stack items"); + Stackitem a,b,c; + b=S.top(); S.pop(); + a=S.top(); S.pop(); + if(a.isstr||b.isstr)throw string("Builtin '-' cannot accept a string argument"); + S.push({false,"",a.intval-b.intval}); + }}, + {"print",[](stack<Stackitem> &S,unordered_map<string,Stackitem> &variables){ + if(S.size()<1)throw string("Builtin 'print' needs 1 stack item"); + Stackitem v; + v=S.top(); S.pop(); + if(v.isstr)cout<<v.strval<<flush; + else cout<<v.intval<<flush; + }} +}; + +void runpass2(const vector<string> &T, + stack<Stackitem> &S, + const unordered_map<string,vector<string>> &functions, + unordered_map<string,Stackitem> &variables){ + unsigned int cursor; + for(cursor=0;cursor<T.size();cursor++){ + const string &word=T[cursor]; + //cerr<<"Executing word <"<<word<<'>'<<endl; + if(word[0]=='#'){ + S.push({false,"",(int)strtol(word.data()+1,NULL,10)}); + } else if(word[0]=='\''){ + S.push({true,word.substr(1),0}); + } else { + auto it=variables.find(word); + if(it!=variables.end()){ + S.push(it->second); + } else { + auto it=functions.find(word); + if(it!=functions.end()){ + runpass2(it->second,S,functions,variables); + } else { + auto it=builtins.find(word); + if(it==builtins.end())throw "Unknown variable or function '"+word+"'"; + it->second(S,variables); + } + } + } + } +} + +void run(vector<string> T){ + unordered_map<string,vector<string>> functions; + unordered_map<string,Stackitem> variables; + stack<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; + for(end=start;end<T.size()&&T[end]!="}";end++); + vector<string> &functoks=functions[name]; + functoks.resize(end-start); + for(unsigned int i=start;i<end;i++)functoks[i]=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, evaluate file + runpass2(T,S,functions,variables); +} + +int main(int argc,char **argv){ + if(argc!=2){ + cerr<<"Call this interpreter with the Postrun source file"<<endl; + return 1; + } + ifstream srcf(argv[1]); + if(!srcf){ + cerr<<"Could not open file '"<<argv[1]<<"'"<<endl; + return 1; + } + //string source=readfile(srcf); + try { + const vector<string> tokens=tokenise(srcf); + for(const string &tok : tokens)cerr<<'['<<tok<<"] "; cerr<<endl; + run(tokens); + srcf.close(); + } catch(string e){ + cerr<<"ERROR: "<<e<<endl; + return 1; + } +} diff --git a/test.prn b/test.prn new file mode 100644 index 0000000..13fe1d7 --- /dev/null +++ b/test.prn @@ -0,0 +1,2 @@ +"a" "b\x0a" + print +1 12 + 3 - print |