commonMain.org.luaj.vm2.compiler.LexState.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of luak Show documentation
Show all versions of luak Show documentation
Multiplatform Kotlin LuaJ port (LUA interpreter)
/*******************************************************************************
* 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.vm2.compiler
import org.luaj.vm2.LocVars
import org.luaj.vm2.Lua
import org.luaj.vm2.LuaError
import org.luaj.vm2.LuaInteger
import org.luaj.vm2.LuaString
import org.luaj.vm2.LuaValue
import org.luaj.vm2.Prototype
import org.luaj.vm2.compiler.FuncState.BlockCnt
import org.luaj.vm2.internal.*
import org.luaj.vm2.io.*
import org.luaj.vm2.lib.MathLib
import kotlin.js.*
import kotlin.math.*
@Suppress("MemberVisibilityCanBePrivate")
class LexState(internal var L: LuaC.CompileState, internal var z: LuaBinInput /* input stream */) : Constants() {
internal var current: Int = 0 /* current character (charint) */
internal var linenumber: Int = 0 /* input line counter */
internal var lastline: Int = 0 /* line of last token `consumed' */
internal val t = Token() /* current token */
@JsName("lookahead_field")
internal val lookahead = Token() /* look ahead token */
internal var fs: FuncState? = null /* `FuncState' is private to the parser */
internal var buff: CharArray? = null /* buffer for tokens */
internal var nbuff: Int = 0 /* length of buffer */
internal var dyd = Dyndata() /* dynamic structures used by the parser */
internal lateinit var source: LuaString /* current source name */
internal lateinit var envn: LuaString /* environment variable name */
internal var decpoint: Byte = 0 /* locale decimal point */
/* semantics information */
class SemInfo {
internal var r: LuaValue? = null
internal var ts: LuaString? = null
}
class Token {
internal var token: Int = 0
internal val seminfo = SemInfo()
fun set(other: Token) {
this.token = other.token
this.seminfo.r = other.seminfo.r
this.seminfo.ts = other.seminfo.ts
}
}
private fun isalnum(c: Int): Boolean {
return (c >= '0'.toInt() && c <= '9'.toInt()
|| c >= 'a'.toInt() && c <= 'z'.toInt()
|| c >= 'A'.toInt() && c <= 'Z'.toInt()
|| c == '_'.toInt())
// return Character.isLetterOrDigit(c);
}
private fun isalpha(c: Int): Boolean {
return c >= 'a'.toInt() && c <= 'z'.toInt() || c >= 'A'.toInt() && c <= 'Z'.toInt()
}
private fun isdigit(c: Int): Boolean {
return c >= '0'.toInt() && c <= '9'.toInt()
}
private fun isxdigit(c: Int): Boolean {
return (c >= '0'.toInt() && c <= '9'.toInt()
|| c >= 'a'.toInt() && c <= 'f'.toInt()
|| c >= 'A'.toInt() && c <= 'F'.toInt())
}
private fun isspace(c: Int): Boolean {
return c <= ' '.toInt()
}
init {
this.buff = CharArray(32)
}
internal fun nextChar() {
try {
current = z.read().toInt()
} catch (e: IOException) {
e.printStackTrace()
current = EOZ
}
}
internal fun currIsNewline(): Boolean {
return current == '\n'.toInt() || current == '\r'.toInt()
}
internal fun save_and_next() {
save(current)
nextChar()
}
internal fun save(c: Int) {
if (buff == null || nbuff + 1 > buff!!.size)
buff = Constants.realloc(buff, nbuff * 2 + 1)
buff!![nbuff++] = c.toChar()
}
internal fun token2str(token: Int): String {
return if (token < FIRST_RESERVED) {
if (iscntrl(token))
L.pushfstring("char($token)")
else
L.pushfstring(token.toChar().toString())
} else {
luaX_tokens[token - FIRST_RESERVED]
}
}
internal fun txtToken(token: Int): String {
when (token) {
TK_NAME, TK_STRING, TK_NUMBER -> return buff!!.concatToString(0, nbuff)
else -> return token2str(token)
}
}
internal fun lexerror(msg: String, token: Int) {
val cid = Lua.chunkid(source.tojstring())
L.pushfstring("$cid:$linenumber: $msg")
if (token != 0)
L.pushfstring("syntax error: " + msg + " near " + txtToken(token))
throw LuaError("$cid:$linenumber: $msg")
}
internal fun syntaxerror(msg: String) {
lexerror(msg, t.token)
}
// only called by new_localvarliteral() for var names.
internal fun newstring(s: String): LuaString {
return L.newTString(s)
}
internal fun newstring(chars: CharArray?, offset: Int, len: Int): LuaString {
return L.newTString(chars!!.concatToString(offset, offset + len))
}
internal fun inclinenumber() {
val old = current
Constants._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")
}
internal fun setinput(L: LuaC.CompileState, firstByte: Int, z: LuaBinInput, source: LuaString) {
this.decpoint = '.'.toByte()
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 fun skipShebang() {
if (current == '#'.toInt())
while (!currIsNewline() && current != EOZ)
nextChar()
}
/*
** =======================================================
** LEXICAL ANALYZER
** =======================================================
*/
internal fun check_next(set: String): Boolean {
if (set.indexOf(current.toChar()) < 0)
return false
save_and_next()
return true
}
internal fun buffreplace(from: Char, to: Char) {
var n = nbuff
val p = buff
while (--n >= 0)
if (p!![n] == from)
p[n] = to
}
@UseExperimental(ExperimentalStdlibApi::class)
internal fun strx2number(str: String, seminfo: SemInfo): LuaValue {
val c = str.toCharArray()
var s = 0
while (s < c.size && isspace(c[s].toInt()))
++s
// Check for negative sign
var sgn = 1.0
if (s < c.size && c[s] == '-') {
sgn = -1.0
++s
}
/* Check for "0x" */
if (s + 2 >= c.size)
return LuaValue.ZERO
if (c[s++] != '0')
return LuaValue.ZERO
if (c[s] != 'x' && c[s] != 'X')
return LuaValue.ZERO
++s
// read integer part.
var m = 0.0
var e = 0
while (s < c.size && isxdigit(c[s].toInt()))
m = m * 16 + hexvalue(c[s++].toInt())
if (s < c.size && c[s] == '.') {
++s // skip dot
while (s < c.size && isxdigit(c[s].toInt())) {
m = m * 16 + hexvalue(c[s++].toInt())
e -= 4 // Each fractional part shifts right by 2^4
}
}
if (s < c.size && (c[s] == 'p' || c[s] == 'P')) {
++s
var exp1 = 0
var neg1 = false
if (s < c.size && c[s] == '-') {
neg1 = true
++s
}
while (s < c.size && isdigit(c[s].toInt()))
exp1 = exp1 * 10 + c[s++].toInt() - '0'.toInt()
if (neg1)
exp1 = -exp1
e += exp1
}
return LuaValue.valueOf(sgn * m * MathLib.dpow_d(2.0, e.toDouble()))
}
internal fun str2d(str: String, seminfo: SemInfo): Boolean {
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((str.trim { it <= ' ' }).toDouble())
return true
}
internal fun read_numeral(seminfo: SemInfo) {
var expo = "Ee"
val first = current
Constants._assert(isdigit(current))
save_and_next()
if (first == '0'.toInt() && check_next("Xx"))
expo = "Pp"
while (true) {
if (check_next(expo))
check_next("+-")
if (isxdigit(current) || current == '.'.toInt())
save_and_next()
else
break
}
save('\u0000'.toInt())
val str = buff!!.concatToString(0, nbuff)
str2d(str, seminfo)
}
internal fun skip_sep(): Int {
var count = 0
val s = current
Constants._assert(s == '['.toInt() || s == ']'.toInt())
save_and_next()
while (current == '='.toInt()) {
save_and_next()
count++
}
return if (current == s) count else -count - 1
}
internal fun read_long_string(seminfo: SemInfo?, sep: Int) {
var cont = 0
save_and_next() /* skip 2nd `[' */
if (currIsNewline())
/* string starts with a newline? */
inclinenumber() /* skip it */
var endloop = false
loop@while (!endloop) {
when (current) {
EOZ -> lexerror(
if (seminfo != null)
"unfinished long string"
else
"unfinished long comment", TK_EOS
)
'['.toInt() -> {
if (skip_sep() == sep) {
save_and_next() /* skip 2nd `[' */
cont++
if (LUA_COMPAT_LSTR == 1) {
if (sep == 0)
lexerror("nesting of [[...]] is deprecated", '['.toInt())
}
}
}
']'.toInt() -> {
if (skip_sep() == sep) {
save_and_next() /* skip 2nd `]' */
if (LUA_COMPAT_LSTR == 2) {
cont--
if (sep == 0 && cont >= 0)
break@loop
}
endloop = true
}
}
'\n'.toInt(), '\r'.toInt() -> {
save('\n'.toInt())
inclinenumber()
if (seminfo == null)
nbuff = 0 /* avoid wasting space */
}
else -> {
if (seminfo != null)
save_and_next()
else
nextChar()
}
}/* to avoid warnings */
}
if (seminfo != null)
seminfo.ts = L.newTString(LuaString.valueOf(buff!!, 2 + sep, nbuff - 2 * (2 + sep)))
}
internal fun hexvalue(c: Int): Int {
return if (c <= '9'.toInt()) c - '0'.toInt() else if (c <= 'F'.toInt()) c + 10 - 'A'.toInt() else c + 10 - 'a'.toInt()
}
internal fun readhexaesc(): Int {
nextChar()
val c1 = current
nextChar()
val c2 = current
if (!isxdigit(c1) || !isxdigit(c2))
lexerror("hexadecimal digit expected 'x" + c1.toChar() + c2.toChar(), TK_STRING)
return (hexvalue(c1) shl 4) + hexvalue(c2)
}
internal fun read_string(del: Int, seminfo: SemInfo) {
save_and_next()
loop@while (current != del) {
when (current) {
EOZ -> {
lexerror("unfinished string", TK_EOS)
continue@loop /* to avoid warnings */
}
'\n'.toInt(), '\r'.toInt() -> {
lexerror("unfinished string", TK_STRING)
continue@loop /* to avoid warnings */
}
'\\'.toInt() -> {
var c: Int
nextChar() /* do not save the `\' */
when (current) {
'a'.toInt() /* bell */ -> c = '\u0007'.toInt()
'b'.toInt() /* backspace */ -> c = '\b'.toInt()
//'f'.toInt() /* form feed */ -> c = '\f'.toInt()
'f'.toInt() /* form feed */ -> c = '\u000c'.toInt()
'n'.toInt() /* newline */ -> c = '\n'.toInt()
'r'.toInt() /* carriage return */ -> c = '\r'.toInt()
't'.toInt() /* tab */ -> c = '\t'.toInt()
'v'.toInt() /* vertical tab */ -> c = '\u000B'.toInt()
'x'.toInt() -> c = readhexaesc()
'\n'.toInt() /* go through */, '\r'.toInt() -> {
save('\n'.toInt())
inclinenumber()
continue@loop
}
EOZ -> continue@loop /* will raise an error next loop */
'z'.toInt() -> { /* zap following span of spaces */
nextChar() /* skip the 'z' */
while (isspace(current)) {
if (currIsNewline())
inclinenumber()
else
nextChar()
}
continue@loop
}
else -> {
if (!isdigit(current))
save_and_next() /* handles \\, \", \', and \? */
else { /* \xxx */
var i = 0
c = 0
do {
c = 10 * c + (current - '0'.toInt())
nextChar()
} while (++i < 3 && isdigit(current))
if (c > UCHAR_MAX)
lexerror("escape sequence too large", TK_STRING)
save(c)
}
continue@loop
}
}
save(c)
nextChar()
continue@loop
}
else -> save_and_next()
}
}
save_and_next() /* skip delimiter */
seminfo.ts = L.newTString(LuaString.valueOf(buff!!, 1, nbuff - 2))
}
internal fun llex(seminfo: SemInfo): Int {
nbuff = 0
loop@while (true) {
when (current) {
'\n'.toInt(), '\r'.toInt() -> {
inclinenumber()
continue@loop
}
'-'.toInt() -> {
nextChar()
if (current != '-'.toInt())
return '-'.toInt()
/* else is a comment */
nextChar()
if (current == '['.toInt()) {
val sep = skip_sep()
nbuff = 0 /* `skip_sep' may dirty the buffer */
if (sep >= 0) {
read_long_string(null, sep) /* long comment */
nbuff = 0
continue@loop
}
}
/* else short comment */
while (!currIsNewline() && current != EOZ)
nextChar()
continue@loop
}
'['.toInt() -> {
run {
val sep = skip_sep()
if (sep >= 0) {
read_long_string(seminfo, sep)
return TK_STRING
} else if (sep == -1)
return '['.toInt()
else
lexerror("invalid long string delimiter", TK_STRING)
}
run {
nextChar()
if (current != '='.toInt())
return '='.toInt()
else {
nextChar()
return TK_EQ
}
}
}
'='.toInt() -> {
nextChar()
if (current != '='.toInt())
return '='.toInt()
else {
nextChar()
return TK_EQ
}
}
'<'.toInt() -> {
nextChar()
if (current != '='.toInt())
return '<'.toInt()
else {
nextChar()
return TK_LE
}
}
'>'.toInt() -> {
nextChar()
if (current != '='.toInt())
return '>'.toInt()
else {
nextChar()
return TK_GE
}
}
'~'.toInt() -> {
nextChar()
if (current != '='.toInt())
return '~'.toInt()
else {
nextChar()
return TK_NE
}
}
':'.toInt() -> {
nextChar()
if (current != ':'.toInt())
return ':'.toInt()
else {
nextChar()
return TK_DBCOLON
}
}
'"'.toInt(), '\''.toInt() -> {
read_string(current, seminfo)
return TK_STRING
}
'.'.toInt() -> {
save_and_next()
if (check_next(".")) {
return if (check_next("."))
TK_DOTS /* ... */
else
TK_CONCAT /* .. */
} else if (!isdigit(current))
return '.'.toInt()
else {
read_numeral(seminfo)
return TK_NUMBER
}
}
'0'.toInt(), '1'.toInt(), '2'.toInt(), '3'.toInt(), '4'.toInt(), '5'.toInt(), '6'.toInt(), '7'.toInt(), '8'.toInt(), '9'.toInt() -> {
read_numeral(seminfo)
return TK_NUMBER
}
EOZ -> {
return TK_EOS
}
else -> {
if (isspace(current)) {
Constants._assert(!currIsNewline())
nextChar()
continue@loop
} else if (isdigit(current)) {
read_numeral(seminfo)
return TK_NUMBER
} else if (isalpha(current) || current == '_'.toInt()) {
/* identifier or reserved word */
val ts: LuaString
do {
save_and_next()
} while (isalnum(current) || current == '_'.toInt())
ts = newstring(buff, 0, nbuff)
if (RESERVED.containsKey(ts))
return (RESERVED.get(ts) as Int).toInt()
else {
seminfo.ts = ts
return TK_NAME
}
} else {
val c = current
nextChar()
return c /* single-char tokens (+ - / ...) */
}
}
}
}
}
internal operator fun 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 */
}
internal fun lookahead() {
Constants._assert(lookahead.token == TK_EOS)
lookahead.token = llex(lookahead.seminfo)
}
class expdesc {
internal var k: Int = 0 // expkind, from enumerated list, above
internal val u = U()
internal val t = IntPtr() /* patch list of `exit when true' */
internal val f = IntPtr() /* patch list of `exit when false' */
internal class U { // originally a union
var ind_idx: Short = 0 // index (R/K)
var ind_t: Short = 0 // table(register or upvalue)
var ind_vt: Short = 0 // whether 't' is register (VLOCAL) or (UPVALUE)
var _nval: LuaValue? = null
var info: Int = 0
fun setNval(r: LuaValue?) {
_nval = r
}
fun nval(): LuaValue {
return if (_nval == null) LuaInteger.valueOf(info) else _nval!!
}
}
internal fun init(k: Int, i: Int) {
this.f.i = NO_JUMP
this.t.i = NO_JUMP
this.k = k
this.u.info = i
}
internal fun hasjumps(): Boolean {
return t.i != f.i
}
internal fun isnumeral(): Boolean {
return k == VKNUM && t.i == NO_JUMP && f.i == NO_JUMP
}
fun setvalue(other: expdesc) {
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 */
class Vardesc internal constructor(idx: Int) {
internal val idx: Short /* variable index in stack */
init {
this.idx = idx.toShort()
}
}
/* description of pending goto statements and label statements */
class Labeldesc(
internal var name: LuaString? /* label identifier */,
internal var pc: Int /* position in code */,
internal var line: Int /* line where it appeared */,
internal var nactvar: Short /* local level where it appears in current block */
)
/* dynamic structures used by the parser */
internal class Dyndata {
var actvar: Array? = null /* list of active local variables */
var n_actvar = 0
var gt: Array? = null /* list of pending gotos */
var n_gt = 0
var label: Array? = null /* list of active labels */
var n_label = 0
}
internal fun hasmultret(k: Int): Boolean {
return k == VCALL || k == VVARARG
}
/*----------------------------------------------------------------------
name args description
------------------------------------------------------------------------*/
internal fun anchor_token() {
/* last token from outer function must be EOS */
Constants._assert(fs != null || t.token == TK_EOS)
if (t.token == TK_NAME || t.token == TK_STRING) {
val ts = t.seminfo.ts
// TODO: is this necessary?
L.cachedLuaString(t.seminfo.ts!!)
}
}
/* semantic error */
internal fun semerror(msg: String) {
t.token = 0 /* remove 'near to' from final message */
syntaxerror(msg)
}
internal fun error_expected(token: Int) {
syntaxerror(L.pushfstring(LUA_QS(token2str(token)) + " expected"))
}
internal fun testnext(c: Int): Boolean {
if (t.token == c) {
next()
return true
} else
return false
}
internal fun check(c: Int) {
if (t.token != c)
error_expected(c)
}
internal fun checknext(c: Int) {
check(c)
next()
}
internal fun check_condition(c: Boolean, msg: String) {
if (!c)
syntaxerror(msg)
}
internal fun check_match(what: Int, who: Int, where: Int) {
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 + ")"
)
)
}
}
}
internal fun str_checkname(): LuaString? {
val ts: LuaString?
check(TK_NAME)
ts = t.seminfo.ts
next()
return ts
}
internal fun codestring(e: expdesc, s: LuaString?) {
e.init(VK, fs!!.stringK(s!!))
}
internal fun checkname(e: expdesc) {
codestring(e, str_checkname())
}
internal fun registerlocalvar(varname: LuaString?): Int {
val fs = this.fs
val f = fs!!.f
if (f!!.locvars == null || fs.nlocvars + 1 > f.locvars.size)
f.locvars = Constants.realloc(f.locvars, fs.nlocvars * 2 + 1)
f.locvars[fs.nlocvars.toInt()] = LocVars(varname!!, 0, 0)
return fs.nlocvars++.toInt()
}
internal fun new_localvar(name: LuaString?) {
val reg = registerlocalvar(name)
fs!!.checklimit(dyd.n_actvar + 1, Constants.LUAI_MAXVARS, "local variables")
if (dyd.actvar == null || dyd.n_actvar + 1 > dyd.actvar!!.size)
dyd.actvar = Constants.realloc(dyd.actvar, max(1, dyd.n_actvar * 2))
dyd.actvar!![dyd.n_actvar++] = Vardesc(reg)
}
internal fun new_localvarliteral(v: String) {
val ts = newstring(v)
new_localvar(ts)
}
internal fun adjustlocalvars(nvars: Int) {
var nvars = nvars
val fs = this.fs
fs!!.nactvar = (fs.nactvar + nvars).toShort()
while (nvars > 0) {
fs.getlocvar(fs.nactvar - nvars).startpc = fs.pc
nvars--
}
}
internal fun removevars(tolevel: Int) {
val fs = this.fs
while (fs!!.nactvar > tolevel)
fs.getlocvar((--fs.nactvar).toInt()).endpc = fs.pc
}
internal fun singlevar(`var`: expdesc) {
val varname = this.str_checkname()
val fs = this.fs
if (FuncState.singlevaraux(fs, varname!!, `var`, 1) == VVOID) { /* global name? */
val key = expdesc()
FuncState.singlevaraux(fs, this.envn, `var`, 1) /* get environment variable */
Constants._assert(`var`.k == VLOCAL || `var`.k == VUPVAL)
this.codestring(key, varname) /* key is variable name */
fs!!.indexed(`var`, key) /* env[varname] */
}
}
internal fun adjust_assign(nvars: Int, nexps: Int, e: expdesc) {
val fs = this.fs
var 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) {
val reg = fs!!.freereg.toInt()
fs.reserveregs(extra)
fs.nil(reg, extra)
}
}
}
internal fun enterlevel() {
if (++L.nCcalls > LUAI_MAXCCALLS)
lexerror("chunk has too many syntax levels", 0)
}
internal fun leavelevel() {
L.nCcalls--
}
internal fun closegoto(g: Int, label: Labeldesc) {
val fs = this.fs
val gl = this.dyd.gt
val gt = gl!![g]!!
Constants._assert(gt.name!!.eq_b(label.name!!))
if (gt.nactvar < label.nactvar) {
val vname = fs!!.getlocvar(gt.nactvar.toInt()).varname
val msg = L.pushfstring(
" 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 */
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
*/
internal fun findlabel(g: Int): Boolean {
var i: Int
val bl = fs!!.bl
val dyd = this.dyd
val gt = dyd.gt!![g]!!
/* check labels in current block for a match */
i = bl!!.firstlabel.toInt()
while (i < dyd.n_label) {
val 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.toInt())
closegoto(g, lb) /* close it */
return true
}
i++
}
return false /* label not found; cannot close goto */
}
/* Caller must grow() the vector before calling this. */
internal fun newlabelentry(l: Array, index: Int, name: LuaString?, line: Int, pc: Int): Int {
l[index] = Labeldesc(name, pc, line, fs!!.nactvar)
return index
}
/*
** check whether new label 'lb' matches any pending gotos in current
** block; solves forward jumps
*/
internal fun findgotos(lb: Labeldesc) {
val gl = dyd.gt
var i = fs!!.bl!!.firstgoto.toInt()
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
*/
internal fun breaklabel() {
val n = LuaString.valueOf("break")
val l = newlabelentry(run {
dyd.label = Constants.grow(dyd.label, dyd.n_label + 1)
dyd.label!! as Array
}, 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')
*/
internal fun undefgoto(gt: Labeldesc) {
val msg = L.pushfstring(
if (isReservedKeyword(gt.name!!.tojstring()))
"<" + gt.name + "> at line " + gt.line + " not inside a loop"
else
"no visible label '" + gt.name + "' for at line " + gt.line
)
semerror(msg)
}
internal fun addprototype(): Prototype {
val clp: Prototype
val f = fs!!.f /* prototype of current function */
if (f!!.p == null || fs!!.np >= f.p.size) {
f.p = Constants.realloc(f.p, max(1, fs!!.np * 2))
}
clp = Prototype()
f.p[fs!!.np++] = clp
return clp
}
internal fun codeclosure(v: expdesc) {
val fs = this.fs!!.prev
v.init(VRELOCABLE, fs!!.codeABx(Lua.OP_CLOSURE, 0, fs.np - 1))
fs.exp2nextreg(v) /* fix it at stack top (for GC) */
}
internal fun open_func(fs: FuncState, bl: BlockCnt) {
fs.prev = this.fs /* linked list of funcstates */
fs.ls = this
this.fs = fs
fs.pc = 0
fs.lasttarget = -1
fs.jpc = 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)
}
internal fun close_func() {
val fs = this.fs
val f = fs!!.f
fs.ret(0, 0) /* final return */
fs.leaveblock()
f!!.code = Constants.realloc(f.code, fs.pc)
f.lineinfo = Constants.realloc(f.lineinfo, fs.pc)
f.k = Constants.realloc(f.k, fs.nk)
f.p = Constants.realloc(f.p, fs.np)
f.locvars = Constants.realloc(f.locvars, fs.nlocvars.toInt())
f.upvalues = Constants.realloc(f.upvalues, fs.nups.toInt())
Constants._assert(fs.bl == null)
this.fs = fs.prev
// last token read was anchored in defunct function; must reanchor it
// ls.anchor_token();
}
/*============================================================*/
/* GRAMMAR RULES */
/*============================================================*/
internal fun fieldsel(v: expdesc) {
/* fieldsel -> ['.' | ':'] NAME */
val fs = this.fs
val key = expdesc()
fs!!.exp2anyregup(v)
this.next() /* skip the dot or colon */
this.checkname(key)
fs.indexed(v, key)
}
internal fun yindex(v: expdesc) {
/* index -> '[' expr ']' */
this.next() /* skip the '[' */
this.expr(v)
this.fs!!.exp2val(v)
this.checknext(']'.toInt())
}
/*
** {======================================================================
** Rules for Constructors
** =======================================================================
*/
class ConsControl {
internal var v = expdesc() /* last list item read */
internal var t: expdesc? = null /* table descriptor */
internal var nh: Int = 0 /* total number of `record' elements */
internal var na: Int = 0 /* total number of array elements */
internal var tostore: Int = 0 /* number of array elements pending to be stored */
}
internal fun recfield(cc: ConsControl) {
/* recfield -> (NAME | `['exp1`]') = exp1 */
val fs = this.fs
val reg = this.fs!!.freereg.toInt()
val key = expdesc()
val `val` = expdesc()
val rkkey: Int
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('='.toInt())
rkkey = fs!!.exp2RK(key)
this.expr(`val`)
fs.codeABC(Lua.OP_SETTABLE, cc.t!!.u.info, rkkey, fs.exp2RK(`val`))
fs.freereg = reg.toShort() /* free registers */
}
internal fun listfield(cc: ConsControl) {
this.expr(cc.v)
fs!!.checklimit(cc.na, MAX_INT, "items in a constructor")
cc.na++
cc.tostore++
}
internal fun constructor(t: expdesc) {
/* constructor -> ?? */
val fs = this.fs
val line = this.linenumber
val pc = fs!!.codeABC(Lua.OP_NEWTABLE, 0, 0, 0)
val cc = ConsControl()
cc.tostore = 0
cc.nh = cc.tostore
cc.na = cc.nh
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('{'.toInt())
do {
Constants._assert(cc.v.k == VVOID || cc.tostore > 0)
if (this.t.token == '}'.toInt())
break
fs.closelistfield(cc)
when (this.t.token) {
TK_NAME -> { /* may be listfields or recfields */
this.lookahead()
if (this.lookahead.token != '='.toInt())
/* expression? */
this.listfield(cc)
else
this.recfield(cc)
}
'['.toInt() -> { /* constructor_item -> recfield */
this.recfield(cc)
}
else -> { /* constructor_part -> listfield */
this.listfield(cc)
}
}
} while (this.testnext(','.toInt()) || this.testnext(';'.toInt()))
this.check_match('}'.toInt(), '{'.toInt(), line)
fs.lastlistfield(cc)
val i = InstructionPtr(fs.f!!.code, pc)
Constants.SETARG_B(i, luaO_int2fb(cc.na)) /* set initial array size */
Constants.SETARG_C(i, luaO_int2fb(cc.nh)) /* set initial table size */
}
/* }====================================================================== */
internal fun parlist() {
/* parlist -> [ param { `,' param } ] */
val fs = this.fs
val f = fs!!.f
var nparams = 0
f!!.is_vararg = 0
if (this.t.token != ')'.toInt()) { /* is `parlist' not empty? */
do {
when (this.t.token) {
TK_NAME -> { /* param . NAME */
this.new_localvar(this.str_checkname())
++nparams
}
TK_DOTS -> { /* param . `...' */
this.next()
f.is_vararg = 1
}
else -> this.syntaxerror(" or " + LUA_QL("...") + " expected")
}
} while (f.is_vararg == 0 && this.testnext(','.toInt()))
}
this.adjustlocalvars(nparams)
f.numparams = fs.nactvar.toInt()
fs.reserveregs(fs.nactvar.toInt()) /* reserve register for parameters */
}
internal fun body(e: expdesc, needself: Boolean, line: Int) {
/* body -> `(' parlist `)' chunk END */
val new_fs = FuncState()
val bl = BlockCnt()
new_fs.f = addprototype()
new_fs.f!!.linedefined = line
open_func(new_fs, bl)
this.checknext('('.toInt())
if (needself) {
new_localvarliteral("self")
adjustlocalvars(1)
}
this.parlist()
this.checknext(')'.toInt())
this.statlist()
new_fs.f!!.lastlinedefined = this.linenumber
this.check_match(TK_END, TK_FUNCTION, line)
this.codeclosure(e)
this.close_func()
}
internal fun explist(v: expdesc): Int {
/* explist1 -> expr { `,' expr } */
var n = 1 /* at least one expression */
this.expr(v)
while (this.testnext(','.toInt())) {
fs!!.exp2nextreg(v)
this.expr(v)
n++
}
return n
}
internal fun funcargs(f: expdesc, line: Int) {
val fs = this.fs
val args = expdesc()
val base: Int
val nparams: Int
when (this.t.token) {
'('.toInt() -> { /* funcargs -> `(' [ explist1 ] `)' */
this.next()
if (this.t.token == ')'.toInt())
/* arg list is empty? */
args.k = VVOID
else {
this.explist(args)
fs!!.setmultret(args)
}
this.check_match(')'.toInt(), '('.toInt(), line)
}
'{'.toInt() -> { /* funcargs -> constructor */
this.constructor(args)
}
TK_STRING -> { /* funcargs -> STRING */
this.codestring(args, this.t.seminfo.ts)
this.next() /* must use `seminfo' before `next' */
}
else -> {
this.syntaxerror("function arguments expected")
return
}
}
Constants._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 = (base + 1).toShort() /* call remove function and arguments and leaves
* (unless changed) one result */
}
/*
** {======================================================================
** Expression parsing
** =======================================================================
*/
internal fun primaryexp(v: expdesc) {
/* primaryexp -> NAME | '(' expr ')' */
when (t.token) {
'('.toInt() -> {
val line = linenumber
this.next()
this.expr(v)
this.check_match(')'.toInt(), '('.toInt(), line)
fs!!.dischargevars(v)
return
}
TK_NAME -> {
singlevar(v)
return
}
else -> {
this.syntaxerror("unexpected symbol " + t.token + " (" + t.token.toChar() + ")")
return
}
}
}
internal fun suffixedexp(v: expdesc) {
/* suffixedexp ->
primaryexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs } */
val line = linenumber
primaryexp(v)
while (true) {
when (t.token) {
'.'.toInt() -> { /* fieldsel */
this.fieldsel(v)
}
'['.toInt() -> { /* `[' exp1 `]' */
val key = expdesc()
fs!!.exp2anyregup(v)
this.yindex(key)
fs!!.indexed(v, key)
}
':'.toInt() -> { /* `:' NAME funcargs */
val key = expdesc()
this.next()
this.checkname(key)
fs!!.self(v, key)
this.funcargs(v, line)
}
'('.toInt(), TK_STRING, '{'.toInt() -> { /* funcargs */
fs!!.exp2nextreg(v)
this.funcargs(v, line)
}
else -> return
}
}
}
internal fun simpleexp(v: expdesc) {
/*
* simpleexp -> NUMBER | STRING | NIL | true | false | ... | constructor |
* FUNCTION body | primaryexp
*/
when (this.t.token) {
TK_NUMBER -> {
v.init(VKNUM, 0)
v.u.setNval(this.t.seminfo.r)
}
TK_STRING -> {
this.codestring(v, this.t.seminfo.ts)
}
TK_NIL -> {
v.init(VNIL, 0)
}
TK_TRUE -> {
v.init(VTRUE, 0)
}
TK_FALSE -> {
v.init(VFALSE, 0)
}
TK_DOTS -> { /* vararg */
val 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))
}
'{'.toInt() -> { /* constructor */
this.constructor(v)
return
}
TK_FUNCTION -> {
this.next()
this.body(v, false, this.linenumber)
return
}
else -> {
this.suffixedexp(v)
return
}
}
this.next()
}
internal fun getunopr(op: Int): Int {
when (op) {
TK_NOT -> return OPR_NOT
'-'.toInt() -> return OPR_MINUS
'#'.toInt() -> return OPR_LEN
else -> return OPR_NOUNOPR
}
}
internal fun getbinopr(op: Int): Int {
when (op) {
'+'.toInt() -> return OPR_ADD
'-'.toInt() -> return OPR_SUB
'*'.toInt() -> return OPR_MUL
'/'.toInt() -> return OPR_DIV
'%'.toInt() -> return OPR_MOD
'^'.toInt() -> return OPR_POW
TK_CONCAT -> return OPR_CONCAT
TK_NE -> return OPR_NE
TK_EQ -> return OPR_EQ
'<'.toInt() -> return OPR_LT
TK_LE -> return OPR_LE
'>'.toInt() -> return OPR_GT
TK_GE -> return OPR_GE
TK_AND -> return OPR_AND
TK_OR -> return OPR_OR
else -> return OPR_NOBINOPR
}
}
internal class Priority(i: Int, j: Int) {
val left: Byte /* left priority for each binary operator */
val right: Byte /* right priority */
init {
left = i.toByte()
right = j.toByte()
}
}
/*
** subexpr -> (simpleexp | unop subexpr) { binop subexpr }
** where `binop' is any binary operator with a priority higher than `limit'
*/
internal fun subexpr(v: expdesc, limit: Int): Int {
var op: Int
val uop: Int
this.enterlevel()
uop = getunopr(this.t.token)
if (uop != OPR_NOUNOPR) {
val 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) {
val v2 = expdesc()
val line = linenumber
this.next()
fs!!.infix(op, v)
/* read sub-expression with higher priority */
val nextop = this.subexpr(v2, priority[op].right.toInt())
fs!!.posfix(op, v, v2, line)
op = nextop
}
this.leavelevel()
return op /* return first untreated operator */
}
internal fun expr(v: expdesc) {
this.subexpr(v, 0)
}
/* }==================================================================== */
/*
** {======================================================================
** Rules for Statements
** =======================================================================
*/
internal fun block_follow(withuntil: Boolean): Boolean {
when (t.token) {
TK_ELSE, TK_ELSEIF, TK_END, TK_EOS -> return true
TK_UNTIL -> return withuntil
else -> return false
}
}
internal fun block() {
/* block -> chunk */
val fs = this.fs
val bl = BlockCnt()
fs!!.enterblock(bl, false)
this.statlist()
fs.leaveblock()
}
/*
** structure to chain all variables in the left-hand side of an
** assignment
*/
internal class LHS_assign {
var prev: LHS_assign? = null
/* variable (global, local, upvalue, or indexed) */
var v = 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.
*/
internal fun check_conflict(lh: LHS_assign?, v: expdesc) {
var lh = lh
val fs = this.fs
val extra = fs!!.freereg /* eventual position to save local variable */
var conflict = false
while (lh != null) {
if (lh.v.k == VINDEXED) {
/* table is the upvalue/local being assigned now? */
if (lh.v.u.ind_vt.toInt() == v.k && lh.v.u.ind_t.toInt() == v.u.info) {
conflict = true
lh.v.u.ind_vt = VLOCAL.toShort()
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.toInt() == v.u.info) {
conflict = true
lh.v.u.ind_idx = extra /* previous assignment will use safe copy */
}
}
lh = lh.prev
}
if (conflict) {
/* copy upvalue/local value to a temporary (in position 'extra') */
val op = if (v.k == VLOCAL) Lua.OP_MOVE else Lua.OP_GETUPVAL
fs.codeABC(op, extra.toInt(), v.u.info, 0)
fs.reserveregs(1)
}
}
internal fun assignment(lh: LHS_assign, nvars: Int) {
val e = expdesc()
this.check_condition(
VLOCAL <= lh.v.k && lh.v.k <= VINDEXED,
"syntax error"
)
if (this.testnext(','.toInt())) { /* assignment -> `,' primaryexp assignment */
val nv = 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 */
val nexps: Int
this.checknext('='.toInt())
nexps = this.explist(e)
if (nexps != nvars) {
this.adjust_assign(nvars, nexps, e)
if (nexps > nvars)
this.fs!!.freereg = (this.fs!!.freereg - (nexps - nvars)).toShort() /* 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)
}
internal fun cond(): Int {
/* cond -> exp */
val v = 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
}
internal fun gotostat(pc: Int) {
val line = linenumber
val label: LuaString?
val g: Int
if (testnext(TK_GOTO))
label = str_checkname()
else {
next() /* skip break */
label = LuaString.valueOf("break")
}
g = newlabelentry(run {
dyd.gt = Constants.grow(dyd.gt as Array?, dyd.n_gt + 1) as Array
dyd.gt!!
}, dyd.n_gt++, label, line, pc)
findlabel(g) /* close it if label already defined */
}
/* skip no-op statements */
internal fun skipnoopstat() {
while (t.token == ';'.toInt() || t.token == TK_DBCOLON)
statement()
}
internal fun labelstat(label: LuaString?, line: Int) {
/* label -> '::' NAME '::' */
val l: Int /* 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(run {
dyd.label = Constants.grow(dyd.label, dyd.n_label + 1)
dyd.label!! as Array
}, 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])
}
internal fun whilestat(line: Int) {
/* whilestat -> WHILE cond DO block END */
val fs = this.fs
val whileinit: Int
val condexit: Int
val bl = 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 */
}
internal fun repeatstat(line: Int) {
/* repeatstat -> REPEAT block UNTIL cond */
val condexit: Int
val fs = this.fs
val repeat_init = fs!!.getlabel()
val bl1 = BlockCnt()
val bl2 = 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.toInt())
}
fs.leaveblock() /* finish scope */
fs.patchlist(condexit, repeat_init) /* close the loop */
fs.leaveblock() /* finish loop */
}
internal fun exp1(): Int {
val e = expdesc()
val k: Int
this.expr(e)
k = e.k
fs!!.exp2nextreg(e)
return k
}
internal fun forbody(base: Int, line: Int, nvars: Int, isnum: Boolean) {
/* forbody -> DO block */
val bl = BlockCnt()
val fs = this.fs
val prep: Int
val endfor: Int
this.adjustlocalvars(3) /* control variables */
this.checknext(TK_DO)
prep = if (isnum) fs!!.codeAsBx(Lua.OP_FORPREP, base, NO_JUMP) else 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)
}
internal fun fornum(varname: LuaString?, line: Int) {
/* fornum -> NAME = exp1,exp1[,exp1] forbody */
val fs = this.fs
val base = fs!!.freereg.toInt()
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('='.toInt())
this.exp1() /* initial value */
this.checknext(','.toInt())
this.exp1() /* limit */
if (this.testnext(','.toInt()))
this.exp1() /* optional step */
else { /* default step = 1 */
fs.codeABx(Lua.OP_LOADK, fs.freereg.toInt(), fs.numberK(LuaInteger.valueOf(1)))
fs.reserveregs(1)
}
this.forbody(base, line, 1, true)
}
internal fun forlist(indexname: LuaString?) {
/* forlist -> NAME {,NAME} IN explist1 forbody */
val fs = this.fs
val e = expdesc()
var nvars = 4 /* gen, state, control, plus at least one declared var */
val line: Int
val base = fs!!.freereg.toInt()
/* 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(','.toInt())) {
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)
}
internal fun forstat(line: Int) {
/* forstat -> FOR (fornum | forlist) END */
val fs = this.fs
val varname: LuaString?
val bl = BlockCnt()
fs!!.enterblock(bl, true) /* scope for loop and control variables */
this.next() /* skip `for' */
varname = this.str_checkname() /* first variable name */
when (this.t.token) {
'='.toInt() -> this.fornum(varname, line)
','.toInt(), TK_IN -> this.forlist(varname)
else -> 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) */
}
internal fun test_then_block(escapelist: IntPtr) {
/* test_then_block -> [IF | ELSEIF] cond THEN block */
val v = expdesc()
val bl = BlockCnt()
val jf: Int /* 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)
}
internal fun ifstat(line: Int) {
val escapelist = 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 */
}
internal fun localfunc() {
val b = expdesc()
val 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
}
internal fun localstat() {
/* stat -> LOCAL NAME {`,' NAME} [`=' explist1] */
var nvars = 0
val nexps: Int
val e = expdesc()
do {
this.new_localvar(this.str_checkname())
++nvars
} while (this.testnext(','.toInt()))
if (this.testnext('='.toInt()))
nexps = this.explist(e)
else {
e.k = VVOID
nexps = 0
}
this.adjust_assign(nvars, nexps, e)
this.adjustlocalvars(nvars)
}
internal fun funcname(v: expdesc): Boolean {
/* funcname -> NAME {field} [`:' NAME] */
var ismethod = false
this.singlevar(v)
while (this.t.token == '.'.toInt())
this.fieldsel(v)
if (this.t.token == ':'.toInt()) {
ismethod = true
this.fieldsel(v)
}
return ismethod
}
internal fun funcstat(line: Int) {
/* funcstat -> FUNCTION funcname body */
val needself: Boolean
val v = expdesc()
val b = 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 */
}
internal fun exprstat() {
/* stat -> func | assignment */
val fs = this.fs
val v = LHS_assign()
this.suffixedexp(v.v)
if (t.token == '='.toInt() || t.token == ','.toInt()) { /* stat -> assignment ? */
v.prev = null
assignment(v, 1)
} else { /* stat -> func */
check_condition(v.v.k == VCALL, "syntax error")
Constants.SETARG_C(fs!!.getcodePtr(v.v), 1) /* call statement uses no results */
}
}
internal fun retstat() {
/* stat -> RETURN explist */
val fs = this.fs
val e = expdesc()
val first: Int
var nret: Int /* registers with returned values */
if (block_follow(true) || this.t.token == ';'.toInt()) {
nret = 0
first = nret /* 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? */
Constants.SET_OPCODE(fs.getcodePtr(e), Lua.OP_TAILCALL)
Constants._assert(Lua.GETARG_A(fs.getcode(e)) == fs.nactvar.toInt())
}
first = fs.nactvar.toInt()
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.toInt() /* return all `active' values */
Constants._assert(nret == fs.freereg - first)
}
}
}
fs!!.ret(first, nret)
testnext(';'.toInt()) /* skip optional semicolon */
}
internal fun statement() {
val line = this.linenumber /* may be needed for error messages */
enterlevel()
when (this.t.token) {
';'.toInt() -> { /* stat -> ';' (empty statement) */
next() /* skip ';' */
}
TK_IF -> { /* stat -> ifstat */
this.ifstat(line)
}
TK_WHILE -> { /* stat -> whilestat */
this.whilestat(line)
}
TK_DO -> { /* stat -> DO block END */
this.next() /* skip DO */
this.block()
this.check_match(TK_END, TK_DO, line)
}
TK_FOR -> { /* stat -> forstat */
this.forstat(line)
}
TK_REPEAT -> { /* stat -> repeatstat */
this.repeatstat(line)
}
TK_FUNCTION -> {
this.funcstat(line) /* stat -> funcstat */
}
TK_LOCAL -> { /* stat -> localstat */
this.next() /* skip LOCAL */
if (this.testnext(TK_FUNCTION))
/* local function? */
this.localfunc()
else
this.localstat()
}
TK_DBCOLON -> { /* stat -> label */
next() /* skip double colon */
labelstat(str_checkname(), line)
}
TK_RETURN -> { /* stat -> retstat */
next() /* skip RETURN */
this.retstat()
}
TK_BREAK, TK_GOTO -> { /* stat -> breakstat */
this.gotostat(fs!!.jump())
}
else -> {
this.exprstat()
}
}
Constants._assert(fs!!.f!!.maxstacksize >= fs!!.freereg && fs!!.freereg >= fs!!.nactvar)
fs!!.freereg = fs!!.nactvar /* free registers */
leavelevel()
}
internal fun 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
*/
fun mainfunc(funcstate: FuncState) {
val bl = BlockCnt()
open_func(funcstate, bl)
fs!!.f!!.is_vararg = 1 /* main function is always vararg */
val v = 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()
}
companion object {
protected val RESERVED_LOCAL_VAR_FOR_CONTROL = "(for control)"
protected val RESERVED_LOCAL_VAR_FOR_STATE = "(for state)"
protected val RESERVED_LOCAL_VAR_FOR_GENERATOR = "(for generator)"
protected val RESERVED_LOCAL_VAR_FOR_STEP = "(for step)"
protected val RESERVED_LOCAL_VAR_FOR_LIMIT = "(for limit)"
protected val RESERVED_LOCAL_VAR_FOR_INDEX = "(for index)"
// keywords array
protected val RESERVED_LOCAL_VAR_KEYWORDS = arrayOf(
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 val RESERVED_LOCAL_VAR_KEYWORDS_TABLE = HashMap()
init {
for (i in RESERVED_LOCAL_VAR_KEYWORDS.indices)
RESERVED_LOCAL_VAR_KEYWORDS_TABLE[RESERVED_LOCAL_VAR_KEYWORDS[i]] = true
}
private val EOZ = -1
private val MAX_INT = Int.MAX_VALUE - 2
private val UCHAR_MAX = 255 // TODO, convert to unicode CHAR_MAX?
private val LUAI_MAXCCALLS = 200
private fun LUA_QS(s: String): String {
return "'$s'"
}
private fun LUA_QL(o: Any): String {
return LUA_QS(o.toString())
}
private val LUA_COMPAT_LSTR = 1 // 1 for compatibility, 2 for old behavior
private val LUA_COMPAT_VARARG = true
fun isReservedKeyword(varName: String): Boolean {
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).
*/
internal val NO_JUMP = -1
/*
** grep "ORDER OPR" if you change these enums
*/
internal val OPR_ADD = 0
internal val OPR_SUB = 1
internal val OPR_MUL = 2
internal val OPR_DIV = 3
internal val OPR_MOD = 4
internal val OPR_POW = 5
internal val OPR_CONCAT = 6
internal val OPR_NE = 7
internal val OPR_EQ = 8
internal val OPR_LT = 9
internal val OPR_LE = 10
internal val OPR_GT = 11
internal val OPR_GE = 12
internal val OPR_AND = 13
internal val OPR_OR = 14
internal val OPR_NOBINOPR = 15
internal val OPR_MINUS = 0
internal val OPR_NOT = 1
internal val OPR_LEN = 2
internal val OPR_NOUNOPR = 3
/* exp kind */
internal val VVOID = 0
/* no value */
internal val VNIL = 1
internal val VTRUE = 2
internal val VFALSE = 3
internal val VK = 4
/* info = index of constant in `k' */
internal val VKNUM = 5
/* nval = numerical value */
internal val VNONRELOC = 6
/* info = result register */
internal val VLOCAL = 7
/* info = local register */
internal val VUPVAL = 8
/* info = index of upvalue in `upvalues' */
internal val VINDEXED = 9
/* info = table register, aux = index register (or `k') */
internal val VJMP = 10
/* info = instruction pc */
internal val VRELOCABLE = 11
/* info = instruction pc */
internal val VCALL = 12
/* info = instruction pc */
internal val VVARARG = 13 /* info = instruction pc */
/* ORDER RESERVED */
internal val luaX_tokens = arrayOf(
"and",
"break",
"do",
"else",
"elseif",
"end",
"false",
"for",
"function",
"goto",
"if",
"in",
"local",
"nil",
"not",
"or",
"repeat",
"return",
"then",
"true",
"until",
"while",
"..",
"...",
"==",
">=",
"<=",
"~=",
"::",
"",
"",
"",
"",
""
)
internal val
/* terminal symbols denoted by reserved words */
TK_AND = 257
internal val TK_BREAK = 258
internal val TK_DO = 259
internal val TK_ELSE = 260
internal val TK_ELSEIF = 261
internal val TK_END = 262
internal val TK_FALSE = 263
internal val TK_FOR = 264
internal val TK_FUNCTION = 265
internal val TK_GOTO = 266
internal val TK_IF = 267
internal val TK_IN = 268
internal val TK_LOCAL = 269
internal val TK_NIL = 270
internal val TK_NOT = 271
internal val TK_OR = 272
internal val TK_REPEAT = 273
internal val TK_RETURN = 274
internal val TK_THEN = 275
internal val TK_TRUE = 276
internal val TK_UNTIL = 277
internal val TK_WHILE = 278
/* other terminal symbols */
internal val TK_CONCAT = 279
internal val TK_DOTS = 280
internal val TK_EQ = 281
internal val TK_GE = 282
internal val TK_LE = 283
internal val TK_NE = 284
internal val TK_DBCOLON = 285
internal val TK_EOS = 286
internal val TK_NUMBER = 287
internal val TK_NAME = 288
internal val TK_STRING = 289
internal val FIRST_RESERVED = TK_AND
internal val NUM_RESERVED = TK_WHILE + 1 - FIRST_RESERVED
internal val RESERVED = HashMap()
init {
for (i in 0 until NUM_RESERVED) {
val ts = LuaValue.valueOf(luaX_tokens[i])
RESERVED[ts] = FIRST_RESERVED + i
}
}
private fun iscntrl(token: Int): Boolean {
return token < ' '.toInt()
}
// =============================================================
// from lcode.h
// =============================================================
// =============================================================
// from lparser.c
// =============================================================
internal fun vkisvar(k: Int): Boolean {
return VLOCAL <= k && k <= VINDEXED
}
internal fun vkisinreg(k: Int): Boolean {
return k == VNONRELOC || k == VLOCAL
}
/*
** 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.
*/
internal fun luaO_int2fb(x: Int): Int {
var x = x
var e = 0 /* expoent */
while (x >= 16) {
x = x + 1 shr 1
e++
}
return if (x < 8)
x
else
e + 1 shl 3 or x - 8
}
internal var priority = arrayOf(/* ORDER OPR */
Priority(6, 6), Priority(6, 6), Priority(7, 7), Priority(7, 7), Priority(7, 7), /* `+' `-' `/' `%' */
Priority(10, 9), Priority(5, 4), /* power and concat (right associative) */
Priority(3, 3), Priority(3, 3), /* equality and inequality */
Priority(3, 3), Priority(3, 3), Priority(3, 3), Priority(3, 3), /* order */
Priority(2, 2), Priority(1, 1) /* logical (and/or) */
)
internal val UNARY_PRIORITY = 8 /* priority for unary operators */
}
/* }====================================================================== */
}