package net.sergeych.ling //sealed class ObjType(name: String, val defaultValue: Obj? = null) { // // class Str : ObjType("string", ObjString("")) // class Int : ObjType("real", ObjReal(0.0)) // //} // ///** // * Descriptor for whatever object could be used as argument, return value, // * field, etc. // */ //data class ObjDescriptor( // val type: ObjType, // val mutable: Boolean, //) // //data class MethodDescriptor( // val args: Array, // val result: ObjDescriptor //) /* Meta context contains map of symbols. Each symbol can be: - a var - a const - a function - a type alias - a class definition Each have in common only its name. Var has: type, value. Const is same as var but value is fixed. Function has args and return value, type alias has target type name. So we have to have something that denotes a _type_ */ //data class MetaContext(val symbols: MutableMap = mutableMapOf()) { // //} class CompilerContext(val tokens: List) : ListIterator by tokens.listIterator() { val labels = mutableSetOf() fun ensureLabelIsValid(pos: Pos, label: String) { if (label !in labels) throw ScriptError(pos, "Undefined label '$label'") } @Suppress("unused") fun requireId() = requireToken(Token.Type.ID, "identifier is required") fun requireToken(type: Token.Type, message: String = "required ${type.name}"): Token = next().also { if (type != it.type) throw ScriptError(it.pos, message) } fun syntaxError(at: Pos, message: String = "Syntax error"): Nothing { throw ScriptError(at, message) } fun currentPos() = if (hasNext()) next().pos.also { previous() } else previous().pos.also { next() } /** * Skips next token if its type is `tokenType`, returns `true` if so. * @param errorMessage message to throw if next token is not `tokenType` * @param isOptional if `true` and token is not of `tokenType`, just return `false` and does not skip it * @return `true` if the token was skipped * @throws ScriptError if [isOptional] is `false` and next token is not of [tokenType] */ fun skipTokenOfType(tokenType: Token.Type, errorMessage: String="expected ${tokenType.name}", isOptional: Boolean = false): Boolean { val t = next() return if (t.type != tokenType) { if (!isOptional) { println("unexpected: $t (needed $tokenType)") throw ScriptError(t.pos, errorMessage) } else { previous() false } } else true } fun ifNextIs(typeId: Token.Type, f: (Token) -> Unit): Boolean { val t = next() return if (t.type == typeId) { f(t) true } else { previous() false } } } class Compiler { fun compile(source: Source): Script { return parseScript(source.startPos, CompilerContext(parseLing(source))) } private fun parseScript(start: Pos, tokens: CompilerContext): Script { val statements = mutableListOf() while (parseStatement(tokens)?.also { statements += it } != null) {/**/ } return Script(start, statements) } private fun parseStatement(tokens: CompilerContext): Statement? { while (true) { val t = tokens.next() return when (t.type) { Token.Type.ID -> { // could be keyword, assignment or just the expression val next = tokens.next() if (next.type == Token.Type.ASSIGN) { // this _is_ assignment statement return AssignStatement( t.pos, t.value, parseStatement(tokens) ?: throw ScriptError( t.pos, "Expecting expression for assignment operator" ) ) } // not assignment, maybe keyword statement: // get back the token which is not '=': tokens.previous() // try keyword statement parseKeywordStatement(t, tokens) ?: run { tokens.previous() parseExpression(tokens) } } Token.Type.PLUS2, Token.Type.MINUS2 -> { tokens.previous() parseExpression(tokens) } Token.Type.LABEL -> continue Token.Type.SINLGE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT -> continue Token.Type.NEWLINE -> continue Token.Type.SEMICOLON -> continue Token.Type.LBRACE -> { tokens.previous() parseBlock(tokens) } Token.Type.RBRACE -> { tokens.previous() return null } Token.Type.EOF -> null else -> { // could be expression tokens.previous() parseExpression(tokens) } } } } private fun parseExpression(tokens: CompilerContext, level: Int = 0): Statement? { if (level == lastLevel) return parseTerm3(tokens) var lvalue = parseExpression(tokens, level + 1) if (lvalue == null) return null while (true) { val opToken = tokens.next() val op = byLevel[level][opToken.type] if (op == null) { tokens.previous() break } val rvalue = parseExpression(tokens, level + 1) ?: throw ScriptError(opToken.pos, "Expecting expression") lvalue = op.generate(opToken.pos, lvalue!!, rvalue) } return lvalue } /* Term compiler Fn calls could be: 1) Fn(...) 2) thisObj.method(...) 1 is a shortcut to this.Fn(...) In general, we can assume any Fn to be of the same king, with `this` that always exist, and set by invocation. In the case of (1), so called regular, or not bound function, it takes current this from the context. In the case of (2), bound function, it creates sub-context binding thisObj to `this` in it. Suppose we do regular parsing. THen we get lparam = statement, and parse to the `(`. Now we have to compile the invocation of lparam, which can be thisObj.method or just method(). Unfortunately we already compiled it and can't easily restore its type, so we have to parse it different way. EBNF to parse term having lparam. boundcall = "." , identifier, "(" We then call instance method bound to `lparam`. call = "(', args, ") we treat current lparam as callable and invoke it on the current context with current value of 'this. Just traversing fields: traverse = ".", not (identifier , ".") Other cases to parse: index = lparam, "[" , ilist , "]" */ /** * Lower level of expr: * * assigning expressions: * * expr = expr: assignment * ++expr, expr++, --expr, expr--, * * update-assigns: * expr += expr, ... * * Dot!: expr , '.', ID * Lambda: { } * index: expr[ ilist ] * call: ( ilist ) * self updating: ++expr, expr++, --expr, expr--, expr+=, * expr-=, expr*=, expr/= * read expr: */ fun parseTerm3(cc: CompilerContext): Statement? { var operand: Accessor? = null while (true) { val t = cc.next() val startPos = t.pos when (t.type) { Token.Type.NEWLINE, Token.Type.SEMICOLON, Token.Type.EOF -> { cc.previous() return operand?.let { op -> statement(startPos) { op.getter(it) } } } Token.Type.DOT -> { operand?.let { left -> // dotcall: calling method on the operand, if next is ID, "(" var isCall = false val next = cc.next() if( next.type == Token.Type.ID) { cc.ifNextIs(Token.Type.LPAREN) { // instance method call val args = parseArgs(cc) isCall = true operand = Accessor { context -> context.pos = next.pos val v = left.getter(context) v.callInstanceMethod( context, next.value, args.toArguments() ) } } } if (!isCall) { operand = Accessor { context -> left.getter(context).readField(context, next.value) } } } ?: throw ScriptError(t.pos, "Expecting expression before dot") } Token.Type.COLONCOLON -> { operand = parseScopeOperator(operand,cc) } Token.Type.LPAREN -> { operand?.let { left -> // this is function call from operand = parseFunctionCall( cc, left, ) } ?: run { // Expression in parentheses val statement = parseStatement(cc) ?: throw ScriptError(t.pos, "Expecting expression") operand = Accessor { statement.execute(it) } cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true) cc.skipTokenOfType(Token.Type.RPAREN, "missing ')'") } } Token.Type.ID -> { // there could be terminal operators or keywords:// variable to read or like when (t.value) { "else" -> { cc.previous() return operand?.let { op -> statement(startPos) { op.getter(it) } } } "if", "when", "do", "while", "return" -> { if( operand != null ) throw ScriptError(t.pos, "unexpected keyword") cc.previous() val s = parseStatement(cc) ?: throw ScriptError(t.pos, "Expecting valid statement") operand = Accessor { s.execute(it) } } "break", "continue" -> { cc.previous() return operand?.let { op -> statement(startPos) { op.getter(it) } } } else -> operand?.let { left -> // selector: , '.' , // we replace operand with selector code, that // is RW: operand = Accessor({ it.pos = t.pos left.getter(it).readField(it, t.value) }) { cxt, newValue -> cxt.pos = t.pos left.getter(cxt).writeField(cxt, t.value, newValue) } } ?: run { // variable to read or like cc.previous() operand = parseAccessor(cc) } } // selector: , '.' , // we replace operand with selector code, that // is RW: } Token.Type.PLUS2 -> { // note: post-increment result is not assignable (truly lvalue) operand?.let { left -> // post increment left.setter(startPos) operand = Accessor({ ctx -> left.getter(ctx).getAndIncrement(ctx) }) } ?: run { // no lvalue means pre-increment, expression to increment follows val next = parseAccessor(cc) ?: throw ScriptError(t.pos, "Expecting expression") operand = Accessor({ ctx -> next.getter(ctx).incrementAndGet(ctx) }) } } Token.Type.MINUS2 -> { // note: post-decrement result is not assignable (truly lvalue) operand?.let { left -> // post decrement left.setter(startPos) operand = Accessor { ctx -> left.getter(ctx).getAndDecrement(ctx) } }?: run { // no lvalue means pre-decrement, expression to decrement follows val next = parseAccessor(cc) ?: throw ScriptError(t.pos, "Expecting expression") operand = Accessor { ctx -> next.getter(ctx).decrementAndGet(ctx) } } } else -> { cc.previous() operand?.let { op -> return statement(startPos) { op.getter(it) } } operand = parseAccessor(cc) ?: throw ScriptError(t.pos, "Expecting expression") } } } } private fun parseScopeOperator(operand: Accessor?, cc: CompilerContext): Accessor { // implement global scope maybe? if( operand == null ) throw ScriptError(cc.next().pos, "Expecting expression before ::") val t = cc.next() if( t.type != Token.Type.ID ) throw ScriptError(t.pos, "Expecting ID after ::") return when(t.value) { "class" -> Accessor { operand.getter(it).objClass } else -> throw ScriptError(t.pos, "Unknown scope operation: ${t.value}") } } fun parseArgs(cc: CompilerContext): List { val args = mutableListOf() do { val t = cc.next() when (t.type) { Token.Type.RPAREN, Token.Type.COMMA -> {} else -> { cc.previous() parseStatement(cc)?.let { args += Arguments.Info(it, t.pos) } ?: throw ScriptError(t.pos, "Expecting arguments list") } } } while (t.type != Token.Type.RPAREN) return args } fun parseFunctionCall(cc: CompilerContext, left: Accessor): Accessor { // insofar, functions always return lvalue val args = parseArgs(cc) return Accessor { context -> val v = left.getter(context) v.callOn(context.copy( context.pos, Arguments( args.map { Arguments.Info((it.value as Statement).execute(context), it.pos) } ), ) ) } } // fun parseReservedWord(word: String): Accessor? = // when(word) { // "true" -> Accessor { ObjBool(true) } // "false" -> Accessor { ObjBool(false) } // "void" -> Accessor { ObjVoid } // "null" -> Accessor { ObjNull } // else -> null // } fun parseAccessor(cc: CompilerContext): Accessor? { // could be: literal val t = cc.next() return when (t.type) { Token.Type.INT, Token.Type.REAL, Token.Type.HEX -> { cc.previous() val n = parseNumber(true, cc) Accessor({ n }) } Token.Type.STRING -> Accessor({ ObjString(t.value) }) Token.Type.PLUS -> { val n = parseNumber(true, cc) Accessor { n } } Token.Type.MINUS -> { val n = parseNumber(false, cc) Accessor { n } } Token.Type.ID -> { when (t.value) { "void" -> Accessor { ObjVoid } "null" -> Accessor { ObjNull } "true" -> Accessor { ObjBool(true) } "false" -> Accessor { ObjBool(false) } else -> { Accessor({ it.pos = t.pos it.get(t.value)?.value?.also { println("got ${t.value} -> $it") } ?: it.raiseError("symbol not defined: '${t.value}'") }) { ctx, newValue -> ctx.get(t.value)?.let { stored -> ctx.pos = t.pos if (stored.isMutable) stored.value = newValue else ctx.raiseError("Cannot assign to immutable value") } ?: ctx.raiseError("symbol not defined: '${t.value}'") } } } } else -> null } } // fun parseTerm(tokens: CompilerContext): Statement? { // // call op // // index op // // unary op // // parenthesis // // number or string // val t = tokens.next() // // todoL var? // return when (t.type) { // Token.Type.ID -> { // when (t.value) { // "void" -> statement(t.pos, true) { ObjVoid } // "null" -> statement(t.pos, true) { ObjNull } // "true" -> statement(t.pos, true) { ObjBool(true) } // "false" -> statement(t.pos, true) { ObjBool(false) } // else -> parseVarAccess(t, tokens) // } // } // // Token.Type.STRING -> statement(t.pos, true) { ObjString(t.value) } // // Token.Type.LPAREN -> { // // ( subexpr ) // parseExpression(tokens)?.also { // val tl = tokens.next() // if (tl.type != Token.Type.RPAREN) // throw ScriptError(t.pos, "unbalanced parenthesis: no ')' for it") // } // } // // Token.Type.PLUS -> { // val n = parseNumber(true, tokens) // statement(t.pos, true) { n } // } // // Token.Type.MINUS -> { // val n = parseNumber(false, tokens) // statement(t.pos, true) { n } // } // // Token.Type.INT, Token.Type.REAL, Token.Type.HEX -> { // tokens.previous() // val n = parseNumber(true, tokens) // statement(t.pos, true) { n } // } // // else -> null // } // // } // fun parseVarAccess(id: Token, tokens: CompilerContext, path: List = emptyList()): Statement { // val nt = tokens.next() // // fun resolve(context: Context): Context { // var targetContext = context // for (n in path) { // val x = targetContext[n] ?: throw ScriptError(id.pos, "undefined symbol: $n") // (x.value as? ObjNamespace)?.let { targetContext = it.context } // ?: throw ScriptError(id.pos, "Invalid symbolic path (wrong type of ${n}: ${x.value}") // } // return targetContext // } // return when (nt.type) { // Token.Type.DOT -> { // // selector // val t = tokens.next() // if (t.type == Token.Type.ID) { // parseVarAccess(t, tokens, path + id.value) // } else // throw ScriptError(t.pos, "Expected identifier after '.'") // } // // Token.Type.PLUS2 -> { // statement(id.pos) { context -> // context.pos = id.pos // val v = resolve(context).get(id.value) // ?: throw ScriptError(id.pos, "Undefined symbol: ${id.value}") // v.value?.getAndIncrement(context) // ?: context.raiseNPE() // } // } // // Token.Type.MINUS2 -> { // statement(id.pos) { context -> // context.pos = id.pos // val v = resolve(context).get(id.value) // ?: throw ScriptError(id.pos, "Undefined symbol: ${id.value}") // v.value?.getAndDecrement(context) // ?: context.raiseNPE() // } // } // // Token.Type.LPAREN -> { // // function call // // Load arg list // val args = mutableListOf() // do { // val t = tokens.next() // when (t.type) { // Token.Type.RPAREN, Token.Type.COMMA -> {} // else -> { // tokens.previous() // parseStatement(tokens)?.let { args += Arguments.Info(it, t.pos) } // ?: throw ScriptError(t.pos, "Expecting arguments list") // } // } // } while (t.type != Token.Type.RPAREN) // // statement(id.pos) { context -> // val v = // resolve(context).get(id.value) ?: throw ScriptError( // id.pos, // "Undefined function: ${id.value}" // ) // (v.value as? Statement)?.execute( // context.copy( // id.pos, // Arguments( // nt.pos, // args.map { Arguments.Info((it.value as Statement).execute(context), it.pos) } // ), // ) // ) // ?: throw ScriptError(id.pos, "Variable $id is not callable ($id)") // } // } // // Token.Type.LBRACKET -> { // TODO("indexing") // } // // else -> { // // just access the var // tokens.previous() //// val t = tokens.next() //// tokens.previous() //// println(t) //// if( path.isEmpty() ) { //// statement? //// tokens.previous() //// parseStatement(tokens) ?: throw ScriptError(id.pos, "Expecting expression/statement") //// } else // statement(id.pos) { // val v = // resolve(it).get(id.value) ?: throw ScriptError(id.pos, "Undefined variable: ${id.value}") // v.value ?: throw ScriptError(id.pos, "Variable $id is not initialized") // } // } // } // } // fun parseNumber(isPlus: Boolean, tokens: CompilerContext): Obj { val t = tokens.next() return when (t.type) { Token.Type.INT, Token.Type.HEX -> { val n = t.value.toLong(if (t.type == Token.Type.HEX) 16 else 10) if (isPlus) ObjInt(n) else ObjInt(-n) } Token.Type.REAL -> { val d = t.value.toDouble() if (isPlus) ObjReal(d) else ObjReal(-d) } else -> { throw ScriptError(t.pos, "expected number") } } } /** * Parse keyword-starting statenment. * @return parsed statement or null if, for example. [id] is not among keywords */ private fun parseKeywordStatement(id: Token, cc: CompilerContext): Statement? = when (id.value) { "val" -> parseVarDeclaration(id.value, false, cc) "var" -> parseVarDeclaration(id.value, true, cc) "while" -> parseWhileStatement(cc) "break" -> parseBreakStatement(id.pos, cc) "continue" -> parseContinueStatement(id.pos, cc) "fn", "fun" -> parseFunctionDeclaration(cc) "if" -> parseIfStatement(cc) else -> null } fun getLabel(cc: CompilerContext, maxDepth: Int = 2): String? { var cnt = 0 var found: String? = null while (cc.hasPrevious() && cnt < maxDepth) { val t = cc.previous() cnt++ if (t.type == Token.Type.LABEL) { found = t.value break } } while (cnt-- > 0) cc.next() return found } private fun parseWhileStatement(cc: CompilerContext): Statement { val label = getLabel(cc)?.also { cc.labels += it } val start = ensureLparen(cc) val condition = parseExpression(cc) ?: throw ScriptError(start, "Bad while statement: expected expression") ensureRparen(cc) val body = parseStatement(cc) ?: throw ScriptError(start, "Bad while statement: expected statement") label?.also { cc.labels -= it } return statement(body.pos) { var result: Obj = ObjVoid while (condition.execute(it).toBool()) { try { result = body.execute(it) } catch (lbe: LoopBreakContinueException) { if (lbe.label == label || lbe.label == null) { if (lbe.doContinue) continue else { result = lbe.result break } } else throw lbe } } result } } private fun parseBreakStatement(start: Pos, cc: CompilerContext): Statement { var t = cc.next() val label = if (t.pos.line != start.line || t.type != Token.Type.ATLABEL) { cc.previous() null } else { t.value }?.also { // check that label is defined cc.ensureLabelIsValid(start, it) } // expression? t = cc.next() cc.previous() val resultExpr = if (t.pos.line == start.line && (!t.isComment && t.type != Token.Type.SEMICOLON && t.type != Token.Type.NEWLINE) ) { // we have something on this line, could be expression parseStatement(cc) } else null return statement(start) { val returnValue = resultExpr?.execute(it)// ?: ObjVoid throw LoopBreakContinueException( doContinue = false, label = label, result = returnValue ?: ObjVoid ) } } private fun parseContinueStatement(start: Pos, cc: CompilerContext): Statement { val t = cc.next() val label = if (t.pos.line != start.line || t.type != Token.Type.ATLABEL) { cc.previous() null } else { t.value }?.also { // check that label is defined cc.ensureLabelIsValid(start, it) } return statement(start) { throw LoopBreakContinueException( doContinue = true, label = label, ) } } private fun ensureRparen(tokens: CompilerContext): Pos { val t = tokens.next() if (t.type != Token.Type.RPAREN) throw ScriptError(t.pos, "expected ')'") return t.pos } private fun ensureLparen(tokens: CompilerContext): Pos { val t = tokens.next() if (t.type != Token.Type.LPAREN) throw ScriptError(t.pos, "expected '('") return t.pos } private fun parseIfStatement(tokens: CompilerContext): Statement { val start = ensureLparen(tokens) val condition = parseExpression(tokens) ?: throw ScriptError(start, "Bad if statement: expected expression") val pos = ensureRparen(tokens) val ifBody = parseStatement(tokens) ?: throw ScriptError(pos, "Bad if statement: expected statement") tokens.skipTokenOfType(Token.Type.NEWLINE, isOptional = true) // could be else block: val t2 = tokens.next() // we generate different statements: optimization return if (t2.type == Token.Type.ID && t2.value == "else") { val elseBody = parseStatement(tokens) ?: throw ScriptError(pos, "Bad else statement: expected statement") return statement(start) { if (condition.execute(it).toBool()) ifBody.execute(it) else elseBody.execute(it) } } else { tokens.previous() statement(start) { if (condition.execute(it).toBool()) ifBody.execute(it) else ObjVoid } } } data class FnParamDef( val name: String, val pos: Pos, val defaultValue: Statement? = null ) private fun parseFunctionDeclaration(tokens: CompilerContext): Statement { var t = tokens.next() val start = t.pos val name = if (t.type != Token.Type.ID) throw ScriptError(t.pos, "Expected identifier after 'fn'") else t.value t = tokens.next() if (t.type != Token.Type.LPAREN) throw ScriptError(t.pos, "Bad function definition: expected '(' after 'fn ${name}'") val params = mutableListOf() var defaultListStarted = false do { t = tokens.next() if (t.type == Token.Type.RPAREN) break if (t.type != Token.Type.ID) throw ScriptError(t.pos, "Expected identifier after '('") val n = tokens.next() val defaultValue = if (n.type == Token.Type.ASSIGN) { parseExpression(tokens)?.also { defaultListStarted = true } ?: throw ScriptError(n.pos, "Expected initialization expression") } else { if (defaultListStarted) throw ScriptError(n.pos, "requires default value too") if (n.type != Token.Type.COMMA) tokens.previous() null } params.add(FnParamDef(t.value, t.pos, defaultValue)) } while (true) // Here we should be at open body val fnStatements = parseBlock(tokens) var closure: Context? = null val fnBody = statement(t.pos) { callerContext -> callerContext.pos = start // restore closure where the function was defined: val context = closure ?: callerContext.raiseError("bug: closure not set") // load params from caller context for ((i, d) in params.withIndex()) { if (i < callerContext.args.size) context.addItem(d.name, false, callerContext.args.list[i].value) else context.addItem( d.name, false, d.defaultValue?.execute(context) ?: throw ScriptError( context.pos, "missing required argument #${1 + i}: ${d.name}" ) ) } // save closure fnStatements.execute(context) } return statement(start) { context -> // we added fn in the context. now we must save closure // for the function closure = context context.addItem(name, false, fnBody) // as the function can be called from anywhere, we have // saved the proper context in the closure fnBody } } private fun parseBlock(tokens: CompilerContext): Statement { val t = tokens.next() if (t.type != Token.Type.LBRACE) throw ScriptError(t.pos, "Expected block body start: {") val block = parseScript(t.pos, tokens) return statement(t.pos) { // block run on inner context: block.execute(it.copy(t.pos)) }.also { val t1 = tokens.next() if (t1.type != Token.Type.RBRACE) throw ScriptError(t1.pos, "unbalanced braces: expected block body end: }") } } private fun parseVarDeclaration(kind: String, mutable: Boolean, tokens: CompilerContext): Statement { val nameToken = tokens.next() val start = nameToken.pos if (nameToken.type != Token.Type.ID) throw ScriptError(nameToken.pos, "Expected identifier after '$kind'") val name = nameToken.value val eqToken = tokens.next() var setNull = false if (eqToken.type != Token.Type.ASSIGN) { if (!mutable) throw ScriptError(start, "val must be initialized") else { tokens.previous() setNull = true } } val initialExpression = if (setNull) null else parseStatement(tokens) ?: throw ScriptError(eqToken.pos, "Expected initializer expression") return statement(nameToken.pos) { context -> if (context.containsLocal(name)) throw ScriptError(nameToken.pos, "Variable $name is already defined") val initValue = initialExpression?.execute(context) ?: ObjNull context.addItem(name, mutable, initValue) ObjVoid } } // fun parseStatement(parser: Parser): Statement? = // parser.withToken { // if (tokens.isEmpty()) null // else { // when (val token = tokens[0]) { // else -> { // rollback() // null // } // } // } // } data class Operator( val tokenType: Token.Type, val priority: Int, val arity: Int, val generate: (Pos, Statement, Statement) -> Statement ) companion object { val allOps = listOf( Operator(Token.Type.OR, 0, 2) { pos, a, b -> LogicalOrStatement(pos, a, b) }, Operator(Token.Type.AND, 1, 2) { pos, a, b -> LogicalAndStatement(pos, a, b) }, // bitwise or 2 // bitwise and 3 // equality/ne 4 LogicalOp(Token.Type.EQ, 4) { c, a, b -> a.compareTo(c, b) == 0 }, LogicalOp(Token.Type.NEQ, 4) { c, a, b -> a.compareTo(c, b) != 0 }, // relational <=,... 5 LogicalOp(Token.Type.LTE, 5) { c, a, b -> a.compareTo(c, b) <= 0 }, LogicalOp(Token.Type.LT, 5) { c, a, b -> a.compareTo(c, b) < 0 }, LogicalOp(Token.Type.GTE, 5) { c, a, b -> a.compareTo(c, b) >= 0 }, LogicalOp(Token.Type.GT, 5) { c, a, b -> a.compareTo(c, b) > 0 }, // shuttle <=> 6 // bitshhifts 7 Operator(Token.Type.PLUS, 8, 2) { pos, a, b -> PlusStatement(pos, a, b) }, Operator(Token.Type.MINUS, 8, 2) { pos, a, b -> MinusStatement(pos, a, b) }, Operator(Token.Type.STAR, 9, 2) { pos, a, b -> MulStatement(pos, a, b) }, Operator(Token.Type.SLASH, 9, 2) { pos, a, b -> DivStatement(pos, a, b) }, Operator(Token.Type.PERCENT, 9, 2) { pos, a, b -> ModStatement(pos, a, b) }, ) val lastLevel = 10 val byLevel: List> = (0.. allOps.filter { it.priority == l } .map { it.tokenType to it }.toMap() } fun compile(code: String): Script = Compiler().compile(Source("", code)) } } suspend fun eval(code: String) = Compiler.compile(code).execute() fun LogicalOp( tokenType: Token.Type, priority: Int, f: suspend (Context, Obj, Obj) -> Boolean ) = Compiler.Operator( tokenType, priority, 2 ) { pos, a, b -> statement(pos) { ObjBool( f(it, a.execute(it), b.execute(it)) ) } }