diff options
Diffstat (limited to 'appl/lib/ecmascript/ecmascript.b')
| -rw-r--r-- | appl/lib/ecmascript/ecmascript.b | 2626 |
1 files changed, 2626 insertions, 0 deletions
diff --git a/appl/lib/ecmascript/ecmascript.b b/appl/lib/ecmascript/ecmascript.b new file mode 100644 index 00000000..ccbce016 --- /dev/null +++ b/appl/lib/ecmascript/ecmascript.b @@ -0,0 +1,2626 @@ +implement Ecmascript; + +include "sys.m"; +include "math.m"; +include "string.m"; +include "daytime.m"; +include "ecmascript.m"; + +include "pprint.b"; +include "obj.b"; +include "exec.b"; +include "date.b"; +include "builtin.b"; +include "regexp.b"; +include "uri.b"; + +FF: con '\u000c'; +LS: con '\u2028'; +PS: con '\u2029'; + +islt(c: int): int +{ + return c == '\n' || c == '\r' || c == LS || c == PS; +} + +me: ESHostobj; + +sys: Sys; +print, sprint: import sys; +stdout: ref Sys->FD; + +math: Math; + isnan, floor, copysign, fabs, fmod, NaN, Infinity: import math; + +str: String; + +daytime: Daytime; + Tm: import daytime; + +labrec: adt{ + s: string; # name + k: int; # kind +}; + +HashSize: con 1024; + +Parser: adt +{ + ex: ref Exec; + + code: ref Code; + + inloop: int; # parser state + incase: int; + infunc: int; + lastnl: int; # parser state for inserting ; + notin: int; # don't allow `in' in expression + + token: int; # lexical token + token1: int; # lexical token + id: int; # associated value + lineno: int; + + src: string; # lexical input state + esrc: int; + srci: int; + + errors: int; + labs: list of ref labrec; +}; + +Keywd: adt +{ + name: string; + token: int; +}; + +# +# lexical tokens and ops +# + Lbase: con 128; + + Leos, + + Landas, + Loras, + Lxoras, + Llshas, + Lrshas, + Lrshuas, + Laddas, + Lsubas, + Lmulas, + Ldivas, + Lmodas, + Loror, + Landand, + Leq, + Lneq, + Lleq, + Lgeq, + Llsh, + Lrsh, + Lrshu, + Linc, + Ldec, + Lnum, + Lid, + Lstr, + Lthis, + Ltypeof, + Ldelete, + Lvoid, + Lwhile, + Lfor, + Lbreak, + Lcontinue, + Lwith, + Lreturn, + Lfunction, + Lvar, + Lif, + Lelse, + Lin, + Lnew, + Lcase, + Ldefault, + Lswitch, + Ldo, + Linstanceof, + Lcatch, + Lfinally, + Lthrow, + Ltry, + Lregexp, + Lseq, + Lsne, + Lprint, + + Lpostinc, # ops that aren't lexical tokens + Lpostdec, + Lpresub, + Lpreadd, + Lcall, + Lnewcall, + Lgetval, + Las, + Lasop, + Lforin, + Lforvar, + Lforvarin, + Larrinit, + Lobjinit, + Lnoval, + Llabel, + Lbreaklab, + Lcontinuelab, + + # + # reserved words + # + Labstract, + Lboolean, + Lbyte, + Lchar, + Lclass, + Lconst, + Ldebugger, + Ldouble, + Lenum, + Lexport, + Lextends, + Lfinal, + Lfloat, + Lgoto, + Limplements, + Limport, + Lint, + Linterface, + Llong, + Lnative, + Lpackage, + Lprivate, + Lprotected, + Lpublic, + Lshort, + Lstatic, + Lsuper, + Lsynchronized, + Lthrows, + Ltransient, + Lvolatile: con Lbase + iota; + + +# +# internals +# + +Mlower, Mupper, Munder, Mdigit, Msign, Mexp, Mhex, Moct: con byte 1 << iota; +Malpha: con Mupper|Mlower|Munder; +map := array[256] of +{ + '_' or '$' => Munder, + '-' or '+' => Msign, + 'a' to 'd' or 'f' => Mlower | Mhex, + 'e' => Mlower | Mhex | Mexp, + 'g' to 'z' => Mlower, + 'A' to 'D' or 'F' => Mupper | Mhex, + 'E' => Mupper | Mhex | Mexp, + 'G' to 'Z' => Mupper, + '0' to '7' => Mdigit | Mhex | Moct, + '8' to '9' => Mdigit | Mhex, + * => byte 0 +}; + +maxerr: int; +toterrors: int; +fabort: int; + +escmap := array[] of +{ + '\'' => byte '\'', + '"' => byte '"', + '\\' => byte '\\', + 'b' => byte '\b', + 'f' => byte FF, + 'n' => byte '\n', + 'r' => byte '\r', + 't' => byte '\t', + 'v' => byte FF, + + * => byte 255 +}; + +# +# must be sorted +# +keywords := array [] of +{ + Keywd("abstract", Labstract), + Keywd("boolean", Lboolean), + Keywd("byte", Lbyte), + Keywd("break", Lbreak), + Keywd("case", Lcase), + Keywd("catch", Lcatch), + Keywd("char", Lchar), + Keywd("class", Lclass), + Keywd("const", Lconst), + Keywd("continue", Lcontinue), + Keywd("debugger", Ldebugger), + Keywd("default", Ldefault), + Keywd("delete", Ldelete), + Keywd("do", Ldo), + Keywd("double", Ldouble), + Keywd("else", Lelse), + Keywd("enum", Lenum), + Keywd("export", Lexport), + Keywd("extends ", Lextends), + Keywd("final", Lfinal), + Keywd("finally", Lfinally), + Keywd("float", Lfloat), + Keywd("for", Lfor), + Keywd("function", Lfunction), + Keywd("goto", Lgoto), + Keywd("if", Lif), + Keywd("implements", Limplements), + Keywd("import", Limport), + Keywd("in", Lin), + Keywd("instanceof", Linstanceof), + Keywd("int", Lint), + Keywd("interface", Linterface), + Keywd("long", Llong), + Keywd("native", Lnative), + Keywd("new", Lnew), + Keywd("package", Lpackage), + # Keywd("print", Lprint), + Keywd("private", Lprivate), + Keywd("protected", Lprotected), + Keywd("public", Lpublic), + Keywd("return", Lreturn), + Keywd("short", Lshort), + Keywd("static", Lstatic), + Keywd("super", Lsuper), + Keywd("switch", Lswitch), + Keywd("synchronized", Lsynchronized), + Keywd("this", Lthis), + Keywd("throw", Lthrow), + Keywd("throws", Lthrows), + Keywd("transient", Ltransient), + Keywd("try", Ltry), + Keywd("typeof", Ltypeof), + Keywd("var", Lvar), + Keywd("void", Lvoid), + Keywd("volatile", Lvolatile), + Keywd("while", Lwhile), + Keywd("with", Lwith), +}; + +debug = array[256] of {* => 0}; + +glbuiltins := array[] of +{ + Builtin("eval", "eval", array[] of {"src"}, 1), + Builtin("parseInt", "parseInt", array[] of {"string", "radix"}, 2), + Builtin("parseFloat", "parseFloat", array[] of {"string"}, 1), + Builtin("escape", "escape", array[] of {"string"}, 1), + Builtin("unescape", "unescape", array[] of {"string"}, 1), + Builtin("isNaN", "isNaN", array[] of {"number"}, 1), + Builtin("isFinite", "isFinite", array[] of {"number"}, 1), + Builtin("decodeURI", "decodeURI", array[] of {"string"}, 1), + Builtin("decodeURIComponent", "decodeURIComponent", array[] of {"string"}, 1), + Builtin("encodeURI", "encodeURI", array[] of {"string"}, 1), + Builtin("encodeURIComponent", "encodeURIComponent", array[] of {"string"}, 1), +}; + +biobj := Builtin("Object", "Object", array[] of {"value"}, 1); +biobjproto := array[] of +{ + Builtin("toString", "Object.prototype.toString", nil, 0), + Builtin("toLocaleString", "Object.prototype.toLocaleString", nil, 0), + Builtin("valueOf", "Object.prototype.valueOf", nil, 0), + Builtin("hasOwnProperty", "Object.prototype.hasOwnProperty", array[] of {"V"}, 1), + Builtin("isPrototypeOf", "Object.prototype.isPrototypeOf", array[] of {"V"}, 1), + Builtin("propertyisEnumerable", "Object.prototype.propertyisEnumerable", array[] of {"V"}, 1), +}; + +bifunc := Builtin("Function", "Function", array[] of {"body"}, 1); +bifuncproto := array[] of +{ + Builtin("toString", "Function.prototype.toString", nil, 0), + Builtin("apply", "Function.prototype.apply", array[] of {"this", "array"}, 2), + Builtin("call", "Function.prototype.call", array[] of {"this", "arg"}, 1), +}; + +bierr := Builtin("Error", "Error", array[] of {"message"}, 1); +bierrproto := array[] of +{ + Builtin("toString", "Error.prototype.toString", nil , 0), +}; + +biarray := Builtin("Array", "Array", array[] of {"length"}, 1); +biarrayproto := array[] of +{ + Builtin("toString", "Array.prototype.toString", nil, 0), + Builtin("toLocaleString", "Array.prototype.toLocaleString", nil, 0), + Builtin("concat", "Array.prototype.concat", array[] of {"item"}, 1), + Builtin("join", "Array.prototype.join", array[] of {"separator"}, 1), + Builtin("pop", "Array.prototype.pop", nil, 0), + Builtin("push", "Array.prototype.push", array[] of {"item"} , 1), + Builtin("reverse", "Array.prototype.reverse", nil, 0), + Builtin("shift", "Array.prototype.shift", nil, 0), + Builtin("slice", "Array.prototype.slice", array[] of {"start", "end"}, 2), + Builtin("splice", "Array.prototype.splice", array[] of {"start", "delcnt", "item"}, 2), + Builtin("sort", "Array.prototype.sort", array[] of {"comparefunc"}, 1), + Builtin("unshift", "Array.prototype.unshift", array[] of {"item"}, 1), +}; + +bistr := Builtin("String", "String", array[] of {"value"}, 1); +bistrproto := array[] of +{ + Builtin("toString", "String.prototype.toString", nil, 0), + Builtin("valueOf", "String.prototype.valueOf", nil, 0), + Builtin("charAt", "String.prototype.charAt", array[] of {"pos"}, 1), + Builtin("charCodeAt", "String.prototype.charCodeAt", array[] of {"pos"}, 1), + Builtin("concat", "String.prototype.concat", array[] of {"string"}, 1), + Builtin("indexOf", "String.prototype.indexOf", array[] of {"string", "pos"}, 2), + Builtin("lastIndexOf", "String.prototype.lastIndexOf", array[] of {"string", "pos"}, 2), + Builtin("localeCompare", "String.prototype.localeCompare", array[] of {"that"}, 1), + Builtin("slice", "String.prototype.slice", array[] of {"start", "end"}, 2), + Builtin("split", "String.prototype.split", array[] of {"separator"}, 2), + Builtin("substr", "String.prototype.substr", array[] of {"start", "length"}, 2), + Builtin("substring", "String.prototype.substring", array[] of {"start", "end"}, 2), + Builtin("toLowerCase", "String.prototype.toLowerCase", nil, 0), + Builtin("toUpperCase", "String.prototype.toUpperCase", nil, 0), + Builtin("toLocaleLowerCase", "String.prototype.toLocaleLowerCase", nil, 0), + Builtin("toLocaleUpperCase", "String.prototype.toLocaleUpperCase", nil, 0), +# JavaScript 1.0 + Builtin("anchor", "String.prototype.anchor", array[] of {"name"}, 1), + Builtin("big", "String.prototype.big", nil, 0), + Builtin("blink", "String.prototype.blink", nil, 0), + Builtin("bold", "String.prototype.bold", nil, 0), + Builtin("fixed", "String.prototype.fixed", nil, 0), + Builtin("fontcolor", "String.prototype.fontcolor", array[] of {"color"}, 1), + Builtin("fontsize", "String.prototype.fontsize", array[] of {"size"}, 1), + Builtin("italics", "String.prototype.italics", nil, 0), + Builtin("link", "String.prototype.link", array[] of {"href"}, 1), + Builtin("small", "String.prototype.small", nil, 0), + Builtin("strike", "String.prototype.strike", nil, 0), + Builtin("sub", "String.prototype.sub", nil, 0), + Builtin("sup", "String.prototype.sup", nil, 0), + Builtin("match", "String.prototype.match", array[] of {"regexp"}, 1), + Builtin("replace", "String.prototype.replace", array[] of {"searchval", "replaceval"}, 2), + Builtin("search", "String.prototype.search", array[] of {"regexp"}, 1), +}; +bistrctor := Builtin("fromCharCode", "String.fromCharCode", array[] of {"characters"}, 1); + +bibool := Builtin("Boolean", "Boolean", array[] of {"value"}, 1); +biboolproto := array[] of +{ + Builtin("toString", "Boolean.prototype.toString", nil, 0), + Builtin("valueOf", "Boolean.prototype.valueOf", nil, 0), +}; + +binum := Builtin("Number", "Number", array[] of {"value"}, 1); +binumproto := array[] of +{ + Builtin("toString", "Number.prototype.toString", nil, 0), + Builtin("toLocaleString", "Number.prototype.toLocaleString", nil, 0), + Builtin("valueOf", "Number.prototype.valueOf", nil, 0), + Builtin("toFixed", "Number.prototype.toFixed", array[] of {"digits"}, 1), + Builtin("toExponential", "Number.prototype.toExponential", array[] of {"digits"}, 1), + Builtin("toPrecision", "Number.prototype.toPrecision", array[] of {"digits"}, 1), +}; + +biregexp := Builtin("RegExp", "RegExp", array[] of {"pattern", "flags"}, 2); +biregexpproto := array[] of +{ + Builtin("exec", "RegExp.prototype.exec", array[] of {"string"}, 1), + Builtin("test", "RegExp.prototype.test", array[] of {"string"}, 1), + Builtin("toString", "RegExp.prototype.toString", nil, 0), +}; + +bidate := Builtin("Date", "Date", array[] of {"value"}, 1); +bidateproto := array[] of +{ + Builtin("toString", "Date.prototype.toString", nil, 0), + Builtin("toDateString", "Date.prototype.toDateString", nil, 0), + Builtin("toTimeString", "Date.prototype.toTimeString", nil, 0), + Builtin("toLocaleString", "Date.prototype.toLocalString", nil, 0), + Builtin("toLocaleDateString", "Date.prototype.toLocaleDateString", nil, 0), + Builtin("toLocaleTimeString", "Date.prototype.toLocaleTimeString", nil, 0), + Builtin("valueOf", "Date.prototype.valueOf", nil, 0), + Builtin("getTime", "Date.prototype.getTime", nil, 0), + Builtin("getYear", "Date.prototype.getYear", nil, 0), + Builtin("getFullYear", "Date.prototype.getFullYear", nil, 0), + Builtin("getUTCFullYear", "Date.prototype.getUTCFullYear", nil, 0), + Builtin("getMonth", "Date.prototype.getMonth", nil, 0), + Builtin("getUTCMonth", "Date.prototype.getUTCMonth", nil, 0), + Builtin("getDate", "Date.prototype.getDate", nil, 0), + Builtin("getUTCDate", "Date.prototype.getUTCDate", nil, 0), + Builtin("getDay", "Date.prototype.getDay", nil, 0), + Builtin("getUTCDay", "Date.prototype.getUTCDay", nil, 0), + Builtin("getHours", "Date.prototype.getHours", nil, 0), + Builtin("getUTCHours", "Date.prototype.getUTCHours", nil, 0), + Builtin("getMinutes", "Date.prototype.getMinutes", nil, 0), + Builtin("getUTCMinutes", "Date.prototype.getUTCMinutes", nil, 0), + Builtin("getSeconds", "Date.prototype.getSeconds", nil, 0), + Builtin("getUTCSeconds", "Date.prototype.getUTCSeconds", nil, 0), + Builtin("getMilliseconds", "Date.prototype.getMilliseconds", nil, 0), + Builtin("getUTCMilliseconds", "Date.prototype.getUTCMilliseconds", nil, 0), + Builtin("getTimezoneOffset", "Date.prototype.getTimezoneOffset", nil, 0), + Builtin("setTime", "Date.prototype.setTime", array[] of {"time"}, 1), + Builtin("setMilliseconds", "Date.prototype.setMilliseconds", array[] of {"ms"}, 1), + Builtin("setUTCMilliseconds", "Date.prototype.setUTCMilliseconds", array[] of {"ms"}, 1), + Builtin("setSeconds", "Date.prototype.setSeconds", array[] of {"sec", "ms"}, 2), + Builtin("setUTCSeconds", "Date.prototype.setUTCSeconds", array[] of {"sec", "ms"}, 2), + Builtin("setMinutes", "Date.prototype.setMinutes", array[] of {"min", "sec", "ms"}, 3), + Builtin("setUTCMinutes", "Date.prototype.setUTCMinutes", array[] of {"min", "sec", "ms"}, 3), + Builtin("setHours", "Date.prototype.setHours", array[] of {"hour", "min", "sec", "ms"}, 4), + Builtin("setUTCHours", "Date.prototype.setUTCHours", array[] of {"hour", "min", "sec", "ms"}, 4), + Builtin("setDate", "Date.prototype.setDate", array[] of {"date"}, 1), + Builtin("setUTCDate", "Date.prototype.setUTCDate", array[] of {"date"}, 1), + Builtin("setMonth", "Date.prototype.setMonth", array[] of {"mon", "date"}, 2), + Builtin("setUTCMonth", "Date.prototype.setUTCMonth", array[] of {"mon", "date"}, 2), + Builtin("setFullYear", "Date.prototype.setFullYear", array[] of {"year", "mon", "date"}, 3), + Builtin("setUTCFullYear", "Date.prototype.setUTCFullYear", array[] of {"year", "mon", "date"}, 3), + Builtin("setYear", "Date.prototype.setYear", array[] of {"year"}, 1), + Builtin("toLocaleString", "Date.prototype.toLocaleString", nil, 0), + Builtin("toUTCString", "Date.prototype.toUTCString", nil, 0), + Builtin("toGMTString", "Date.prototype.toGMTString", nil, 0), +}; +bidatector := array[] of +{ + Builtin("parse", "Date.parse", array[] of {"string"}, 1), + Builtin("UTC", "Date.UTC", array[] of {"year", "month", "date", "hours", "minutes", "seconds", "ms"}, 7), +}; + +bimath := array[] of +{ + Builtin("abs", "Math.abs", array[] of {"x"}, 1), + Builtin("acos", "Math.acos", array[] of {"x"}, 1), + Builtin("asin", "Math.asin", array[] of {"x"}, 1), + Builtin("atan", "Math.atan", array[] of {"x"}, 1), + Builtin("atan2", "Math.atan2", array[] of {"y", "x"}, 2), + Builtin("ceil", "Math.ceil", array[] of {"x"}, 1), + Builtin("cos", "Math.cos", array[] of {"x"}, 1), + Builtin("exp", "Math.exp", array[] of {"x"}, 1), + Builtin("floor", "Math.floor", array[] of {"x"}, 1), + Builtin("log", "Math.log", array[] of {"x"}, 1), + Builtin("max", "Math.max", array[] of {"x", "y"}, 2), + Builtin("min", "Math.min", array[] of {"x", "y"}, 2), + Builtin("pow", "Math.pow", array[] of {"x", "y"}, 2), + Builtin("random", "Math.random", nil, 0), + Builtin("round", "Math.round", array[] of {"x"}, 1), + Builtin("sin", "Math.sin", array[] of {"x"}, 1), + Builtin("sqrt", "Math.sqrt", array[] of {"x"}, 1), + Builtin("tan", "Math.tan", array[] of {"x"}, 1), +}; + +init(): string +{ + sys = load Sys Sys->PATH; + math = load Math Math->PATH; + if(math == nil) + return sys->sprint("can't load module %s: %r", Math->PATH); + + str = load String String->PATH; + if(str == nil) + return sys->sprint("can't load module %s: %r", String->PATH); + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + return sys->sprint("can't load module %s: %r", Daytime->PATH); + + me = load ESHostobj SELF; + if(me == nil) + return "can't load builtin functions"; + + randinit(big sys->millisec()); + stdout = sys->fildes(1); + # + # maximum number of syntax errors reported + # + maxerr = 1; + + undefined = ref Val(TUndef, 0., nil, nil, nil); + null = ref Val(TNull, 0., nil, nil, nil); + true = ref Val(TBool, 1., nil, nil, nil); + false = ref Val(TBool, 0., nil, nil, nil); + return ""; +} + +mkcall(ex : ref Exec, p: array of string): ref Call +{ + return ref Call(p, nil, ex); +} + +mkbiobj(ex: ref Exec, meth: Builtin, proto: ref Obj): ref Obj +{ + o := biinst(ex.global, meth, ex.funcproto, me); + o.construct = o.call; + valinstant(o, DontEnum|DontDelete|ReadOnly, "prototype", objval(proto)); + valinstant(proto, DontEnum, "constructor", objval(o)); + return o; +} + +mkexec(go: ref Obj): ref Exec +{ + o: ref Obj; + if(go == nil) + go = mkobj(nil, "global"); + ex := ref Exec; + ex.this = go; + ex.scopechain = go :: nil; + ex.stack = array[4] of ref Ref; + ex.sp = 0; + ex.global = go; + + # + # builtin object prototypes + # + ex.objproto = mkobj(nil, "Object"); + ex.funcproto = mkobj(ex.objproto, "Function"); + ex.arrayproto = mkobj(ex.objproto, "Array"); + ex.strproto = mkobj(ex.objproto, "String"); + ex.numproto = mkobj(ex.objproto, "Number"); + ex.boolproto = mkobj(ex.objproto, "Boolean"); + ex.dateproto = mkobj(ex.objproto, "Date"); + ex.regexpproto = mkobj(ex.objproto, "RegExp"); + ex.errproto = mkobj(ex.objproto, "Error"); + + biminst(ex.objproto, biobjproto, ex.funcproto, me); + + biminst(ex.funcproto, bifuncproto, ex.funcproto, me); + ex.funcproto.call = mkcall(ex, nil); + ex.funcproto.val = strval("Function.prototype"); + valinstant(ex.funcproto, DontEnum|DontDelete|ReadOnly, "length", numval(real 0)); + + biminst(ex.arrayproto, biarrayproto, ex.funcproto, me); + valinstant(ex.arrayproto, DontEnum|DontDelete, "length", numval(real 0)); + + biminst(ex.errproto, bierrproto, ex.funcproto, me); + ex.errproto.val = strval(""); + valinstant(ex.errproto, DontEnum|DontDelete, "length", numval(real 0)); + valinstant(ex.errproto, DontEnum|DontDelete, "name", strval("")); + valinstant(ex.errproto, DontEnum|DontDelete, "message", strval("Error")); + + biminst(ex.strproto, bistrproto, ex.funcproto, me); + ex.strproto.val = strval(""); + valinstant(ex.strproto, DontEnum|DontDelete|ReadOnly, "length", numval(real 0)); + + biminst(ex.boolproto, biboolproto, ex.funcproto, me); + ex.boolproto.val = false; + + biminst(ex.numproto, binumproto, ex.funcproto, me); + ex.numproto.val = numval(real +0); + + biminst(ex.regexpproto, biregexpproto, ex.funcproto, me); + ex.regexpproto.val = strval(""); + valinstant(ex.regexpproto, DontEnum|DontDelete|ReadOnly, "length", numval(real 2)); + valinstant(ex.regexpproto, DontEnum|DontDelete|ReadOnly, "source", strval("")); + valinstant(ex.regexpproto, DontEnum|DontDelete|ReadOnly, "global", false); + valinstant(ex.regexpproto, DontEnum|DontDelete|ReadOnly, "ignoreCase", false); + valinstant(ex.regexpproto, DontEnum|DontDelete|ReadOnly, "multiline", false); + valinstant(ex.regexpproto, DontEnum|DontDelete, "lastIndex", numval(real 0)); + + biminst(ex.dateproto, bidateproto, ex.funcproto, me); + ex.dateproto.val = numval(Math->NaN); + valinstant(ex.dateproto, DontEnum|DontDelete|ReadOnly, "length", numval(real 7)); + + # + # simple builtin functions and values + # + valinstant(go, DontEnum, "NaN", numval(Math->NaN)); + valinstant(go, DontEnum, "Infinity", numval(Math->Infinity)); + + biminst(go, glbuiltins, ex.funcproto, me); + + # + # builtin objects, and cross-link them to their prototypes + # + mkbiobj(ex, biobj, ex.objproto); + mkbiobj(ex, bifunc, ex.funcproto); + mkbiobj(ex, biarray, ex.arrayproto); + o = mkbiobj(ex, bistr, ex.strproto); + biinst(o, bistrctor, ex.funcproto, me); + mkbiobj(ex, bibool, ex.boolproto); + o = mkbiobj(ex, binum, ex.numproto); + mkbiobj(ex, biregexp, ex.regexpproto); + mkbiobj(ex, bierr, ex.errproto); + + math->FPcontrol(0, Math->INVAL|Math->ZDIV|Math->OVFL|Math->UNFL|Math->INEX); + + valinstant(o, DontEnum|DontDelete|ReadOnly, "MAX_VALUE", numval(math->nextafter(Math->Infinity, 0.))); + valinstant(o, DontEnum|DontDelete|ReadOnly, "MIN_VALUE", numval(math->nextafter(0., 1.))); + valinstant(o, DontEnum|DontDelete|ReadOnly, "NaN", numval(Math->NaN)); + valinstant(o, DontEnum|DontDelete|ReadOnly, "NEGATIVE_INFINITY", numval(-Math->Infinity)); + valinstant(o, DontEnum|DontDelete|ReadOnly, "POSITIVE_INFINITY", numval(+Math->Infinity)); + o = mkbiobj(ex, bidate, ex.dateproto); + biminst(o, bidatector, ex.funcproto, me); + + # + # the math object is a little different + # + o = mkobj(ex.objproto, "Object"); + valinstant(go, DontEnum, "Math", objval(o)); + biminst(o, bimath, ex.funcproto, me); + + # + # these are computed so they are consistent with numbers ecma might calculate + # + mathe := math->exp(1.); + valinstant(o, DontEnum|DontDelete|ReadOnly, "E", numval(mathe)); + valinstant(o, DontEnum|DontDelete|ReadOnly, "LN10", numval(math->log(10.))); + valinstant(o, DontEnum|DontDelete|ReadOnly, "LN2", numval(math->log(2.))); + valinstant(o, DontEnum|DontDelete|ReadOnly, "LOG2E", numval(1./math->log(2.))); + valinstant(o, DontEnum|DontDelete|ReadOnly, "LOG10E", numval(1./math->log(10.))); + valinstant(o, DontEnum|DontDelete|ReadOnly, "PI", numval(Math->Pi)); + valinstant(o, DontEnum|DontDelete|ReadOnly, "SQRT1_2", numval(math->sqrt(1./2.))); + valinstant(o, DontEnum|DontDelete|ReadOnly, "SQRT2", numval(math->sqrt(2.))); + + (EvalError, ex.evlerrproto) = mkerr(ex, "EvalError"); + (RangeError, ex.ranerrproto) = mkerr(ex, "RangeError"); + (ReferenceError, ex.referrproto) = mkerr(ex, "ReferenceError"); + (SyntaxError, ex.synerrproto) = mkerr(ex, "SyntaxError"); + (TypeError, ex.typerrproto) = mkerr(ex, "TypeError"); + (URIError, ex.urierrproto) = mkerr(ex, "URIError"); + (InternalError, ex.interrproto) = mkerr(ex, "InternalError"); + + return ex; +} + +mkerr(ex: ref Exec, e: string): (ref Obj, ref Obj) +{ + errproto := mkobj(ex.objproto, e); + biminst(errproto, array[] of { Builtin("toString", e+".prototype.toString", nil, 0) }, ex.funcproto, me); + errproto.val = strval(""); + valinstant(errproto, DontEnum|DontDelete, "length", numval(real 0)); + valinstant(errproto, DontEnum|DontDelete, "name", strval(e)); + valinstant(errproto, DontEnum|DontDelete, "message", strval(e)); + eo := mkbiobj(ex, Builtin(e, e, array[] of {"message"}, 1), errproto); + # return (eo, errproto); + return (nerr(ex, eo, array[] of {strval(e)}, errproto), errproto); +} + +mkparser(ex: ref Exec, src: string): ref Parser +{ + p := ref Parser; + p.ex = ex; + p.src = src; + p.esrc = len src; + p.srci = 0; + p.errors = 0; + p.lineno = 1; + p.token = -1; + p.token1 = -1; + p.lastnl = 0; + p.inloop = 0; + p.incase = 0; + p.infunc = 0; + p.notin = 0; + p.code = mkcode(); + return p; +} + +eval(ex: ref Exec, src: string): Completion +{ + { + p := mkparser(ex, src); + + if(debug['t']) + parset := sys->millisec(); + + prog(ex, p); + + toterrors += p.errors; + + if(p.errors) + runtime(ex, SyntaxError, ex.error); + if(debug['p']){ + s := array of byte pprint(ex, p.code, ""); + if(len s) + sys->write(stdout, s, len s); + } + + if(debug['t']) + xect := sys->millisec(); + + globalinstant(hd ex.scopechain, p.code.vars); + c := exec(ex, p.code); + + if(debug['t']) + print("parse time %d exec time %d\n", xect - parset, sys->millisec() - xect); + + return c; + }exception{ + "throw" => + return (CThrow, ex.errval, nil); + } +} + +#prog : selems +# ; +# +#selems : selem +# | selems selem +# ; +#selem : stmt +# | fundecl +# ; +prog(ex: ref Exec, p: ref Parser) +{ + while(look(p) != Leos) + if(look(p) == Lfunction) + fundecl(ex, p, 0); + else + stmt(p); +} + +#fundecl : Lfunction Lid '(' zplist ')' '{' stmts '}' +# ; +#zplist : +# | plist +# ; +# +#plist : Lid +# | plist ',' Lid +# ; +fundecl(ex: ref Exec, p: ref Parser, expr: int): ref Obj +{ + jp: ref Prop; + + c := p.code; + labs := p.labs; + p.labs = nil; + mustbe(p, Lfunction); + if(!expr || look(p) == Lid){ + mustbe(p, Lid); + jp = codevar(p, expr); + } + p.code = mkcode(); + mustbe(p, '('); + if(look(p) != ')'){ + for(;;){ + mustbe(p, Lid); + codevar(p, 1); + if(look(p) == ')') + break; + mustbe(p, ','); + } + } + params := p.code.vars; + p.code.vars = nil; + mustbe(p, ')'); + mustbe(p, '{'); + p.infunc++; + stmts(p); + p.infunc--; + mustbe(p, '}'); + + # + # override any existing value, + # as per sec. 10.1.3 Variable instantiation + # + sparams := array[len params] of string; + for(i := 0; i < len sparams; i++) + sparams[i] = params[i].name; + + # + # construct a function object; + # see section 15.3.21 + o := mkobj(ex.funcproto, "Function"); + o.call = ref Call(sparams, p.code, ex); + o.construct = o.call; + if(jp != nil) + o.val = strval(jp.name); + else + o.val = strval(""); + valinstant(o, DontDelete|DontEnum|ReadOnly, "length", numval(real len sparams)); + po := nobj(ex, nil, nil); + valinstant(o, DontEnum, "prototype", objval(po)); + valinstant(po, DontEnum, "constructor", objval(o)); + valinstant(o, DontDelete|DontEnum|ReadOnly, "arguments", null); + if(jp != nil) + jp.val.val = objval(o); + + if(debug['p']){ + s := array of byte (funcprint(ex, o) + "\n"); + sys->write(stdout, s, len s); + } + + p.code = c; + p.labs = labs; + if(expr && jp != nil) + popvar(p); + return o; +} + +# +# install a variable for the id just lexed +# +codevar(p: ref Parser, forcenew: int): ref Prop +{ + name := p.code.strs[p.id]; + vs := p.code.vars; + i : int; + if(!forcenew){ + for(i = 0; i < len vs; i++) + if(vs[i].name == name) + return vs[i]; + }else{ + i = len vs; + } + vs = array[i+1] of ref Prop; + vs[:] = p.code.vars; + p.code.vars = vs; + vs[i] = ref Prop(0, name, ref RefVal(undefined)); + return vs[i]; +} + +popvar(p: ref Parser) +{ + vs := p.code.vars; + p.code.vars = vs[0: len vs - 1]; +} + +#stmts : +# | stmts stmt +# ; +stmts(p: ref Parser) +{ + while((op := look(p)) != '}' && op != Leos) + stmt(p); +} + +#stmt : '{' stmts '}' +# | Lvar varlist ';' +# | exp ';' +# | ';' +# | Lif '(' exp ')' stmt +# | Lif '(' exp ')' stmt Lelse stmt +# | Lwhile '(' exp ')' stmt +# | Ldo stmt Lwhile '(' exp ')' +# | Lfor '(' zexp-notin ';' zexp ';' zexp ')' stmt +# | Lfor '(' Lvar varlist-notin ';' zexp ';' zexp ')' stmt +# | Lfor '(' lhsexp Lin exp ')' stmt +# | Lfor '(' Lvar Lid [init] Lin exp ')' stmt +# | Lcontinue ';' +# | Lbreak ';' +# | Lreturn zexp ';' # no line term after return +# | Lwith '(' exp ')' stmt +# | Lswitch '(' exp ')' '{' caseblk '}' +# | Lthrow exp ';' +# | Ltry block Lcatch '(' Lid ')' block +# | Ltry block finally block +# | Ltry block Lcatch '(' Lid ')' block finally block +# ; +stmt(p: ref Parser) +{ + pc: int; + + seenlabs := 0; + while(look(p) == Lid && look2(p) == ':'){ + pushlab(p, p.code.strs[p.id]); + emitconst(p, Llabel, p.id); + lex(p); + lex(p); + seenlabs++; + } + + op := look(p); + if(seenlabs) + setkindlab(p, op, seenlabs); + case op{ + ';' => + lexemit(p); + '{' => + if(seenlabs == 0){ + lex(p); + stmts(p); + } + else{ + lexemit(p); + pc = epatch(p); + stmts(p); + patch(p, pc); + } + mustbe(p, '}'); + Lvar => + lexemit(p); + pc = epatch(p); + varlist(p); + semi(p); + patch(p, pc); + * => + exp(p); + semi(p); + emit(p, ';'); + Lif => + lexemit(p); + pc = epatch(p); + mustbe(p, '('); + exp(p); + mustbe(p, ')'); + patch(p, pc); + pc = epatch(p); + stmt(p); + patch(p, pc); + pc = epatch(p); + if(look(p) == Lelse){ + lex(p); + stmt(p); + } + patch(p, pc); + Lwhile or + Lwith => + lexemit(p); + pc = epatch(p); + mustbe(p, '('); + exp(p); + mustbe(p, ')'); + patch(p, pc); + if(op == Lwhile) + p.inloop++; + pc = epatch(p); + stmt(p); + patch(p, pc); + if(op == Lwhile) + p.inloop--; + Ldo => + p.inloop++; + lexemit(p); + pc = epatch(p); + stmt(p); + patch(p, pc); + mustbe(p, Lwhile); + mustbe(p, '('); + pc = epatch(p); + exp(p); + patch(p, pc); + mustbe(p, ')'); + mustbe(p, ';'); + p.inloop--; + Lfor => + fpc := p.code.npc; + lexemit(p); + mustbe(p, '('); + p.notin++; + if(look(p) == Lvar){ + lex(p); + pc = epatch(p); + varlist(p); + patch(p, pc); + p.notin--; + if(look(p) == Lin){ + check1var(p); + p.code.ops[fpc] = byte Lforvarin; + lex(p); + pc = epatch(p); + exp(p); + patch(p, pc); + }else{ + p.code.ops[fpc] = byte Lforvar; + mustbe(p, ';'); + pc = epatch(p); + zexp(p); + patch(p, pc); + mustbe(p, ';'); + pc = epatch(p); + zexp(p); + patch(p, pc); + } + }else{ + pc = epatch(p); + lhspc := p.code.npc; + zexp(p); + patch(p, pc); + p.notin--; + if(look(p) == Lin){ + p.code.ops[fpc] = byte Lforin; + checklhsexp(p, lhspc); + lex(p); + pc = epatch(p); + exp(p); + patch(p, pc); + }else{ + mustbe(p, ';'); + pc = epatch(p); + zexp(p); + patch(p, pc); + mustbe(p, ';'); + pc = epatch(p); + zexp(p); + patch(p, pc); + } + } + mustbe(p, ')'); + p.inloop++; + pc = epatch(p); + stmt(p); + patch(p, pc); + p.inloop--; + Lcontinue or + Lbreak => + lex(p); + lab := 0; + if(look(p) == Lid){ + if((lr := findlab(p, p.code.strs[p.id])) == nil) + error(p, "missing label"); + if(op == Lcontinue && !itstmt(lr.k)) + error(p, "continue label not on iteration statement"); + if(op == Lbreak) + nop := Lbreaklab; + else + nop = Lcontinuelab; + if(!inlocallabs(p, lr, seenlabs)) # otherwise noop + emitconst(p, nop, p.id); + lex(p); + lab = 1; + } + else + emit(p, op); + semi(p); + if(op == Lbreak && !lab && !p.inloop && !p.incase) + error(p, "break not in a do or for or while or case"); + if(op == Lcontinue && !p.inloop) + error(p, "continue not in a do or for or while"); + Lreturn => + lexemit(p); + nextop := look(p); + if(nextop != ';' && nextop != '}' && !p.lastnl) + exp(p); + semi(p); + emit(p, ';'); + if(!p.infunc) + error(p, tokname(op)+" not in a function"); + Lswitch => + lexemit(p); + mustbe(p, '('); + pc = epatch(p); + exp(p); + patch(p, pc); + mustbe(p, ')'); + mustbe(p, '{'); + pc = epatch(p); + caseblk(p); + patch(p, pc); + mustbe(p, '}'); + Lthrow => + lexemit(p); + nextop := look(p); + if(!p.lastnl) + exp(p); + mustbe(p, ';'); + emit(p, ';'); + Lprint => + lexemit(p); + nextop := look(p); + if(!p.lastnl) + exp(p); + mustbe(p, ';'); + emit(p, ';'); + Ltry => + lexemit(p); + pc = epatch(p); + block(p); + patch(p, pc); + pc = epatch(p); + if(look(p) == Lcatch){ + lex(p); + mustbe(p, '('); + mustbe(p, Lid); + emitconst(p, Lid, p.id); + mustbe(p, ')'); + block(p); + } + patch(p, pc); + pc = epatch(p); + if(look(p) == Lfinally){ + lex(p); + block(p); + } + patch(p, pc); + } + while(--seenlabs >= 0) + poplab(p); +} + +block(p : ref Parser) +{ + mustbe(p, '{'); + stmts(p); + mustbe(p, '}'); +} + +caseblk(p : ref Parser) +{ + pc, defaultpc, clausepc : int; + gotdef := 0; + p.incase++; + + defaultpc = epatch(p); + while((op := look(p)) != '}' && op != Leos) { + if (op != Lcase && op != Ldefault) { + err := "expected " + tokname(Lcase) + + " or " + tokname(Ldefault) + + " found " + tokname(op); + error(p, err); + } + if (op == Ldefault) { + if (gotdef) + error(p, "default case already defined"); + gotdef = 1; + + patch(p, defaultpc); + } + lex(p); + clausepc = epatch(p); + if (op == Lcase) { + pc = epatch(p); + exp(p); + patch(p, pc); + } + mustbe(p, ':'); + casestmts(p); + patch(p, clausepc); + } + clausepc = epatch(p); + patch(p, clausepc); + if (!gotdef) + patch(p, defaultpc); + p.incase--; +} + +casestmts(p : ref Parser) +{ + while((op := look(p)) != '}' && op != Lcase && op != Ldefault && op != Leos) + stmt(p); +} + +semi(p: ref Parser) +{ + op := look(p); + if(op == ';'){ + lex(p); + return; + } + if(op == '}' || op == Leos || p.lastnl) + return; + mustbe(p, ';'); +} + +#varlist : vardecl +# | varlist ',' vardecl +# ; +# +#vardecl : Lid init +# ; +# +#init : +# | '=' asexp +# ; +varlist(p: ref Parser) +{ + # + # these declaration aren't supposed + # to override current definitions + # + mustbe(p, Lid); + codevar(p, 0); + emitconst(p, Lid, p.id); + if(look(p) == '='){ + lex(p); + asexp(p); + emit(p, '='); + } + if(look(p) != ',') + return; + emit(p, Lgetval); + lex(p); + varlist(p); + emit(p, ','); +} + +# +# check that only 1 id is declared in the var list +# +check1var(p: ref Parser) +{ + if(p.code.ops[p.code.npc-1] == byte ',') + error(p, "only one identifier allowed"); +} + +#zexp : +# | exp +# ; +zexp(p: ref Parser) +{ + op := look(p); + if(op == ';' || op == ')') + return; + exp(p); +} + +#exp : asexp +# | exp ',' asexp +# ; +exp(p: ref Parser) +{ + asexp(p); + while(look(p) == ','){ + lex(p); + emit(p, Lgetval); + asexp(p); + emit(p, ','); + } +} + +#asexp : condexp +# | lhsexp asop asexp +# ; +# +#asop : '=' | Lmulas | Ldivas | Lmodas | Laddas | Lsubas +# | Llshas | Lrshas | Lrshuas | Landas | Lxoras | Loras +# ; +asops := array[] of { '=', Lmulas, Ldivas, Lmodas, Laddas, Lsubas, + Llshas, Lrshas, Lrshuas, Landas, Lxoras, Loras }; +asbaseops := array[] of { '=', '*', '/', '%', '+', '-', + Llsh, Lrsh, Lrshu, '&', '^', '|' }; +asexp(p: ref Parser) +{ + lhspc := p.code.npc; + condexp(p); + i := inops(look(p), asops); + if(i >= 0){ + op := lex(p); + checklhsexp(p, lhspc); + if(op != '=') + emit(p, Lasop); + asexp(p); + emit(p, asbaseops[i]); + if(op != '=') + emit(p, Las); + } +} + +#condexp : ororexp +# | ororexp '?' asexp ':' asexp +# ; +condexp(p: ref Parser) +{ + ororexp(p); + if(look(p) == '?'){ + lexemit(p); + pc := epatch(p); + asexp(p); + mustbe(p, ':'); + patch(p, pc); + pc = epatch(p); + asexp(p); + patch(p, pc); + } +} + +#ororexp : andandexp +# | ororexp op andandexp +# ; +ororexp(p: ref Parser) +{ + andandexp(p); + while(look(p) == Loror){ + lexemit(p); + pc := epatch(p); + andandexp(p); + patch(p, pc); + } +} + +#andandexp : laexp +# | andandexp op laexp +# ; +andandexp(p: ref Parser) +{ + laexp(p, 0); + while(look(p) == Landand){ + lexemit(p); + pc := epatch(p); + laexp(p, 0); + patch(p, pc); + } +} + +#laexp : unexp +# | laexp op laexp +# ; +prectab := array[] of +{ + array[] of { '|' }, + array[] of { '^' }, + array[] of { '&' }, + array[] of { Leq, Lneq, Lseq, Lsne }, + array[] of { '<', '>', Lleq, Lgeq, Lin, Linstanceof }, + array[] of { Llsh, Lrsh, Lrshu }, + array[] of { '+', '-' }, + array[] of { '*', '/', '%' }, +}; +laexp(p: ref Parser, prec: int) +{ + unexp(p); + for(pr := len prectab - 1; pr >= prec; pr--){ + while(inops(look(p), prectab[pr]) >= 0){ + emit(p, Lgetval); + op := lex(p); + laexp(p, pr + 1); + emit(p, op); + } + } +} + +#unexp : postexp +# | Ldelete unexp +# | Lvoid unexp +# | Ltypeof unexp +# | Linc unexp +# | Ldec unexp +# | '+' unexp +# | '-' unexp +# | '~' unexp +# | '!' unexp +# ; +preops := array[] of { Ldelete, Lvoid, Ltypeof, Linc, Ldec, '+', '-', '~', '!' }; +unexp(p: ref Parser) +{ + if(inops(look(p), preops) >= 0){ + op := lex(p); + unexp(p); + if(op == '-') + op = Lpresub; + else if(op == '+') + op = Lpreadd; + emit(p, op); + return; + } + postexp(p); +} + +#postexp : lhsexp +# | lhsexp Linc # no line terminators before Linc or Ldec +# | lhsexp Ldec +# ; +postexp(p: ref Parser) +{ + lhsexp(p, 0); + if(p.lastnl) + return; + op := look(p); + if(op == Linc || op == Ldec){ + if(op == Linc) + op = Lpostinc; + else + op = Lpostdec; + lex(p); + emit(p, op); + } +} + +# +# verify that the last expression is actually a lhsexp +# +checklhsexp(p: ref Parser, pc: int) +{ + + case int p.code.ops[p.code.npc-1]{ + Lthis or + ')' or + '.' or + '[' or + Lcall or + Lnew or + Lnewcall => + return; + } + + case int p.code.ops[pc]{ + Lid or + Lnum or + Lstr or + Lregexp => + npc := pc + 1; + (npc, nil) = getconst(p.code.ops, npc); + if(npc == p.code.npc) + return; + } + + (nil, e) := pexp(mkpprint(p.ex, p.code), pc, p.code.npc); + error(p, "only left-hand-side expressions allowed: "+e); +} + +#lhsexp : newexp +# | callexp +# ; +#callexp: memexp args +# | callexp args +# | callexp '[' exp ']' +# | callexp '.' Lid +# ; +#newexp : memexp +# | Lnew newexp +# ; +#memexp : primexp +# | Lfunction id(opt) '(' zplist ')' '{' stmts '}' +# | memexp '[' exp ']' +# | memexp '.' Lid +# | Lnew memexp args +# ; +lhsexp(p: ref Parser, hasnew: int): int +{ + a: int; + if(look(p) == Lnew){ + lex(p); + hasnew = lhsexp(p, hasnew + 1); + if(hasnew){ + emit(p, Lnew); + hasnew--; + } + return hasnew; + } + if(look(p) == Lfunction){ + o := fundecl(p.ex, p, 1); + emitconst(p, Lfunction, fexplook(p, o)); + return 0; + } + primexp(p); + for(;;){ + op := look(p); + if(op == '('){ + op = Lcall; + if(hasnew){ + hasnew--; + # + # stupid different order of evaluation + # + emit(p, Lgetval); + op = Lnewcall; + } + a = args(p); + emitconst(p, op, a); + }else if(op == '['){ + emit(p, Lgetval); + lex(p); + exp(p); + mustbe(p, ']'); + emit(p, '['); + }else if(op == '.'){ + lex(p); + mustbe(p, Lid); + emitconst(p, Lid, p.id); + emit(p, '.'); + }else + return hasnew; + } +} + +#primexp : Lthis +# | Lid +# | Lnum +# | Lstr +# | Lregexp +# | '(' exp ')' +# | '[' array initializer ']' +# | '{' propandval '}' +# ; +primexp(p: ref Parser) +{ + case t := lex(p){ + Lthis => + emit(p, t); + Lid or + Lnum or + Lstr => + emitconst(p, t, p.id); + '/' => + lexregexp(p); + emitconst(p, Lregexp, p.id); + '(' => + emit(p, '('); + exp(p); + mustbe(p, ')'); + emit(p, ')'); + '[' => + a := 0; + if(look(p) == ']') + lex(p); + else{ + for(;;){ + if(look(p) == ']'){ + lex(p); + break; + } + if(look(p) == ',') + emit(p, Lnoval); + else + asexp(p); + emit(p, Lgetval); + a++; + if(look(p) == ']'){ + lex(p); + break; + } + mustbe(p, ','); + } + } + emitconst(p, Larrinit, a); + '{' => + a := 0; + if(look(p) == '}') + lex(p); + else{ + for(;;){ + case(tt := lex(p)){ + Lid => + emitconst(p, Lstr, p.id); + Lnum or + Lstr => + emitconst(p, tt, p.id); + * => + error(p, "expected identifier, number or string"); + } + mustbe(p, ':'); + asexp(p); + emit(p, Lgetval); + a++; + if(look(p) == '}'){ + lex(p); + break; + } + mustbe(p, ','); + } + } + emitconst(p, Lobjinit, a); + * => + error(p, "expected an expression"); + } +} + +#args : '(' ')' +# | '(' arglist ')' +# ; +# +#arglist : asexp +# | arglist ',' asexp +# ; +args(p: ref Parser): int +{ + mustbe(p, '('); + if(look(p) == ')'){ + lex(p); + return 0; + } + a := 0; + for(;;){ + asexp(p); + emit(p, Lgetval); + a++; + if(look(p) == ')'){ + lex(p); + return a; + } + mustbe(p, ','); + } +} + +inops(tok: int, ops: array of int): int +{ + for(i := 0; i < len ops; i++) + if(tok == ops[i]) + return i; + return -1; +} + +mustbe(p: ref Parser, t: int) +{ + tt := lex(p); + if(tt != t) + error(p, "expected "+tokname(t)+" found "+tokname(tt)); +} + +toknames := array[] of +{ + Leos-Lbase => "end of input", + Landas-Lbase => "&=", + Loras-Lbase => "|=", + Lxoras-Lbase => "^=", + Llshas-Lbase => "<<=", + Lrshas-Lbase => ">>=", + Lrshuas-Lbase => ">>>=", + Laddas-Lbase => "+=", + Lsubas-Lbase => "-=", + Lmulas-Lbase => "*=", + Ldivas-Lbase => "/=", + Lmodas-Lbase => "%=", + Loror-Lbase => "||", + Landand-Lbase => "&&", + Leq-Lbase => "==", + Lneq-Lbase => "!=", + Lleq-Lbase => "<=", + Lgeq-Lbase => ">=", + Llsh-Lbase => "<<", + Lrsh-Lbase => ">>", + Lrshu-Lbase => ">>>", + Linc-Lbase => "++", + Ldec-Lbase => "--", + Lnum-Lbase => "a number", + Lid-Lbase => "an identifier", + Lstr-Lbase => "a string", + Lthis-Lbase => "this", + Ltypeof-Lbase => "typeof", + Ldelete-Lbase => "delete", + Lvoid-Lbase => "void", + Lwhile-Lbase => "while", + Lfor-Lbase => "for", + Lbreak-Lbase => "break", + Lcontinue-Lbase => "continue", + Lwith-Lbase => "with", + Lreturn-Lbase => "return", + Lfunction-Lbase => "function", + Lvar-Lbase => "var", + Lif-Lbase => "if", + Lelse-Lbase => "else", + Lin-Lbase => "in", + Lnew-Lbase => "new", + + Lpreadd-Lbase => "+", + Lpresub-Lbase => "-", + Lpostinc-Lbase => "++", + Lpostdec-Lbase => "--", + Lcall-Lbase => "call", + Lnewcall-Lbase => "newcall", + Lgetval-Lbase => "[[GetValue]]", + Las-Lbase => "[[as]]", + Lasop-Lbase => "[[asop]]", + Lforin-Lbase => "forin", + Lforvar-Lbase => "forvar", + Lforvarin-Lbase => "forvarin", + Lcase-Lbase => "case", + Labstract-Lbase => "abstract", + Lboolean-Lbase => "boolean", + Lbyte-Lbase => "byte", + Lcatch-Lbase => "catch", + Lchar-Lbase => "char", + Lclass-Lbase => "class", + Lconst-Lbase => "const", + Ldebugger-Lbase => "debugger", + Ldefault-Lbase => "default", + Ldo-Lbase => "do", + Ldouble-Lbase => "double", + Lenum-Lbase => "enum", + Lexport-Lbase => "export", + Lextends-Lbase => "extends", + Lfinal-Lbase => "final", + Lfinally-Lbase => "finally", + Lfloat-Lbase => "float", + Lgoto-Lbase => "goto", + Limplements-Lbase => "implements", + Limport-Lbase => "import", + Linstanceof-Lbase => "instanceof", + Lint-Lbase => "int", + Linterface-Lbase => "interface", + Llong-Lbase => "long", + Lnative-Lbase => "native", + Lpackage-Lbase => "package", + Lprint-Lbase => "print", + Lprivate-Lbase => "private", + Lprotected-Lbase => "protected", + Lpublic-Lbase => "public", + Lregexp-Lbase => "regexp", + Lseq-Lbase => "===", + Lsne-Lbase => "!==", + Lshort-Lbase => "short", + Lstatic-Lbase => "static", + Lsuper-Lbase => "super", + Lswitch-Lbase => "switch", + Lsynchronized-Lbase => "synchronized", + Lthrow-Lbase => "throw", + Lthrows-Lbase => "throws", + Ltransient-Lbase => "transient", + Ltry-Lbase=> "try", + Lvolatile-Lbase => "volatile", + Larrinit-Lbase => "arrayinit", + Lobjinit-Lbase => "objinit", + Lnoval-Lbase => "novalue", + Llabel-Lbase => "label", + Lbreaklab-Lbase => "break", + Lcontinuelab-Lbase => "continue", +}; + +tokname(t: int): string +{ + if(t < Lbase){ + s := ""; + s[0] = t; + return s; + } + if(t-Lbase >= len toknames || toknames[t-Lbase] == "") + return sprint("<%d>", t); + return toknames[t-Lbase]; +} + +lexemit(p: ref Parser) +{ + emit(p, lex(p)); + if(debug['s']) + sys->print("%d: %s\n", p.code.npc-1, tokname(int p.code.ops[p.code.npc-1])); +} + +emit(p: ref Parser, t: int) +{ + if(t > 255) + fatal(p.ex, sprint("emit too big: %d\n", t)); + if(p.code.npc >= len p.code.ops){ + ops := array[2 * len p.code.ops] of byte; + ops[:] = p.code.ops; + p.code.ops = ops; + } + p.code.ops[p.code.npc++] = byte t; +} + +emitconst(p: ref Parser, op, c: int) +{ + emit(p, op); + if(c < 0) + fatal(p.ex, "emit negative constant"); + if(c >= 255){ + if(c >= 65536) + fatal(p.ex, "constant too large"); + emit(p, 255); + emit(p, c & 16rff); + c >>= 8; + } + emit(p, c); +} + +epatch(p: ref Parser): int +{ + pc := p.code.npc; + emit(p, 0); + emit(p, 0); + return pc; +} + +patch(p: ref Parser, pc: int) +{ + val := p.code.npc - pc; + if(val >= 65536) + fatal(p.ex, "patch constant too large"); + p.code.ops[pc] = byte val; + p.code.ops[pc+1] = byte(val >> 8); +} + +getconst(ops: array of byte, pc: int): (int, int) +{ + c := int ops[pc++]; + if(c == 255){ + c = int ops[pc] + (int ops[pc+1] << 8); + pc += 2; + } + return (pc, c); +} + +getjmp(ops: array of byte, pc: int): (int, int) +{ + c := int ops[pc] + (int ops[pc+1] << 8) + pc; + pc += 2; + return (pc, c); +} + +mkcode(): ref Code +{ + return ref Code(array[16] of byte, 0, nil, nil, nil, nil, nil); +} + +look(p: ref Parser): int +{ + if(p.token == -1) + p.token = lex(p); + if(p.notin && p.token == Lin) + return ~Lin; + return p.token; +} + +look2(p: ref Parser): int +{ + look(p); + if(p.token1 == -1){ + # fool lex() + t := p.token; + p.token = -1; + p.token1 = lex(p); + p.token = t; + } + return p.token1; +} + +lex(p: ref Parser): int +{ + t := lex0(p); + if(0) + sys->print("tok=%d %s\n", t, tokname(t)); + return t; +} + +lex0(p: ref Parser): int +{ + t := p.token; + if(t != -1){ + p.token = p.token1; + p.token1 = -1; + return t; + } + + p.lastnl = 0; + while(p.srci < p.esrc){ + c := p.src[p.srci++]; + case c{ + '\r' or LS or PS => + p.lastnl = 1; + '\n' => + p.lineno++; + p.lastnl = 1; + ' ' or + '\t' or + '\v' or + FF or # form feed + '\u00a0' => # no-break space + ; + '"' or + '\''=> + return lexstring(p, c); + '(' or + ')' or + '[' or + ']' or + '{' or + '}' or + ',' or + ';' or + '~' or + '?' or + ':' => + return c; + '.' => + if(p.srci < p.esrc && (map[p.src[p.srci]] & Mdigit) != byte 0){ + p.srci--; + return lexnum(p); + } + return '.'; + '^' => + if(p.srci < p.esrc && p.src[p.srci] == '='){ + p.srci++; + return Lxoras; + } + return '^'; + '*' => + if(p.srci < p.esrc && p.src[p.srci] == '='){ + p.srci++; + return Lmulas; + } + return '*'; + '%' => + if(p.srci < p.esrc && p.src[p.srci] == '='){ + p.srci++; + return Lmodas; + } + return '%'; + '=' => + if(p.srci < p.esrc && p.src[p.srci] == '='){ + p.srci++; + if(p.srci < p.esrc && p.src[p.srci] == '='){ + p.srci++; + return Lseq; + } + return Leq; + } + return '='; + '!' => + if(p.srci < p.esrc && p.src[p.srci] == '='){ + p.srci++; + if(p.srci < p.esrc && p.src[p.srci] == '='){ + p.srci++; + return Lsne; + } + return Lneq; + } + return '!'; + '+' => + if(p.srci < p.esrc){ + c = p.src[p.srci]; + if(c == '='){ + p.srci++; + return Laddas; + } + if(c == '+'){ + p.srci++; + return Linc; + } + } + return '+'; + '-' => + if(p.srci < p.esrc){ + c = p.src[p.srci]; + if(c == '='){ + p.srci++; + return Lsubas; + } + if(c == '-'){ + p.srci++; + return Ldec; + } + } + return '-'; + '|' => + if(p.srci < p.esrc){ + c = p.src[p.srci]; + if(c == '='){ + p.srci++; + return Loras; + } + if(c == '|'){ + p.srci++; + return Loror; + } + } + return '|'; + '&' => + if(p.srci < p.esrc){ + c = p.src[p.srci]; + if(c == '='){ + p.srci++; + return Landas; + } + if(c == '&'){ + p.srci++; + return Landand; + } + } + return '&'; + '/' => + if(p.srci < p.esrc){ + c = p.src[p.srci]; + if(c == '='){ + p.srci++; + return Ldivas; + } + if(c == '/'){ + p.srci++; + if(lexcom(p) < 0) + return Leos; + break; + } + if(c == '*'){ + p.srci++; + if(lexmcom(p) < 0) + return Leos; + break; + } + } + return '/'; + '>' => + if(p.srci < p.esrc){ + c = p.src[p.srci]; + if(c == '='){ + p.srci++; + return Lgeq; + } + if(c == '>'){ + p.srci++; + if (p.srci < p.esrc) { + c = p.src[p.srci]; + if(c == '='){ + p.srci++; + return Lrshas; + } + if(c == '>'){ + p.srci++; + c = p.src[p.srci]; + if(c == '='){ + p.srci++; + return Lrshuas; + } + return Lrshu; + } + } + return Lrsh; + } + } + return '>'; + '<' => + if(p.srci < p.esrc){ + c = p.src[p.srci]; + case c { + '=' => + p.srci++; + return Lleq; + '<' => + p.srci++; + if (p.srci < p.esrc) { + c = p.src[p.srci]; + if(c == '='){ + p.srci++; + return Llshas; + } + } + return Llsh; + '!' => + # HTML comment - consume to end of line or end of comment + # No way of having the HTML parser do this + if (p.srci+2 >= p.esrc) + return Leos; + + if (p.src[p.srci+1] != '-' || p.src[p.srci+2] != '-') + # don't treat as a comment, let the parser report syntax error + return '<'; + # consume "!--" + p.srci += 3; + if(lexhtmlcom(p) < 0) + return Leos; + continue; + } + } + return '<'; + '0' to '9' => + p.srci--; + return lexnum(p); + '\\' => + return lexid(p); + * => + if((map[c] & Malpha) != byte 0) + return lexid(p); + s := ""; + s[0] = c; + error(p, "unknown character '"+s+"'"); + } + } + return Leos; +} + +# +# single line comment +# +lexcom(p: ref Parser): int +{ + while(p.srci < p.esrc){ + c := p.src[p.srci]; + if(islt(c)) + return 0; + p.srci++; + } + return -1; +} + +# +# multi-line comment +# +lexmcom(p: ref Parser): int +{ + star := 0; + while(p.srci < p.esrc){ + c := p.src[p.srci++]; + if(c == '/' && star) + return 0; + star = c == '*'; + } + return -1; +} + +# HTML comment +# consume to end of line or end of comment (-->), whichever we see first. +# [not strict HTML comment semantics because of +# the way in which HTML comments are used in JavaScript] +# +lexhtmlcom(p: ref Parser): int +{ + nmin := 0; + for (;p.srci < p.esrc;) { + c := p.src[p.srci++]; + if (c == '-') { + nmin++; + continue; + } + if (c == '>' && nmin >= 2) + return 0; + if (islt(c)) + return 0; + nmin = 0; + } + return -1; +} + +lexid(p: ref Parser): int +{ + p.srci--; + id := ""; + ch := "Z"; + while(p.srci < p.esrc){ + c := p.src[p.srci]; + if(c == '\\'){ + p.srci++; + c = uniescchar(p); + if(c == -1) + error(p, "malformed unicode escape sequence in identifier"); + else + ; + } + else{ + if(c >= 0 && c < 256 && (map[c] & (Malpha|Mdigit)) == byte 0) + # if(c >= 256 || (map[c] & (Malpha|Mdigit)) == byte 0) + break; + p.srci++; + } + ch[0] = c; + id += ch; + } + # id := p.src[srci:p.srci]; + t := keywdlook(id); + if(t != -1) + return t; + p.id = strlook(p, id); + return Lid; +} + +ParseReal, ParseHex, ParseOct, ParseTrim, ParseEmpty: con 1 << iota; + +# +# parse a numeric identifier +# format [0-9]+(r[0-9A-Za-z]+)? +# or ([0-9]+(\.[0-9]*)?|\.[0-9]+)([eE][+-]?[0-9]+)? +# +lexnum(p: ref Parser): int +{ + v: real; + (p.srci, v) = parsenum(p.ex, p.src, p.srci, ParseReal|ParseHex|ParseOct); + p.id = numlook(p, v); + return Lnum; +} + +parsenum(ex: ref Exec, s: string, si, how: int): (int, real) +{ + Inf: con "Infinity"; + + osi := si; + lens := len s; + if (how & ParseTrim) { + while(si < lens && iswhite(s[si])) + si++; + } + if(si >= lens) { + if (how & ParseEmpty) + return (si, 0.); + return (osi, Math->NaN); + } + c := s[si]; + neg := 0; + if(c == '+') + si++; + else if(c == '-'){ + si++; + neg = 1; + } + v := 0.; + if((how & ParseReal) && si + len Inf <= lens && s[si:si+len Inf] == Inf){ + si += len Inf; + v = Math->Infinity; + }else{ + nsi := si; + (si, v) = parsenumval(ex, s, si, how); + if(si == nsi) + return (osi, Math->NaN); + } + if(neg) + v = -v; + if (how & ParseTrim) { + while(si < lens && iswhite(s[si])) + si++; + } + return (si, v); +} + +# +# parse a bunch of difference subsets of numbers +# +parsenumval(ex: ref Exec, s: string, si, how: int): (int, real) +{ + Int, Oct, Hex, FracSeen, Frac, ExpSeen, ExpSignSeen, Exp: con iota; + + lens := len s; + if(si >= lens) + return (si, Math->NaN); + ssi := si; + c := s[si]; + state := Int; + if(c == '.' && (how & ParseReal)){ + state = FracSeen; + si++; + }else if(c == '0'){ + if(si+1 >= lens) + return (si+1, 0.); + c = s[si+1]; + if(c == '.' && (how & ParseReal)){ + state = Frac; + si += 2; + }else if((c == 'x' || c == 'X') && (how & ParseHex)){ + state = Hex; + ssi += 2; + si += 2; + }else if(how & ParseOct) + state = Oct; + } + +done: while(si < lens){ + c = s[si]; + case state{ + Int => + if((map[c] & Mdigit) != byte 0) + break; + if((map[c] & Mexp) != byte 0 && (how & ParseReal)) + state = ExpSeen; + else if(c == '.' && (how & ParseReal)) + state = Frac; + else + break done; + Hex => + if((map[c] & Mhex) == byte 0) + break done; + Oct => + if((map[c] & Moct) == byte 0) + break done; + FracSeen or + Frac => + if((map[c] & Mdigit) != byte 0) + state = Frac; + else if((map[c] & Mexp) != byte 0) + state = ExpSeen; + else + break done; + ExpSeen => + if((map[c] & Msign) != byte 0) + state = ExpSignSeen; + else if((map[c] & Mdigit) != byte 0) + state = Exp; + else + break done; + ExpSignSeen or + Exp => + if((map[c] & Mdigit) != byte 0) + state = Exp; + else + break done; + } + si++; + } + + esi := si; + if(state == FracSeen) + return (si - 1, Math->NaN); + if(state == ExpSeen){ + state = Frac; + esi--; + }else if(state == ExpSignSeen){ + state = Frac; + esi -= 2; + } + buf := s[ssi:esi]; + v: real; + case state{ + * => + # only if the above lexing code is wrong + fatal(ex, "bad parse of numerical constant '"+buf+"'"); + v = 0.; + Oct => + v = strtoi(ex, buf, 8); + Hex => + v = strtoi(ex, buf, 16); + Int or + Frac or + Exp => + v = real buf; + } + return (si, v); +} + +# +# called only from parsenumval +# can never fatal error if that routine works correctly +# +strtoi(ex: ref Exec, t: string, base: int): real +{ + if(len t == 0) + return Math->NaN; + + v := 0.; + for(i := 0; i < len t; i++){ + c := t[i]; + if(c >= '0' && c <= '9') + c -= '0'; + else if(c >= 'a' && c <= 'z') + c -= 'a' - 10; + else + c -= 'A' - 10; + if(c >= base){ + fatal(ex, "digit '"+t[i:i+1]+"' is not radix "+string base); + return Math->NaN; + } + v = v * real base + real c; + } + return v; +} + +lexstring(p: ref Parser, end: int): int +{ + s := ""; + i := 0; + for(;;){ + if(p.srci >= p.esrc){ + error(p, "end of file in string constant"); + break; + } + c := p.src[p.srci]; + if(islt(c)){ + error(p, "newline in string constant"); + break; + } + p.srci++; + if(c == end) + break; + if(c == '\\'){ + c = escchar(p); + if(c == Leos) + continue; + } + s[i++] = c; + } + p.id = strlook(p, s); + return Lstr; +} + +lexregexp(p: ref Parser): int +{ + c := esc := 0; + s := ""; + i := 0; + s[i++] = '/'; + for(;;){ + if(p.srci >= p.esrc){ + error(p, "end of file in regexp constant"); + break; + } + c = p.src[p.srci]; + if(islt(c)){ + error(p, "newline in regexp constant"); + break; + } + p.srci++; + s[i++] = c; + if(!esc && c == '/') + break; + esc = !esc && c == '\\'; + } + if(esc) + error(p, "missing escaped character"); + if(i == 2) + error(p, "missing regexp"); + while(p.srci < p.esrc){ + c = p.src[p.srci]; + if(c >= 256 || (map[c] & (Malpha|Mdigit)) == byte 0) + break; + p.srci++; + s[i++] = c; + } + p.id = strlook(p, s); + return Lregexp; +} + +uniescchar(p: ref Parser): int +{ + if(p.srci >= p.esrc) + return -1; + c := p.src[p.srci++]; + if(c != 'u') + return -1; + v := 0; + for(i := 0; i < 4; i++){ + if(p.srci >= p.esrc || (map[c = p.src[p.srci]] & (Mdigit|Mhex)) == byte 0) + return -1; + p.srci++; + if((map[c] & Mdigit) != byte 0) + c -= '0'; + else if((map[c] & Mlower) != byte 0) + c = c - 'a' + 10; + else if((map[c] & Mupper) != byte 0) + c = c - 'A' + 10; + v = v * 16 + c; + } + return v; +} + +escchar(p: ref Parser): int +{ + v: int; + if(p.srci >= p.esrc) + return Leos; + c := p.src[p.srci++]; + if(c == 'u' || c == 'x'){ + d := 2; + if(c == 'u') + d = 4; + v = 0; + for(i := 0; i < d; i++){ + if(p.srci >= p.esrc || (map[c = p.src[p.srci]] & (Mdigit|Mhex)) == byte 0){ + error(p, "malformed hex escape sequence"); + break; + } + p.srci++; + if((map[c] & Mdigit) != byte 0) + c -= '0'; + else if((map[c] & Mlower) != byte 0) + c = c - 'a' + 10; + else if((map[c] & Mupper) != byte 0) + c = c - 'A' + 10; + v = v * 16 + c; + } + return v; + } + if(c >= '0' && c <= '7'){ + v = c - '0'; + if(p.srci < p.esrc && (c = p.src[p.srci]) >= '0' && c <= '7'){ + p.srci++; + v = v * 8 + c - '0'; + if(v <= 8r37 && p.srci < p.esrc && (c = p.src[p.srci]) >= '0' && c <= '7'){ + p.srci++; + v = v * 8 + c - '0'; + } + } + return v; + } + + if(c < len escmap && (v = int escmap[c]) < 255) + return v; + return c; +} + +keywdlook(s: string): int +{ + m: int; + l := 1; + r := len keywords - 1; + while(l <= r){ + m = (r + l) >> 1; + if(keywords[m].name <= s) + l = m + 1; + else + r = m - 1; + } + m = l - 1; + if(keywords[m].name == s) + return keywords[m].token; + return -1; +} + +strlook(p: ref Parser, s: string): int +{ + for(i := 0; i < len p.code.strs; i++) + if(p.code.strs[i] == s) + return i; + strs := array[i + 1] of string; + strs[:] = p.code.strs; + strs[i] = s; + p.code.strs = strs; + return i; +} + +numlook(p: ref Parser, r: real): int +{ + for(i := 0; i < len p.code.nums; i++) + if(p.code.nums[i] == r) + return i; + nums := array[i + 1] of real; + nums[:] = p.code.nums; + nums[i] = r; + p.code.nums = nums; + return i; +} + +fexplook(p: ref Parser, o: ref Obj): int +{ + i := len p.code.fexps; + fexps := array[i+1] of ref Obj; + fexps[:] = p.code.fexps; + fexps[i] = o; + p.code.fexps = fexps; + return i; +} + +iswhite(c: int): int +{ + if(islt(c)) + return 1; + case c { + ' ' or + '\t' or + '\v' or + FF or # form feed + '\u00a0' => # no-break space + return 1; + } + return 0; +} + +error(p: ref Parser, s: string) +{ + p.errors++; + p.ex.error += sys->sprint("%d: syntax error: %s\n", p.lineno, s); + if(p.errors >= maxerr) + runtime(p.ex, SyntaxError, p.ex.error); +} + +fatal(ex: ref Exec, msg: string) +{ + if(debug['f']){ + print("fatal ecmascript error: %s\n", msg); + if(""[5] == -1); # abort + } + runtime(ex, InternalError, "unrecoverable internal ecmascript error: "+ msg); +} + +# scanb(p: ref Parser, s: string): int +# { +# n := len s; +# for(i := p.srci; i+n > p.esrc || p.src[i: i+n] != s; --i) +# ; +# return i; +# } + +setkindlab(p: ref Parser, op: int, n: int) +{ + l := p.labs; + for(i := 0; i < n; i++){ + (hd l).k = op; + l = tl l; + } +} + +inlocallabs(p: ref Parser, lr: ref labrec, n: int): int +{ + l := p.labs; + for(i := 0; i < n; i++){ + if(hd l == lr) + return 1; + l = tl l; + } + return 0; +} + +findlab(p: ref Parser, s: string): ref labrec +{ + for(l := p.labs; l != nil; l = tl l) + if((hd l).s == s) + return hd l; + return nil; +} + +pushlab(p: ref Parser, s: string) +{ + if(findlab(p, s) != nil) + error(p, "duplicate labels"); + p.labs = ref labrec(s, 0) :: p.labs; +} + +poplab(p: ref Parser) +{ + p.labs = tl p.labs; +} + +itstmt(k: int): int +{ + return k == Lwhile || k == Ldo || k == Lfor; +} |
