614 lines
21 KiB
Kotlin
614 lines
21 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(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'")
|
|
}
|
|
}
|
|
|
|
|
|
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.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 parseTerm(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
|
|
}
|
|
|
|
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 ${x.name}: ${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.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(
|
|
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)
|
|
"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 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")
|
|
|
|
// 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)
|
|
|
|
val fnBody = statement(t.pos) { context ->
|
|
// load params
|
|
for ((i, d) in params.withIndex()) {
|
|
if (i < context.args.size)
|
|
context.addItem(d.name, false, context.args.list[i].value)
|
|
else
|
|
context.addItem(
|
|
d.name,
|
|
false,
|
|
d.defaultValue?.execute(context)
|
|
?: throw ScriptError(
|
|
context.args.callerPos,
|
|
"missing required argument #${1 + i}: ${d.name}"
|
|
)
|
|
)
|
|
}
|
|
|
|
fnStatements.execute(context)
|
|
}
|
|
return statement(start) { context ->
|
|
context.addItem(name, false, fnBody)
|
|
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())
|
|
}.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) { a, b -> a == b },
|
|
LogicalOp(Token.Type.NEQ, 4) { a, b -> a != b },
|
|
// relational <=,... 5
|
|
LogicalOp(Token.Type.LTE, 5) { a, b -> a <= b },
|
|
LogicalOp(Token.Type.LT, 5) { a, b -> a < b },
|
|
LogicalOp(Token.Type.GTE, 5) { a, b -> a >= b },
|
|
LogicalOp(Token.Type.GT, 5) { a, b -> a > b },
|
|
// 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: (Obj, Obj) -> Boolean) = Compiler.Operator(
|
|
tokenType,
|
|
priority,
|
|
2
|
|
) { pos, a, b ->
|
|
statement(pos) {
|
|
ObjBool(
|
|
f(a.execute(it), b.execute(it))
|
|
)
|
|
}
|
|
} |