All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.org.luaj.vm2.compiler.LexState.kt Maven / Gradle / Ivy

There is a newer version: 4.0.0-alpha-2
Show newest version
/*******************************************************************************
 * 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 */
    }

    /* }====================================================================== */

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy