big refactoring towards OO support just started
This commit is contained in:
parent
baf9eab3ba
commit
eba29aedb7
@ -45,13 +45,50 @@ type alias has target type name. So we have to have something that denotes a _ty
|
||||
//}
|
||||
|
||||
|
||||
class CompilerContext(tokens: List<Token>) : ListIterator<Token> by tokens.listIterator() {
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -97,6 +134,7 @@ class Compiler {
|
||||
parseExpression(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
Token.Type.PLUS2, Token.Type.MINUS2 -> {
|
||||
tokens.previous()
|
||||
parseExpression(tokens)
|
||||
@ -132,7 +170,7 @@ class Compiler {
|
||||
|
||||
private fun parseExpression(tokens: CompilerContext, level: Int = 0): Statement? {
|
||||
if (level == lastLevel)
|
||||
return parseTerm(tokens)
|
||||
return parseTerm3(tokens)
|
||||
var lvalue = parseExpression(tokens, level + 1)
|
||||
if (lvalue == null) return null
|
||||
|
||||
@ -153,153 +191,404 @@ class Compiler {
|
||||
return lvalue
|
||||
}
|
||||
|
||||
fun parseTerm(tokens: CompilerContext): Statement? {
|
||||
// call op
|
||||
// index op
|
||||
// unary op
|
||||
// parenthesis
|
||||
// number or string
|
||||
val t = tokens.next()
|
||||
// todoL var?
|
||||
|
||||
/*
|
||||
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 -> {
|
||||
if (operand == null)
|
||||
throw ScriptError(t.pos, "Expecting expression before dot")
|
||||
continue
|
||||
}
|
||||
|
||||
Token.Type.LPAREN -> {
|
||||
operand?.let { left ->
|
||||
// this is function call from <left>
|
||||
operand = parseFunctionCall(
|
||||
cc,
|
||||
left,
|
||||
thisObj = null,
|
||||
)
|
||||
} ?: run {
|
||||
// Expression in parentheses
|
||||
val statement = parseStatement(cc) ?: throw ScriptError(t.pos, "Expecting expression")
|
||||
operand = Accessor {
|
||||
statement.execute(it)
|
||||
}
|
||||
cc.skipTokenOfType(Token.Type.RPAREN, "missing ')'")
|
||||
}
|
||||
}
|
||||
|
||||
Token.Type.ID -> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun parseFunctionCall(cc: CompilerContext, left: Accessor, thisObj: Statement?): Accessor {
|
||||
// insofar, functions always return lvalue
|
||||
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 Accessor { context ->
|
||||
val v = left.getter(context)
|
||||
v.callOn(context.copy(
|
||||
context.pos,
|
||||
Arguments(
|
||||
context.pos,
|
||||
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.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.INT, Token.Type.REAL, Token.Type.HEX -> {
|
||||
cc.previous()
|
||||
val n = parseNumber(true, cc)
|
||||
Accessor({ n })
|
||||
}
|
||||
|
||||
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.STRING -> Accessor({ ObjString(t.value) })
|
||||
|
||||
Token.Type.PLUS -> {
|
||||
val n = parseNumber(true, tokens)
|
||||
statement(t.pos, true) { n }
|
||||
val n = parseNumber(true, cc)
|
||||
Accessor { n }
|
||||
}
|
||||
|
||||
Token.Type.MINUS -> {
|
||||
val n = parseNumber(false, tokens)
|
||||
statement(t.pos, true) { n }
|
||||
val n = parseNumber(false, cc)
|
||||
Accessor { n }
|
||||
}
|
||||
|
||||
Token.Type.INT, Token.Type.REAL, Token.Type.HEX -> {
|
||||
tokens.previous()
|
||||
val n = parseNumber(true, tokens)
|
||||
statement(t.pos, true) { 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 ?: 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 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.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()
|
||||
// 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()
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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()
|
||||
@ -458,12 +747,14 @@ class Compiler {
|
||||
|
||||
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")
|
||||
val elseBody =
|
||||
parseStatement(tokens) ?: throw ScriptError(pos, "Bad else statement: expected statement")
|
||||
return statement(start) {
|
||||
if (condition.execute(it).toBool())
|
||||
ifBody.execute(it)
|
||||
@ -626,13 +917,13 @@ class Compiler {
|
||||
// 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 },
|
||||
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) { 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 },
|
||||
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 ->
|
||||
@ -657,14 +948,17 @@ class Compiler {
|
||||
|
||||
suspend fun eval(code: String) = Compiler.compile(code).execute()
|
||||
|
||||
fun LogicalOp(tokenType: Token.Type, priority: Int, f: (Obj, Obj) -> Boolean) = Compiler.Operator(
|
||||
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(a.execute(it), b.execute(it))
|
||||
f(it, a.execute(it), b.execute(it))
|
||||
)
|
||||
}
|
||||
}
|
@ -32,13 +32,12 @@ class Context(
|
||||
fun copy(pos: Pos, args: Arguments = Arguments.EMPTY): Context = Context(this, args, pos)
|
||||
|
||||
fun addItem(name: String, isMutable: Boolean, value: Obj?) {
|
||||
objects.put(name, StoredObj(name, value, isMutable))
|
||||
objects.put(name, StoredObj(value, isMutable))
|
||||
}
|
||||
|
||||
fun getOrCreateNamespace(name: String) =
|
||||
(objects.getOrPut(name) {
|
||||
StoredObj(
|
||||
name,
|
||||
ObjNamespace(name, copy(pos)),
|
||||
isMutable = false
|
||||
)
|
||||
|
@ -8,41 +8,65 @@ import kotlin.math.floor
|
||||
|
||||
typealias InstanceMethod = (Context, Obj) -> Obj
|
||||
|
||||
data class Item<T>(var value: T, val isMutable: Boolean = false)
|
||||
data class WithAccess<T>(var value: T, val isMutable: Boolean)
|
||||
|
||||
data class Accessor(
|
||||
val getter: suspend (Context) -> Obj,
|
||||
val setterOrNull: (suspend (Context, Obj) -> Unit)?
|
||||
) {
|
||||
constructor(getter: suspend (Context) -> Obj) : this(getter, null)
|
||||
|
||||
fun setter(pos: Pos) = setterOrNull ?: throw ScriptError(pos,"can't assign value")
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class ClassDef(
|
||||
val className: String
|
||||
) {
|
||||
val baseClasses: List<ClassDef> get() = emptyList()
|
||||
private val instanceMethods: MutableMap<String, Item<InstanceMethod>> get() = mutableMapOf()
|
||||
protected val instanceMembers: MutableMap<String, WithAccess<Obj>> = mutableMapOf()
|
||||
private val monitor = Mutex()
|
||||
|
||||
private val instanceLock = Mutex()
|
||||
|
||||
suspend fun addInstanceMethod(
|
||||
context: Context,
|
||||
name: String,
|
||||
freeze: Boolean = false,
|
||||
pos: Pos = Pos.builtIn,
|
||||
body: InstanceMethod
|
||||
isOpen: Boolean = false,
|
||||
body: Obj
|
||||
) {
|
||||
instanceLock.withLock {
|
||||
instanceMethods[name]?.let {
|
||||
monitor.withLock {
|
||||
instanceMembers[name]?.let {
|
||||
if (!it.isMutable)
|
||||
throw ScriptError(pos, "existing method $name is frozen and can't be updated")
|
||||
context.raiseError("method $name is not open and can't be overridden")
|
||||
it.value = body
|
||||
} ?: instanceMethods.put(name, Item(body, freeze))
|
||||
} ?: instanceMembers.put(name, WithAccess(body, isOpen))
|
||||
}
|
||||
}
|
||||
|
||||
//suspend fun callInstanceMethod(context: Context, self: Obj,args: Arguments): Obj {
|
||||
//
|
||||
// }
|
||||
suspend fun getInstanceMethodOrNull(name: String): Obj? =
|
||||
monitor.withLock { instanceMembers[name]?.value }
|
||||
|
||||
suspend fun getInstanceMethod(context: Context, name: String): Obj =
|
||||
getInstanceMethodOrNull(name) ?: context.raiseError("no method found: $name")
|
||||
|
||||
// suspend fun callInstanceMethod(context: Context, name: String, self: Obj,args: Arguments): Obj {
|
||||
// getInstanceMethod(context, name).invoke(context, self,args)
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
object ObjClassDef : ClassDef("Obj")
|
||||
|
||||
@Serializable
|
||||
sealed class Obj : Comparable<Obj> {
|
||||
sealed class Obj {
|
||||
open val classDef: ClassDef = ObjClassDef
|
||||
var isFrozen: Boolean = false
|
||||
|
||||
protected val instanceMethods: Map<String, WithAccess<InstanceMethod>> = mutableMapOf()
|
||||
private val monitor = Mutex()
|
||||
|
||||
open suspend fun compareTo(context: Context, other: Obj): Int {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
open val asStr: ObjString by lazy {
|
||||
if (this is ObjString) this else ObjString(this.toString())
|
||||
}
|
||||
@ -53,39 +77,49 @@ sealed class Obj : Comparable<Obj> {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
open fun assign(context: Context, other: Obj): Obj {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
open fun plusAssign(context: Context, other: Obj): Obj {
|
||||
assign(context, plus(context, other))
|
||||
return this
|
||||
}
|
||||
|
||||
open fun getAndIncrement(context: Context): Obj {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
open fun incrementAndGet(context: Context): Obj {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
open fun decrementAndGet(context: Context): Obj {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
open fun getAndDecrement(context: Context): Obj {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
enum class Type {
|
||||
@SerialName("Void")
|
||||
Void,
|
||||
fun willMutate(context: Context) {
|
||||
if (isFrozen) context.raiseError("attempt to mutate frozen object")
|
||||
}
|
||||
|
||||
@SerialName("Null")
|
||||
Null,
|
||||
suspend fun getInstanceMember(context: Context, name: String): Obj? = definition.getInstanceMethodOrNull(name)
|
||||
|
||||
@SerialName("String")
|
||||
String,
|
||||
suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() }
|
||||
|
||||
@SerialName("Int")
|
||||
Int,
|
||||
open suspend fun readField(context: Context, name: String): Obj {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
@SerialName("Real")
|
||||
Real,
|
||||
open suspend fun writeField(context: Context,name: String, newValue: Obj) {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
@SerialName("Bool")
|
||||
Bool,
|
||||
|
||||
@SerialName("Fn")
|
||||
Fn,
|
||||
|
||||
@SerialName("Any")
|
||||
Any,
|
||||
open suspend fun callOn(context: Context): Obj {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ -117,7 +151,7 @@ object ObjVoid : Obj() {
|
||||
return other is ObjVoid || other is Unit
|
||||
}
|
||||
|
||||
override fun compareTo(other: Obj): Int {
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||
return if (other === this) 0 else -1
|
||||
}
|
||||
|
||||
@ -127,7 +161,7 @@ object ObjVoid : Obj() {
|
||||
@Serializable
|
||||
@SerialName("null")
|
||||
object ObjNull : Obj() {
|
||||
override fun compareTo(other: Obj): Int {
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||
return if (other === this) 0 else -1
|
||||
}
|
||||
|
||||
@ -140,8 +174,8 @@ object ObjNull : Obj() {
|
||||
@SerialName("string")
|
||||
data class ObjString(val value: String) : Obj() {
|
||||
|
||||
override fun compareTo(other: Obj): Int {
|
||||
if (other !is ObjString) throw IllegalArgumentException("cannot compare string with $other")
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||
if (other !is ObjString) context.raiseError("cannot compare string with $other")
|
||||
return this.value.compareTo(other.value)
|
||||
}
|
||||
|
||||
@ -181,8 +215,8 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
||||
override val toObjInt: ObjInt by lazy { ObjInt(longValue) }
|
||||
override val toObjReal: ObjReal by lazy { ObjReal(value) }
|
||||
|
||||
override fun compareTo(other: Obj): Int {
|
||||
if (other !is Numeric) throw IllegalArgumentException("cannot compare $this with $other")
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||
if (other !is Numeric) context.raiseError("cannot compare $this with $other")
|
||||
return value.compareTo(other.doubleValue)
|
||||
}
|
||||
|
||||
@ -206,15 +240,16 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
|
||||
return ObjInt(value).also { value-- }
|
||||
}
|
||||
|
||||
// override fun plus(context: Context, other: Obj): Obj {
|
||||
// if (other !is Numeric)
|
||||
// context.raiseError("cannot add $this with $other")
|
||||
// return if (other is ObjInt)
|
||||
//
|
||||
// }
|
||||
override fun incrementAndGet(context: Context): Obj {
|
||||
return ObjInt(++value)
|
||||
}
|
||||
|
||||
override fun compareTo(other: Obj): Int {
|
||||
if (other !is Numeric) throw IllegalArgumentException("cannot compare $this with $other")
|
||||
override fun decrementAndGet(context: Context): Obj {
|
||||
return ObjInt(--value)
|
||||
}
|
||||
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||
if (other !is Numeric) context.raiseError("cannot compare $this with $other")
|
||||
return value.compareTo(other.doubleValue)
|
||||
}
|
||||
|
||||
@ -226,8 +261,8 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
|
||||
data class ObjBool(val value: Boolean) : Obj() {
|
||||
override val asStr by lazy { ObjString(value.toString()) }
|
||||
|
||||
override fun compareTo(other: Obj): Int {
|
||||
if (other !is ObjBool) throw IllegalArgumentException("cannot compare $this with $other")
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||
if (other !is ObjBool) context.raiseError("cannot compare $this with $other")
|
||||
return value.compareTo(other.value)
|
||||
}
|
||||
|
||||
@ -239,16 +274,25 @@ data class ObjNamespace(val name: String, val context: Context) : Obj() {
|
||||
return "namespace ${name}"
|
||||
}
|
||||
|
||||
override fun compareTo(other: Obj): Int {
|
||||
throw IllegalArgumentException("cannot compare namespaces")
|
||||
override suspend fun readField(callerContext: Context,name: String): Obj {
|
||||
return context[name]?.value ?: callerContext.raiseError("not found: $name")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
open class ObjError(val context: Context, val message: String) : Obj() {
|
||||
override val asStr: ObjString by lazy { ObjString("Error: $message") }
|
||||
override fun compareTo(other: Obj): Int {
|
||||
if (other === this) return 0 else return -1
|
||||
}
|
||||
}
|
||||
|
||||
class ObjNullPointerError(context: Context) : ObjError(context, "object is null")
|
||||
|
||||
class ObjClass(override val definition: ClassDef) : Obj() {
|
||||
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||
// definition.callInstanceMethod(":compareTo", context, other)?.let {
|
||||
// it(context, this)
|
||||
// }
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
}
|
@ -51,19 +51,31 @@ private class Parser(fromPos: Pos) {
|
||||
}
|
||||
|
||||
'+' -> {
|
||||
if( currentChar == '+') {
|
||||
advance()
|
||||
Token("+", from, Token.Type.PLUS2)
|
||||
when (currentChar) {
|
||||
'+' -> {
|
||||
advance()
|
||||
Token("+", from, Token.Type.PLUS2)
|
||||
}
|
||||
'=' -> {
|
||||
advance()
|
||||
Token("+", from, Token.Type.PLUSASSIGN)
|
||||
}
|
||||
else ->
|
||||
Token("+", from, Token.Type.PLUS)
|
||||
}
|
||||
else
|
||||
Token("+", from, Token.Type.PLUS)
|
||||
}
|
||||
'-' -> {
|
||||
if (currentChar == '-') {
|
||||
advance()
|
||||
Token("--", from, Token.Type.MINUS2)
|
||||
} else
|
||||
Token("-", from, Token.Type.MINUS)
|
||||
when (currentChar) {
|
||||
'-' -> {
|
||||
advance()
|
||||
Token("--", from, Token.Type.MINUS2)
|
||||
}
|
||||
'=' -> {
|
||||
advance()
|
||||
Token("-", from, Token.Type.MINUSASSIGN)
|
||||
}
|
||||
else -> Token("-", from, Token.Type.MINUS)
|
||||
}
|
||||
}
|
||||
'*' -> Token("*", from, Token.Type.STAR)
|
||||
'/' -> {
|
||||
@ -285,6 +297,7 @@ private class Parser(fromPos: Pos) {
|
||||
private fun skipws(): Char? {
|
||||
while (!pos.end) {
|
||||
val ch = pos.currentChar
|
||||
if( ch == '\n') break
|
||||
if (ch.isWhitespace())
|
||||
advance()
|
||||
else
|
||||
|
@ -4,7 +4,6 @@ package net.sergeych.ling
|
||||
* Whatever [Obj] stored somewhere
|
||||
*/
|
||||
data class StoredObj(
|
||||
val name: String,
|
||||
var value: Obj?,
|
||||
val isMutable: Boolean = false
|
||||
)
|
@ -7,10 +7,11 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
|
||||
enum class Type {
|
||||
ID, INT, REAL, HEX, STRING, LPAREN, RPAREN, LBRACE, RBRACE, LBRACKET, RBRACKET, COMMA,
|
||||
SEMICOLON, COLON,
|
||||
PLUS, MINUS, STAR, SLASH, ASSIGN,
|
||||
PLUS, MINUS, STAR, SLASH, PERCENT,
|
||||
ASSIGN, PLUSASSIGN, MINUSASSIGN, STARASSIGN, SLASHASSIGN, PERCENTASSIGN,
|
||||
PLUS2, MINUS2,
|
||||
EQ, NEQ, LT, LTE, GT, GTE,
|
||||
AND, BITAND, OR, BITOR, NOT, DOT, ARROW, QUESTION, COLONCOLON, PERCENT,
|
||||
AND, BITAND, OR, BITOR, NOT, DOT, ARROW, QUESTION, COLONCOLON,
|
||||
SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
|
||||
LABEL,ATLABEL, // label@ at@label
|
||||
NEWLINE,
|
||||
|
@ -2,21 +2,30 @@ package net.sergeych.ling
|
||||
|
||||
fun String.toSource(name: String = "eval"): Source = Source(name, this)
|
||||
|
||||
sealed class ObjType {
|
||||
object Any : ObjType()
|
||||
object Int : ObjType()
|
||||
}
|
||||
|
||||
|
||||
@Suppress("unused")
|
||||
abstract class Statement(
|
||||
val isStaticConst: Boolean = false,
|
||||
val isConst: Boolean = false,
|
||||
val returnType: Type = Type.Any
|
||||
val returnType: ObjType = ObjType.Any
|
||||
) : Obj() {
|
||||
|
||||
abstract val pos: Pos
|
||||
abstract suspend fun execute(context: Context): Obj
|
||||
|
||||
override fun compareTo(other: Obj): Int {
|
||||
override suspend fun compareTo(context: Context,other: Obj): Int {
|
||||
throw UnsupportedOperationException("not comparable")
|
||||
}
|
||||
|
||||
override suspend fun callOn(context: Context): Obj {
|
||||
return execute(context)
|
||||
}
|
||||
|
||||
override fun toString(): String = "Callable@${this.hashCode()}"
|
||||
|
||||
}
|
||||
@ -30,8 +39,8 @@ fun Statement.require(cond: Boolean, message: () -> String) {
|
||||
if (!cond) raise(message())
|
||||
}
|
||||
|
||||
fun statement(pos: Pos, isStaticConst: Boolean = false, f: suspend (Context) -> Obj): Statement =
|
||||
object : Statement(isStaticConst) {
|
||||
fun statement(pos: Pos, isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend (Context) -> Obj): Statement =
|
||||
object : Statement(isStaticConst, isConst) {
|
||||
override val pos: Pos = pos
|
||||
override suspend fun execute(context: Context): Obj = f(context)
|
||||
}
|
||||
|
@ -7,6 +7,24 @@ import kotlin.test.*
|
||||
|
||||
class ScriptTest {
|
||||
|
||||
@Test
|
||||
fun parseNewlines() {
|
||||
fun check(expected: String, type: Token.Type, row: Int, col: Int, src: String, offset: Int = 0) {
|
||||
val source = src.toSource()
|
||||
assertEquals(
|
||||
Token(expected, source.posAt(row, col), type),
|
||||
parseLing(source)[offset]
|
||||
)
|
||||
}
|
||||
check("1", Token.Type.INT, 0, 0, "1 + x\n2", 0)
|
||||
check("+", Token.Type.PLUS, 0, 2, "1 + x\n2", 1)
|
||||
check("x", Token.Type.ID, 0, 4, "1 + x\n2", 2)
|
||||
check("\n", Token.Type.NEWLINE, 0, 5, "1 + x\n2", 3)
|
||||
// check("2", Token.Type.INT, 1, 0, "1 + x\n2", 4)
|
||||
// check("", Token.Type.EOF, 1, 0, "1 + x\n2", 5)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseNumbersTest() {
|
||||
fun check(expected: String, type: Token.Type, row: Int, col: Int, src: String, offset: Int = 0) {
|
||||
@ -69,7 +87,7 @@ class ScriptTest {
|
||||
assertEquals(Token("(", src.posAt(0, 7), Token.Type.LPAREN), p.next())
|
||||
assertEquals(Token("Hello", src.posAt(0, 8), Token.Type.STRING), p.next())
|
||||
assertEquals(Token(")", src.posAt(0, 15), Token.Type.RPAREN), p.next())
|
||||
|
||||
assertEquals(Token("\n", src.posAt(0, 16), Token.Type.NEWLINE), p.next())
|
||||
assertEquals(Token("println", src.posAt(1, 0), Token.Type.ID), p.next())
|
||||
assertEquals(Token("(", src.posAt(1, 7), Token.Type.LPAREN), p.next())
|
||||
assertEquals(Token("world", src.posAt(1, 9), Token.Type.STRING), p.next())
|
||||
@ -290,7 +308,7 @@ class ScriptTest {
|
||||
context.eval(
|
||||
"""
|
||||
fn test1(n) {
|
||||
if( n >= 10 )
|
||||
if( n >= 10 )
|
||||
"enough"
|
||||
else
|
||||
"more"
|
||||
@ -301,13 +319,13 @@ class ScriptTest {
|
||||
assertEquals("more", context.eval("test1(1)").toString())
|
||||
|
||||
// if/else with blocks
|
||||
context = Context(pos = Pos.builtIn )
|
||||
context = Context(pos = Pos.builtIn)
|
||||
context.eval(
|
||||
"""
|
||||
fn test1(n) {
|
||||
if( n > 20 ) {
|
||||
"too much"
|
||||
} else if( n >= 10 ) {
|
||||
} else if( n >= 10 ) {
|
||||
"enough"
|
||||
}
|
||||
else {
|
||||
@ -425,6 +443,7 @@ class ScriptTest {
|
||||
"""
|
||||
val count = 3
|
||||
val res = if( count > 10 ) "too much" else "just " + count
|
||||
println(res)
|
||||
res
|
||||
""".trimIndent()
|
||||
)
|
||||
@ -513,16 +532,21 @@ class ScriptTest {
|
||||
@Test
|
||||
fun testIncrAndDecr() = runTest {
|
||||
val c = Context()
|
||||
assertEquals( "8", c.eval("""
|
||||
assertEquals(
|
||||
"8", c.eval(
|
||||
"""
|
||||
var x = 5
|
||||
x--
|
||||
x--
|
||||
x++
|
||||
x * 2
|
||||
""").toString())
|
||||
"""
|
||||
).toString()
|
||||
)
|
||||
|
||||
assertEquals( "4", c.eval("x").toString())
|
||||
assertEquals("4", c.eval("x").toString())
|
||||
// assertEquals( "8", c.eval("x*2").toString())
|
||||
// assertEquals( "4", c.eval("x+0").toString())
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user