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>()
|
val labels = mutableSetOf<String>()
|
||||||
|
|
||||||
fun ensureLabelIsValid(pos: Pos, label: String) {
|
fun ensureLabelIsValid(pos: Pos, label: String) {
|
||||||
if (label !in labels)
|
if (label !in labels)
|
||||||
throw ScriptError(pos, "Undefined label '$label'")
|
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)
|
parseExpression(tokens)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.PLUS2, Token.Type.MINUS2 -> {
|
Token.Type.PLUS2, Token.Type.MINUS2 -> {
|
||||||
tokens.previous()
|
tokens.previous()
|
||||||
parseExpression(tokens)
|
parseExpression(tokens)
|
||||||
@ -132,7 +170,7 @@ class Compiler {
|
|||||||
|
|
||||||
private fun parseExpression(tokens: CompilerContext, level: Int = 0): Statement? {
|
private fun parseExpression(tokens: CompilerContext, level: Int = 0): Statement? {
|
||||||
if (level == lastLevel)
|
if (level == lastLevel)
|
||||||
return parseTerm(tokens)
|
return parseTerm3(tokens)
|
||||||
var lvalue = parseExpression(tokens, level + 1)
|
var lvalue = parseExpression(tokens, level + 1)
|
||||||
if (lvalue == null) return null
|
if (lvalue == null) return null
|
||||||
|
|
||||||
@ -153,153 +191,404 @@ class Compiler {
|
|||||||
return lvalue
|
return lvalue
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parseTerm(tokens: CompilerContext): Statement? {
|
|
||||||
// call op
|
/*
|
||||||
// index op
|
Term compiler
|
||||||
// unary op
|
|
||||||
// parenthesis
|
Fn calls could be:
|
||||||
// number or string
|
|
||||||
val t = tokens.next()
|
1) Fn(...)
|
||||||
// todoL var?
|
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) {
|
return when (t.type) {
|
||||||
Token.Type.ID -> {
|
Token.Type.INT, Token.Type.REAL, Token.Type.HEX -> {
|
||||||
when (t.value) {
|
cc.previous()
|
||||||
"void" -> statement(t.pos, true) { ObjVoid }
|
val n = parseNumber(true, cc)
|
||||||
"null" -> statement(t.pos, true) { ObjNull }
|
Accessor({ n })
|
||||||
"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.STRING -> Accessor({ 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 -> {
|
Token.Type.PLUS -> {
|
||||||
val n = parseNumber(true, tokens)
|
val n = parseNumber(true, cc)
|
||||||
statement(t.pos, true) { n }
|
Accessor { n }
|
||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.MINUS -> {
|
Token.Type.MINUS -> {
|
||||||
val n = parseNumber(false, tokens)
|
val n = parseNumber(false, cc)
|
||||||
statement(t.pos, true) { n }
|
Accessor { n }
|
||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.INT, Token.Type.REAL, Token.Type.HEX -> {
|
Token.Type.ID -> {
|
||||||
tokens.previous()
|
when (t.value) {
|
||||||
val n = parseNumber(true, tokens)
|
"void" -> Accessor { ObjVoid }
|
||||||
statement(t.pos, true) { n }
|
"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
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parseVarAccess(id: Token, tokens: CompilerContext, path: List<String> = emptyList()): Statement {
|
|
||||||
val nt = tokens.next()
|
|
||||||
|
|
||||||
fun resolve(context: Context): Context {
|
// fun parseTerm(tokens: CompilerContext): Statement? {
|
||||||
var targetContext = context
|
// // call op
|
||||||
for (n in path) {
|
// // index op
|
||||||
val x = targetContext[n] ?: throw ScriptError(id.pos, "undefined symbol: $n")
|
// // unary op
|
||||||
(x.value as? ObjNamespace)?.let { targetContext = it.context }
|
// // parenthesis
|
||||||
?: throw ScriptError(id.pos, "Invalid symbolic path (wrong type of ${x.name}: ${x.value}")
|
// // number or string
|
||||||
}
|
// val t = tokens.next()
|
||||||
return targetContext
|
// // todoL var?
|
||||||
}
|
// return when (t.type) {
|
||||||
return when (nt.type) {
|
// Token.Type.ID -> {
|
||||||
Token.Type.DOT -> {
|
// when (t.value) {
|
||||||
// selector
|
// "void" -> statement(t.pos, true) { ObjVoid }
|
||||||
val t = tokens.next()
|
// "null" -> statement(t.pos, true) { ObjNull }
|
||||||
if (t.type == Token.Type.ID) {
|
// "true" -> statement(t.pos, true) { ObjBool(true) }
|
||||||
parseVarAccess(t, tokens, path + id.value)
|
// "false" -> statement(t.pos, true) { ObjBool(false) }
|
||||||
} else
|
// else -> parseVarAccess(t, tokens)
|
||||||
throw ScriptError(t.pos, "Expected identifier after '.'")
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
Token.Type.PLUS2 -> {
|
// Token.Type.STRING -> statement(t.pos, true) { ObjString(t.value) }
|
||||||
statement(id.pos) { context ->
|
//
|
||||||
context.pos = id.pos
|
// Token.Type.LPAREN -> {
|
||||||
val v = resolve(context).get(id.value)
|
// // ( subexpr )
|
||||||
?: throw ScriptError(id.pos, "Undefined symbol: ${id.value}")
|
// parseExpression(tokens)?.also {
|
||||||
v.value?.getAndIncrement(context)
|
// val tl = tokens.next()
|
||||||
?: context.raiseNPE()
|
// if (tl.type != Token.Type.RPAREN)
|
||||||
}
|
// throw ScriptError(t.pos, "unbalanced parenthesis: no ')' for it")
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
Token.Type.MINUS2 -> {
|
//
|
||||||
statement(id.pos) { context ->
|
// Token.Type.PLUS -> {
|
||||||
context.pos = id.pos
|
// val n = parseNumber(true, tokens)
|
||||||
val v = resolve(context).get(id.value)
|
// statement(t.pos, true) { n }
|
||||||
?: throw ScriptError(id.pos, "Undefined symbol: ${id.value}")
|
// }
|
||||||
v.value?.getAndDecrement(context)
|
//
|
||||||
?: context.raiseNPE()
|
// Token.Type.MINUS -> {
|
||||||
}
|
// val n = parseNumber(false, tokens)
|
||||||
}
|
// statement(t.pos, true) { n }
|
||||||
Token.Type.LPAREN -> {
|
// }
|
||||||
// function call
|
//
|
||||||
// Load arg list
|
// Token.Type.INT, Token.Type.REAL, Token.Type.HEX -> {
|
||||||
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()
|
// tokens.previous()
|
||||||
// println(t)
|
// val n = parseNumber(true, tokens)
|
||||||
// if( path.isEmpty() ) {
|
// statement(t.pos, true) { n }
|
||||||
// statement?
|
// }
|
||||||
// tokens.previous()
|
//
|
||||||
// parseStatement(tokens) ?: throw ScriptError(id.pos, "Expecting expression/statement")
|
// else -> null
|
||||||
// } 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 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 {
|
fun parseNumber(isPlus: Boolean, tokens: CompilerContext): Obj {
|
||||||
val t = tokens.next()
|
val t = tokens.next()
|
||||||
@ -458,12 +747,14 @@ class Compiler {
|
|||||||
|
|
||||||
val ifBody = parseStatement(tokens) ?: throw ScriptError(pos, "Bad if statement: expected statement")
|
val ifBody = parseStatement(tokens) ?: throw ScriptError(pos, "Bad if statement: expected statement")
|
||||||
|
|
||||||
|
tokens.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
|
||||||
// could be else block:
|
// could be else block:
|
||||||
val t2 = tokens.next()
|
val t2 = tokens.next()
|
||||||
|
|
||||||
// we generate different statements: optimization
|
// we generate different statements: optimization
|
||||||
return if (t2.type == Token.Type.ID && t2.value == "else") {
|
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) {
|
return statement(start) {
|
||||||
if (condition.execute(it).toBool())
|
if (condition.execute(it).toBool())
|
||||||
ifBody.execute(it)
|
ifBody.execute(it)
|
||||||
@ -626,13 +917,13 @@ class Compiler {
|
|||||||
// bitwise or 2
|
// bitwise or 2
|
||||||
// bitwise and 3
|
// bitwise and 3
|
||||||
// equality/ne 4
|
// equality/ne 4
|
||||||
LogicalOp(Token.Type.EQ, 4) { a, b -> a == b },
|
LogicalOp(Token.Type.EQ, 4) { c, a, b -> a.compareTo(c, b) == 0 },
|
||||||
LogicalOp(Token.Type.NEQ, 4) { a, b -> a != b },
|
LogicalOp(Token.Type.NEQ, 4) { c, a, b -> a.compareTo(c, b) != 0 },
|
||||||
// relational <=,... 5
|
// relational <=,... 5
|
||||||
LogicalOp(Token.Type.LTE, 5) { a, b -> a <= b },
|
LogicalOp(Token.Type.LTE, 5) { c, a, b -> a.compareTo(c, b) <= 0 },
|
||||||
LogicalOp(Token.Type.LT, 5) { a, b -> a < b },
|
LogicalOp(Token.Type.LT, 5) { c, a, b -> a.compareTo(c, b) < 0 },
|
||||||
LogicalOp(Token.Type.GTE, 5) { a, b -> a >= b },
|
LogicalOp(Token.Type.GTE, 5) { c, a, b -> a.compareTo(c, b) >= 0 },
|
||||||
LogicalOp(Token.Type.GT, 5) { a, b -> a > b },
|
LogicalOp(Token.Type.GT, 5) { c, a, b -> a.compareTo(c, b) > 0 },
|
||||||
// shuttle <=> 6
|
// shuttle <=> 6
|
||||||
// bitshhifts 7
|
// bitshhifts 7
|
||||||
Operator(Token.Type.PLUS, 8, 2) { pos, a, b ->
|
Operator(Token.Type.PLUS, 8, 2) { pos, a, b ->
|
||||||
@ -657,14 +948,17 @@ class Compiler {
|
|||||||
|
|
||||||
suspend fun eval(code: String) = Compiler.compile(code).execute()
|
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,
|
tokenType,
|
||||||
priority,
|
priority,
|
||||||
2
|
2
|
||||||
) { pos, a, b ->
|
) { pos, a, b ->
|
||||||
statement(pos) {
|
statement(pos) {
|
||||||
ObjBool(
|
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 copy(pos: Pos, args: Arguments = Arguments.EMPTY): Context = Context(this, args, pos)
|
||||||
|
|
||||||
fun addItem(name: String, isMutable: Boolean, value: Obj?) {
|
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) =
|
fun getOrCreateNamespace(name: String) =
|
||||||
(objects.getOrPut(name) {
|
(objects.getOrPut(name) {
|
||||||
StoredObj(
|
StoredObj(
|
||||||
name,
|
|
||||||
ObjNamespace(name, copy(pos)),
|
ObjNamespace(name, copy(pos)),
|
||||||
isMutable = false
|
isMutable = false
|
||||||
)
|
)
|
||||||
|
@ -8,41 +8,65 @@ import kotlin.math.floor
|
|||||||
|
|
||||||
typealias InstanceMethod = (Context, Obj) -> Obj
|
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(
|
sealed class ClassDef(
|
||||||
val className: String
|
val className: String
|
||||||
) {
|
) {
|
||||||
val baseClasses: List<ClassDef> get() = emptyList()
|
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(
|
suspend fun addInstanceMethod(
|
||||||
|
context: Context,
|
||||||
name: String,
|
name: String,
|
||||||
freeze: Boolean = false,
|
isOpen: Boolean = false,
|
||||||
pos: Pos = Pos.builtIn,
|
body: Obj
|
||||||
body: InstanceMethod
|
|
||||||
) {
|
) {
|
||||||
instanceLock.withLock {
|
monitor.withLock {
|
||||||
instanceMethods[name]?.let {
|
instanceMembers[name]?.let {
|
||||||
if (!it.isMutable)
|
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
|
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")
|
object ObjClassDef : ClassDef("Obj")
|
||||||
|
|
||||||
@Serializable
|
sealed class Obj {
|
||||||
sealed class Obj : Comparable<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 {
|
open val asStr: ObjString by lazy {
|
||||||
if (this is ObjString) this else ObjString(this.toString())
|
if (this is ObjString) this else ObjString(this.toString())
|
||||||
}
|
}
|
||||||
@ -53,39 +77,49 @@ sealed class Obj : Comparable<Obj> {
|
|||||||
context.raiseNotImplemented()
|
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 {
|
open fun getAndIncrement(context: Context): Obj {
|
||||||
context.raiseNotImplemented()
|
context.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open fun incrementAndGet(context: Context): Obj {
|
||||||
|
context.raiseNotImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun decrementAndGet(context: Context): Obj {
|
||||||
|
context.raiseNotImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
open fun getAndDecrement(context: Context): Obj {
|
open fun getAndDecrement(context: Context): Obj {
|
||||||
context.raiseNotImplemented()
|
context.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
fun willMutate(context: Context) {
|
||||||
enum class Type {
|
if (isFrozen) context.raiseError("attempt to mutate frozen object")
|
||||||
@SerialName("Void")
|
}
|
||||||
Void,
|
|
||||||
|
|
||||||
@SerialName("Null")
|
suspend fun getInstanceMember(context: Context, name: String): Obj? = definition.getInstanceMethodOrNull(name)
|
||||||
Null,
|
|
||||||
|
|
||||||
@SerialName("String")
|
suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() }
|
||||||
String,
|
|
||||||
|
|
||||||
@SerialName("Int")
|
open suspend fun readField(context: Context, name: String): Obj {
|
||||||
Int,
|
context.raiseNotImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
@SerialName("Real")
|
open suspend fun writeField(context: Context,name: String, newValue: Obj) {
|
||||||
Real,
|
context.raiseNotImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
@SerialName("Bool")
|
open suspend fun callOn(context: Context): Obj {
|
||||||
Bool,
|
context.raiseNotImplemented()
|
||||||
|
|
||||||
@SerialName("Fn")
|
|
||||||
Fn,
|
|
||||||
|
|
||||||
@SerialName("Any")
|
|
||||||
Any,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -117,7 +151,7 @@ object ObjVoid : Obj() {
|
|||||||
return other is ObjVoid || other is Unit
|
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
|
return if (other === this) 0 else -1
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,7 +161,7 @@ object ObjVoid : Obj() {
|
|||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("null")
|
@SerialName("null")
|
||||||
object ObjNull : Obj() {
|
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
|
return if (other === this) 0 else -1
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,8 +174,8 @@ object ObjNull : Obj() {
|
|||||||
@SerialName("string")
|
@SerialName("string")
|
||||||
data class ObjString(val value: String) : Obj() {
|
data class ObjString(val value: String) : Obj() {
|
||||||
|
|
||||||
override fun compareTo(other: Obj): Int {
|
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||||
if (other !is ObjString) throw IllegalArgumentException("cannot compare string with $other")
|
if (other !is ObjString) context.raiseError("cannot compare string with $other")
|
||||||
return this.value.compareTo(other.value)
|
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 toObjInt: ObjInt by lazy { ObjInt(longValue) }
|
||||||
override val toObjReal: ObjReal by lazy { ObjReal(value) }
|
override val toObjReal: ObjReal by lazy { ObjReal(value) }
|
||||||
|
|
||||||
override fun compareTo(other: Obj): Int {
|
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||||
if (other !is Numeric) throw IllegalArgumentException("cannot compare $this with $other")
|
if (other !is Numeric) context.raiseError("cannot compare $this with $other")
|
||||||
return value.compareTo(other.doubleValue)
|
return value.compareTo(other.doubleValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,15 +240,16 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
|
|||||||
return ObjInt(value).also { value-- }
|
return ObjInt(value).also { value-- }
|
||||||
}
|
}
|
||||||
|
|
||||||
// override fun plus(context: Context, other: Obj): Obj {
|
override fun incrementAndGet(context: Context): Obj {
|
||||||
// if (other !is Numeric)
|
return ObjInt(++value)
|
||||||
// context.raiseError("cannot add $this with $other")
|
}
|
||||||
// return if (other is ObjInt)
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
||||||
override fun compareTo(other: Obj): Int {
|
override fun decrementAndGet(context: Context): Obj {
|
||||||
if (other !is Numeric) throw IllegalArgumentException("cannot compare $this with $other")
|
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)
|
return value.compareTo(other.doubleValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,8 +261,8 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
|
|||||||
data class ObjBool(val value: Boolean) : Obj() {
|
data class ObjBool(val value: Boolean) : Obj() {
|
||||||
override val asStr by lazy { ObjString(value.toString()) }
|
override val asStr by lazy { ObjString(value.toString()) }
|
||||||
|
|
||||||
override fun compareTo(other: Obj): Int {
|
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||||
if (other !is ObjBool) throw IllegalArgumentException("cannot compare $this with $other")
|
if (other !is ObjBool) context.raiseError("cannot compare $this with $other")
|
||||||
return value.compareTo(other.value)
|
return value.compareTo(other.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,16 +274,25 @@ data class ObjNamespace(val name: String, val context: Context) : Obj() {
|
|||||||
return "namespace ${name}"
|
return "namespace ${name}"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun compareTo(other: Obj): Int {
|
override suspend fun readField(callerContext: Context,name: String): Obj {
|
||||||
throw IllegalArgumentException("cannot compare namespaces")
|
return context[name]?.value ?: callerContext.raiseError("not found: $name")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open class ObjError(val context: Context, val message: String) : Obj() {
|
open class ObjError(val context: Context, val message: String) : Obj() {
|
||||||
override val asStr: ObjString by lazy { ObjString("Error: $message") }
|
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 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 == '+') {
|
when (currentChar) {
|
||||||
advance()
|
'+' -> {
|
||||||
Token("+", from, Token.Type.PLUS2)
|
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 == '-') {
|
when (currentChar) {
|
||||||
advance()
|
'-' -> {
|
||||||
Token("--", from, Token.Type.MINUS2)
|
advance()
|
||||||
} else
|
Token("--", from, Token.Type.MINUS2)
|
||||||
Token("-", from, Token.Type.MINUS)
|
}
|
||||||
|
'=' -> {
|
||||||
|
advance()
|
||||||
|
Token("-", from, Token.Type.MINUSASSIGN)
|
||||||
|
}
|
||||||
|
else -> Token("-", from, Token.Type.MINUS)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
'*' -> Token("*", from, Token.Type.STAR)
|
'*' -> Token("*", from, Token.Type.STAR)
|
||||||
'/' -> {
|
'/' -> {
|
||||||
@ -285,6 +297,7 @@ private class Parser(fromPos: Pos) {
|
|||||||
private fun skipws(): Char? {
|
private fun skipws(): Char? {
|
||||||
while (!pos.end) {
|
while (!pos.end) {
|
||||||
val ch = pos.currentChar
|
val ch = pos.currentChar
|
||||||
|
if( ch == '\n') break
|
||||||
if (ch.isWhitespace())
|
if (ch.isWhitespace())
|
||||||
advance()
|
advance()
|
||||||
else
|
else
|
||||||
|
@ -4,7 +4,6 @@ package net.sergeych.ling
|
|||||||
* Whatever [Obj] stored somewhere
|
* Whatever [Obj] stored somewhere
|
||||||
*/
|
*/
|
||||||
data class StoredObj(
|
data class StoredObj(
|
||||||
val name: String,
|
|
||||||
var value: Obj?,
|
var value: Obj?,
|
||||||
val isMutable: Boolean = false
|
val isMutable: Boolean = false
|
||||||
)
|
)
|
@ -7,10 +7,11 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
|
|||||||
enum class Type {
|
enum class Type {
|
||||||
ID, INT, REAL, HEX, STRING, LPAREN, RPAREN, LBRACE, RBRACE, LBRACKET, RBRACKET, COMMA,
|
ID, INT, REAL, HEX, STRING, LPAREN, RPAREN, LBRACE, RBRACE, LBRACKET, RBRACKET, COMMA,
|
||||||
SEMICOLON, COLON,
|
SEMICOLON, COLON,
|
||||||
PLUS, MINUS, STAR, SLASH, ASSIGN,
|
PLUS, MINUS, STAR, SLASH, PERCENT,
|
||||||
|
ASSIGN, PLUSASSIGN, MINUSASSIGN, STARASSIGN, SLASHASSIGN, PERCENTASSIGN,
|
||||||
PLUS2, MINUS2,
|
PLUS2, MINUS2,
|
||||||
EQ, NEQ, LT, LTE, GT, GTE,
|
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,
|
SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
|
||||||
LABEL,ATLABEL, // label@ at@label
|
LABEL,ATLABEL, // label@ at@label
|
||||||
NEWLINE,
|
NEWLINE,
|
||||||
|
@ -2,21 +2,30 @@ package net.sergeych.ling
|
|||||||
|
|
||||||
fun String.toSource(name: String = "eval"): Source = Source(name, this)
|
fun String.toSource(name: String = "eval"): Source = Source(name, this)
|
||||||
|
|
||||||
|
sealed class ObjType {
|
||||||
|
object Any : ObjType()
|
||||||
|
object Int : ObjType()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
abstract class Statement(
|
abstract class Statement(
|
||||||
val isStaticConst: Boolean = false,
|
val isStaticConst: Boolean = false,
|
||||||
val isConst: Boolean = false,
|
val isConst: Boolean = false,
|
||||||
val returnType: Type = Type.Any
|
val returnType: ObjType = ObjType.Any
|
||||||
) : Obj() {
|
) : Obj() {
|
||||||
|
|
||||||
abstract val pos: Pos
|
abstract val pos: Pos
|
||||||
abstract suspend fun execute(context: Context): Obj
|
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")
|
throw UnsupportedOperationException("not comparable")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun callOn(context: Context): Obj {
|
||||||
|
return execute(context)
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString(): String = "Callable@${this.hashCode()}"
|
override fun toString(): String = "Callable@${this.hashCode()}"
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -30,8 +39,8 @@ fun Statement.require(cond: Boolean, message: () -> String) {
|
|||||||
if (!cond) raise(message())
|
if (!cond) raise(message())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun statement(pos: Pos, isStaticConst: Boolean = false, f: suspend (Context) -> Obj): Statement =
|
fun statement(pos: Pos, isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend (Context) -> Obj): Statement =
|
||||||
object : Statement(isStaticConst) {
|
object : Statement(isStaticConst, isConst) {
|
||||||
override val pos: Pos = pos
|
override val pos: Pos = pos
|
||||||
override suspend fun execute(context: Context): Obj = f(context)
|
override suspend fun execute(context: Context): Obj = f(context)
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,24 @@ import kotlin.test.*
|
|||||||
|
|
||||||
class ScriptTest {
|
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
|
@Test
|
||||||
fun parseNumbersTest() {
|
fun parseNumbersTest() {
|
||||||
fun check(expected: String, type: Token.Type, row: Int, col: Int, src: String, offset: Int = 0) {
|
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("(", src.posAt(0, 7), Token.Type.LPAREN), p.next())
|
||||||
assertEquals(Token("Hello", src.posAt(0, 8), Token.Type.STRING), 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(")", 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("println", src.posAt(1, 0), Token.Type.ID), p.next())
|
||||||
assertEquals(Token("(", src.posAt(1, 7), Token.Type.LPAREN), 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())
|
assertEquals(Token("world", src.posAt(1, 9), Token.Type.STRING), p.next())
|
||||||
@ -290,7 +308,7 @@ class ScriptTest {
|
|||||||
context.eval(
|
context.eval(
|
||||||
"""
|
"""
|
||||||
fn test1(n) {
|
fn test1(n) {
|
||||||
if( n >= 10 )
|
if( n >= 10 )
|
||||||
"enough"
|
"enough"
|
||||||
else
|
else
|
||||||
"more"
|
"more"
|
||||||
@ -301,13 +319,13 @@ class ScriptTest {
|
|||||||
assertEquals("more", context.eval("test1(1)").toString())
|
assertEquals("more", context.eval("test1(1)").toString())
|
||||||
|
|
||||||
// if/else with blocks
|
// if/else with blocks
|
||||||
context = Context(pos = Pos.builtIn )
|
context = Context(pos = Pos.builtIn)
|
||||||
context.eval(
|
context.eval(
|
||||||
"""
|
"""
|
||||||
fn test1(n) {
|
fn test1(n) {
|
||||||
if( n > 20 ) {
|
if( n > 20 ) {
|
||||||
"too much"
|
"too much"
|
||||||
} else if( n >= 10 ) {
|
} else if( n >= 10 ) {
|
||||||
"enough"
|
"enough"
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -425,6 +443,7 @@ class ScriptTest {
|
|||||||
"""
|
"""
|
||||||
val count = 3
|
val count = 3
|
||||||
val res = if( count > 10 ) "too much" else "just " + count
|
val res = if( count > 10 ) "too much" else "just " + count
|
||||||
|
println(res)
|
||||||
res
|
res
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
@ -513,16 +532,21 @@ class ScriptTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testIncrAndDecr() = runTest {
|
fun testIncrAndDecr() = runTest {
|
||||||
val c = Context()
|
val c = Context()
|
||||||
assertEquals( "8", c.eval("""
|
assertEquals(
|
||||||
|
"8", c.eval(
|
||||||
|
"""
|
||||||
var x = 5
|
var x = 5
|
||||||
x--
|
x--
|
||||||
x--
|
x--
|
||||||
x++
|
x++
|
||||||
x * 2
|
x * 2
|
||||||
""").toString())
|
"""
|
||||||
|
).toString()
|
||||||
|
)
|
||||||
|
|
||||||
assertEquals( "4", c.eval("x").toString())
|
assertEquals("4", c.eval("x").toString())
|
||||||
// assertEquals( "8", c.eval("x*2").toString())
|
// assertEquals( "8", c.eval("x*2").toString())
|
||||||
// assertEquals( "4", c.eval("x+0").toString())
|
// assertEquals( "4", c.eval("x+0").toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user