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(...)
return when (t.type) {
Token.Type.ID -> { 1 is a shortcut to this.Fn(...)
when (t.value) {
"void" -> statement(t.pos, true) { ObjVoid } In general, we can assume any Fn to be of the same king, with `this` that always exist, and set by invocation.
"null" -> statement(t.pos, true) { ObjNull }
"true" -> statement(t.pos, true) { ObjBool(true) } In the case of (1), so called regular, or not bound function, it takes current this from the context.
"false" -> statement(t.pos, true) { ObjBool(false) } In the case of (2), bound function, it creates sub-context binding thisObj to `this` in it.
else -> parseVarAccess(t, tokens)
} 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.STRING -> statement(t.pos, true) { ObjString(t.value) } Token.Type.DOT -> {
if (operand == null)
throw ScriptError(t.pos, "Expecting expression before dot")
continue
}
Token.Type.LPAREN -> { Token.Type.LPAREN -> {
// ( subexpr ) operand?.let { left ->
parseExpression(tokens)?.also { // this is function call from <left>
val tl = tokens.next() operand = parseFunctionCall(
if (tl.type != Token.Type.RPAREN) cc,
throw ScriptError(t.pos, "unbalanced parenthesis: no ')' for it") 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.PLUS -> { Token.Type.ID -> {
val n = parseNumber(true, tokens) operand?.let { left ->
statement(t.pos, true) { n } // 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 {
Token.Type.MINUS -> { // variable to read or like
val n = parseNumber(false, tokens) cc.previous()
statement(t.pos, true) { n } operand = parseAccessor(cc)
} }
Token.Type.INT, Token.Type.REAL, Token.Type.HEX -> {
tokens.previous()
val n = parseNumber(true, tokens)
statement(t.pos, true) { n }
}
else -> null
}
}
fun parseVarAccess(id: Token, tokens: CompilerContext, path: List<String> = emptyList()): Statement {
val nt = tokens.next()
fun resolve(context: Context): Context {
var targetContext = context
for (n in path) {
val x = targetContext[n] ?: throw ScriptError(id.pos, "undefined symbol: $n")
(x.value as? ObjNamespace)?.let { targetContext = it.context }
?: throw ScriptError(id.pos, "Invalid symbolic path (wrong type of ${x.name}: ${x.value}")
}
return targetContext
}
return when (nt.type) {
Token.Type.DOT -> {
// selector
val t = tokens.next()
if (t.type == Token.Type.ID) {
parseVarAccess(t, tokens, path + id.value)
} else
throw ScriptError(t.pos, "Expected identifier after '.'")
} }
Token.Type.PLUS2 -> { Token.Type.PLUS2 -> {
statement(id.pos) { context -> // note: post-increment result is not assignable (truly lvalue)
context.pos = id.pos operand?.let { left ->
val v = resolve(context).get(id.value) // post increment
?: throw ScriptError(id.pos, "Undefined symbol: ${id.value}") left.setter(startPos)
v.value?.getAndIncrement(context) operand = Accessor({ ctx ->
?: context.raiseNPE() 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 -> { Token.Type.MINUS2 -> {
statement(id.pos) { context -> // note: post-decrement result is not assignable (truly lvalue)
context.pos = id.pos operand?.let { left ->
val v = resolve(context).get(id.value) // post decrement
?: throw ScriptError(id.pos, "Undefined symbol: ${id.value}") left.setter(startPos)
v.value?.getAndDecrement(context) operand = Accessor { ctx ->
?: context.raiseNPE() 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")
} }
} }
Token.Type.LPAREN -> { }
// function call }
// Load arg list
fun parseFunctionCall(cc: CompilerContext, left: Accessor, thisObj: Statement?): Accessor {
// insofar, functions always return lvalue
val args = mutableListOf<Arguments.Info>() val args = mutableListOf<Arguments.Info>()
do { do {
val t = tokens.next() val t = cc.next()
when (t.type) { when (t.type) {
Token.Type.RPAREN, Token.Type.COMMA -> {} Token.Type.RPAREN, Token.Type.COMMA -> {}
else -> { else -> {
tokens.previous() cc.previous()
parseStatement(tokens)?.let { args += Arguments.Info(it, t.pos) } parseStatement(cc)?.let { args += Arguments.Info(it, t.pos) }
?: throw ScriptError(t.pos, "Expecting arguments list") ?: throw ScriptError(t.pos, "Expecting arguments list")
} }
} }
} while (t.type != Token.Type.RPAREN) } while (t.type != Token.Type.RPAREN)
statement(id.pos) { context -> return Accessor { context ->
val v = val v = left.getter(context)
resolve(context).get(id.value) ?: throw ScriptError(id.pos, "Undefined function: ${id.value}") v.callOn(context.copy(
(v.value as? Statement)?.execute( context.pos,
context.copy(
id.pos,
Arguments( Arguments(
nt.pos, context.pos,
args.map { Arguments.Info((it.value as Statement).execute(context), it.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 -> { // fun parseReservedWord(word: String): Accessor? =
TODO("indexing") // when(word) {
// "true" -> Accessor { ObjBool(true) }
// "false" -> Accessor { ObjBool(false) }
// "void" -> Accessor { ObjVoid }
// "null" -> Accessor { ObjNull }
// else -> null
// }
fun parseAccessor(cc: CompilerContext): Accessor? {
// could be: literal
val t = cc.next()
return when (t.type) {
Token.Type.INT, Token.Type.REAL, Token.Type.HEX -> {
cc.previous()
val n = parseNumber(true, cc)
Accessor({ n })
} }
Token.Type.STRING -> Accessor({ ObjString(t.value) })
Token.Type.PLUS -> {
val n = parseNumber(true, cc)
Accessor { n }
}
Token.Type.MINUS -> {
val n = parseNumber(false, cc)
Accessor { n }
}
Token.Type.ID -> {
when (t.value) {
"void" -> Accessor { ObjVoid }
"null" -> Accessor { ObjNull }
"true" -> Accessor { ObjBool(true) }
"false" -> Accessor { ObjBool(false) }
else -> { else -> {
// just access the var Accessor({
tokens.previous() it.pos = t.pos
// val t = tokens.next() it.get(t.value)?.value ?: it.raiseError("symbol not defined: '${t.value}'")
// tokens.previous() }) { ctx, newValue ->
// println(t) ctx.get(t.value)?.let { stored ->
// if( path.isEmpty() ) { ctx.pos = t.pos
// statement? if (stored.isMutable)
// tokens.previous() stored.value = newValue
// parseStatement(tokens) ?: throw ScriptError(id.pos, "Expecting expression/statement") else
// } else ctx.raiseError("Cannot assign to immutable value")
statement(id.pos) { } ?: ctx.raiseError("symbol not defined: '${t.value}'")
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")
} }
} }
} }
} }
else -> null
}
}
// fun parseTerm(tokens: CompilerContext): Statement? {
// // call op
// // index op
// // unary op
// // parenthesis
// // number or string
// val t = tokens.next()
// // todoL var?
// return when (t.type) {
// Token.Type.ID -> {
// when (t.value) {
// "void" -> statement(t.pos, true) { ObjVoid }
// "null" -> statement(t.pos, true) { ObjNull }
// "true" -> statement(t.pos, true) { ObjBool(true) }
// "false" -> statement(t.pos, true) { ObjBool(false) }
// else -> parseVarAccess(t, tokens)
// }
// }
//
// Token.Type.STRING -> statement(t.pos, true) { ObjString(t.value) }
//
// Token.Type.LPAREN -> {
// // ( subexpr )
// parseExpression(tokens)?.also {
// val tl = tokens.next()
// if (tl.type != Token.Type.RPAREN)
// throw ScriptError(t.pos, "unbalanced parenthesis: no ')' for it")
// }
// }
//
// Token.Type.PLUS -> {
// val n = parseNumber(true, tokens)
// statement(t.pos, true) { n }
// }
//
// Token.Type.MINUS -> {
// val n = parseNumber(false, tokens)
// statement(t.pos, true) { n }
// }
//
// Token.Type.INT, Token.Type.REAL, Token.Type.HEX -> {
// tokens.previous()
// val n = parseNumber(true, tokens)
// statement(t.pos, true) { n }
// }
//
// else -> null
// }
//
// }
// fun parseVarAccess(id: Token, tokens: CompilerContext, path: List<String> = emptyList()): Statement {
// val nt = tokens.next()
//
// fun resolve(context: Context): Context {
// var targetContext = context
// for (n in path) {
// val x = targetContext[n] ?: throw ScriptError(id.pos, "undefined symbol: $n")
// (x.value as? ObjNamespace)?.let { targetContext = it.context }
// ?: throw ScriptError(id.pos, "Invalid symbolic path (wrong type of ${n}: ${x.value}")
// }
// return targetContext
// }
// return when (nt.type) {
// Token.Type.DOT -> {
// // selector
// val t = tokens.next()
// if (t.type == Token.Type.ID) {
// parseVarAccess(t, tokens, path + id.value)
// } else
// throw ScriptError(t.pos, "Expected identifier after '.'")
// }
//
// Token.Type.PLUS2 -> {
// statement(id.pos) { context ->
// context.pos = id.pos
// val v = resolve(context).get(id.value)
// ?: throw ScriptError(id.pos, "Undefined symbol: ${id.value}")
// v.value?.getAndIncrement(context)
// ?: context.raiseNPE()
// }
// }
//
// Token.Type.MINUS2 -> {
// statement(id.pos) { context ->
// context.pos = id.pos
// val v = resolve(context).get(id.value)
// ?: throw ScriptError(id.pos, "Undefined symbol: ${id.value}")
// v.value?.getAndDecrement(context)
// ?: context.raiseNPE()
// }
// }
//
// Token.Type.LPAREN -> {
// // function call
// // Load arg list
// val args = mutableListOf<Arguments.Info>()
// do {
// val t = tokens.next()
// when (t.type) {
// Token.Type.RPAREN, Token.Type.COMMA -> {}
// else -> {
// tokens.previous()
// parseStatement(tokens)?.let { args += Arguments.Info(it, t.pos) }
// ?: throw ScriptError(t.pos, "Expecting arguments list")
// }
// }
// } while (t.type != Token.Type.RPAREN)
//
// statement(id.pos) { context ->
// val v =
// resolve(context).get(id.value) ?: throw ScriptError(
// id.pos,
// "Undefined function: ${id.value}"
// )
// (v.value as? Statement)?.execute(
// context.copy(
// id.pos,
// Arguments(
// nt.pos,
// args.map { Arguments.Info((it.value as Statement).execute(context), it.pos) }
// ),
// )
// )
// ?: throw ScriptError(id.pos, "Variable $id is not callable ($id)")
// }
// }
//
// Token.Type.LBRACKET -> {
// TODO("indexing")
// }
//
// else -> {
// // just access the var
// tokens.previous()
//// val t = tokens.next()
//// tokens.previous()
//// println(t)
//// if( path.isEmpty() ) {
//// statement?
//// tokens.previous()
//// parseStatement(tokens) ?: throw ScriptError(id.pos, "Expecting expression/statement")
//// } else
// statement(id.pos) {
// val v =
// resolve(it).get(id.value) ?: throw ScriptError(id.pos, "Undefined variable: ${id.value}")
// v.value ?: throw ScriptError(id.pos, "Variable $id is not initialized")
// }
// }
// }
// }
//
fun parseNumber(isPlus: Boolean, tokens: CompilerContext): Obj { 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() advance()
Token("+", from, Token.Type.PLUS2) Token("+", from, Token.Type.PLUS2)
} }
else '=' -> {
advance()
Token("+", from, Token.Type.PLUSASSIGN)
}
else ->
Token("+", from, Token.Type.PLUS) Token("+", from, Token.Type.PLUS)
} }
}
'-' -> {
when (currentChar) {
'-' -> { '-' -> {
if (currentChar == '-') {
advance() advance()
Token("--", from, Token.Type.MINUS2) Token("--", from, Token.Type.MINUS2)
} else }
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())
@ -301,7 +319,7 @@ 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) {
@ -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())
} }
} }