1044 lines
37 KiB
Kotlin
1044 lines
37 KiB
Kotlin
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<ObjDescriptor>,
|
|
// 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<String, > = mutableMapOf()) {
|
|
//
|
|
//}
|
|
|
|
|
|
class CompilerContext(val tokens: List<Token>) : ListIterator<Token> by tokens.listIterator() {
|
|
val labels = mutableSetOf<String>()
|
|
|
|
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<Statement>()
|
|
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: { <expr> }
|
|
* index: expr[ ilist ]
|
|
* call: <expr>( ilist )
|
|
* self updating: ++expr, expr++, --expr, expr--, expr+=<expr>,
|
|
* expr-=<expr>, expr*=<expr>, expr/=<expr>
|
|
* read expr: <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 <left>
|
|
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: <lvalue>, '.' , <id>
|
|
// 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: <lvalue>, '.' , <id>
|
|
// 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<Arguments.Info> {
|
|
val args = mutableListOf<Arguments.Info>()
|
|
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<String> = 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<Arguments.Info>()
|
|
// 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<FnParamDef>()
|
|
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<Map<Token.Type, Operator>> = (0..<lastLevel).map { l ->
|
|
allOps.filter { it.priority == l }
|
|
.map { it.tokenType to it }.toMap()
|
|
}
|
|
|
|
fun compile(code: String): Script = Compiler().compile(Source("<eval>", 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))
|
|
)
|
|
}
|
|
} |