#include #include #include #include #include #include #include #include #include #include #include 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("+-*/=> tokenise(istream &stream){ vector 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; } 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; Stackitem(void):isstr(false),intval(0){} Stackitem(int _i):isstr(false),intval(_i){} Stackitem(const string &_s):isstr(true),strval(_s),intval(0){} Stackitem(const Stackitem&)=default; Stackitem(Stackitem &&other):isstr(other.isstr),strval(move(other.strval)),intval(other.intval){ other.isstr=false; } Stackitem& operator=(const Stackitem&)=default; Stackitem& operator=(Stackitem &&other){ isstr=other.isstr; strval=move(other.strval); intval=other.intval; other.isstr=false; return *this; } explicit operator bool(void) const{return (isstr&&strval.size())||(!isstr&&intval);} bool operator==(const Stackitem &other) const{return isstr==other.isstr&&(isstr?strval==other.strval:intval==other.intval);} }; #define BUILTIN_GUARD_STACKSIZE(nm,sz) {if(S.size()<(sz))throw string("Builtin '" nm "' needs " #sz " stack item")+(sz==1?"":"s");} const unordered_map&,unordered_map&)>> builtins={ {"+",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("+",2) Stackitem b=move(S.back()); S.pop_back(); Stackitem a=move(S.back()); S.pop_back(); if(!a.isstr&&!b.isstr){ S.emplace_back(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.emplace_back(a.strval+b.strval); }}, {"-",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("-",2) Stackitem b=move(S.back()); S.pop_back(); Stackitem a=move(S.back()); S.pop_back(); if(a.isstr||b.isstr)throw string("Builtin '-' cannot accept a string argument"); S.emplace_back(a.intval-b.intval); }}, {"*",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("*",2) Stackitem b=move(S.back()); S.pop_back(); Stackitem a=move(S.back()); S.pop_back(); if(a.isstr||b.isstr)throw string("Builtin '*' cannot accept a string argument"); S.emplace_back(a.intval*b.intval); }}, {"/",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("/",2) Stackitem b=move(S.back()); S.pop_back(); Stackitem a=move(S.back()); S.pop_back(); if(a.isstr||b.isstr)throw string("Builtin '/' cannot accept a string argument"); S.emplace_back(a.intval/b.intval); }}, {"%",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("%",2) Stackitem b=move(S.back()); S.pop_back(); Stackitem a=move(S.back()); S.pop_back(); if(a.isstr||b.isstr){ cerr<<"modding "; if(a.isstr)cerr<<'"'< &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("^",2) Stackitem b=move(S.back()); S.pop_back(); Stackitem a=move(S.back()); S.pop_back(); if(a.isstr||b.isstr)throw string("Builtin '^' cannot accept a string argument"); S.emplace_back((int)pow(a.intval,b.intval)); }}, {"!",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("!",1) Stackitem v=move(S.back()); S.pop_back(); if(v.isstr)S.emplace_back(!v.strval.size()); else S.emplace_back(!v.intval); }}, {"=",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("=",2) Stackitem b=move(S.back()); S.pop_back(); Stackitem a=move(S.back()); S.pop_back(); S.emplace_back(a==b); }}, {"<",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("<",2) Stackitem b=move(S.back()); S.pop_back(); Stackitem a=move(S.back()); S.pop_back(); if(a.isstr&&b.isstr)S.emplace_back(a.strval",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE(">",2) Stackitem b=move(S.back()); S.pop_back(); Stackitem a=move(S.back()); S.pop_back(); if(a.isstr&&b.isstr)S.emplace_back(a.strval>b.strval); else if(!a.isstr&&!b.isstr)S.emplace_back(a.intval>b.intval); else if(a.isstr)S.emplace_back(a.strval>to_string(b.intval)); else S.emplace_back(to_string(a.intval)>b.strval); }}, {"print",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("print",1) Stackitem v=move(S.back()); S.pop_back(); if(v.isstr)cout< &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("lf",0) cout< &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("getline",0) string line; getline(cin,line); if(!cin)S.emplace_back(-1); else S.emplace_back(line); }}, {"getc",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("getc",0) char c=cin.get(); if(!cin)S.emplace_back(-1); else S.emplace_back(string(1,c)); }}, {"fprint",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("fprint",2) Stackitem file=move(S.back()); S.pop_back(); Stackitem v=move(S.back()); S.pop_back(); if(file.isstr)throw string("Second argument to 'fprint' not a number"); if(v.isstr)write(file.intval,v.strval.data(),v.strval.size()); else { string s=to_string(v.intval); write(file.intval,s.data(),s.size()); } }}, {"flf",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("flf",1) Stackitem file=move(S.back()); S.pop_back(); if(file.isstr)throw string("Argument to 'flf' not a number"); write(file.intval,"\n",1); }}, {"fgetc",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("fgetc",1) Stackitem file=move(S.back()); S.pop_back(); if(file.isstr)throw string("Argument to 'fgetc' not a number"); char c; int ret=read(file.intval,&c,1); if(ret==-1)perror("read"); if(ret<=0)S.emplace_back(-1); else S.emplace_back(string(1,c)); }}, {"fopen",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("fopen",2) Stackitem mode=move(S.back()); S.pop_back(); Stackitem fname=move(S.back()); S.pop_back(); if(!fname.isstr)throw string("First argument to 'fopen' not a string"); if(!mode.isstr)throw string("Second argument to 'fopen' not a string"); int oflag=0; if(mode.strval.find('r')!=string::npos)oflag|=O_RDONLY; if(mode.strval.find('w')!=string::npos){ if(oflag&O_RDONLY)oflag=(oflag&~O_RDONLY)|O_RDWR; else oflag|=O_WRONLY; } if(mode.strval.find('a')!=string::npos)oflag|=O_APPEND; if(mode.strval.find('x')!=string::npos)oflag|=O_EXCL|O_CREAT; if(oflag==0)oflag=O_RDONLY; int ret=open(fname.strval.c_str(),oflag); if(ret<0)perror("open"); S.emplace_back(ret); }}, {"argc",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("argc",0) S.emplace_back(g_argc); }}, {"argvget",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("argvget",1) Stackitem idx=move(S.back()); S.pop_back(); if(idx.isstr)throw string("Argument to 'argvget' not a number"); if(idx.intval<0||idx.intval>=g_argc)throw string("Argument to 'argvget' out of bounds"); S.emplace_back(g_argv[idx.intval]); }}, {"dup",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("dup",1) S.push_back(S.back()); }}, {"pop",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("pop",1) S.pop_back(); }}, {"swap",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("swap",2) Stackitem b=move(S.back()); S.pop_back(); Stackitem a=move(S.back()); S.pop_back(); S.push_back(b); S.push_back(a); }}, {"store",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("store",2) Stackitem name=move(S.back()); S.pop_back(); if(!name.isstr)throw string("Second argument to 'store' not a string"); variables[name.strval]=move(S.back()); S.pop_back(); }}, {"get",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("get",1) Stackitem name=move(S.back()); S.pop_back(); if(!name.isstr)throw string("Argument to 'get' not a string"); S.push_back(variables[name.strval]); }}, //leaves variable with value 0 {"swapoutvar",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("swapoutvar",1) Stackitem name=move(S.back()); S.pop_back(); if(!name.isstr)throw string("Second argument to 'swapoutvar' not a string"); S.push_back(move(variables[name.strval])); variables[name.strval].intval=0; }}, //positive n: roll OFF top to bottom; negative n: roll off bottom ONTO top {"roll",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("roll",1) Stackitem ns=move(S.back()); S.pop_back(); if(ns.isstr)throw string("Argument to 'roll' not a number"); int n=ns.intval; if(S.size()<2)return; bool negative=n<0; if(negative)n=-n; n%=S.size(); if(n==0)return; if(negative){ vector tempstore; tempstore.reserve(n); int ssz=S.size(); for(int i=ssz-n;i &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("stridx",2) Stackitem idx=move(S.back()); S.pop_back(); const Stackitem &str=S.back(); if(!str.isstr)throw string("First argument to 'stridx' not a string"); if(idx.isstr)throw string("Second argument to 'stridx' not a number"); if(idx.intval<0||idx.intval>=(int)str.strval.size())throw string("Index argument to 'stridx' out of range"); S.emplace_back(string(1,str.strval[idx.intval])); }}, //leaves the string on the stack {"strlen",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("strlen",1) const Stackitem &str=S.back(); if(!str.isstr)throw string("Argument to 'strlen' not a string"); S.emplace_back(str.strval.size()); }}, //leaves the string on the stack {"substr",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("substr",3) const Stackitem to=move(S.back()); S.pop_back(); const Stackitem from=move(S.back()); S.pop_back(); const Stackitem &str=S.back(); if(!str.isstr)throw string("First argument to 'substr' not a string"); if(from.isstr)throw string("Second argument to 'substr' not a number"); if(to.isstr)throw string("Third argument to 'substr' not a number"); S.emplace_back(str.strval.substr(from.intval,to.intval-from.intval)); }}, {"chr",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("chr",1) Stackitem ascii=move(S.back()); S.pop_back(); if(ascii.isstr)throw string("Argument to 'chr' not a number"); S.emplace_back(string(1,ascii.intval)); }}, {"ord",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("ord",1) if(!S.back().isstr)throw string("Argument to 'ord' not a string"); S.back().isstr=false; S.back().intval=(int)S.back().strval[0]; S.back().strval=""; }}, {"stackdump",[](vector &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("stackdump",0) cerr<<"STACKDUMP: "; for(const Stackitem &si : S){ if(si.isstr)cerr<<'"'< &S,unordered_map &variables){ BUILTIN_GUARD_STACKSIZE("exit",0) exit(0); }}, }; #undef BUILTIN_GUARD_STACKSIZE void runpasseval(const vector &T, vector &S, const unordered_map,unordered_map>> &functions, unordered_map &variables, const unordered_map &jumpmap){ unsigned int cursor; for(cursor=0;cursor'<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 "<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 "<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 "<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 &T,unordered_map &jumpmap){ //first, index flow control keywords vector flowindexes; for(unsigned int cursor=0;cursor T){ unordered_map,unordered_map>> functions; //functions is a pair of tokens and jumpmap unordered_map variables; unordered_map jumpmap; vector S; unsigned int cursor=0; //first pass, handle functions and includes while(cursor=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 &functoks=functions[name].first; functoks.resize(end-start); for(unsigned int i=start;i=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 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); } int main(int argc,char **argv){ if(argc<2){ cerr<<"Call this interpreter with the Postrun source file"< tokens=tokenise(srcf); //for(const string &tok : tokens)cerr<<'['<