| /******************************************************************************* |
| * Copyright (c) 2009 Luaj.org. All rights reserved. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to deal |
| * in the Software without restriction, including without limitation the rights |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| * copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| * THE SOFTWARE. |
| ******************************************************************************/ |
| package org.luaj.vm3.compiler; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.Hashtable; |
| |
| import org.luaj.vm3.LocVars; |
| import org.luaj.vm3.Lua; |
| import org.luaj.vm3.LuaError; |
| import org.luaj.vm3.LuaInteger; |
| import org.luaj.vm3.LuaString; |
| import org.luaj.vm3.LuaValue; |
| import org.luaj.vm3.Prototype; |
| import org.luaj.vm3.compiler.FuncState.BlockCnt; |
| import org.luaj.vm3.lib.MathLib; |
| |
| |
| public class LexState { |
| |
| protected static final String RESERVED_LOCAL_VAR_FOR_CONTROL = "(for control)"; |
| protected static final String RESERVED_LOCAL_VAR_FOR_STATE = "(for state)"; |
| protected static final String RESERVED_LOCAL_VAR_FOR_GENERATOR = "(for generator)"; |
| protected static final String RESERVED_LOCAL_VAR_FOR_STEP = "(for step)"; |
| protected static final String RESERVED_LOCAL_VAR_FOR_LIMIT = "(for limit)"; |
| protected static final String RESERVED_LOCAL_VAR_FOR_INDEX = "(for index)"; |
| |
| // keywords array |
| protected static final String[] RESERVED_LOCAL_VAR_KEYWORDS = new String[] { |
| RESERVED_LOCAL_VAR_FOR_CONTROL, |
| RESERVED_LOCAL_VAR_FOR_GENERATOR, |
| RESERVED_LOCAL_VAR_FOR_INDEX, |
| RESERVED_LOCAL_VAR_FOR_LIMIT, |
| RESERVED_LOCAL_VAR_FOR_STATE, |
| RESERVED_LOCAL_VAR_FOR_STEP |
| }; |
| private static final Hashtable RESERVED_LOCAL_VAR_KEYWORDS_TABLE = new Hashtable(); |
| static { |
| for ( int i=0; i<RESERVED_LOCAL_VAR_KEYWORDS.length; i++ ) |
| RESERVED_LOCAL_VAR_KEYWORDS_TABLE.put( RESERVED_LOCAL_VAR_KEYWORDS[i], Boolean.TRUE ); |
| } |
| |
| private static final int EOZ = (-1); |
| 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; |
| |
| public static boolean isReservedKeyword(String varName) { |
| return RESERVED_LOCAL_VAR_KEYWORDS_TABLE.containsKey(varName); |
| } |
| |
| /* |
| ** 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 */ |
| VNONRELOC = 6, /* info = result register */ |
| VLOCAL = 7, /* info = local register */ |
| VUPVAL = 8, /* info = index of upvalue in `upvalues' */ |
| VINDEXED = 9, /* info = table register, aux = index register (or `k') */ |
| VJMP = 10, /* info = instruction pc */ |
| VRELOCABLE = 11, /* info = instruction pc */ |
| VCALL = 12, /* info = instruction pc */ |
| VVARARG = 13; /* info = instruction pc */ |
| |
| /* semantics information */ |
| private static class SemInfo { |
| LuaValue r; |
| LuaString 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 */ |
| LuaC L; |
| InputStream z; /* input stream */ |
| char[] buff; /* buffer for tokens */ |
| int nbuff; /* length of buffer */ |
| Dyndata dyd = new Dyndata(); /* dynamic structures used by the parser */ |
| LuaString source; /* current source name */ |
| LuaString envn; /* environment variable name */ |
| byte decpoint; /* locale decimal point */ |
| |
| /* ORDER RESERVED */ |
| final static String luaX_tokens [] = { |
| "and", "break", "do", "else", "elseif", |
| "end", "false", "for", "function", "goto", "if", |
| "in", "local", "nil", "not", "or", "repeat", |
| "return", "then", "true", "until", "while", |
| "..", "...", "==", ">=", "<=", "~=", |
| "::", "<eos>", "<number>", "<name>", "<string>", "<eof>", |
| }; |
| |
| 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_GOTO=266, TK_IF=267, |
| TK_IN=268, TK_LOCAL=269, TK_NIL=270, TK_NOT=271, TK_OR=272, TK_REPEAT=273, |
| TK_RETURN=274, TK_THEN=275, TK_TRUE=276, TK_UNTIL=277, TK_WHILE=278, |
| /* other terminal symbols */ |
| TK_CONCAT=279, TK_DOTS=280, TK_EQ=281, TK_GE=282, TK_LE=283, TK_NE=284, |
| TK_DBCOLON=285, TK_EOS=286, TK_NUMBER=287, TK_NAME=288, TK_STRING=289; |
| |
| 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<NUM_RESERVED; i++ ) { |
| LuaString ts = (LuaString) LuaValue.valueOf( luaX_tokens[i] ); |
| RESERVED.put(ts, new Integer(FIRST_RESERVED+i)); |
| } |
| } |
| |
| private boolean isalnum(int c) { |
| return (c >= '0' && c <= '9') |
| || (c >= 'a' && c <= 'z') |
| || (c >= 'A' && c <= 'Z') |
| || (c == '_'); |
| // return Character.isLetterOrDigit(c); |
| } |
| |
| private boolean isalpha(int c) { |
| return (c >= 'a' && c <= 'z') |
| || (c >= 'A' && c <= 'Z'); |
| } |
| |
| private boolean isdigit(int c) { |
| return (c >= '0' && c <= '9'); |
| } |
| |
| private boolean isxdigit(int c) { |
| return (c >= '0' && c <= '9') |
| || (c >= 'a' && c <= 'f') |
| || (c >= 'A' && c <= 'F'); |
| } |
| |
| private boolean isspace(int c) { |
| return (c <= ' '); |
| } |
| |
| |
| public LexState(LuaC state, InputStream stream) { |
| this.z = stream; |
| this.buff = new char[32]; |
| this.L = state; |
| } |
| |
| void nextChar() { |
| try { |
| current = z.read(); |
| } catch ( IOException e ) { |
| e.printStackTrace(); |
| current = EOZ; |
| } |
| } |
| |
| boolean currIsNewline() { |
| return current == '\n' || current == '\r'; |
| } |
| |
| void save_and_next() { |
| save( current ); |
| nextChar(); |
| } |
| |
| void save(int c) { |
| if ( buff == null || nbuff + 1 > buff.length ) |
| buff = LuaC.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 ).replace("\0", ""); |
| default: |
| return token2str( token ); |
| } |
| } |
| |
| void lexerror( String msg, int token ) { |
| String cid = Lua.chunkid( source.tojstring() ); |
| L.pushfstring( cid+":"+linenumber+": "+msg ); |
| if ( token != 0 ) |
| L.pushfstring( "syntax error: "+msg+" near "+txtToken(token) ); |
| if ( token != 0 ) |
| throw new LuaError(cid+":"+linenumber+": "+msg+" near "+LUA_QS(txtToken(token))); |
| else |
| throw new LuaError(cid+":"+linenumber+": "+msg); |
| } |
| |
| void syntaxerror( String msg ) { |
| lexerror( msg, t.token ); |
| } |
| |
| // only called by new_localvarliteral() for var names. |
| LuaString newstring( String s ) { |
| return L.newTString(s); |
| } |
| |
| LuaString newstring( char[] chars, int offset, int len ) { |
| return L.newTString(new String(chars, offset, len)); |
| } |
| |
| void inclinenumber() { |
| int old = current; |
| LuaC._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( LuaC L, int firstByte, InputStream z, LuaString 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.envn = LuaValue.ENV; /* environment variable name */ |
| this.nbuff = 0; /* initialize buffer */ |
| this.current = firstByte; /* 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; |
| } |
| |
| LuaValue strx2number(String str, SemInfo seminfo) { |
| char[] c = str.toCharArray(); |
| int s = 0; |
| while ( s < c.length && isspace(c[s])) |
| ++s; |
| // Check for negative sign |
| double sgn = 1.0; |
| if (s < c.length && c[s] == '-') { |
| sgn = -1.0; |
| ++s; |
| } |
| /* Check for "0x" */ |
| if (s + 2 >= c.length ) |
| return LuaValue.ZERO; |
| if (c[s++] != '0') |
| return LuaValue.ZERO; |
| if (c[s] != 'x' && c[s] != 'X') |
| return LuaValue.ZERO; |
| ++s; |
| |
| // read integer part. |
| double m = 0; |
| int e = 0; |
| while (s < c.length && isxdigit(c[s])) |
| m = (m * 16) + hexvalue(c[s++]); |
| if (s < c.length && c[s] == '.') { |
| ++s; // skip dot |
| while (s < c.length && isxdigit(c[s])) { |
| m = (m * 16) + hexvalue(c[s++]); |
| e -= 4; // Each fractional part shifts right by 2^4 |
| } |
| } |
| if (s < c.length && (c[s] == 'p' || c[s] == 'P')) { |
| ++s; |
| int exp1 = 0; |
| boolean neg1 = false; |
| if (s < c.length && c[s] == '-') { |
| neg1 = true; |
| ++s; |
| } |
| while (s < c.length && isdigit(c[s])) |
| exp1 = exp1 * 10 + c[s++] - '0'; |
| if (neg1) |
| exp1 = -exp1; |
| e += exp1; |
| } |
| return LuaValue.valueOf(sgn * m * MathLib.dpow_d(2.0, e)); |
| } |
| |
| boolean str2d(String str, SemInfo seminfo) { |
| if (str.indexOf('n')>=0 || str.indexOf('N')>=0) |
| seminfo.r = LuaValue.ZERO; |
| else if (str.indexOf('x')>=0 || str.indexOf('X')>=0) |
| seminfo.r = strx2number(str, seminfo); |
| else |
| seminfo.r = LuaValue.valueOf(Double.parseDouble(str.trim())); |
| return true; |
| } |
| |
| void read_numeral(SemInfo seminfo) { |
| String expo = "Ee"; |
| int first = current; |
| LuaC._assert (isdigit(current)); |
| save_and_next(); |
| if (first == '0' && check_next("Xx")) |
| expo = "Pp"; |
| while (true) { |
| if (check_next(expo)) |
| check_next("+-"); |
| if(isxdigit(current) || current == '.') |
| save_and_next(); |
| else |
| break; |
| } |
| save('\0'); |
| String str = new String(buff, 0, nbuff); |
| str2d(str, seminfo); |
| } |
| |
| int skip_sep() { |
| int count = 0; |
| int s = current; |
| LuaC._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 = L.newTString(LuaString.valueOf(buff, 2 + sep, nbuff - 2 * (2 + sep))); |
| } |
| |
| int hexvalue(int c) { |
| return c <= '9'? c - '0': c <= 'F'? c + 10 - 'A': c + 10 - 'a'; |
| } |
| |
| int readhexaesc() { |
| nextChar(); |
| int c1 = current; |
| nextChar(); |
| int c2 = current; |
| if (!isxdigit(c1) || !isxdigit(c2)) |
| lexerror("hexadecimal digit expected 'x"+((char)c1)+((char)c2), TK_STRING); |
| return (hexvalue(c1) << 4) + hexvalue(c2); |
| } |
| |
| 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 'x': |
| c = readhexaesc(); |
| break; |
| case '\n': /* go through */ |
| case '\r': |
| save('\n'); |
| inclinenumber(); |
| continue; |
| case EOZ: |
| continue; /* will raise an error next loop */ |
| case 'z': /* zap following span of spaces */ |
| nextChar(); /* skip the 'z' */ |
| while (isspace(current)) { |
| if (currIsNewline()) inclinenumber(); |
| else nextChar(); |
| } |
| continue; |
| case '\\': |
| case '"': |
| case '\'': |
| save_and_next(); |
| continue; |
| default: |
| if (!isdigit(current)) |
| lexerror("invalid escape sequence near '\\" + ((char) current) + "'", 0); |
| else { /* \xxx */ |
| int i = 0; |
| c = 0; |
| do { |
| c = 10 * c + (current - '0'); |
| nextChar(); |
| } while (++i < 3 && isdigit(current)); |
| if (c > UCHAR_MAX) |
| lexerror("decimal escape too large near '\\" + c + "'", 0); |
| save(c); |
| } |
| continue; |
| } |
| save(c); |
| nextChar(); |
| continue; |
| } |
| default: |
| save_and_next(); |
| } |
| } |
| save_and_next(); /* skip delimiter */ |
| seminfo.ts = L.newTString(LuaString.valueOf(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 ':': { |
| nextChar(); |
| if (current != ':') |
| return ':'; |
| else { |
| nextChar(); |
| return TK_DBCOLON; |
| } |
| } |
| 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 '0': case '1': case '2': case '3': case '4': |
| case '5': case '6': case '7': case '8': case '9': { |
| read_numeral(seminfo); |
| return TK_NUMBER; |
| } |
| case EOZ: { |
| return TK_EOS; |
| } |
| default: { |
| if (isspace(current)) { |
| LuaC._assert (!currIsNewline()); |
| nextChar(); |
| continue; |
| } else if (isdigit(current)) { |
| read_numeral(seminfo); |
| return TK_NUMBER; |
| } else if (isalpha(current) || current == '_') { |
| /* identifier or reserved word */ |
| LuaString 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() { |
| LuaC._assert (lookahead.token == TK_EOS); |
| lookahead.token = llex(lookahead.seminfo); |
| } |
| |
| // ============================================================= |
| // from lcode.h |
| // ============================================================= |
| |
| |
| // ============================================================= |
| // from lparser.c |
| // ============================================================= |
| |
| static final boolean vkisvar(final int k) { |
| return (VLOCAL <= (k) && (k) <= VINDEXED); |
| } |
| |
| static final boolean vkisinreg(final int k) { |
| return ((k) == VNONRELOC || (k) == VLOCAL); |
| } |
| |
| static class expdesc { |
| int k; // expkind, from enumerated list, above |
| static class U { // originally a union |
| short ind_idx; // index (R/K) |
| short ind_t; // table(register or upvalue) |
| short ind_vt; // whether 't' is register (VLOCAL) or (UPVALUE) |
| private LuaValue _nval; |
| int info; |
| public void setNval(LuaValue r) { |
| _nval = r; |
| } |
| public LuaValue nval() { |
| return (_nval == null? LuaInteger.valueOf(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.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.f.i = other.f.i; |
| this.k = other.k; |
| this.t.i = other.t.i; |
| this.u._nval = other.u._nval; |
| this.u.ind_idx = other.u.ind_idx; |
| this.u.ind_t = other.u.ind_t; |
| this.u.ind_vt = other.u.ind_vt; |
| this.u.info = other.u.info; |
| } |
| } |
| |
| |
| /* description of active local variable */ |
| static class Vardesc { |
| final short idx; /* variable index in stack */ |
| Vardesc(int idx) { |
| this.idx = (short) idx; |
| } |
| }; |
| |
| |
| /* description of pending goto statements and label statements */ |
| static class Labeldesc { |
| LuaString name; /* label identifier */ |
| int pc; /* position in code */ |
| int line; /* line where it appeared */ |
| short nactvar; /* local level where it appears in current block */ |
| public Labeldesc(LuaString name, int pc, int line, short nactvar) { |
| this.name = name; |
| this.pc = pc; |
| this.line = line; |
| this.nactvar = nactvar; |
| } |
| }; |
| |
| |
| /* dynamic structures used by the parser */ |
| static class Dyndata { |
| Vardesc[] actvar; /* list of active local variables */ |
| int n_actvar = 0; |
| Labeldesc[] gt; /* list of pending gotos */ |
| int n_gt = 0; |
| Labeldesc[] label; /* list of active labels */ |
| int n_label = 0; |
| }; |
| |
| |
| boolean hasmultret(int k) { |
| return ((k) == VCALL || (k) == VVARARG); |
| } |
| |
| /*---------------------------------------------------------------------- |
| name args description |
| ------------------------------------------------------------------------*/ |
| |
| void anchor_token () { |
| /* last token from outer function must be EOS */ |
| LuaC._assert(fs != null || t.token == TK_EOS); |
| if (t.token == TK_NAME || t.token == TK_STRING) { |
| LuaString ts = t.seminfo.ts; |
| // TODO: is this necessary? |
| L.cachedLuaString(t.seminfo.ts); |
| } |
| } |
| |
| /* semantic error */ |
| void semerror (String msg) { |
| t.token = 0; /* remove 'near to' from final message */ |
| syntaxerror(msg); |
| } |
| |
| 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 + ")")); |
| } |
| } |
| } |
| |
| LuaString str_checkname() { |
| LuaString ts; |
| check(TK_NAME); |
| ts = t.seminfo.ts; |
| next(); |
| return ts; |
| } |
| |
| void codestring(expdesc e, LuaString s) { |
| e.init(VK, fs.stringK(s)); |
| } |
| |
| void checkname(expdesc e) { |
| codestring(e, str_checkname()); |
| } |
| |
| |
| int registerlocalvar(LuaString varname) { |
| FuncState fs = this.fs; |
| Prototype f = fs.f; |
| if (f.locvars == null || fs.nlocvars + 1 > f.locvars.length) |
| f.locvars = LuaC.realloc( f.locvars, fs.nlocvars*2+1 ); |
| f.locvars[fs.nlocvars] = new LocVars(varname,0,0); |
| return fs.nlocvars++; |
| } |
| |
| void new_localvar(LuaString name) { |
| int reg = registerlocalvar(name); |
| fs.checklimit(dyd.n_actvar + 1, FuncState.LUAI_MAXVARS, "local variables"); |
| if (dyd.actvar == null || dyd.n_actvar + 1 > dyd.actvar.length) |
| dyd.actvar = LuaC.realloc(dyd.actvar, Math.max(1, dyd.n_actvar * 2)); |
| dyd.actvar[dyd.n_actvar++] = new Vardesc(reg); |
| } |
| |
| void new_localvarliteral(String v) { |
| LuaString ts = newstring(v); |
| new_localvar(ts); |
| } |
| |
| void adjustlocalvars(int nvars) { |
| FuncState fs = this.fs; |
| fs.nactvar = (short) (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) { |
| LuaString varname = this.str_checkname(); |
| FuncState fs = this.fs; |
| if (FuncState.singlevaraux(fs, varname, var, 1) == VVOID) { /* global name? */ |
| expdesc key = new expdesc(); |
| FuncState.singlevaraux(fs, this.envn, var, 1); /* get environment variable */ |
| LuaC._assert(var.k == VLOCAL || var.k == VUPVAL); |
| this.codestring(key, varname); /* key is variable name */ |
| fs.indexed(var, key); /* env[varname] */ |
| } |
| } |
| |
| 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 closegoto(int g, Labeldesc label) { |
| FuncState fs = this.fs; |
| Labeldesc[] gl = this.dyd.gt; |
| Labeldesc gt = gl[g]; |
| LuaC._assert(gt.name.eq_b(label.name)); |
| if (gt.nactvar < label.nactvar) { |
| LuaString vname = fs.getlocvar(gt.nactvar).varname; |
| String msg = L.pushfstring("<goto " + gt.name + "> at line " |
| + gt.line + " jumps into the scope of local '" |
| + vname.tojstring() + "'"); |
| semerror(msg); |
| } |
| fs.patchlist(gt.pc, label.pc); |
| /* remove goto from pending list */ |
| System.arraycopy(gl, g + 1, gl, g, this.dyd.n_gt - g - 1); |
| gl[--this.dyd.n_gt] = null; |
| } |
| |
| /* |
| ** try to close a goto with existing labels; this solves backward jumps |
| */ |
| boolean findlabel (int g) { |
| int i; |
| BlockCnt bl = fs.bl; |
| Dyndata dyd = this.dyd; |
| Labeldesc gt = dyd.gt[g]; |
| /* check labels in current block for a match */ |
| for (i = bl.firstlabel; i < dyd.n_label; i++) { |
| Labeldesc lb = dyd.label[i]; |
| if (lb.name.eq_b(gt.name)) { /* correct label? */ |
| if (gt.nactvar > lb.nactvar && |
| (bl.upval || dyd.n_label > bl.firstlabel)) |
| fs.patchclose(gt.pc, lb.nactvar); |
| closegoto(g, lb); /* close it */ |
| return true; |
| } |
| } |
| return false; /* label not found; cannot close goto */ |
| } |
| |
| /* Caller must LuaC.grow() the vector before calling this. */ |
| int newlabelentry(Labeldesc[] l, int index, LuaString name, int line, int pc) { |
| l[index] = new Labeldesc(name, pc, line, fs.nactvar); |
| return index; |
| } |
| |
| /* |
| ** check whether new label 'lb' matches any pending gotos in current |
| ** block; solves forward jumps |
| */ |
| void findgotos (Labeldesc lb) { |
| Labeldesc[] gl = dyd.gt; |
| int i = fs.bl.firstgoto; |
| while (i < dyd.n_gt) { |
| if (gl[i].name.eq_b(lb.name)) |
| closegoto(i, lb); |
| else |
| i++; |
| } |
| } |
| |
| |
| /* |
| ** create a label named "break" to resolve break statements |
| */ |
| void breaklabel () { |
| LuaString n = LuaString.valueOf("break"); |
| int l = newlabelentry(dyd.label=LuaC.grow(dyd.label, dyd.n_label+1), dyd.n_label++, n, 0, fs.pc); |
| findgotos(dyd.label[l]); |
| } |
| |
| /* |
| ** generates an error for an undefined 'goto'; choose appropriate |
| ** message when label name is a reserved word (which can only be 'break') |
| */ |
| void undefgoto (Labeldesc gt) { |
| String msg = L.pushfstring(isReservedKeyword(gt.name.tojstring()) |
| ? "<"+gt.name+"> at line "+gt.line+" not inside a loop" |
| : "no visible label '"+gt.name+"' for <goto> at line "+gt.line); |
| semerror(msg); |
| } |
| |
| Prototype addprototype () { |
| Prototype clp; |
| Prototype f = fs.f; /* prototype of current function */ |
| if (f.p == null || fs.np >= f.p.length) { |
| f.p = LuaC.realloc(f.p, Math.max(1, fs.np * 2)); |
| } |
| f.p[fs.np++] = clp = new Prototype(); |
| return clp; |
| } |
| |
| void codeclosure (expdesc v) { |
| FuncState fs = this.fs.prev; |
| v.init(VRELOCABLE, fs.codeABx(LuaC.OP_CLOSURE, 0, fs.np - 1)); |
| fs.exp2nextreg(v); /* fix it at stack top (for GC) */ |
| } |
| |
| void open_func (FuncState fs, BlockCnt bl) { |
| fs.prev = this.fs; /* linked list of funcstates */ |
| fs.ls = this; |
| 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.nups = 0; |
| fs.nlocvars = 0; |
| fs.nactvar = 0; |
| fs.firstlocal = dyd.n_actvar; |
| fs.bl = null; |
| fs.f.source = this.source; |
| fs.f.maxstacksize = 2; /* registers 0/1 are always valid */ |
| fs.enterblock(bl, false); |
| } |
| |
| void close_func() { |
| FuncState fs = this.fs; |
| Prototype f = fs.f; |
| fs.ret(0, 0); /* final return */ |
| fs.leaveblock(); |
| f.code = LuaC.realloc(f.code, fs.pc); |
| f.lineinfo = LuaC.realloc(f.lineinfo, fs.pc); |
| f.k = LuaC.realloc(f.k, fs.nk); |
| f.p = LuaC.realloc(f.p, fs.np); |
| f.locvars = LuaC.realloc(f.locvars, fs.nlocvars); |
| f.upvalues = LuaC.realloc(f.upvalues, fs.nups); |
| LuaC._assert (fs.bl == null); |
| this.fs = fs.prev; |
| // last token read was anchored in defunct function; must reanchor it |
| // ls.anchor_token(); |
| } |
| |
| /*============================================================*/ |
| /* GRAMMAR RULES */ |
| /*============================================================*/ |
| |
| void fieldsel(expdesc v) { |
| /* fieldsel -> ['.' | ':'] NAME */ |
| FuncState fs = this.fs; |
| expdesc key = new expdesc(); |
| fs.exp2anyregup(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.info, rkkey, fs.exp2RK(val)); |
| fs.freereg = (short)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 { |
| LuaC._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); |
| LuaC.SETARG_B(i, luaO_int2fb(cc.na)); /* set initial array size */ |
| LuaC.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; |
| Prototype f = fs.f; |
| int nparams = 0; |
| f.is_vararg = 0; |
| 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(); |
| f.is_vararg = 1; |
| break; |
| } |
| default: this.syntaxerror("<name> or " + LUA_QL("...") + " expected"); |
| } |
| } while ((f.is_vararg==0) && this.testnext(',')); |
| } |
| this.adjustlocalvars(nparams); |
| f.numparams = fs.nactvar; |
| 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(); |
| BlockCnt bl = new BlockCnt(); |
| new_fs.f = addprototype(); |
| new_fs.f.linedefined = line; |
| open_func(new_fs, bl); |
| this.checknext('('); |
| if (needself) { |
| new_localvarliteral("self"); |
| adjustlocalvars(1); |
| } |
| this.parlist(); |
| this.checknext(')'); |
| this.statlist(); |
| new_fs.f.lastlinedefined = this.linenumber; |
| this.check_match(TK_END, TK_FUNCTION, line); |
| this.codeclosure(e); |
| this.close_func(); |
| } |
| |
| int explist(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, int line) { |
| FuncState fs = this.fs; |
| expdesc args = new expdesc(); |
| int base, nparams; |
| switch (this.t.token) { |
| case '(': { /* funcargs -> `(' [ explist1 ] `)' */ |
| this.next(); |
| if (this.t.token == ')') /* arg list is empty? */ |
| args.k = VVOID; |
| else { |
| this.explist(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; |
| } |
| } |
| LuaC._assert (f.k == VNONRELOC); |
| base = f.u.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 = (short)(base+1); /* call remove function and arguments and leaves |
| * (unless changed) one result */ |
| } |
| |
| |
| /* |
| ** {====================================================================== |
| ** Expression parsing |
| ** ======================================================================= |
| */ |
| |
| void primaryexp (expdesc v) { |
| /* primaryexp -> NAME | '(' expr ')' */ |
| switch (t.token) { |
| case '(': { |
| int line = linenumber; |
| this.next(); |
| this.expr(v); |
| this.check_match(')', '(', line); |
| fs.dischargevars(v); |
| return; |
| } |
| case TK_NAME: { |
| singlevar(v); |
| return; |
| } |
| default: { |
| this.syntaxerror("unexpected symbol"); |
| return; |
| } |
| } |
| } |
| |
| |
| void suffixedexp (expdesc v) { |
| /* suffixedexp -> |
| primaryexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs } */ |
| int line = linenumber; |
| primaryexp(v); |
| for (;;) { |
| switch (t.token) { |
| case '.': { /* fieldsel */ |
| this.fieldsel(v); |
| break; |
| } |
| case '[': { /* `[' exp1 `]' */ |
| expdesc key = new expdesc(); |
| fs.exp2anyregup(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, line); |
| break; |
| } |
| case '(': |
| case TK_STRING: |
| case '{': { /* funcargs */ |
| fs.exp2nextreg(v); |
| this.funcargs(v, line); |
| 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!=0, "cannot use " + LUA_QL("...") |
| + " outside a vararg function"); |
| 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.suffixedexp(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) { |
| int line = linenumber; |
| this.next(); |
| this.subexpr(v, UNARY_PRIORITY); |
| fs.prefix(uop, v, line); |
| } 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 line = linenumber; |
| this.next(); |
| fs.infix(op, v); |
| /* read sub-expression with higher priority */ |
| int nextop = this.subexpr(v2, priority[op].right); |
| fs.posfix(op, v, v2, line); |
| op = nextop; |
| } |
| this.leavelevel(); |
| return op; /* return first untreated operator */ |
| } |
| |
| void expr(expdesc v) { |
| this.subexpr(v, 0); |
| } |
| |
| /* }==================================================================== */ |
| |
| |
| |
| /* |
| ** {====================================================================== |
| ** Rules for Statements |
| ** ======================================================================= |
| */ |
| |
| |
| boolean block_follow (boolean withuntil) { |
| switch (t.token) { |
| case TK_ELSE: case TK_ELSEIF: case TK_END: case TK_EOS: |
| return true; |
| case TK_UNTIL: |
| return withuntil; |
| default: return false; |
| } |
| } |
| |
| |
| void block () { |
| /* block -> chunk */ |
| FuncState fs = this.fs; |
| BlockCnt bl = new BlockCnt(); |
| fs.enterblock(bl, false); |
| this.statlist(); |
| 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; |
| short extra = (short) fs.freereg; /* eventual position to save local variable */ |
| boolean conflict = false; |
| for (; lh!=null; lh = lh.prev) { |
| if (lh.v.k == VINDEXED) { |
| /* table is the upvalue/local being assigned now? */ |
| if (lh.v.u.ind_vt == v.k && lh.v.u.ind_t == v.u.info) { |
| conflict = true; |
| lh.v.u.ind_vt = VLOCAL; |
| lh.v.u.ind_t = extra; /* previous assignment will use safe copy */ |
| } |
| /* index is the local being assigned? (index cannot be upvalue) */ |
| if (v.k == VLOCAL && lh.v.u.ind_idx == v.u.info) { |
| conflict = true; |
| lh.v.u.ind_idx = extra; /* previous assignment will use safe copy */ |
| } |
| } |
| } |
| if (conflict) { |
| /* copy upvalue/local value to a temporary (in position 'extra') */ |
| int op = (v.k == VLOCAL) ? Lua.OP_MOVE : Lua.OP_GETUPVAL; |
| fs.codeABC(op, extra, v.u.info, 0); |
| 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.suffixedexp(nv.v); |
| if (nv.v.k != VINDEXED) |
| this.check_conflict(lh, nv.v); |
| this.assignment(nv, nvars+1); |
| } |
| else { /* assignment . `=' explist1 */ |
| int nexps; |
| this.checknext('='); |
| nexps = this.explist(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 gotostat(int pc) { |
| int line = linenumber; |
| LuaString label; |
| int g; |
| if (testnext(TK_GOTO)) |
| label = str_checkname(); |
| else { |
| next(); /* skip break */ |
| label = LuaString.valueOf("break"); |
| } |
| g = newlabelentry(dyd.gt =LuaC.grow(dyd.gt, dyd.n_gt+1), dyd.n_gt++, label, line, pc); |
| findlabel(g); /* close it if label already defined */ |
| } |
| |
| |
| /* skip no-op statements */ |
| void skipnoopstat () { |
| while (t.token == ';' || t.token == TK_DBCOLON) |
| statement(); |
| } |
| |
| |
| void labelstat (LuaString label, int line) { |
| /* label -> '::' NAME '::' */ |
| int l; /* index of new label being created */ |
| fs.checkrepeated(dyd.label, dyd.n_label, label); /* check for repeated labels */ |
| checknext(TK_DBCOLON); /* skip double colon */ |
| /* create new entry for this label */ |
| l = newlabelentry(dyd.label=LuaC.grow(dyd.label, dyd.n_label+1), dyd.n_label++, label, line, fs.pc); |
| skipnoopstat(); /* skip other no-op statements */ |
| if (block_follow(false)) { /* label is last no-op statement in the block? */ |
| /* assume that locals are already out of scope */ |
| dyd.label[l].nactvar = fs.bl.nactvar; |
| } |
| findgotos(dyd.label[l]); |
| } |
| |
| |
| 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.statlist(); |
| this.check_match(TK_UNTIL, TK_REPEAT, line); |
| condexit = this.cond(); /* read condition (inside scope block) */ |
| if (bl2.upval) { /* upvalues? */ |
| fs.patchclose(condexit, bl2.nactvar); |
| } |
| fs.leaveblock(); /* finish scope */ |
| fs.patchlist(condexit, repeat_init); /* close the loop */ |
| 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); |
| if (isnum) /* numeric for? */ |
| endfor = fs.codeAsBx(Lua.OP_FORLOOP, base, NO_JUMP); |
| else { /* generic for */ |
| fs.codeABC(Lua.OP_TFORCALL, base, 0, nvars); |
| fs.fixline(line); |
| endfor = fs.codeAsBx(Lua.OP_TFORLOOP, base + 2, NO_JUMP); |
| } |
| fs.patchlist(endfor, prep + 1); |
| fs.fixline(line); |
| } |
| |
| |
| void fornum(LuaString varname, int line) { |
| /* fornum -> NAME = exp1,exp1[,exp1] forbody */ |
| FuncState fs = this.fs; |
| int base = fs.freereg; |
| this.new_localvarliteral(RESERVED_LOCAL_VAR_FOR_INDEX); |
| this.new_localvarliteral(RESERVED_LOCAL_VAR_FOR_LIMIT); |
| this.new_localvarliteral(RESERVED_LOCAL_VAR_FOR_STEP); |
| this.new_localvar(varname); |
| 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(LuaInteger.valueOf(1))); |
| fs.reserveregs(1); |
| } |
| this.forbody(base, line, 1, true); |
| } |
| |
| |
| void forlist(LuaString indexname) { |
| /* forlist -> NAME {,NAME} IN explist1 forbody */ |
| FuncState fs = this.fs; |
| expdesc e = new expdesc(); |
| int nvars = 4; /* gen, state, control, plus at least one declared var */ |
| int line; |
| int base = fs.freereg; |
| /* create control variables */ |
| this.new_localvarliteral(RESERVED_LOCAL_VAR_FOR_GENERATOR); |
| this.new_localvarliteral(RESERVED_LOCAL_VAR_FOR_STATE); |
| this.new_localvarliteral(RESERVED_LOCAL_VAR_FOR_CONTROL); |
| /* create declared variables */ |
| this.new_localvar(indexname); |
| while (this.testnext(',')) { |
| this.new_localvar(this.str_checkname()); |
| ++nvars; |
| } |
| this.checknext(TK_IN); |
| line = this.linenumber; |
| this.adjust_assign(3, this.explist(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; |
| LuaString 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) */ |
| } |
| |
| |
| void test_then_block(IntPtr escapelist) { |
| /* test_then_block -> [IF | ELSEIF] cond THEN block */ |
| expdesc v = new expdesc(); |
| BlockCnt bl = new BlockCnt(); |
| int jf; /* instruction to skip 'then' code (if condition is false) */ |
| this.next(); /* skip IF or ELSEIF */ |
| expr(v); /* read expression */ |
| this.checknext(TK_THEN); |
| if (t.token == TK_GOTO || t.token == TK_BREAK) { |
| fs.goiffalse(v); /* will jump to label if condition is true */ |
| fs.enterblock(bl, false); /* must enter block before 'goto' */ |
| gotostat(v.t.i); /* handle goto/break */ |
| skipnoopstat(); /* skip other no-op statements */ |
| if (block_follow(false)) { /* 'goto' is the entire block? */ |
| fs.leaveblock(); |
| return; /* and that is it */ |
| } else |
| /* must skip over 'then' part if condition is false */ |
| jf = fs.jump(); |
| } else { /* regular case (not goto/break) */ |
| fs.goiftrue(v); /* skip over block if condition is false */ |
| fs.enterblock(bl, false); |
| jf = v.f.i; |
| } |
| statlist(); /* `then' part */ |
| fs.leaveblock(); |
| if (t.token == TK_ELSE || t.token == TK_ELSEIF) |
| fs.concat(escapelist, fs.jump()); /* must jump over it */ |
| fs.patchtohere(jf); |
| } |
| |
| |
| void ifstat(int line) { |
| IntPtr escapelist = new IntPtr(NO_JUMP); /* exit list for finished parts */ |
| test_then_block(escapelist); /* IF cond THEN block */ |
| while (t.token == TK_ELSEIF) |
| test_then_block(escapelist); /* ELSEIF cond THEN block */ |
| if (testnext(TK_ELSE)) |
| block(); /* `else' part */ |
| check_match(TK_END, TK_IF, line); |
| fs.patchtohere(escapelist.i); /* patch escape list to 'if' end */ |
| } |
| |
| void localfunc() { |
| expdesc b = new expdesc(); |
| FuncState fs = this.fs; |
| this.new_localvar(this.str_checkname()); |
| this.adjustlocalvars(1); |
| this.body(b, false, this.linenumber); |
| /* 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.explist(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 ismethod = false; |
| this.singlevar(v); |
| while (this.t.token == '.') |
| this.fieldsel(v); |
| if (this.t.token == ':') { |
| ismethod = true; |
| this.fieldsel(v); |
| } |
| return ismethod; |
| } |
| |
| |
| 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.suffixedexp(v.v); |
| if (t.token == '=' || t.token == ',') { /* stat -> assignment ? */ |
| v.prev = null; |
| assignment(v, 1); |
| } |
| else { /* stat -> func */ |
| check_condition(v.v.k == VCALL, "syntax error"); |
| LuaC.SETARG_C(fs.getcodePtr(v.v), 1); /* call statement uses no results */ |
| } |
| } |
| |
| void retstat() { |
| /* stat -> RETURN explist */ |
| FuncState fs = this.fs; |
| expdesc e = new expdesc(); |
| int first, nret; /* registers with returned values */ |
| if (block_follow(true) || this.t.token == ';') |
| first = nret = 0; /* return no values */ |
| else { |
| nret = this.explist(e); /* optional return values */ |
| if (hasmultret(e.k)) { |
| fs.setmultret(e); |
| if (e.k == VCALL && nret == 1) { /* tail call? */ |
| LuaC.SET_OPCODE(fs.getcodePtr(e), Lua.OP_TAILCALL); |
| LuaC._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 */ |
| LuaC._assert (nret == fs.freereg - first); |
| } |
| } |
| } |
| fs.ret(first, nret); |
| testnext(';'); /* skip optional semicolon */ |
| } |
| |
| void statement() { |
| int line = this.linenumber; /* may be needed for error messages */ |
| enterlevel(); |
| switch (this.t.token) { |
| case ';': { /* stat -> ';' (empty statement) */ |
| next(); /* skip ';' */ |
| break; |
| } |
| case TK_IF: { /* stat -> ifstat */ |
| this.ifstat(line); |
| break; |
| } |
| case TK_WHILE: { /* stat -> whilestat */ |
| this.whilestat(line); |
| break; |
| } |
| case TK_DO: { /* stat -> DO block END */ |
| this.next(); /* skip DO */ |
| this.block(); |
| this.check_match(TK_END, TK_DO, line); |
| break; |
| } |
| case TK_FOR: { /* stat -> forstat */ |
| this.forstat(line); |
| break; |
| } |
| case TK_REPEAT: { /* stat -> repeatstat */ |
| this.repeatstat(line); |
| break; |
| } |
| case TK_FUNCTION: { |
| this.funcstat(line); /* stat -> funcstat */ |
| break; |
| } |
| case TK_LOCAL: { /* stat -> localstat */ |
| this.next(); /* skip LOCAL */ |
| if (this.testnext(TK_FUNCTION)) /* local function? */ |
| this.localfunc(); |
| else |
| this.localstat(); |
| break; |
| } |
| case TK_DBCOLON: { /* stat -> label */ |
| next(); /* skip double colon */ |
| labelstat(str_checkname(), line); |
| break; |
| } |
| case TK_RETURN: { /* stat -> retstat */ |
| next(); /* skip RETURN */ |
| this.retstat(); |
| break; |
| } |
| case TK_BREAK: |
| case TK_GOTO: { /* stat -> breakstat */ |
| this.gotostat(fs.jump()); |
| break; |
| } |
| default: { |
| this.exprstat(); |
| break; |
| } |
| } |
| LuaC._assert(fs.f.maxstacksize >= fs.freereg |
| && fs.freereg >= fs.nactvar); |
| fs.freereg = fs.nactvar; /* free registers */ |
| leavelevel(); |
| } |
| |
| void statlist() { |
| /* statlist -> { stat [`;'] } */ |
| while (!block_follow(true)) { |
| if (t.token == TK_RETURN) { |
| statement(); |
| return; /* 'return' must be last statement */ |
| } |
| statement(); |
| } |
| } |
| |
| /* |
| ** compiles the main function, which is a regular vararg function with an |
| ** upvalue named LUA_ENV |
| */ |
| public void mainfunc(FuncState funcstate) { |
| BlockCnt bl = new BlockCnt(); |
| open_func(funcstate, bl); |
| fs.f.is_vararg = 1; /* main function is always vararg */ |
| expdesc v = new expdesc(); |
| v.init(VLOCAL, 0); /* create and... */ |
| fs.newupvalue(envn, v); /* ...set environment upvalue */ |
| next(); /* read first token */ |
| statlist(); /* parse main body */ |
| check(TK_EOS); |
| close_func(); |
| } |
| |
| /* }====================================================================== */ |
| |
| } |