diff --git a/src/addon/java/lua/addon/compile/CheckCode.java b/src/addon/java/lua/addon/compile/CheckCode.java new file mode 100644 index 00000000..9d59a4cf --- /dev/null +++ b/src/addon/java/lua/addon/compile/CheckCode.java @@ -0,0 +1,12 @@ +package lua.addon.compile; + +import lua.io.Proto; + +public class CheckCode { + + public static boolean checkcode(Proto f) { + // TODO: port logic from ldebug.c + return true; + } + +} diff --git a/src/addon/java/lua/addon/compile/Compiler.java b/src/addon/java/lua/addon/compile/Compiler.java new file mode 100644 index 00000000..644341b0 --- /dev/null +++ b/src/addon/java/lua/addon/compile/Compiler.java @@ -0,0 +1,66 @@ +package lua.addon.compile; + +import java.io.Reader; +import java.util.Hashtable; + +import lua.io.Proto; +import lua.value.LString; + +public class Compiler { + + public int nCcalls; + + public static Proto compile( Reader reader, String name ) { + Compiler compiler = new Compiler(); + return compiler.luaY_parser(reader, name); + } + + + Proto luaY_parser(Reader z, String name) { + LexState lexstate = new LexState(this, z); + FuncState funcstate = new FuncState(); + // lexstate.buff = buff; + lexstate.setinput( this, z, new LString(name) ); + lexstate.open_func(funcstate); + /* main func. is always vararg */ + funcstate.varargflags = LuaC.VARARG_ISVARARG; + funcstate.f.is_vararg = true; + funcstate.f.source = new LString("@"+name); + lexstate.next(); /* read first token */ + lexstate.chunk(); + lexstate.check(LexState.TK_EOS); + lexstate.close_func(); + LuaC._assert (funcstate.prev == null); + LuaC._assert (funcstate.f.nups == 0); + LuaC._assert (lexstate.fs == null); + return funcstate.f; + } + + + // these string utilities were originally + // part of the Lua C State object, so we have + // left them here for now until we deterimine + // what exact purpose they serve. + + // table of all strings -> TString mapping. + // this includes reserved words that must be identified + // during lexical analysis for the compiler to work at all. + // TODO: consider moving this to LexState and simplifying + // all the various "newstring()" like functions + Hashtable strings = new Hashtable(); + + public LString newTString(String s) { + LString t = (LString) strings.get(s); + if ( t == null ) + strings.put( s, t = new LString(s) ); + return t; + } + + public String pushfstring(String string) { + return string; + } + + public LString newlstr(char[] chars, int offset, int len) { + return newTString( new String(chars,offset,len) ); + } +} diff --git a/src/addon/java/lua/addon/compile/DumpState.java b/src/addon/java/lua/addon/compile/DumpState.java new file mode 100644 index 00000000..5c85f273 --- /dev/null +++ b/src/addon/java/lua/addon/compile/DumpState.java @@ -0,0 +1,174 @@ +package lua.addon.compile; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import lua.io.Proto; +import lua.value.LBoolean; +import lua.value.LNil; +import lua.value.LNumber; +import lua.value.LString; +import lua.value.LValue; + +public class DumpState { + + /** mark for precompiled code (`Lua') */ + public static final String LUA_SIGNATURE = "\033Lua"; + + /** for header of binary files -- this is Lua 5.1 */ + public static final int LUAC_VERSION = 0x51; + + /** for header of binary files -- this is the official format */ + public static final int LUAC_FORMAT = 0; + + /** size of header of binary files */ + public static final int LUAC_HEADERSIZE = 12; + + /** expected lua header bytes */ + private static final byte[] LUAC_HEADER_SIGNATURE = { '\033', 'L', 'u', 'a' }; + + // header fields + private static final int IS_LITTLE_ENDIAN = 0; + private static final int SIZEOF_INT = 4; + private static final int SIZEOF_SIZET = 4; + private static final int SIZEOF_INSTRUCTION = 4; + private static final int SIZEOF_LUA_NUMBER = 8; + private static final int IS_NUMBER_INTEGRAL = 0; + + // types of lua constants + private static final int LUA_TNIL = 0; + private static final int LUA_TBOOLEAN = 1; + private static final int LUA_TLIGHTUSERDATA = 2; + private static final int LUA_TNUMBER = 3; + private static final int LUA_TSTRING = 4; + private static final int LUA_TTABLE = 5; + private static final int LUA_TFUNCTION = 6; + private static final int LUA_TUSERDATA = 7; + private static final int LUA_TTHREAD = 8; + + DataOutputStream writer; + boolean strip; + int status; + + public DumpState(OutputStream w, boolean strip) { + this.writer = new DataOutputStream( w ); + this.strip = strip; + this.status = 0; + } + + void dumpBlock(final byte[] b, int size) throws IOException { + writer.write(b, 0, size); + } + + void dumpChar(int b) throws IOException { + writer.write( b ); + } + + void dumpInt(int x) throws IOException { + writer.writeInt(x); + } + + void dumpString(LString s) throws IOException { + final int len = s.length(); + dumpInt( len+1 ); + s.write( writer, 0, len ); + writer.write( 0 ); + } + + void dumpNumber(double d) throws IOException { + long l = Double.doubleToLongBits(d); + writer.writeLong(l); + } + + void dumpCode( final Proto f ) throws IOException { + int n = f.code.length; + dumpInt( n ); + for ( int i=0; i l ) + errorlimit( l, msg ); + } + + void errorlimit (int limit, String what) { + String msg = (f.linedefined == 0) ? + L.pushfstring("main function has more than "+limit+" "+what) : + L.pushfstring("function at line "+f.linedefined+" has more than "+limit+" "+what); + ls.lexerror(msg, 0); + } + + + int indexupvalue(LString name, expdesc v) { + int i; + for (i = 0; i < f.nups; i++) { + if (upvalues[i].k == v.k && upvalues[i].info == v.u.s.info) { + _assert(f.upvalues[i] == name); + return i; + } + } + /* new one */ + checklimit(f.nups + 1, LUAI_MAXUPVALUES, "upvalues"); + if ( f.upvalues == null || f.nups + 1 > f.upvalues.length) + f.upvalues = realloc( f.upvalues, f.nups*2+1 ); + f.upvalues[f.nups] = name; + _assert (v.k == LexState.VLOCAL || v.k == LexState.VUPVAL); + upvalues[f.nups] = new upvaldesc(); + upvalues[f.nups].k = (byte) (v.k); + upvalues[f.nups].info = (byte) (v.u.s.info); + return f.nups++; + } + + int searchvar(LString n) { + int i; + for (i = nactvar - 1; i >= 0; i--) { + if (n == getlocvar(i).varname) + return i; + } + return -1; /* not found */ + } + + void markupval(int level) { + BlockCnt bl = this.bl; + while (bl != null && bl.nactvar > level) + bl = bl.previous; + if (bl != null) + bl.upval = true; + } + + int singlevaraux(LString n, expdesc var, int base) { + int v = searchvar(n); /* look up at current level */ + if (v >= 0) { + var.init(LexState.VLOCAL, v); + if (base == 0) + markupval(v); /* local will be used as an upval */ + return LexState.VLOCAL; + } else { /* not found at current level; try upper one */ + if (prev == null) { /* no more levels? */ + /* default is global variable */ + var.init(LexState.VGLOBAL, NO_REG); + return LexState.VGLOBAL; + } + if (prev.singlevaraux(n, var, 0) == LexState.VGLOBAL) + return LexState.VGLOBAL; + var.u.s.info = indexupvalue(n, var); /* else was LOCAL or UPVAL */ + var.k = LexState.VUPVAL; /* upvalue in this level */ + return LexState.VUPVAL; + } + } + + void enterblock (BlockCnt bl, boolean isbreakable) { + bl.breaklist.i = LexState.NO_JUMP; + bl.isbreakable = isbreakable; + bl.nactvar = this.nactvar; + bl.upval = false; + bl.previous = this.bl; + this.bl = bl; + _assert(this.freereg == this.nactvar); + } + + // +// void leaveblock (FuncState *fs) { +// BlockCnt *bl = this.bl; +// this.bl = bl.previous; +// removevars(this.ls, bl.nactvar); +// if (bl.upval) +// this.codeABC(OP_CLOSE, bl.nactvar, 0, 0); +// /* a block either controls scope or breaks (never both) */ +// assert(!bl.isbreakable || !bl.upval); +// assert(bl.nactvar == this.nactvar); +// this.freereg = this.nactvar; /* free registers */ +// this.patchtohere(bl.breaklist); +// } + + void leaveblock() { + BlockCnt bl = this.bl; + this.bl = bl.previous; + ls.removevars(bl.nactvar); + if (bl.upval) + this.codeABC(OP_CLOSE, bl.nactvar, 0, 0); + /* a block either controls scope or breaks (never both) */ + _assert (!bl.isbreakable || !bl.upval); + _assert (bl.nactvar == this.nactvar); + this.freereg = this.nactvar; /* free registers */ + this.patchtohere(bl.breaklist.i); + } + + void closelistfield(ConsControl cc) { + if (cc.v.k == LexState.VVOID) + return; /* there is no list item */ + this.exp2nextreg(cc.v); + cc.v.k = LexState.VVOID; + if (cc.tostore == LFIELDS_PER_FLUSH) { + this.setlist(cc.t.u.s.info, cc.na, cc.tostore); /* flush */ + cc.tostore = 0; /* no more items pending */ + } + } + + boolean hasmultret(int k) { + return ((k) == LexState.VCALL || (k) == LexState.VVARARG); + } + + void lastlistfield (ConsControl cc) { + if (cc.tostore == 0) return; + if (hasmultret(cc.v.k)) { + this.setmultret(cc.v); + this.setlist(cc.t.u.s.info, cc.na, LUA_MULTRET); + cc.na--; /** do not count last expression (unknown number of elements) */ + } + else { + if (cc.v.k != LexState.VVOID) + this.exp2nextreg(cc.v); + this.setlist(cc.t.u.s.info, cc.na, cc.tostore); + } + } + + + + // ============================================================= + // from lcode.c + // ============================================================= + + void nil(int from, int n) { + InstructionPtr previous; + if (this.pc > this.lasttarget) { /* no jumps to current position? */ + if (this.pc == 0) { /* function start? */ + if (from >= this.nactvar) + return; /* positions are already clean */ + } else { + previous = new InstructionPtr(this.f.code, this.pc - 1); + if (GET_OPCODE(previous.get()) == OP_LOADNIL) { + int pfrom = GETARG_A(previous.get()); + int pto = GETARG_B(previous.get()); + if (pfrom <= from && from <= pto + 1) { /* can connect both? */ + if (from + n - 1 > pto) + SETARG_B(previous, from + n - 1); + return; + } + } + } + } + /* else no optimization */ + this.codeABC(OP_LOADNIL, from, from + n - 1, 0); + } + + + int jump() { + int jpc = this.jpc.i; /* save list of jumps to here */ + this.jpc.i = LexState.NO_JUMP; + IntPtr j = new IntPtr(this.codeAsBx(OP_JMP, 0, LexState.NO_JUMP)); + this.concat(j, jpc); /* keep them on hold */ + return j.i; + } + + void ret(int first, int nret) { + this.codeABC(OP_RETURN, first, nret + 1, 0); + } + + int condjump(int /* OpCode */op, int A, int B, int C) { + this.codeABC(op, A, B, C); + return this.jump(); + } + + void fixjump(int pc, int dest) { + InstructionPtr jmp = new InstructionPtr(this.f.code, pc); + int offset = dest - (pc + 1); + _assert (dest != LexState.NO_JUMP); + if (Math.abs(offset) > MAXARG_sBx) + ls.syntaxerror("control structure too long"); + SETARG_sBx(jmp, offset); + } + + + /* + * * returns current `pc' and marks it as a jump target (to avoid wrong * + * optimizations with consecutive instructions not in the same basic block). + */ + int getlabel() { + this.lasttarget = this.pc; + return this.pc; + } + + + int getjump(int pc) { + int offset = GETARG_sBx(this.f.code[pc]); + /* point to itself represents end of list */ + if (offset == LexState.NO_JUMP) + /* end of list */ + return LexState.NO_JUMP; + else + /* turn offset into absolute position */ + return (pc + 1) + offset; + } + + + InstructionPtr getjumpcontrol(int pc) { + InstructionPtr pi = new InstructionPtr(this.f.code, pc); + if (pc >= 1 && testTMode(GET_OPCODE(pi.code[pi.idx - 1]))) + return new InstructionPtr(pi.code, pi.idx - 1); + else + return pi; + } + + + /* + * * check whether list has any jump that do not produce a value * (or + * produce an inverted value) + */ + boolean need_value(int list) { + for (; list != LexState.NO_JUMP; list = this.getjump(list)) { + int i = this.getjumpcontrol(list).get(); + if (GET_OPCODE(i) != OP_TESTSET) + return true; + } + return false; /* not found */ + } + + + boolean patchtestreg(int node, int reg) { + InstructionPtr i = this.getjumpcontrol(node); + if (GET_OPCODE(i.get()) != OP_TESTSET) + /* cannot patch other instructions */ + return false; + if (reg != NO_REG && reg != GETARG_B(i.get())) + SETARG_A(i, reg); + else + /* no register to put value or register already has the value */ + i.set(CREATE_ABC(OP_TEST, GETARG_B(i.get()), 0, Lua.GETARG_C(i.get()))); + + return true; + } + + + void removevalues(int list) { + for (; list != LexState.NO_JUMP; list = this.getjump(list)) + this.patchtestreg(list, NO_REG); + } + + void patchlistaux(int list, int vtarget, int reg, int dtarget) { + while (list != LexState.NO_JUMP) { + int next = this.getjump(list); + if (this.patchtestreg(list, reg)) + this.fixjump(list, vtarget); + else + this.fixjump(list, dtarget); /* jump to default target */ + list = next; + } + } + + void dischargejpc() { + this.patchlistaux(this.jpc.i, this.pc, NO_REG, this.pc); + this.jpc.i = LexState.NO_JUMP; + } + + void patchlist(int list, int target) { + if (target == this.pc) + this.patchtohere(list); + else { + _assert (target < this.pc); + this.patchlistaux(list, target, NO_REG, target); + } + } + + void patchtohere(int list) { + this.getlabel(); + this.concat(this.jpc, list); + } + + void concat(IntPtr l1, int l2) { + if (l2 == LexState.NO_JUMP) + return; + if (l1.i == LexState.NO_JUMP) + l1.i = l2; + else { + int list = l1.i; + int next; + while ((next = this.getjump(list)) != LexState.NO_JUMP) + /* find last element */ + list = next; + this.fixjump(list, l2); + } + } + + void checkstack(int n) { + int newstack = this.freereg + n; + if (newstack > this.f.maxstacksize) { + if (newstack >= MAXSTACK) + ls.syntaxerror("function or expression too complex"); + this.f.maxstacksize = (byte) newstack; + } + } + + void reserveregs(int n) { + this.checkstack(n); + this.freereg += n; + } + + void freereg(int reg) { + if (!ISK(reg) && reg >= this.nactvar) { + this.freereg--; + _assert (reg == this.freereg); + } + } + + void freeexp(expdesc e) { + if (e.k == LexState.VNONRELOC) + this.freereg(e.u.s.info); + } + + int addk(LValue v) { + int idx; + if (this.htable.containsKey(v)) { + idx = ((Integer) htable.get(v)).intValue(); + } else { + idx = this.nk; + this.htable.put(v, new Integer(idx)); + final Proto f = this.f; + if (f.k == null || nk + 1 >= f.k.length) + f.k = realloc( f.k, nk*2 + 1 ); + f.k[this.nk++] = v; + } + return idx; + } + + int stringK(LString s) { + return this.addk(s); + } + + int numberK(LNumber r) { + if ( r instanceof LDouble ) { + double d = r.luaAsDouble(); + int i = (int) d; + if ( d == (double) i ) + r = new LInteger(i); + } + return this.addk(r); + } + + int boolK(boolean b) { + return this.addk((b ? LBoolean.TRUE : LBoolean.FALSE)); + } + + int nilK() { + return this.addk(LNil.NIL); + } + + void setreturns(expdesc e, int nresults) { + if (e.k == LexState.VCALL) { /* expression is an open function call? */ + SETARG_C(this.getcodePtr(e), nresults + 1); + } else if (e.k == LexState.VVARARG) { + SETARG_B(this.getcodePtr(e), nresults + 1); + SETARG_A(this.getcodePtr(e), this.freereg); + this.reserveregs(1); + } + } + + void setoneret(expdesc e) { + if (e.k == LexState.VCALL) { /* expression is an open function call? */ + e.k = LexState.VNONRELOC; + e.u.s.info = GETARG_A(this.getcode(e)); + } else if (e.k == LexState.VVARARG) { + SETARG_B(this.getcodePtr(e), 2); + e.k = LexState.VRELOCABLE; /* can relocate its simple result */ + } + } + + void dischargevars(expdesc e) { + switch (e.k) { + case LexState.VLOCAL: { + e.k = LexState.VNONRELOC; + break; + } + case LexState.VUPVAL: { + e.u.s.info = this.codeABC(OP_GETUPVAL, 0, e.u.s.info, 0); + e.k = LexState.VRELOCABLE; + break; + } + case LexState.VGLOBAL: { + e.u.s.info = this.codeABx(OP_GETGLOBAL, 0, e.u.s.info); + e.k = LexState.VRELOCABLE; + break; + } + case LexState.VINDEXED: { + this.freereg(e.u.s.aux); + this.freereg(e.u.s.info); + e.u.s.info = this + .codeABC(OP_GETTABLE, 0, e.u.s.info, e.u.s.aux); + e.k = LexState.VRELOCABLE; + break; + } + case LexState.VVARARG: + case LexState.VCALL: { + this.setoneret(e); + break; + } + default: + break; /* there is one value available (somewhere) */ + } + } + + int code_label(int A, int b, int jump) { + this.getlabel(); /* those instructions may be jump targets */ + return this.codeABC(OP_LOADBOOL, A, b, jump); + } + + void discharge2reg(expdesc e, int reg) { + this.dischargevars(e); + switch (e.k) { + case LexState.VNIL: { + this.nil(reg, 1); + break; + } + case LexState.VFALSE: + case LexState.VTRUE: { + this.codeABC(OP_LOADBOOL, reg, (e.k == LexState.VTRUE ? 1 : 0), + 0); + break; + } + case LexState.VK: { + this.codeABx(OP_LOADK, reg, e.u.s.info); + break; + } + case LexState.VKNUM: { + this.codeABx(OP_LOADK, reg, this.numberK(e.u.nval())); + break; + } + case LexState.VRELOCABLE: { + InstructionPtr pc = this.getcodePtr(e); + SETARG_A(pc, reg); + break; + } + case LexState.VNONRELOC: { + if (reg != e.u.s.info) + this.codeABC(OP_MOVE, reg, e.u.s.info, 0); + break; + } + default: { + _assert (e.k == LexState.VVOID || e.k == LexState.VJMP); + return; /* nothing to do... */ + } + } + e.u.s.info = reg; + e.k = LexState.VNONRELOC; + } + + void discharge2anyreg(expdesc e) { + if (e.k != LexState.VNONRELOC) { + this.reserveregs(1); + this.discharge2reg(e, this.freereg - 1); + } + } + + void exp2reg(expdesc e, int reg) { + this.discharge2reg(e, reg); + if (e.k == LexState.VJMP) + this.concat(e.t, e.u.s.info); /* put this jump in `t' list */ + if (e.hasjumps()) { + int _final; /* position after whole expression */ + int p_f = LexState.NO_JUMP; /* position of an eventual LOAD false */ + int p_t = LexState.NO_JUMP; /* position of an eventual LOAD true */ + if (this.need_value(e.t.i) || this.need_value(e.f.i)) { + int fj = (e.k == LexState.VJMP) ? LexState.NO_JUMP : this + .jump(); + p_f = this.code_label(reg, 0, 1); + p_t = this.code_label(reg, 1, 0); + this.patchtohere(fj); + } + _final = this.getlabel(); + this.patchlistaux(e.f.i, _final, reg, p_f); + this.patchlistaux(e.t.i, _final, reg, p_t); + } + e.f.i = e.t.i = LexState.NO_JUMP; + e.u.s.info = reg; + e.k = LexState.VNONRELOC; + } + + void exp2nextreg(expdesc e) { + this.dischargevars(e); + this.freeexp(e); + this.reserveregs(1); + this.exp2reg(e, this.freereg - 1); + } + + int exp2anyreg(expdesc e) { + this.dischargevars(e); + if (e.k == LexState.VNONRELOC) { + if (!e.hasjumps()) + return e.u.s.info; /* exp is already in a register */ + if (e.u.s.info >= this.nactvar) { /* reg. is not a local? */ + this.exp2reg(e, e.u.s.info); /* put value on it */ + return e.u.s.info; + } + } + this.exp2nextreg(e); /* default */ + return e.u.s.info; + } + + void exp2val(expdesc e) { + if (e.hasjumps()) + this.exp2anyreg(e); + else + this.dischargevars(e); + } + + int exp2RK(expdesc e) { + this.exp2val(e); + switch (e.k) { + case LexState.VKNUM: + case LexState.VTRUE: + case LexState.VFALSE: + case LexState.VNIL: { + if (this.nk <= MAXINDEXRK) { /* constant fit in RK operand? */ + e.u.s.info = (e.k == LexState.VNIL) ? this.nilK() + : (e.k == LexState.VKNUM) ? this.numberK(e.u.nval()) + : this.boolK((e.k == LexState.VTRUE)); + e.k = LexState.VK; + return RKASK(e.u.s.info); + } else + break; + } + case LexState.VK: { + if (e.u.s.info <= MAXINDEXRK) /* constant fit in argC? */ + return RKASK(e.u.s.info); + else + break; + } + default: + break; + } + /* not a constant in the right range: put it in a register */ + return this.exp2anyreg(e); + } + + void storevar(expdesc var, expdesc ex) { + switch (var.k) { + case LexState.VLOCAL: { + this.freeexp(ex); + this.exp2reg(ex, var.u.s.info); + return; + } + case LexState.VUPVAL: { + int e = this.exp2anyreg(ex); + this.codeABC(OP_SETUPVAL, e, var.u.s.info, 0); + break; + } + case LexState.VGLOBAL: { + int e = this.exp2anyreg(ex); + this.codeABx(OP_SETGLOBAL, e, var.u.s.info); + break; + } + case LexState.VINDEXED: { + int e = this.exp2RK(ex); + this.codeABC(OP_SETTABLE, var.u.s.info, var.u.s.aux, e); + break; + } + default: { + _assert (false); /* invalid var kind to store */ + break; + } + } + this.freeexp(ex); + } + + void self(expdesc e, expdesc key) { + int func; + this.exp2anyreg(e); + this.freeexp(e); + func = this.freereg; + this.reserveregs(2); + this.codeABC(OP_SELF, func, e.u.s.info, this.exp2RK(key)); + this.freeexp(key); + e.u.s.info = func; + e.k = LexState.VNONRELOC; + } + + void invertjump(expdesc e) { + InstructionPtr pc = this.getjumpcontrol(e.u.s.info); + _assert (testTMode(GET_OPCODE(pc.get())) + && GET_OPCODE(pc.get()) != OP_TESTSET && Lua + .GET_OPCODE(pc.get()) != OP_TEST); + // SETARG_A(pc, !(GETARG_A(pc.get()))); + int a = GETARG_A(pc.get()); + int nota = (a!=0? 0: 1); + SETARG_A(pc, nota); + } + + int jumponcond(expdesc e, int cond) { + if (e.k == LexState.VRELOCABLE) { + int ie = this.getcode(e); + if (GET_OPCODE(ie) == OP_NOT) { + this.pc--; /* remove previous OP_NOT */ + return this.condjump(OP_TEST, GETARG_B(ie), 0, (cond!=0? 0: 1)); + } + /* else go through */ + } + this.discharge2anyreg(e); + this.freeexp(e); + return this.condjump(OP_TESTSET, NO_REG, e.u.s.info, cond); + } + + void goiftrue(expdesc e) { + int pc; /* pc of last jump */ + this.dischargevars(e); + switch (e.k) { + case LexState.VK: + case LexState.VKNUM: + case LexState.VTRUE: { + pc = LexState.NO_JUMP; /* always true; do nothing */ + break; + } + case LexState.VFALSE: { + pc = this.jump(); /* always jump */ + break; + } + case LexState.VJMP: { + this.invertjump(e); + pc = e.u.s.info; + break; + } + default: { + pc = this.jumponcond(e, 0); + break; + } + } + this.concat(e.f, pc); /* insert last jump in `f' list */ + this.patchtohere(e.t.i); + e.t.i = LexState.NO_JUMP; + } + + void goiffalse(expdesc e) { + int pc; /* pc of last jump */ + this.dischargevars(e); + switch (e.k) { + case LexState.VNIL: + case LexState.VFALSE: { + pc = LexState.NO_JUMP; /* always false; do nothing */ + break; + } + case LexState.VTRUE: { + pc = this.jump(); /* always jump */ + break; + } + case LexState.VJMP: { + pc = e.u.s.info; + break; + } + default: { + pc = this.jumponcond(e, 1); + break; + } + } + this.concat(e.t, pc); /* insert last jump in `t' list */ + this.patchtohere(e.f.i); + e.f.i = LexState.NO_JUMP; + } + + void codenot(expdesc e) { + this.dischargevars(e); + switch (e.k) { + case LexState.VNIL: + case LexState.VFALSE: { + e.k = LexState.VTRUE; + break; + } + case LexState.VK: + case LexState.VKNUM: + case LexState.VTRUE: { + e.k = LexState.VFALSE; + break; + } + case LexState.VJMP: { + this.invertjump(e); + break; + } + case LexState.VRELOCABLE: + case LexState.VNONRELOC: { + this.discharge2anyreg(e); + this.freeexp(e); + e.u.s.info = this.codeABC(OP_NOT, 0, e.u.s.info, 0); + e.k = LexState.VRELOCABLE; + break; + } + default: { + _assert (false); /* cannot happen */ + break; + } + } + /* interchange true and false lists */ + { + int temp = e.f.i; + e.f.i = e.t.i; + e.t.i = temp; + } + this.removevalues(e.f.i); + this.removevalues(e.t.i); + } + + void indexed(expdesc t, expdesc k) { + t.u.s.aux = this.exp2RK(k); + t.k = LexState.VINDEXED; + } + + boolean constfolding(int op, expdesc e1, expdesc e2) { + LNumber v1, v2, r; + if (!e1.isnumeral() || !e2.isnumeral()) + return false; + v1 = e1.u.nval(); + v2 = e2.u.nval(); + switch (op) { + case OP_ADD: + case OP_SUB: + case OP_MUL: + case OP_DIV: + case OP_MOD: + r = (LNumber) v2.luaBinOpUnknown(op, v1); + break; + case OP_POW: + r = new LDouble( Math.pow(v1.luaAsDouble(), v2.luaAsDouble() ) ); + break; + case OP_UNM: + r = (LNumber) v1.luaUnaryMinus(); + break; + case OP_LEN: + return false; /* no constant folding for 'len' */ + default: + _assert (false); + r = null; + break; + } + if (Double.NaN == r.luaAsDouble()) + return false; /* do not attempt to produce NaN */ + e1.u.setNval( r ); + return true; + } + + void codearith(int op, expdesc e1, expdesc e2) { + if (constfolding(op, e1, e2)) + return; + else { + int o2 = (op != OP_UNM && op != OP_LEN) ? this.exp2RK(e2) + : 0; + int o1 = this.exp2RK(e1); + if (o1 > o2) { + this.freeexp(e1); + this.freeexp(e2); + } else { + this.freeexp(e2); + this.freeexp(e1); + } + e1.u.s.info = this.codeABC(op, 0, o1, o2); + e1.k = LexState.VRELOCABLE; + } + } + + void codecomp(int /* OpCode */op, int cond, expdesc e1, expdesc e2) { + int o1 = this.exp2RK(e1); + int o2 = this.exp2RK(e2); + this.freeexp(e2); + this.freeexp(e1); + if (cond == 0 && op != OP_EQ) { + int temp; /* exchange args to replace by `<' or `<=' */ + temp = o1; + o1 = o2; + o2 = temp; /* o1 <==> o2 */ + cond = 1; + } + e1.u.s.info = this.condjump(op, cond, o1, o2); + e1.k = LexState.VJMP; + } + + void prefix(int /* UnOpr */op, expdesc e) { + expdesc e2 = new expdesc(); + e2.init(LexState.VKNUM, 0); + switch (op) { + case LexState.OPR_MINUS: { + if (e.k == LexState.VK) + this.exp2anyreg(e); /* cannot operate on non-numeric constants */ + this.codearith(OP_UNM, e, e2); + break; + } + case LexState.OPR_NOT: + this.codenot(e); + break; + case LexState.OPR_LEN: { + this.exp2anyreg(e); /* cannot operate on constants */ + this.codearith(OP_LEN, e, e2); + break; + } + default: + _assert (false); + } + } + + void infix(int /* BinOpr */op, expdesc v) { + switch (op) { + case LexState.OPR_AND: { + this.goiftrue(v); + break; + } + case LexState.OPR_OR: { + this.goiffalse(v); + break; + } + case LexState.OPR_CONCAT: { + this.exp2nextreg(v); /* operand must be on the `stack' */ + break; + } + case LexState.OPR_ADD: + case LexState.OPR_SUB: + case LexState.OPR_MUL: + case LexState.OPR_DIV: + case LexState.OPR_MOD: + case LexState.OPR_POW: { + if (!v.isnumeral()) + this.exp2RK(v); + break; + } + default: { + this.exp2RK(v); + break; + } + } + } + + + void posfix(int op, expdesc e1, expdesc e2) { + switch (op) { + case LexState.OPR_AND: { + _assert (e1.t.i == LexState.NO_JUMP); /* list must be closed */ + this.dischargevars(e2); + this.concat(e2.f, e1.f.i); + // *e1 = *e2; + e1.setvalue(e2); + break; + } + case LexState.OPR_OR: { + _assert (e1.f.i == LexState.NO_JUMP); /* list must be closed */ + this.dischargevars(e2); + this.concat(e2.t, e1.t.i); + // *e1 = *e2; + e1.setvalue(e2); + break; + } + case LexState.OPR_CONCAT: { + this.exp2val(e2); + if (e2.k == LexState.VRELOCABLE + && GET_OPCODE(this.getcode(e2)) == OP_CONCAT) { + _assert (e1.u.s.info == GETARG_B(this.getcode(e2)) - 1); + this.freeexp(e1); + SETARG_B(this.getcodePtr(e2), e1.u.s.info); + e1.k = LexState.VRELOCABLE; + e1.u.s.info = e2.u.s.info; + } else { + this.exp2nextreg(e2); /* operand must be on the 'stack' */ + this.codearith(OP_CONCAT, e1, e2); + } + break; + } + case LexState.OPR_ADD: + this.codearith(OP_ADD, e1, e2); + break; + case LexState.OPR_SUB: + this.codearith(OP_SUB, e1, e2); + break; + case LexState.OPR_MUL: + this.codearith(OP_MUL, e1, e2); + break; + case LexState.OPR_DIV: + this.codearith(OP_DIV, e1, e2); + break; + case LexState.OPR_MOD: + this.codearith(OP_MOD, e1, e2); + break; + case LexState.OPR_POW: + this.codearith(OP_POW, e1, e2); + break; + case LexState.OPR_EQ: + this.codecomp(OP_EQ, 1, e1, e2); + break; + case LexState.OPR_NE: + this.codecomp(OP_EQ, 0, e1, e2); + break; + case LexState.OPR_LT: + this.codecomp(OP_LT, 1, e1, e2); + break; + case LexState.OPR_LE: + this.codecomp(OP_LE, 1, e1, e2); + break; + case LexState.OPR_GT: + this.codecomp(OP_LT, 0, e1, e2); + break; + case LexState.OPR_GE: + this.codecomp(OP_LE, 0, e1, e2); + break; + default: + _assert (false); + } + } + + + void fixline(int line) { + this.f.lineinfo[this.pc - 1] = line; + } + + + int code(int instruction, int line) { + Proto f = this.f; + this.dischargejpc(); /* `pc' will change */ + /* put new instruction in code array */ + if (f.code == null || this.pc + 1 > f.code.length) + f.code = LexState.realloc(f.code, this.pc * 2 + 1); + f.code[this.pc] = instruction; + /* save corresponding line information */ + if (f.lineinfo == null || this.pc + 1 > f.lineinfo.length) + f.lineinfo = LexState.realloc(f.lineinfo, + this.pc * 2 + 1); + f.lineinfo[this.pc] = line; + return this.pc++; + } + + + int codeABC(int o, int a, int b, int c) { + _assert (getOpMode(o) == iABC); + _assert (getBMode(o) != OpArgN || b == 0); + _assert (getCMode(o) != OpArgN || c == 0); + return this.code(CREATE_ABC(o, a, b, c), this.ls.lastline); + } + + + int codeABx(int o, int a, int bc) { + _assert (getOpMode(o) == iABx || getOpMode(o) == iAsBx); + _assert (getCMode(o) == OpArgN); + return this.code(CREATE_ABx(o, a, bc), this.ls.lastline); + } + + + void setlist(int base, int nelems, int tostore) { + int c = (nelems - 1) / LFIELDS_PER_FLUSH + 1; + int b = (tostore == LUA_MULTRET) ? 0 : tostore; + _assert (tostore != 0); + if (c <= MAXARG_C) + this.codeABC(OP_SETLIST, base, b, c); + else { + this.codeABC(OP_SETLIST, base, b, 0); + this.code(c, this.ls.lastline); + } + this.freereg = base + 1; /* free registers with list values */ + } + +} diff --git a/src/addon/java/lua/addon/compile/InstructionPtr.java b/src/addon/java/lua/addon/compile/InstructionPtr.java new file mode 100644 index 00000000..23ef70dc --- /dev/null +++ b/src/addon/java/lua/addon/compile/InstructionPtr.java @@ -0,0 +1,17 @@ + +package lua.addon.compile; + +class InstructionPtr { + final int[] code; + final int idx; + InstructionPtr(int[] code, int idx ) { + this.code = code; + this.idx = idx; + } + int get() { + return code[idx]; + } + void set(int value) { + code[idx] = value; + } +} \ No newline at end of file diff --git a/src/addon/java/lua/addon/compile/IntPtr.java b/src/addon/java/lua/addon/compile/IntPtr.java new file mode 100644 index 00000000..5157cf9c --- /dev/null +++ b/src/addon/java/lua/addon/compile/IntPtr.java @@ -0,0 +1,10 @@ +package lua.addon.compile; + +public class IntPtr { + int i; + IntPtr() { + } + IntPtr(int value) { + this.i = value; + } +} diff --git a/src/addon/java/lua/addon/compile/LexState.java b/src/addon/java/lua/addon/compile/LexState.java new file mode 100644 index 00000000..4d2173ae --- /dev/null +++ b/src/addon/java/lua/addon/compile/LexState.java @@ -0,0 +1,1858 @@ +package lua.addon.compile; + +import java.io.IOException; +import java.io.Reader; +import java.text.NumberFormat; +import java.text.ParseException; +import java.util.Hashtable; + +import lua.Lua; +import lua.addon.compile.FuncState.BlockCnt; +import lua.io.LocVars; +import lua.io.Proto; +import lua.value.LDouble; +import lua.value.LInteger; +import lua.value.LNumber; +import lua.value.LString; + +public class LexState extends LuaC { + + private static final int EOZ = (-1); + private static final int MAXSRC = 80; + private static final int MAX_INT = Integer.MAX_VALUE-2; + private static final int UCHAR_MAX = 255; // TODO, convert to unicode CHAR_MAX? + private static final int LUAI_MAXCCALLS = 200; + + private static final String LUA_QS(String s) { return "'"+s+"'"; } + private static final String LUA_QL(Object o) { return LUA_QS(String.valueOf(o)); } + + + private static final int LUA_COMPAT_LSTR = 1; // 1 for compatibility, 2 for old behavior + private static final boolean LUA_COMPAT_VARARG = true; + + + + /* + ** Marks the end of a patch list. It is an invalid value both as an absolute + ** address, and as a list link (would link an element to itself). + */ + static final int NO_JUMP = (-1); + + /* + ** grep "ORDER OPR" if you change these enums + */ + static final int + OPR_ADD=0, OPR_SUB=1, OPR_MUL=2, OPR_DIV=3, OPR_MOD=4, OPR_POW=5, + OPR_CONCAT=6, + OPR_NE=7, OPR_EQ=8, + OPR_LT=9, OPR_LE=10, OPR_GT=11, OPR_GE=12, + OPR_AND=13, OPR_OR=14, + OPR_NOBINOPR=15; + + static final int + OPR_MINUS=0, OPR_NOT=1, OPR_LEN=2, OPR_NOUNOPR=3; + + /* exp kind */ + static final int + VVOID = 0, /* no value */ + VNIL = 1, + VTRUE = 2, + VFALSE = 3, + VK = 4, /* info = index of constant in `k' */ + VKNUM = 5, /* nval = numerical value */ + VLOCAL = 6, /* info = local register */ + VUPVAL = 7, /* info = index of upvalue in `upvalues' */ + VGLOBAL = 8, /* info = index of table, aux = index of global name in `k' */ + VINDEXED = 9, /* info = table register, aux = index register (or `k') */ + VJMP = 10, /* info = instruction pc */ + VRELOCABLE = 11, /* info = instruction pc */ + VNONRELOC = 12, /* info = result register */ + VCALL = 13, /* info = instruction pc */ + VVARARG = 14; /* info = instruction pc */ + + /* semantics information */ + private static class SemInfo { + LNumber r; + LString ts; + }; + + private static class Token { + int token; + final SemInfo seminfo = new SemInfo(); + public void set(Token other) { + this.token = other.token; + this.seminfo.r = other.seminfo.r; + this.seminfo.ts = other.seminfo.ts; + } + }; + + int current; /* current character (charint) */ + int linenumber; /* input line counter */ + int lastline; /* line of last token `consumed' */ + final Token t = new Token(); /* current token */ + final Token lookahead = new Token(); /* look ahead token */ + FuncState fs; /* `FuncState' is private to the parser */ + Compiler L; + Reader z; /* input stream */ + char[] buff; /* buffer for tokens */ + int nbuff; /* length of buffer */ + LString source; /* current source name */ + char decpoint; /* locale decimal point */ + + /* ORDER RESERVED */ + final static String luaX_tokens [] = { + "and", "break", "do", "else", "elseif", + "end", "false", "for", "function", "if", + "in", "local", "nil", "not", "or", "repeat", + "return", "then", "true", "until", "while", + "..", "...", "==", ">=", "<=", "~=", + "", "", "", "", + }; + + final static int + /* terminal symbols denoted by reserved words */ + TK_AND=257, TK_BREAK=258, TK_DO=259, TK_ELSE=260, TK_ELSEIF=261, + TK_END=262, TK_FALSE=263, TK_FOR=264, TK_FUNCTION=265, TK_IF=266, + TK_IN=267, TK_LOCAL=268, TK_NIL=269, TK_NOT=270, TK_OR=271, TK_REPEAT=272, + TK_RETURN=273, TK_THEN=274, TK_TRUE=275, TK_UNTIL=276, TK_WHILE=277, + /* other terminal symbols */ + TK_CONCAT=278, TK_DOTS=279, TK_EQ=280, TK_GE=281, TK_LE=282, TK_NE=283, + TK_NUMBER=284, TK_NAME=285, TK_STRING=286, TK_EOS=287; + + final static int FIRST_RESERVED = TK_AND; + final static int NUM_RESERVED = TK_WHILE+1-FIRST_RESERVED; + + final static Hashtable RESERVED = new Hashtable(); + static { + for ( int i=0; i buff.length ) + buff = realloc( buff, nbuff*2+1 ); + buff[nbuff++] = (char) c; + } + + + String token2str( int token ) { + if ( token < FIRST_RESERVED ) { + return iscntrl(token)? + L.pushfstring( "char("+((int)token)+")" ): + L.pushfstring( String.valueOf( (char) token ) ); + } else { + return luaX_tokens[token-FIRST_RESERVED]; + } + } + + private static boolean iscntrl(int token) { + return token < ' '; + } + + String txtToken(int token) { + switch ( token ) { + case TK_NAME: + case TK_STRING: + case TK_NUMBER: + return new String( buff, 0, nbuff ); + default: + return token2str( token ); + } + } + + void lexerror( String msg, int token ) { + String cid = chunkid( source.toString() ); // TODO: get source name from source + L.pushfstring( cid+":"+linenumber+": "+msg ); + if ( token != 0 ) + L.pushfstring( "syntax error: "+msg+" near "+txtToken(token) ); + throw new RuntimeException(cid+":"+linenumber+": "+msg); + } + + String chunkid( String source ) { + if ( source.startsWith("=") ) + return source.substring(1); + String end = ""; + if ( source.startsWith("@") ) { + source = source.substring(1); + } else { + source = "[string \""+source; + end = "\"]"; + } + int n = source.length() + end.length(); + if ( n > MAXSRC ) + source = source.substring(0,MAXSRC-end.length()-3) + "..."; + return source + end; + } + + void syntaxerror( String msg ) { + lexerror( msg, t.token ); + } + + LString newstring( char[] chars, int offset, int len) { + return newstring( new String(chars, offset, len) ); + } + + LString newstring( String s ) { + return L.newTString( s ); + } + + void inclinenumber() { + int old = current; + _assert( currIsNewline() ); + nextChar(); /* skip '\n' or '\r' */ + if ( currIsNewline() && current != old ) + nextChar(); /* skip '\n\r' or '\r\n' */ + if ( ++linenumber >= MAX_INT ) + syntaxerror("chunk has too many lines"); + } + + void setinput( Compiler L, Reader z, LString source ) { + this.decpoint = '.'; + this.L = L; + this.lookahead.token = TK_EOS; /* no look-ahead token */ + this.z = z; + this.fs = null; + this.linenumber = 1; + this.lastline = 1; + this.source = source; + this.nbuff = 0; /* initialize buffer */ + this.nextChar(); /* read first char */ + this.skipShebang(); + } + + private void skipShebang() { + if ( current == '#' ) + while (!currIsNewline() && current != EOZ) + nextChar(); + } + + + + /* + ** ======================================================= + ** LEXICAL ANALYZER + ** ======================================================= + */ + + + boolean check_next(String set) { + if (set.indexOf(current) < 0) + return false; + save_and_next(); + return true; + } + + void buffreplace(char from, char to) { + int n = nbuff; + char[] p = buff; + while ((--n) >= 0) + if (p[n] == from) + p[n] = to; + } + + boolean str2d(String str, SemInfo seminfo) { + try { + double d = Double.parseDouble(str); + seminfo.r = new LDouble(d); + return true; + } catch (NumberFormatException e) { + e.printStackTrace(); + return false; + } + } + + // + // TODO: reexamine this source and see if it should be ported differently + // + // static void trydecpoint (LexState *ls, SemInfo *seminfo) { + // /* format error: try to update decimal point separator */ + // struct lconv *cv = localeconv(); + // char old = this.decpoint; + // this.decpoint = (cv ? cv->decimal_point[0] : '.'); + // buffreplace(ls, old, this.decpoint); /* try updated decimal separator */ + // if (!luaO_str2d(luaZ_buffer(this.buff), &seminfo->r)) { + // /* format error with correct decimal point: no more options */ + // buffreplace(ls, this.decpoint, '.'); /* undo change (for error message) */ + // luaX_lexerror(ls, "malformed number", TK_NUMBER); + // } + // } + // + void trydecpoint(String str, SemInfo seminfo) { + NumberFormat nf = NumberFormat.getInstance(); + try { + Number n = nf.parse(str); + double d = n.doubleValue(); + seminfo.r = new LDouble(d); + } catch (ParseException e) { + lexerror("malformed number", TK_NUMBER); + } + } + + void read_numeral(SemInfo seminfo) { + _assert (isdigit(current)); + do { + save_and_next(); + } while (isdigit(current) || current == '.'); + if (check_next("Ee")) /* `E'? */ + check_next("+-"); /* optional exponent sign */ + while (isalnum(current) || current == '_') + save_and_next(); + save('\0'); + buffreplace('.', decpoint); /* follow locale for decimal point */ + String str = new String(buff, 0, nbuff); + if (!str2d(str, seminfo)) /* format error? */ + trydecpoint(str, seminfo); /* try to update decimal point separator */ + } + + int skip_sep() { + int count = 0; + int s = current; + _assert (s == '[' || s == ']'); + save_and_next(); + while (current == '=') { + save_and_next(); + count++; + } + return (current == s) ? count : (-count) - 1; + } + + void read_long_string(SemInfo seminfo, int sep) { + int cont = 0; + save_and_next(); /* skip 2nd `[' */ + if (currIsNewline()) /* string starts with a newline? */ + inclinenumber(); /* skip it */ + for (boolean endloop = false; !endloop;) { + switch (current) { + case EOZ: + lexerror((seminfo != null) ? "unfinished long string" + : "unfinished long comment", TK_EOS); + break; /* to avoid warnings */ + case '[': { + if (skip_sep() == sep) { + save_and_next(); /* skip 2nd `[' */ + cont++; + if (LUA_COMPAT_LSTR == 1) { + if (sep == 0) + lexerror("nesting of [[...]] is deprecated", '['); + } + } + break; + } + case ']': { + if (skip_sep() == sep) { + save_and_next(); /* skip 2nd `]' */ + if (LUA_COMPAT_LSTR == 2) { + cont--; + if (sep == 0 && cont >= 0) + break; + } + endloop = true; + } + break; + } + case '\n': + case '\r': { + save('\n'); + inclinenumber(); + if (seminfo == null) + nbuff = 0; /* avoid wasting space */ + break; + } + default: { + if (seminfo != null) + save_and_next(); + else + nextChar(); + } + } + } + if (seminfo != null) + seminfo.ts = newstring(buff, 2 + sep, nbuff - 2 * (2 + sep)); + } + + void read_string(int del, SemInfo seminfo) { + save_and_next(); + while (current != del) { + switch (current) { + case EOZ: + lexerror("unfinished string", TK_EOS); + continue; /* to avoid warnings */ + case '\n': + case '\r': + lexerror("unfinished string", TK_STRING); + continue; /* to avoid warnings */ + case '\\': { + int c; + nextChar(); /* do not save the `\' */ + switch (current) { + case 'a': /* bell */ + c = '\u0007'; + break; + case 'b': /* backspace */ + c = '\b'; + break; + case 'f': /* form feed */ + c = '\f'; + break; + case 'n': /* newline */ + c = '\n'; + break; + case 'r': /* carriage return */ + c = '\r'; + break; + case 't': /* tab */ + c = '\t'; + break; + case 'v': /* vertical tab */ + c = '\u000B'; + break; + case '\n': /* go through */ + case '\r': + save('\n'); + inclinenumber(); + continue; + case EOZ: + continue; /* will raise an error next loop */ + default: { + if (!isdigit(current)) + save_and_next(); /* handles \\, \", \', and \? */ + else { /* \xxx */ + int i = 0; + c = 0; + do { + c = 10 * c + (current - '0'); + nextChar(); + } while (++i < 3 && isdigit(current)); + if (c > UCHAR_MAX) + lexerror("escape sequence too large", TK_STRING); + save(c); + } + continue; + } + } + save(c); + nextChar(); + continue; + } + default: + save_and_next(); + } + } + save_and_next(); /* skip delimiter */ + seminfo.ts = newstring(buff, 1, nbuff - 2); + } + + int llex(SemInfo seminfo) { + nbuff = 0; + while (true) { + switch (current) { + case '\n': + case '\r': { + inclinenumber(); + continue; + } + case '-': { + nextChar(); + if (current != '-') + return '-'; + /* else is a comment */ + nextChar(); + if (current == '[') { + int sep = skip_sep(); + nbuff = 0; /* `skip_sep' may dirty the buffer */ + if (sep >= 0) { + read_long_string(null, sep); /* long comment */ + nbuff = 0; + continue; + } + } + /* else short comment */ + while (!currIsNewline() && current != EOZ) + nextChar(); + continue; + } + case '[': { + int sep = skip_sep(); + if (sep >= 0) { + read_long_string(seminfo, sep); + return TK_STRING; + } else if (sep == -1) + return '['; + else + lexerror("invalid long string delimiter", TK_STRING); + } + case '=': { + nextChar(); + if (current != '=') + return '='; + else { + nextChar(); + return TK_EQ; + } + } + case '<': { + nextChar(); + if (current != '=') + return '<'; + else { + nextChar(); + return TK_LE; + } + } + case '>': { + nextChar(); + if (current != '=') + return '>'; + else { + nextChar(); + return TK_GE; + } + } + case '~': { + nextChar(); + if (current != '=') + return '~'; + else { + nextChar(); + return TK_NE; + } + } + case '"': + case '\'': { + read_string(current, seminfo); + return TK_STRING; + } + case '.': { + save_and_next(); + if (check_next(".")) { + if (check_next(".")) + return TK_DOTS; /* ... */ + else + return TK_CONCAT; /* .. */ + } else if (!isdigit(current)) + return '.'; + else { + read_numeral(seminfo); + return TK_NUMBER; + } + } + case EOZ: { + return TK_EOS; + } + default: { + if (isspace(current)) { + _assert (!currIsNewline()); + nextChar(); + continue; + } else if (isdigit(current)) { + read_numeral(seminfo); + return TK_NUMBER; + } else if (isalpha(current) || current == '_') { + /* identifier or reserved word */ + LString ts; + do { + save_and_next(); + } while (isalnum(current) || current == '_'); + ts = newstring(buff, 0, nbuff); + if ( RESERVED.containsKey(ts) ) + return ((Integer)RESERVED.get(ts)).intValue(); + else { + seminfo.ts = ts; + return TK_NAME; + } + } else { + int c = current; + nextChar(); + return c; /* single-char tokens (+ - / ...) */ + } + } + } + } + } + + void next() { + lastline = linenumber; + if (lookahead.token != TK_EOS) { /* is there a look-ahead token? */ + t.set( lookahead ); /* use this one */ + lookahead.token = TK_EOS; /* and discharge it */ + } else + t.token = llex(t.seminfo); /* read next token */ + } + + void lookahead() { + _assert (lookahead.token == TK_EOS); + lookahead.token = llex(lookahead.seminfo); + } + + // ============================================================= + // from lcode.h + // ============================================================= + + + // ============================================================= + // from lparser.c + // ============================================================= + + static class expdesc { + int k; // expkind, from enumerated list, above + static class U { // originally a union + static class S { + int info, aux; + } + final S s = new S(); + private LNumber _nval; + public void setNval(LNumber r) { + _nval = r; + } + public LNumber nval() { + return (_nval == null? new LInteger(s.info): _nval); + } + }; + final U u = new U(); + final IntPtr t = new IntPtr(); /* patch list of `exit when true' */ + final IntPtr f = new IntPtr(); /* patch list of `exit when false' */ + void init( int k, int i ) { + this.f.i = NO_JUMP; + this.t.i = NO_JUMP; + this.k = k; + this.u.s.info = i; + } + + boolean hasjumps() { + return (t.i != f.i); + } + + boolean isnumeral() { + return (k == VKNUM && t.i == NO_JUMP && f.i == NO_JUMP); + } + + public void setvalue(expdesc other) { + this.k = other.k; + this.u._nval = other.u._nval; + this.u.s.info = other.u.s.info; + this.u.s.aux = other.u.s.aux; + this.t.i = other.t.i; + this.f.i = other.f.i; + } + } + + boolean hasmultret(int k) { + return ((k) == VCALL || (k) == VVARARG); + } + + /*---------------------------------------------------------------------- + name args description + ------------------------------------------------------------------------*/ + + /* + * * prototypes for recursive non-terminal functions + */ + + void error_expected(int token) { + syntaxerror(L.pushfstring(LUA_QS(token2str(token)) + " expected")); + } + + boolean testnext(int c) { + if (t.token == c) { + next(); + return true; + } else + return false; + } + + void check(int c) { + if (t.token != c) + error_expected(c); + } + + void checknext (int c) { + check(c); + next(); + } + + void check_condition(boolean c, String msg) { + if (!(c)) + syntaxerror(msg); + } + + + void check_match(int what, int who, int where) { + if (!testnext(what)) { + if (where == linenumber) + error_expected(what); + else { + syntaxerror(L.pushfstring(LUA_QS(token2str(what)) + + " expected " + "(to close " + LUA_QS(token2str(who)) + + " at line " + where + ")")); + } + } + } + + LString str_checkname() { + LString ts; + check(TK_NAME); + ts = t.seminfo.ts; + next(); + return ts; + } + + void codestring(expdesc e, LString s) { + e.init(VK, fs.stringK(s)); + } + + void checkname(expdesc e) { + codestring(e, str_checkname()); + } + + + int registerlocalvar(LString varname) { + FuncState fs = this.fs; + Proto f = fs.f; + if (f.locvars == null || fs.nlocvars + 1 > f.locvars.length) + f.locvars = realloc( f.locvars, fs.nlocvars*2+1 ); + f.locvars[fs.nlocvars] = new LocVars(varname,0,0); + return fs.nlocvars++; + } + + +// +// #define new_localvarliteral(ls,v,n) \ +// this.new_localvar(luaX_newstring(ls, "" v, (sizeof(v)/sizeof(char))-1), n) +// + void new_localvarliteral(String v, int n) { + LString ts = newstring(v); + new_localvar(ts, n); + } + + void new_localvar(LString name, int n) { + FuncState fs = this.fs; + fs.checklimit(fs.nactvar + n + 1, FuncState.LUAI_MAXVARS, "local variables"); + fs.actvar[fs.nactvar + n] = (short) registerlocalvar(name); + } + + void adjustlocalvars(int nvars) { + FuncState fs = this.fs; + fs.nactvar = (byte) (fs.nactvar + nvars); + for (; nvars > 0; nvars--) { + fs.getlocvar(fs.nactvar - nvars).startpc = fs.pc; + } + } + + void removevars(int tolevel) { + FuncState fs = this.fs; + while (fs.nactvar > tolevel) + fs.getlocvar(--fs.nactvar).endpc = fs.pc; + } + + void singlevar(expdesc var) { + LString varname = this.str_checkname(); + FuncState fs = this.fs; + if (fs.singlevaraux(varname, var, 1) == VGLOBAL) + var.u.s.info = fs.stringK(varname); /* info points to global name */ + } + + void adjust_assign(int nvars, int nexps, expdesc e) { + FuncState fs = this.fs; + int extra = nvars - nexps; + if (hasmultret(e.k)) { + /* includes call itself */ + extra++; + if (extra < 0) + extra = 0; + /* last exp. provides the difference */ + fs.setreturns(e, extra); + if (extra > 1) + fs.reserveregs(extra - 1); + } else { + /* close last expression */ + if (e.k != VVOID) + fs.exp2nextreg(e); + if (extra > 0) { + int reg = fs.freereg; + fs.reserveregs(extra); + fs.nil(reg, extra); + } + } + } + + void enterlevel() { + if (++L.nCcalls > LUAI_MAXCCALLS) + lexerror("chunk has too many syntax levels", 0); + } + + void leavelevel() { + L.nCcalls--; + } + + void pushclosure(FuncState func, expdesc v) { + FuncState fs = this.fs; + Proto f = fs.f; + if (f.p == null || fs.np + 1 > f.p.length) + f.p = realloc( f.p, fs.np*2 + 1 ); + f.p[fs.np++] = func.f; + v.init(VRELOCABLE, fs.codeABx(Lua.OP_CLOSURE, 0, fs.np - 1)); + for (int i = 0; i < func.f.nups; i++) { + int o = (func.upvalues[i].k == VLOCAL) ? Lua.OP_MOVE + : Lua.OP_GETUPVAL; + fs.codeABC(o, 0, func.upvalues[i].info, 0); + } + } + + void open_func (FuncState fs) { + Compiler L = this.L; + Proto f = new Proto(); + if ( this.fs!=null ) + f.source = this.fs.f.source; + fs.f = f; + fs.prev = this.fs; /* linked list of funcstates */ + fs.ls = this; + fs.L = L; + this.fs = fs; + fs.pc = 0; + fs.lasttarget = -1; + fs.jpc = new IntPtr( NO_JUMP ); + fs.freereg = 0; + fs.nk = 0; + fs.np = 0; + fs.nlocvars = 0; + fs.nactvar = 0; + fs.bl = null; + f.maxstacksize = 2; /* registers 0/1 are always valid */ + //fs.h = new LTable(); + fs.htable = new Hashtable(); + fs.varargflags = 0; + } + + void close_func() { + Compiler L = this.L; + FuncState fs = this.fs; + Proto f = fs.f; + this.removevars(0); + fs.ret(0, 0); /* final return */ + f.code = realloc(f.code, fs.pc); + f.lineinfo = realloc(f.lineinfo, fs.pc); + // f.sizelineinfo = fs.pc; + f.k = realloc(f.k, fs.nk); + f.p = realloc(f.p, fs.np); + f.locvars = realloc(f.locvars, fs.nlocvars); + // f.sizelocvars = fs.nlocvars; + f.upvalues = realloc(f.upvalues, f.nups); + _assert (CheckCode.checkcode(f)); + _assert (fs.bl == null); + this.fs = fs.prev; +// L.top -= 2; /* remove table and prototype from the stack */ + // /* last token read was anchored in defunct function; must reanchor it + // */ + // if (fs!=null) ls.anchor_token(); + } + + /*============================================================*/ + /* GRAMMAR RULES */ + /*============================================================*/ + + void field(expdesc v) { + /* field -> ['.' | ':'] NAME */ + FuncState fs = this.fs; + expdesc key = new expdesc(); + fs.exp2anyreg(v); + this.next(); /* skip the dot or colon */ + this.checkname(key); + fs.indexed(v, key); + } + + void yindex(expdesc v) { + /* index -> '[' expr ']' */ + this.next(); /* skip the '[' */ + this.expr(v); + this.fs.exp2val(v); + this.checknext(']'); + } + + + /* + ** {====================================================================== + ** Rules for Constructors + ** ======================================================================= + */ + + + static class ConsControl { + expdesc v = new expdesc(); /* last list item read */ + expdesc t; /* table descriptor */ + int nh; /* total number of `record' elements */ + int na; /* total number of array elements */ + int tostore; /* number of array elements pending to be stored */ + }; + + + void recfield(ConsControl cc) { + /* recfield -> (NAME | `['exp1`]') = exp1 */ + FuncState fs = this.fs; + int reg = this.fs.freereg; + expdesc key = new expdesc(); + expdesc val = new expdesc(); + int rkkey; + if (this.t.token == TK_NAME) { + fs.checklimit(cc.nh, MAX_INT, "items in a constructor"); + this.checkname(key); + } else + /* this.t.token == '[' */ + this.yindex(key); + cc.nh++; + this.checknext('='); + rkkey = fs.exp2RK(key); + this.expr(val); + fs.codeABC(Lua.OP_SETTABLE, cc.t.u.s.info, rkkey, fs.exp2RK(val)); + fs.freereg = reg; /* free registers */ + } + + void listfield (ConsControl cc) { + this.expr(cc.v); + fs.checklimit(cc.na, MAX_INT, "items in a constructor"); + cc.na++; + cc.tostore++; + } + + + void constructor(expdesc t) { + /* constructor -> ?? */ + FuncState fs = this.fs; + int line = this.linenumber; + int pc = fs.codeABC(Lua.OP_NEWTABLE, 0, 0, 0); + ConsControl cc = new ConsControl(); + cc.na = cc.nh = cc.tostore = 0; + cc.t = t; + t.init(VRELOCABLE, pc); + cc.v.init(VVOID, 0); /* no value (yet) */ + fs.exp2nextreg(t); /* fix it at stack top (for gc) */ + this.checknext('{'); + do { + _assert (cc.v.k == VVOID || cc.tostore > 0); + if (this.t.token == '}') + break; + fs.closelistfield(cc); + switch (this.t.token) { + case TK_NAME: { /* may be listfields or recfields */ + this.lookahead(); + if (this.lookahead.token != '=') /* expression? */ + this.listfield(cc); + else + this.recfield(cc); + break; + } + case '[': { /* constructor_item -> recfield */ + this.recfield(cc); + break; + } + default: { /* constructor_part -> listfield */ + this.listfield(cc); + break; + } + } + } while (this.testnext(',') || this.testnext(';')); + this.check_match('}', '{', line); + fs.lastlistfield(cc); + InstructionPtr i = new InstructionPtr(fs.f.code, pc); + SETARG_B(i, luaO_int2fb(cc.na)); /* set initial array size */ + SETARG_C(i, luaO_int2fb(cc.nh)); /* set initial table size */ + } + + /* + ** converts an integer to a "floating point byte", represented as + ** (eeeeexxx), where the real value is (1xxx) * 2^(eeeee - 1) if + ** eeeee != 0 and (xxx) otherwise. + */ + static int luaO_int2fb (int x) { + int e = 0; /* expoent */ + while (x >= 16) { + x = (x+1) >> 1; + e++; + } + if (x < 8) return x; + else return ((e+1) << 3) | (((int)x) - 8); + } + + + /* }====================================================================== */ + + void parlist () { + /* parlist -> [ param { `,' param } ] */ + FuncState fs = this.fs; + Proto f = fs.f; + int nparams = 0; + f.is_vararg = false; + if (this.t.token != ')') { /* is `parlist' not empty? */ + do { + switch (this.t.token) { + case TK_NAME: { /* param . NAME */ + this.new_localvar(this.str_checkname(), nparams++); + break; + } + case TK_DOTS: { /* param . `...' */ + this.next(); + if (LUA_COMPAT_VARARG) { + /* use `arg' as default name */ + this.new_localvarliteral("arg", nparams++); + // f.is_vararg = VARARG_HASARG | VARARG_NEEDSARG; + fs.varargflags = VARARG_HASARG | VARARG_NEEDSARG; + } + // f.is_vararg |= VARARG_ISVARARG; + fs.varargflags |= VARARG_ISVARARG; + f.is_vararg = true; + break; + } + default: this.syntaxerror(" or " + LUA_QL("...") + " expected"); + } + } while (!f.is_vararg && this.testnext(',')); + } + this.adjustlocalvars(nparams); + f.numparams = (fs.nactvar - (fs.varargflags & VARARG_HASARG)); + fs.reserveregs(fs.nactvar); /* reserve register for parameters */ + } + + + void body(expdesc e, boolean needself, int line) { + /* body -> `(' parlist `)' chunk END */ + FuncState new_fs = new FuncState(); + open_func(new_fs); + new_fs.f.linedefined = line; + this.checknext('('); + if (needself) { + new_localvarliteral("self", 0); + adjustlocalvars(1); + } + this.parlist(); + this.checknext(')'); + this.chunk(); + new_fs.f.lastlinedefined = this.linenumber; + this.check_match(TK_END, TK_FUNCTION, line); + this.close_func(); + this.pushclosure(new_fs, e); + } + + int explist1(expdesc v) { + /* explist1 -> expr { `,' expr } */ + int n = 1; /* at least one expression */ + this.expr(v); + while (this.testnext(',')) { + fs.exp2nextreg(v); + this.expr(v); + n++; + } + return n; + } + + + void funcargs(expdesc f) { + FuncState fs = this.fs; + expdesc args = new expdesc(); + int base, nparams; + int line = this.linenumber; + switch (this.t.token) { + case '(': { /* funcargs -> `(' [ explist1 ] `)' */ + if (line != this.lastline) + this.syntaxerror("ambiguous syntax (function call x new statement)"); + this.next(); + if (this.t.token == ')') /* arg list is empty? */ + args.k = VVOID; + else { + this.explist1(args); + fs.setmultret(args); + } + this.check_match(')', '(', line); + break; + } + case '{': { /* funcargs -> constructor */ + this.constructor(args); + break; + } + case TK_STRING: { /* funcargs -> STRING */ + this.codestring(args, this.t.seminfo.ts); + this.next(); /* must use `seminfo' before `next' */ + break; + } + default: { + this.syntaxerror("function arguments expected"); + return; + } + } + _assert (f.k == VNONRELOC); + base = f.u.s.info; /* base register for call */ + if (hasmultret(args.k)) + nparams = Lua.LUA_MULTRET; /* open call */ + else { + if (args.k != VVOID) + fs.exp2nextreg(args); /* close last argument */ + nparams = fs.freereg - (base + 1); + } + f.init(VCALL, fs.codeABC(Lua.OP_CALL, base, nparams + 1, 2)); + fs.fixline(line); + fs.freereg = base+1; /* call remove function and arguments and leaves + * (unless changed) one result */ + } + + + /* + ** {====================================================================== + ** Expression parsing + ** ======================================================================= + */ + + void prefixexp(expdesc v) { + /* prefixexp -> NAME | '(' expr ')' */ + switch (this.t.token) { + case '(': { + int line = this.linenumber; + this.next(); + this.expr(v); + this.check_match(')', '(', line); + fs.dischargevars(v); + return; + } + case TK_NAME: { + this.singlevar(v); + return; + } + default: { + this.syntaxerror("unexpected symbol"); + return; + } + } + } + + + void primaryexp(expdesc v) { + /* + * primaryexp -> prefixexp { `.' NAME | `[' exp `]' | `:' NAME funcargs | + * funcargs } + */ + FuncState fs = this.fs; + this.prefixexp(v); + for (;;) { + switch (this.t.token) { + case '.': { /* field */ + this.field(v); + break; + } + case '[': { /* `[' exp1 `]' */ + expdesc key = new expdesc(); + fs.exp2anyreg(v); + this.yindex(key); + fs.indexed(v, key); + break; + } + case ':': { /* `:' NAME funcargs */ + expdesc key = new expdesc(); + this.next(); + this.checkname(key); + fs.self(v, key); + this.funcargs(v); + break; + } + case '(': + case TK_STRING: + case '{': { /* funcargs */ + fs.exp2nextreg(v); + this.funcargs(v); + break; + } + default: + return; + } + } + } + + + void simpleexp(expdesc v) { + /* + * simpleexp -> NUMBER | STRING | NIL | true | false | ... | constructor | + * FUNCTION body | primaryexp + */ + switch (this.t.token) { + case TK_NUMBER: { + v.init(VKNUM, 0); + v.u.setNval(this.t.seminfo.r); + break; + } + case TK_STRING: { + this.codestring(v, this.t.seminfo.ts); + break; + } + case TK_NIL: { + v.init(VNIL, 0); + break; + } + case TK_TRUE: { + v.init(VTRUE, 0); + break; + } + case TK_FALSE: { + v.init(VFALSE, 0); + break; + } + case TK_DOTS: { /* vararg */ + FuncState fs = this.fs; + this.check_condition(fs.f.is_vararg, "cannot use " + LUA_QL("...") + + " outside a vararg function"); + // fs.f.is_vararg &= ~VARARG_NEEDSARG; /* don't need 'arg' */ + fs.varargflags &= ~VARARG_NEEDSARG; /* don't need 'arg' */ + fs.f.is_vararg = (fs.varargflags != 0); + v.init(VVARARG, fs.codeABC(Lua.OP_VARARG, 0, 1, 0)); + break; + } + case '{': { /* constructor */ + this.constructor(v); + return; + } + case TK_FUNCTION: { + this.next(); + this.body(v, false, this.linenumber); + return; + } + default: { + this.primaryexp(v); + return; + } + } + this.next(); + } + + + int getunopr(int op) { + switch (op) { + case TK_NOT: + return OPR_NOT; + case '-': + return OPR_MINUS; + case '#': + return OPR_LEN; + default: + return OPR_NOUNOPR; + } + } + + + int getbinopr(int op) { + switch (op) { + case '+': + return OPR_ADD; + case '-': + return OPR_SUB; + case '*': + return OPR_MUL; + case '/': + return OPR_DIV; + case '%': + return OPR_MOD; + case '^': + return OPR_POW; + case TK_CONCAT: + return OPR_CONCAT; + case TK_NE: + return OPR_NE; + case TK_EQ: + return OPR_EQ; + case '<': + return OPR_LT; + case TK_LE: + return OPR_LE; + case '>': + return OPR_GT; + case TK_GE: + return OPR_GE; + case TK_AND: + return OPR_AND; + case TK_OR: + return OPR_OR; + default: + return OPR_NOBINOPR; + } + } + + static class Priority { + final byte left; /* left priority for each binary operator */ + + final byte right; /* right priority */ + + public Priority(int i, int j) { + left = (byte) i; + right = (byte) j; + } + }; + + static Priority[] priority = { /* ORDER OPR */ + new Priority(6, 6), new Priority(6, 6), new Priority(7, 7), new Priority(7, 7), new Priority(7, 7), /* `+' `-' `/' `%' */ + new Priority(10, 9), new Priority(5, 4), /* power and concat (right associative) */ + new Priority(3, 3), new Priority(3, 3), /* equality and inequality */ + new Priority(3, 3), new Priority(3, 3), new Priority(3, 3), new Priority(3, 3), /* order */ + new Priority(2, 2), new Priority(1, 1) /* logical (and/or) */ + }; + + static final int UNARY_PRIORITY = 8; /* priority for unary operators */ + + + /* + ** subexpr -> (simpleexp | unop subexpr) { binop subexpr } + ** where `binop' is any binary operator with a priority higher than `limit' + */ + int subexpr(expdesc v, int limit) { + int op; + int uop; + this.enterlevel(); + uop = getunopr(this.t.token); + if (uop != OPR_NOUNOPR) { + this.next(); + this.subexpr(v, UNARY_PRIORITY); + fs.prefix(uop, v); + } else + this.simpleexp(v); + /* expand while operators have priorities higher than `limit' */ + op = getbinopr(this.t.token); + while (op != OPR_NOBINOPR && priority[op].left > limit) { + expdesc v2 = new expdesc(); + int nextop; + this.next(); + fs.infix(op, v); + /* read sub-expression with higher priority */ + nextop = this.subexpr(v2, priority[op].right); + fs.posfix(op, v, v2); + op = nextop; + } + this.leavelevel(); + return op; /* return first untreated operator */ + } + + void expr(expdesc v) { + this.subexpr(v, 0); + } + + /* }==================================================================== */ + + + + /* + ** {====================================================================== + ** Rules for Statements + ** ======================================================================= + */ + + + boolean block_follow (int token) { + switch (token) { + case TK_ELSE: case TK_ELSEIF: case TK_END: + case TK_UNTIL: case TK_EOS: + return true; + default: return false; + } + } + + + void block () { + /* block -> chunk */ + FuncState fs = this.fs; + BlockCnt bl = new BlockCnt(); + fs.enterblock(bl, false); + this.chunk(); + _assert(bl.breaklist.i == NO_JUMP); + fs.leaveblock(); + } + + + /* + ** structure to chain all variables in the left-hand side of an + ** assignment + */ + static class LHS_assign { + LHS_assign prev; + /* variable (global, local, upvalue, or indexed) */ + expdesc v = new expdesc(); + }; + + + /* + ** check whether, in an assignment to a local variable, the local variable + ** is needed in a previous assignment (to a table). If so, save original + ** local value in a safe place and use this safe copy in the previous + ** assignment. + */ + void check_conflict (LHS_assign lh, expdesc v) { + FuncState fs = this.fs; + int extra = fs.freereg; /* eventual position to save local variable */ + boolean conflict = false; + for (; lh!=null; lh = lh.prev) { + if (lh.v.k == VINDEXED) { + if (lh.v.u.s.info == v.u.s.info) { /* conflict? */ + conflict = true; + lh.v.u.s.info = extra; /* previous assignment will use safe copy */ + } + if (lh.v.u.s.aux == v.u.s.info) { /* conflict? */ + conflict = true; + lh.v.u.s.aux = extra; /* previous assignment will use safe copy */ + } + } + } + if (conflict) { + fs.codeABC(Lua.OP_MOVE, fs.freereg, v.u.s.info, 0); /* make copy */ + fs.reserveregs(1); + } + } + + + void assignment (LHS_assign lh, int nvars) { + expdesc e = new expdesc(); + this.check_condition(VLOCAL <= lh.v.k && lh.v.k <= VINDEXED, + "syntax error"); + if (this.testnext(',')) { /* assignment -> `,' primaryexp assignment */ + LHS_assign nv = new LHS_assign(); + nv.prev = lh; + this.primaryexp(nv.v); + if (nv.v.k == VLOCAL) + this.check_conflict(lh, nv.v); + this.assignment(nv, nvars+1); + } + else { /* assignment . `=' explist1 */ + int nexps; + this.checknext('='); + nexps = this.explist1(e); + if (nexps != nvars) { + this.adjust_assign(nvars, nexps, e); + if (nexps > nvars) + this.fs.freereg -= nexps - nvars; /* remove extra values */ + } + else { + fs.setoneret(e); /* close last expression */ + fs.storevar(lh.v, e); + return; /* avoid default */ + } + } + e.init(VNONRELOC, this.fs.freereg-1); /* default assignment */ + fs.storevar(lh.v, e); + } + + + int cond() { + /* cond -> exp */ + expdesc v = new expdesc(); + /* read condition */ + this.expr(v); + /* `falses' are all equal here */ + if (v.k == VNIL) + v.k = VFALSE; + fs.goiftrue(v); + return v.f.i; + } + + + void breakstat() { + FuncState fs = this.fs; + BlockCnt bl = fs.bl; + boolean upval = false; + while (bl != null && !bl.isbreakable) { + upval |= bl.upval; + bl = bl.previous; + } + if (bl == null) + this.syntaxerror("no loop to break"); + if (upval) + fs.codeABC(Lua.OP_CLOSE, bl.nactvar, 0, 0); + fs.concat(bl.breaklist, fs.jump()); + } + + + void whilestat (int line) { + /* whilestat -> WHILE cond DO block END */ + FuncState fs = this.fs; + int whileinit; + int condexit; + BlockCnt bl = new BlockCnt(); + this.next(); /* skip WHILE */ + whileinit = fs.getlabel(); + condexit = this.cond(); + fs.enterblock(bl, true); + this.checknext(TK_DO); + this.block(); + fs.patchlist(fs.jump(), whileinit); + this.check_match(TK_END, TK_WHILE, line); + fs.leaveblock(); + fs.patchtohere(condexit); /* false conditions finish the loop */ + } + + void repeatstat(int line) { + /* repeatstat -> REPEAT block UNTIL cond */ + int condexit; + FuncState fs = this.fs; + int repeat_init = fs.getlabel(); + BlockCnt bl1 = new BlockCnt(); + BlockCnt bl2 = new BlockCnt(); + fs.enterblock(bl1, true); /* loop block */ + fs.enterblock(bl2, false); /* scope block */ + this.next(); /* skip REPEAT */ + this.chunk(); + this.check_match(TK_UNTIL, TK_REPEAT, line); + condexit = this.cond(); /* read condition (inside scope block) */ + if (!bl2.upval) { /* no upvalues? */ + fs.leaveblock(); /* finish scope */ + fs.patchlist(condexit, repeat_init); /* close the loop */ + } else { /* complete semantics when there are upvalues */ + this.breakstat(); /* if condition then break */ + fs.patchtohere(condexit); /* else... */ + fs.leaveblock(); /* finish scope... */ + fs.patchlist(fs.jump(), repeat_init); /* and repeat */ + } + fs.leaveblock(); /* finish loop */ + } + + + int exp1() { + expdesc e = new expdesc(); + int k; + this.expr(e); + k = e.k; + fs.exp2nextreg(e); + return k; + } + + + void forbody(int base, int line, int nvars, boolean isnum) { + /* forbody -> DO block */ + BlockCnt bl = new BlockCnt(); + FuncState fs = this.fs; + int prep, endfor; + this.adjustlocalvars(3); /* control variables */ + this.checknext(TK_DO); + prep = isnum ? fs.codeAsBx(Lua.OP_FORPREP, base, NO_JUMP) : fs.jump(); + fs.enterblock(bl, false); /* scope for declared variables */ + this.adjustlocalvars(nvars); + fs.reserveregs(nvars); + this.block(); + fs.leaveblock(); /* end of scope for declared variables */ + fs.patchtohere(prep); + endfor = (isnum) ? fs.codeAsBx(Lua.OP_FORLOOP, base, NO_JUMP) : fs + .codeABC(Lua.OP_TFORLOOP, base, 0, nvars); + fs.fixline(line); /* pretend that `Lua.OP_FOR' starts the loop */ + fs.patchlist((isnum ? endfor : fs.jump()), prep + 1); + } + + + void fornum(LString varname, int line) { + /* fornum -> NAME = exp1,exp1[,exp1] forbody */ + FuncState fs = this.fs; + int base = fs.freereg; + this.new_localvarliteral("(for index)", 0); + this.new_localvarliteral("(for limit)", 1); + this.new_localvarliteral("(for step)", 2); + this.new_localvar(varname, 3); + this.checknext('='); + this.exp1(); /* initial value */ + this.checknext(','); + this.exp1(); /* limit */ + if (this.testnext(',')) + this.exp1(); /* optional step */ + else { /* default step = 1 */ + fs.codeABx(Lua.OP_LOADK, fs.freereg, fs.numberK(new LInteger(1))); + fs.reserveregs(1); + } + this.forbody(base, line, 1, true); + } + + + void forlist(LString indexname) { + /* forlist -> NAME {,NAME} IN explist1 forbody */ + FuncState fs = this.fs; + expdesc e = new expdesc(); + int nvars = 0; + int line; + int base = fs.freereg; + /* create control variables */ + this.new_localvarliteral("(for generator)", nvars++); + this.new_localvarliteral("(for state)", nvars++); + this.new_localvarliteral("(for control)", nvars++); + /* create declared variables */ + this.new_localvar(indexname, nvars++); + while (this.testnext(',')) + this.new_localvar(this.str_checkname(), nvars++); + this.checknext(TK_IN); + line = this.linenumber; + this.adjust_assign(3, this.explist1(e), e); + fs.checkstack(3); /* extra space to call generator */ + this.forbody(base, line, nvars - 3, false); + } + + + void forstat(int line) { + /* forstat -> FOR (fornum | forlist) END */ + FuncState fs = this.fs; + LString varname; + BlockCnt bl = new BlockCnt(); + fs.enterblock(bl, true); /* scope for loop and control variables */ + this.next(); /* skip `for' */ + varname = this.str_checkname(); /* first variable name */ + switch (this.t.token) { + case '=': + this.fornum(varname, line); + break; + case ',': + case TK_IN: + this.forlist(varname); + break; + default: + this.syntaxerror(LUA_QL("=") + " or " + LUA_QL("in") + " expected"); + } + this.check_match(TK_END, TK_FOR, line); + fs.leaveblock(); /* loop scope (`break' jumps to this point) */ + } + + + int test_then_block() { + /* test_then_block -> [IF | ELSEIF] cond THEN block */ + int condexit; + this.next(); /* skip IF or ELSEIF */ + condexit = this.cond(); + this.checknext(TK_THEN); + this.block(); /* `then' part */ + return condexit; + } + + + void ifstat(int line) { + /* ifstat -> IF cond THEN block {ELSEIF cond THEN block} [ELSE block] + * END */ + FuncState fs = this.fs; + int flist; + IntPtr escapelist = new IntPtr(NO_JUMP); + flist = test_then_block(); /* IF cond THEN block */ + while (this.t.token == TK_ELSEIF) { + fs.concat(escapelist, fs.jump()); + fs.patchtohere(flist); + flist = test_then_block(); /* ELSEIF cond THEN block */ + } + if (this.t.token == TK_ELSE) { + fs.concat(escapelist, fs.jump()); + fs.patchtohere(flist); + this.next(); /* skip ELSE (after patch, for correct line info) */ + this.block(); /* `else' part */ + } else + fs.concat(escapelist, flist); + fs.patchtohere(escapelist.i); + this.check_match(TK_END, TK_IF, line); + } + + void localfunc() { + expdesc v = new expdesc(); + expdesc b = new expdesc(); + FuncState fs = this.fs; + this.new_localvar(this.str_checkname(), 0); + v.init(VLOCAL, fs.freereg); + fs.reserveregs(1); + this.adjustlocalvars(1); + this.body(b, false, this.linenumber); + fs.storevar(v, b); + /* debug information will only see the variable after this point! */ + fs.getlocvar(fs.nactvar - 1).startpc = fs.pc; + } + + + void localstat() { + /* stat -> LOCAL NAME {`,' NAME} [`=' explist1] */ + int nvars = 0; + int nexps; + expdesc e = new expdesc(); + do { + this.new_localvar(this.str_checkname(), nvars++); + } while (this.testnext(',')); + if (this.testnext('=')) + nexps = this.explist1(e); + else { + e.k = VVOID; + nexps = 0; + } + this.adjust_assign(nvars, nexps, e); + this.adjustlocalvars(nvars); + } + + + boolean funcname(expdesc v) { + /* funcname -> NAME {field} [`:' NAME] */ + boolean needself = false; + this.singlevar(v); + while (this.t.token == '.') + this.field(v); + if (this.t.token == ':') { + needself = true; + this.field(v); + } + return needself; + } + + + void funcstat(int line) { + /* funcstat -> FUNCTION funcname body */ + boolean needself; + expdesc v = new expdesc(); + expdesc b = new expdesc(); + this.next(); /* skip FUNCTION */ + needself = this.funcname(v); + this.body(b, needself, line); + fs.storevar(v, b); + fs.fixline(line); /* definition `happens' in the first line */ + } + + + void exprstat() { + /* stat -> func | assignment */ + FuncState fs = this.fs; + LHS_assign v = new LHS_assign(); + this.primaryexp(v.v); + if (v.v.k == VCALL) /* stat -> func */ + SETARG_C(fs.getcodePtr(v.v), 1); /* call statement uses no results */ + else { /* stat -> assignment */ + v.prev = null; + this.assignment(v, 1); + } + } + + void retstat() { + /* stat -> RETURN explist */ + FuncState fs = this.fs; + expdesc e = new expdesc(); + int first, nret; /* registers with returned values */ + this.next(); /* skip RETURN */ + if (block_follow(this.t.token) || this.t.token == ';') + first = nret = 0; /* return no values */ + else { + nret = this.explist1(e); /* optional return values */ + if (hasmultret(e.k)) { + fs.setmultret(e); + if (e.k == VCALL && nret == 1) { /* tail call? */ + SET_OPCODE(fs.getcodePtr(e), Lua.OP_TAILCALL); + _assert (Lua.GETARG_A(fs.getcode(e)) == fs.nactvar); + } + first = fs.nactvar; + nret = Lua.LUA_MULTRET; /* return all values */ + } else { + if (nret == 1) /* only one single value? */ + first = fs.exp2anyreg(e); + else { + fs.exp2nextreg(e); /* values must go to the `stack' */ + first = fs.nactvar; /* return all `active' values */ + _assert (nret == fs.freereg - first); + } + } + } + fs.ret(first, nret); + } + + + boolean statement() { + int line = this.linenumber; /* may be needed for error messages */ + switch (this.t.token) { + case TK_IF: { /* stat -> ifstat */ + this.ifstat(line); + return false; + } + case TK_WHILE: { /* stat -> whilestat */ + this.whilestat(line); + return false; + } + case TK_DO: { /* stat -> DO block END */ + this.next(); /* skip DO */ + this.block(); + this.check_match(TK_END, TK_DO, line); + return false; + } + case TK_FOR: { /* stat -> forstat */ + this.forstat(line); + return false; + } + case TK_REPEAT: { /* stat -> repeatstat */ + this.repeatstat(line); + return false; + } + case TK_FUNCTION: { + this.funcstat(line); /* stat -> funcstat */ + return false; + } + case TK_LOCAL: { /* stat -> localstat */ + this.next(); /* skip LOCAL */ + if (this.testnext(TK_FUNCTION)) /* local function? */ + this.localfunc(); + else + this.localstat(); + return false; + } + case TK_RETURN: { /* stat -> retstat */ + this.retstat(); + return true; /* must be last statement */ + } + case TK_BREAK: { /* stat -> breakstat */ + this.next(); /* skip BREAK */ + this.breakstat(); + return true; /* must be last statement */ + } + default: { + this.exprstat(); + return false; /* to avoid warnings */ + } + } + } + + void chunk() { + /* chunk -> { stat [`;'] } */ + boolean islast = false; + this.enterlevel(); + while (!islast && !block_follow(this.t.token)) { + islast = this.statement(); + this.testnext(';'); + _assert (this.fs.f.maxstacksize >= this.fs.freereg + && this.fs.freereg >= this.fs.nactvar); + this.fs.freereg = this.fs.nactvar; /* free registers */ + } + this.leavelevel(); + } + + /* }====================================================================== */ + +} diff --git a/src/addon/java/lua/addon/compile/LuaC.java b/src/addon/java/lua/addon/compile/LuaC.java new file mode 100644 index 00000000..a91fbdf4 --- /dev/null +++ b/src/addon/java/lua/addon/compile/LuaC.java @@ -0,0 +1,125 @@ +package lua.addon.compile; + +import lua.Lua; +import lua.io.LocVars; +import lua.io.Proto; +import lua.value.LString; +import lua.value.LValue; + +/** + * Additional constants and utilities required for the compiler, + * but not required for the interpreter. + * + */ +public class LuaC extends Lua { + protected static void _assert(boolean b) { + if (!b) throw new RuntimeException("assert failed"); + } + + static final int LUAI_MAXUPVALUES = 60; + static final int LUAI_MAXVARS = 200; + static final int LFIELDS_PER_FLUSH = 50; + static final int NO_REG = MAXARG_A; + + /* masks for new-style vararg */ + static final int VARARG_HASARG = 1; + static final int VARARG_ISVARARG = 2; + static final int VARARG_NEEDSARG = 4; + + + /* OpMode - basic instruction format */ + static final int + iABC = 0, + iABx = 1, + iAsBx = 2; + + /* OpArgMask */ + static final int + OpArgN = 0, /* argument is not used */ + OpArgU = 1, /* argument is used */ + OpArgR = 2, /* argument is a register or a jump offset */ + OpArgK = 3; /* argument is a constant or register/constant */ + + + static void SET_OPCODE(InstructionPtr i,int o) { + i.set( ( i.get() & (MASK_NOT_OP)) | ((o << POS_OP) & MASK_OP) ); + } + + static void SETARG_A(InstructionPtr i,int u) { + i.set( ( i.get() & (MASK_NOT_A)) | ((u << POS_A) & MASK_A) ); + } + + static void SETARG_B(InstructionPtr i,int u) { + i.set( ( i.get() & (MASK_NOT_B)) | ((u << POS_B) & MASK_B) ); + } + + static void SETARG_C(InstructionPtr i,int u) { + i.set( ( i.get() & (MASK_NOT_C)) | ((u << POS_C) & MASK_C) ); + } + + static void SETARG_Bx(InstructionPtr i,int u) { + i.set( ( i.get() & (MASK_NOT_Bx)) | ((u << POS_Bx) & MASK_Bx) ); + } + + static void SETARG_sBx(InstructionPtr i,int u) { + SETARG_Bx( i, u + MAXARG_sBx ); + } + + static int CREATE_ABC(int o, int a, int b, int c) { + return ((o << POS_OP) & MASK_OP) | + ((a << POS_A) & MASK_A) | + ((b << POS_B) & MASK_B) | + ((c << POS_C) & MASK_C) ; + } + + static int CREATE_ABx(int o, int a, int bc) { + return ((o << POS_OP) & MASK_OP) | + ((a << POS_A) & MASK_A) | + ((bc << POS_Bx) & MASK_Bx) ; + } + + // vector reallocation + + static LValue[] realloc(LValue[] v, int n) { + LValue[] a = new LValue[n]; + if ( v != null ) + System.arraycopy(v, 0, a, 0, Math.min(v.length,n)); + return a; + } + + static Proto[] realloc(Proto[] v, int n) { + Proto[] a = new Proto[n]; + if ( v != null ) + System.arraycopy(v, 0, a, 0, Math.min(v.length,n)); + return a; + } + + static LString[] realloc(LString[] v, int n) { + LString[] a = new LString[n]; + if ( v != null ) + System.arraycopy(v, 0, a, 0, Math.min(v.length,n)); + return a; + } + + static LocVars[] realloc(LocVars[] v, int n) { + LocVars[] a = new LocVars[n]; + if ( v != null ) + System.arraycopy(v, 0, a, 0, Math.min(v.length,n)); + return a; + } + + static int[] realloc(int[] v, int n) { + int[] a = new int[n]; + if ( v != null ) + System.arraycopy(v, 0, a, 0, Math.min(v.length,n)); + return a; + } + + static char[] realloc(char[] v, int n) { + char[] a = new char[n]; + if ( v != null ) + System.arraycopy(v, 0, a, 0, Math.min(v.length,n)); + return a; + } + +} diff --git a/src/test/compile/lua5.1-tests.zip b/src/test/compile/lua5.1-tests.zip new file mode 100644 index 00000000..c937b1ae Binary files /dev/null and b/src/test/compile/lua5.1-tests.zip differ diff --git a/src/test/compile/regressions.zip b/src/test/compile/regressions.zip new file mode 100644 index 00000000..76e431d9 Binary files /dev/null and b/src/test/compile/regressions.zip differ diff --git a/src/test/compile/repack.sh b/src/test/compile/repack.sh new file mode 100644 index 00000000..a7dd2d77 --- /dev/null +++ b/src/test/compile/repack.sh @@ -0,0 +1,20 @@ +#!/bin/bash +LUA_HOME=/cygdrive/c/programs/lua5.1 +#DIRS="lua5.1-tests regressions" +DIRS="regressions" +for d in $DIRS; do + + # clean out the old + rm -f $d/*.luac + + # compile the tests + TESTS=`echo $d/*.lua` + for x in $TESTS; do + echo compiling $x + luac -o ${x}c ${x} + done + + # rebuild the directory + rm -f ${d}.zip + jar -cvf ${d}.zip ${d} +done \ No newline at end of file diff --git a/src/test/compile/unpack.sh b/src/test/compile/unpack.sh new file mode 100644 index 00000000..ffe81bf4 --- /dev/null +++ b/src/test/compile/unpack.sh @@ -0,0 +1,12 @@ +#!/bin/bash +LUA_HOME=/cygdrive/c/programs/lua5.1 +#DIRS="lua5.1-tests regressions" +DIRS="regressions" +for d in $DIRS; do + + # unpack files into the directory + rm -rf $d + mkdir -p $d + jar -xvf $d.zip + +done \ No newline at end of file diff --git a/src/test/java/lua/addon/compile/AbstractUnitTests.java b/src/test/java/lua/addon/compile/AbstractUnitTests.java new file mode 100644 index 00000000..1cced23a --- /dev/null +++ b/src/test/java/lua/addon/compile/AbstractUnitTests.java @@ -0,0 +1,96 @@ +package lua.addon.compile; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.Reader; +import java.net.URL; + +import junit.framework.TestCase; + +import lua.Print; +import lua.StackState; +import lua.addon.compile.Compiler; +import lua.addon.compile.DumpState; +import lua.io.LoadState; +import lua.io.Proto; + +abstract +public class AbstractUnitTests extends TestCase { + + private final String zipfile; + private final String dir; + + public AbstractUnitTests(String zipfile, String dir) { + this.zipfile = zipfile; + this.dir = dir; + } + + protected void doTest( String file ) { + try { + // load source from jar + String path = "jar:file:" + zipfile + "!/" + dir + "/" + file; + byte[] lua = bytesFromJar( path ); + + // compile in memory + InputStream is = new ByteArrayInputStream( lua ); + Reader r = new InputStreamReader( is ); + Proto p = Compiler.compile(r, dir+"/"+file); + String actual = protoToString( p ); + + // load expected value from jar + byte[] luac = bytesFromJar( path + "c" ); + Proto e = loadFromBytes( luac, file ); + String expected = protoToString( e ); + + // compare results + assertEquals( expected, actual ); + + // dump into memory + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DumpState.dump(p, baos, false); + byte[] dumped = baos.toByteArray(); + + // re-undump + Proto p2 = loadFromBytes( dumped, file ); + String actual2 = protoToString( p2 ); + + // compare again + assertEquals( actual, actual2 ); + + } catch (IOException e) { + fail( e.toString() ); + } + } + + protected byte[] bytesFromJar(String path) throws IOException { + URL url = new URL(path); + InputStream is = url.openStream(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[2048]; + int n; + while ( (n = is.read(buffer)) >= 0 ) + baos.write( buffer, 0, n ); + is.close(); + return baos.toByteArray(); + } + + protected Proto loadFromBytes(byte[] bytes, String script) throws IOException { + StackState state = new StackState(); + InputStream is = new ByteArrayInputStream( bytes ); + return LoadState.undump(state, is, script); + } + + protected String protoToString(Proto p) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream( baos ); + Print.ps = ps; + new Print().printFunction(p, true); + return baos.toString(); + } + + +} diff --git a/src/test/java/lua/addon/compile/CompilerUnitTests.java b/src/test/java/lua/addon/compile/CompilerUnitTests.java new file mode 100644 index 00000000..cdd31e52 --- /dev/null +++ b/src/test/java/lua/addon/compile/CompilerUnitTests.java @@ -0,0 +1,35 @@ +package lua.addon.compile; + + +public class CompilerUnitTests extends AbstractUnitTests { + + public CompilerUnitTests() { + super( "src/test/compile/lua5.1-tests.zip", + "lua5.1-tests" ); + } + + public void testAll() { doTest("all.lua"); } + public void testApi() { doTest("api.lua"); } + public void testAttrib() { doTest("attrib.lua"); } + public void testBig() { doTest("big.lua"); } + public void testCalls() { doTest("calls.lua"); } + public void testChecktable() { doTest("checktable.lua"); } + public void testClosure() { doTest("closure.lua"); } + public void testCode() { doTest("code.lua"); } + public void testConstruct() { doTest("constructs.lua"); } + public void testDb() { doTest("db.lua"); } + public void testErrors() { doTest("errors.lua"); } + public void testEvents() { doTest("events.lua"); } + public void testFiles() { doTest("files.lua"); } + public void testGc() { doTest("gc.lua"); } + public void testLiterals() { doTest("literals.lua"); } + public void testLocals() { doTest("locals.lua"); } + public void testMain() { doTest("main.lua"); } + public void testMath() { doTest("math.lua"); } + public void testNextvar() { doTest("nextvar.lua"); } + public void testPm() { doTest("pm.lua"); } + public void testSort() { doTest("sort.lua"); } + public void testStrings() { doTest("strings.lua"); } + public void testVararg() { doTest("vararg.lua"); } + public void testVerybig() { doTest("verybig.lua"); } +} diff --git a/src/test/java/lua/addon/compile/RegressionTests.java b/src/test/java/lua/addon/compile/RegressionTests.java new file mode 100644 index 00000000..2cdb2674 --- /dev/null +++ b/src/test/java/lua/addon/compile/RegressionTests.java @@ -0,0 +1,31 @@ +package lua.addon.compile; + +/** + * Framework to add regression tests as problem areas are found. + * + * To add a new regression test: + * 1) run "unpack.sh" in the project root + * 2) add a new "lua" file in the "regressions" subdirectory + * 3) run "repack.sh" in the project root + * 4) add a line to the source file naming the new test + * + * After adding a test, check in the zip file + * rather than the individual regression test files. + * + * @author jrosebor + */ +public class RegressionTests extends AbstractUnitTests { + + public RegressionTests() { + super( "src/test/compile/regressions.zip", + "regressions" ); + } + + public void testModulo() { doTest("modulo.lua"); } + public void testConstruct() { doTest("construct.lua"); } + public void testBigAttrs() { doTest("bigattr.lua"); } + public void testControlChars() { doTest("controlchars.lua"); } + public void testComparators() { doTest("comparators.lua"); } + public void testMathRandomseed() { doTest("mathrandomseed.lua"); } + +} diff --git a/src/test/java/lua/addon/compile/SimpleTests.java b/src/test/java/lua/addon/compile/SimpleTests.java new file mode 100644 index 00000000..bb7195ba --- /dev/null +++ b/src/test/java/lua/addon/compile/SimpleTests.java @@ -0,0 +1,76 @@ +package lua.addon.compile; + +import java.io.Reader; +import java.io.StringReader; + +import junit.framework.TestCase; +import lua.Print; +import lua.StackState; +import lua.addon.compile.Compiler; +import lua.io.Closure; +import lua.io.Proto; +import lua.value.LValue; + +public class SimpleTests extends TestCase { + + private void doTest( String script ) { + Reader r = new StringReader( script ); + Proto p = Compiler.compile( r, "script" ); + assertNotNull( p ); + Print.printCode( p ); + + // try running the code! + StackState state = new StackState(); + Closure c = new Closure( state, p ); + state.doCall( c, new LValue[0] ); + + } + + public void testTrivial() { + String s = "print( 2 )\n"; + doTest( s ); + } + + public void testAlmostTrivial() { + String s = "print( 2 )\n" + + "print( 3 )\n"; + doTest( s ); + } + + public void testSimple() { + String s = "print( 'hello, world' )\n"+ + "for i = 2,4 do\n" + + " print( 'i', i )\n" + + "end\n"; + doTest( s ); + } + + public void testBreak() { + String s = "a=1\n"+ + "while true do\n"+ + " if a>10 then\n"+ + " break\n"+ + " end\n"+ + " a=a+1\n"+ + " print( a )\n"+ + "end\n"; + doTest( s ); + } + + public void testShebang() { + String s = "#!../lua\n"+ + "print( 2 )\n"; + doTest( s ); + } + + public void testInlineTable() { + String s = "A = {g=10}\n"+ + "print( A )\n"; + doTest( s ); + } + + public void testEqualsAnd() { + String s = "print( 1 == b and b )\n"; + doTest( s ); + } +}