| #!/usr/bin/gawk -f | |
| # a simple interpreter for GWBASIC(subset) written in AWK | |
| # following 'The AWK Programming Language' Chapter 6 | |
| # @date June 20,2012 | |
| # @link basic.sh https://gist.github.com/2959816 | |
| # @link test.sh https://gist.github.com/2959877 | |
| # TODO: | |
| # gosub/return...ok | |
| # if/else/elseif/endif...ok | |
| # while/wend...ok | |
| # more unit test | |
| # error warning | |
| # builtin functon | |
| # document | |
| # clean code | |
| # more features in gwbasic | |
| # @ref 'The AWK Programming Language' | |
| # @ref 'man mawk' | |
| # @ref http://en.wikipedia.org/wiki/GW-BASIC | |
| BEGIN{ | |
| DEBUG_MODE = 0; | |
| printf("BASIC\n"); | |
| code = "\n" | |
| #top = "top" | |
| ifstack[0] = 1; | |
| #ifstack[top] = 0; | |
| ifstack["top"] = 0; | |
| callstack[0] = 0; | |
| callstack["top"] = 0; | |
| #whilestack[0] = 0; | |
| #whilestack["top"] = 0; | |
| } | |
| !/^( |\t)*\#/{ | |
| if($0 ~ /^ *[A-Za-z_0-9]+ *\:$/){ | |
| gsub(/ |\:/,""); | |
| $0 = "@" $0; | |
| } | |
| code = code $0 "\n"; | |
| } | |
| END{ | |
| debug("<CODE>"); | |
| debug(code,length(code)); | |
| debug("<RUN>\n\n\n"); | |
| line = code; | |
| codelen = length(code); | |
| advance(); | |
| stat(); | |
| } | |
| function push(Stack,x){ | |
| Stack[++Stack["top"]] = x; | |
| } | |
| function pop(Stack){ | |
| return Stack[Stack["top"]--]; | |
| } | |
| function advance() { | |
| #if (tok == "(eof)") return "(eof)" | |
| #while (length(line) == 0)if (getline line == 0)return tok = "(eof)"; | |
| if (line == "") | |
| return tok = "(eof)"; | |
| if (match(line, /^[A-Za-z_][A-Za-z_0-9]*/) || | |
| match(line, /^-?([0-9]+\.?[0-9]*|\.[0-9]+)/) || | |
| #match(line, /^\"(\\.|[^"])*\"/) || | |
| match(line, /^\"[^\"]*\"/) || | |
| match(line, /^(<|<=|=|<>|>=|>)/) || | |
| #match(line, /^\*\/\+\-]/) || | |
| match(line, /^./)) { | |
| tok = substr(line, 1, RLENGTH); | |
| line = substr(line, RLENGTH+1); | |
| if(tok ~ /^ +/) | |
| return advance(); | |
| return tok; | |
| } | |
| error("advance>line " substr(line,1,10) " incomprehensible at " line); | |
| } | |
| function eat(s) { # read next token if s == tok | |
| if (tok != s) error("line " substr(line,1,20) ": saw " tok ", expected " s) | |
| advance(); | |
| } | |
| function nl() { # absorb newlines and semicolons | |
| while(tok ~ /^(\n|\:)/) | |
| advance(); | |
| #while (tok == "\n" || tok == ":") | |
| #advance(); | |
| } | |
| function error(s) { print("Error: " s)| "cat 1>&2"; exit 1 } | |
| function jump(label){ | |
| if(label ~ /^[0-9]+/){ | |
| if(!match(code,"\n *" tok " *")) | |
| error("goto line"); | |
| line = substr(code, RSTART+RLENGTH) | |
| advance(); | |
| }else if(label ~ /^[A-Za-z_][A-Za-z_0-9]*/){ | |
| if(!match(code,"\n *@" tok " *(\n|$)")) | |
| error("goto label"); | |
| line = substr(code, RSTART+RLENGTH) | |
| debug(RSTART","RLENGTH "goto>" "@" label " " line) | |
| advance(); | |
| }else{error("goto");} | |
| } | |
| function ifskip(){ | |
| #debug("ifskipline> " ifstack["top"]) | |
| #print_array(ifstack) | |
| return (ifstack[ifstack["top"]]!=1) | |
| } | |
| function print_array(A, i,len,s){ | |
| len=length(A); | |
| s = A[0] "|" | |
| for(i=1;i<=len;i++) | |
| s = s " " A[i]; | |
| debug(s); | |
| } | |
| function stat( lhs,op,e) { | |
| if(tok=="(eof)") | |
| return; | |
| nl(); | |
| if(tok ~ /^[0-9]+/) | |
| advance(); #linenum | |
| if(tok=="(eof)") | |
| return; | |
| if(tok == "@"){ | |
| eat("@") | |
| if(1)#use F_if_block | |
| if(tok=="if"){ | |
| debug(">if"); | |
| eat("if"); | |
| if(!ifskip()){ | |
| e = (conj()!=0); | |
| eat("\n"); | |
| debug("if> " e); | |
| ifstack[++ifstack["top"]] = e; | |
| }else{ | |
| ifstack[++ifstack["top"]] = -1; | |
| } | |
| }else if(tok=="else"){ | |
| debug(">else"); | |
| eat("else"); | |
| eat("\n"); | |
| if(ifstack[ifstack["top"]]==1){ | |
| ifstack[ifstack["top"]] = 0; | |
| }else if(ifstack[ifstack["top"]]==0) | |
| ifstack[ifstack["top"]] = 1; | |
| else | |
| ; | |
| }else if(tok=="elseif"){ | |
| debug(">elseif"); | |
| eat("elseif"); | |
| e = (conj()!=0) | |
| eat("\n"); | |
| if(ifstack[ifstack["top"]]==1){ | |
| ifstack[ifstack["top"]] = 0; | |
| }else if(ifstack[ifstack["top"]]==0){ | |
| ifstack[ifstack["top"]] = e; | |
| }else | |
| ; | |
| }else if(tok=="endif"){ | |
| debug(">endif"); | |
| eat("endif"); | |
| eat("\n"); | |
| debug(ifstack["top"],ifstack[ifstack["top"]]); | |
| delete ifstack[ifstack["top"]]; | |
| #debug(ifstack["top"]--) | |
| ifstack["top"]--; | |
| debug(ifstack["top"],ifstack[ifstack["top"]]); | |
| }else{ | |
| #while(tok !~ /^\n/) | |
| while(tok!="\n") | |
| advance(); | |
| } | |
| else | |
| while(tok !~ /^\n/) | |
| advance(); | |
| }else if(ifskip()){ | |
| debug("skiptok>" tok); | |
| while(tok !="\n" && tok!="(eof)"){ | |
| advance(); | |
| debug("skiptok->" tok); | |
| } | |
| }else{ | |
| #debug("eval ing...") | |
| lhs = lvalue(); | |
| if (lhs == "print"){ | |
| #eat("print") | |
| #debug(conj()) | |
| #return "" | |
| if(tok !~ /\n|\:/){ | |
| printf("%s",exprlist()); | |
| if(tok==";"){ | |
| eat(";"); | |
| }else | |
| printf("\n") | |
| }else | |
| printf("\n"); | |
| #debug(e) | |
| }else if(lhs == "let"){ | |
| #eat("let") | |
| lhs = lvalue(); | |
| eat("="); | |
| env[lhs] = conj(); | |
| #return "" | |
| }else if(lhs == "label"){ | |
| advance(); | |
| }else if(lhs == "input"){ | |
| if(tok ~ /^\".*\"$/){ | |
| gsub(/\"/,"",tok); | |
| printf("%s",tok); | |
| advance(); | |
| eat(","); | |
| } | |
| e = lvalue(); | |
| getline env[e]<"-" | |
| #advance(); | |
| }else if(lhs == "rem"){ | |
| while(tok !~ /^\n/) | |
| advance(); | |
| }else if(lhs == "cls"){ | |
| system("clear || cls"); | |
| }else if(lhs == "if"){ | |
| e = conj(); | |
| debug("if " e ":"); | |
| if(tok=="goto"){ | |
| eat("goto"); | |
| #debug("label>" tok "!") | |
| if(e) | |
| jump(tok); | |
| else{ | |
| advance(); | |
| } | |
| }else if(tok == "then"){ | |
| eat("then") | |
| if(tok ~ /[0-9]+/){ | |
| if(e) | |
| jump(tok); | |
| else | |
| advance(); | |
| }else{ | |
| if(e) | |
| stat() | |
| else | |
| while(tok !~ /^\n/) | |
| advance(); | |
| } | |
| }else{error("if")} | |
| }else if(lhs == "goto"){ | |
| jump(tok); | |
| }else if(lhs == "end"){ | |
| exit 0; | |
| }else if(lhs == "gosub"){ | |
| callstack[++callstack["top"]] = length(code)-length(line)+1 | |
| debug("here is |" substr(code,callstack[callstack["top"]])); | |
| jump(tok); | |
| }else if(lhs == "return"){ | |
| #jump(tok); | |
| line = substr(code,callstack[callstack["top"]--]); | |
| advance(); | |
| }else if(lhs == "while"){ | |
| e = conj(); | |
| debug("while " e ":"); | |
| if(e)#e!=0 | |
| ; | |
| else{ | |
| debug("skip while"); | |
| e = 0; | |
| #!!!!!!!!!!!!!!!!!!!!!!!!!! | |
| while(e>=0){ | |
| debug("while level> " e); | |
| if(tok == "wend") | |
| e--; | |
| else if(tok=="while") | |
| e++; | |
| advance(); | |
| } | |
| } | |
| #jump(tok) | |
| #callstack[++callstack["top"]] = 0 | |
| }else if(lhs == "wend"){ | |
| debug("wend"); | |
| goto_while(length(code)-length(line)+1); | |
| debug(substr(code,1,length(code)-length(line)) "{end}"); | |
| #jump(tok) | |
| #line = substr(code,callstack[callstack["top"]--]); | |
| #advance(); | |
| }else{ | |
| eat("="); | |
| env[lhs] = conj(); | |
| } | |
| } | |
| if(tok=="(eof)") | |
| return; | |
| else | |
| stat() # use while later | |
| } | |
| function goto_while(len, i,l,offset,A){ | |
| A["top"] = 0; | |
| offset = 1; | |
| line = substr(code,1,len); | |
| advance(); | |
| while(tok != "(eof)"){ | |
| if(tok=="while"){ | |
| debug("see>while{" substr(code,offset,20) "...}"); | |
| A[++A["top"]] = offset;} | |
| else if(tok=="wend"){ | |
| A["top"]--; | |
| } | |
| offset = len-length(line); | |
| advance(); | |
| } | |
| if(A["top"]>=0){ | |
| line = substr(code,A[A["top"]+1]); | |
| advance(); | |
| }else | |
| error(); | |
| #exit 0; | |
| } | |
| function expr(e) { | |
| e = rel(); | |
| while (tok ~ /<|<=|=|<>|>=|>|&/) { | |
| op = tok; | |
| advance(); | |
| if(op=="=") e = 0+e == 0+rel(); | |
| else if(op=="<>") e = 0+e != 0+rel(); | |
| else if(op=="<") e = 0+e < 0+rel(); | |
| else if(op=="<=") e = 0+e <= 0+rel(); | |
| else if(op==">") e = 0+e > 0+rel(); | |
| else if(op==">=") e = 0+e >= 0+rel(); | |
| else if(op=="&") e = e "" rel(); | |
| } | |
| return e | |
| } | |
| function conj( op,e){ | |
| e = expr(); | |
| while (tok == "and" || tok == "or") { | |
| op = tok; | |
| advance(); | |
| if(op=="and") | |
| e = e && expr(); | |
| else if(op=="or") | |
| e = e || expr(); | |
| } | |
| return e | |
| } | |
| function rel( op, e) { | |
| e = term(); | |
| while (tok == "+" || tok == "-") { | |
| op = tok; | |
| advance(); | |
| if(op=="+") | |
| e = e + term(); | |
| else if(op=="-") | |
| e = e - term(); | |
| } | |
| return e | |
| } | |
| function term( op, e) { # fact | fact [*/%] fact | |
| e = fact(); | |
| while (tok == "*" || tok == "/") { | |
| op = tok; | |
| advance(); | |
| if(op=="*") | |
| e = e * fact(); | |
| else if(op=="/") | |
| e = e / fact(); | |
| } | |
| return e; | |
| } | |
| function fact( e) { | |
| debug("fact>" tok); | |
| if (tok == "(") { | |
| eat("("); e = expr(); eat(")"); | |
| return e | |
| } else if (tok ~ /^[A-Za-z][A-Za-z0-9]*/) { | |
| return ident(); | |
| } else if (tok ~ /^-?([0-9]+\.?[0-9]*|\.[0-9]+)/) { | |
| e = tok | |
| advance(); | |
| return e | |
| }else if(match(tok, /^\"[^\"]*\"/)){ | |
| e = substr(tok,2,length(tok)-2); | |
| debug("string>" e); | |
| advance(); | |
| return e | |
| }else | |
| error("fact>unexpected " tok " at line " substr(line,1,10)); | |
| } | |
| function ident( id, e) { | |
| if (!match(tok, /^[A-Za-z_][A-Za-z_0-9]*/)) | |
| error("ident>unexpected " tok " at line " substr(line,1,10)); | |
| id = tok; | |
| advance(); | |
| if (tok == "[") { # array | |
| eat("["); e = expr(); eat("]"); | |
| return env[id,e] | |
| } else if (tok == "(") { # function | |
| eat("("); | |
| e = ""; | |
| if (tok != ")") { | |
| e = exprlist(); | |
| eat(")"); | |
| } else eat(")"); | |
| return builtin(id,e); | |
| } else | |
| return env[id]; | |
| } | |
| function lvalue( id,e){ | |
| if (!match(tok, /^[A-Za-z_][A-Za-z_0-9]*/)) | |
| error("lvalue>unexpected " tok " at line " substr(line,1,10)); | |
| id = tok; | |
| advance(); | |
| if (tok == "[") { | |
| eat("["); e = expr(); eat("]"); | |
| return id SUBSEP e; | |
| }else | |
| return id; | |
| } | |
| function exprlist( n, e) { | |
| e = conj() # has to be at least one | |
| for (n = 1; tok == ","; n++) { | |
| advance(); | |
| if(tok ~ /\n|:/) | |
| e = e SUBSEP " "; | |
| else | |
| e = e SUBSEP " " conj(); | |
| } | |
| return e | |
| } | |
| function builtin(fun_name,arg_list, n,A){ | |
| n = split(arg_list,A,SUBSEP " "); | |
| if(fun_name=="len"){ | |
| return length(A[1]); | |
| }else if(fun_name=="mod"){ | |
| return A[1] % A[2]; | |
| }else error(); | |
| return; | |
| } | |
| function debug(x,y,z){ | |
| #if(x) | |
| if(DEBUG_MODE) | |
| printf("%s\n",x y z); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment