big refactoring towards OO support just started

This commit is contained in:
Sergey Chernov 2025-05-26 18:51:42 +04:00
parent baf9eab3ba
commit eba29aedb7
8 changed files with 605 additions and 222 deletions

View File

@ -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))
) )
} }
} }

View File

@ -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
) )

View File

@ -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")
}
}

View File

@ -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

View File

@ -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
) )

View File

@ -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,

View File

@ -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)
} }

View File

@ -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())
} }
} }