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>()
fun ensureLabelIsValid(pos: Pos, label: String) {
if (label !in labels)
throw ScriptError(pos, "Undefined label '$label'")
}
@Suppress("unused")
fun requireId() = requireToken(Token.Type.ID, "identifier is required")
fun requireToken(type: Token.Type, message: String = "required ${type.name}"): Token =
next().also {
if (type != it.type) throw ScriptError(it.pos, message)
}
fun syntaxError(at: Pos, message: String = "Syntax error"): Nothing {
throw ScriptError(at, message)
}
fun currentPos() =
if (hasNext()) next().pos.also { previous() }
else previous().pos.also { next() }
/**
* Skips next token if its type is `tokenType`, returns `true` if so.
* @param errorMessage message to throw if next token is not `tokenType`
* @param isOptional if `true` and token is not of `tokenType`, just return `false` and does not skip it
* @return `true` if the token was skipped
* @throws ScriptError if [isOptional] is `false` and next token is not of [tokenType]
*/
fun skipTokenOfType(tokenType: Token.Type, errorMessage: String="expected ${tokenType.name}", isOptional: Boolean = false): Boolean {
val t = next()
return if (t.type != tokenType) {
if (!isOptional) {
println("unexpected: $t (needed $tokenType)")
throw ScriptError(t.pos, errorMessage)
} else {
previous()
false
}
} else true
}
}
@ -97,6 +134,7 @@ class Compiler {
parseExpression(tokens)
}
}
Token.Type.PLUS2, Token.Type.MINUS2 -> {
tokens.previous()
parseExpression(tokens)
@ -132,7 +170,7 @@ class Compiler {
private fun parseExpression(tokens: CompilerContext, level: Int = 0): Statement? {
if (level == lastLevel)
return parseTerm(tokens)
return parseTerm3(tokens)
var lvalue = parseExpression(tokens, level + 1)
if (lvalue == null) return null
@ -153,153 +191,404 @@ class Compiler {
return lvalue
}
fun parseTerm(tokens: CompilerContext): Statement? {
// call op
// index op
// unary op
// parenthesis
// number or string
val t = tokens.next()
// todoL var?
/*
Term compiler
Fn calls could be:
1) Fn(...)
2) thisObj.method(...)
1 is a shortcut to this.Fn(...)
In general, we can assume any Fn to be of the same king, with `this` that always exist, and set by invocation.
In the case of (1), so called regular, or not bound function, it takes current this from the context.
In the case of (2), bound function, it creates sub-context binding thisObj to `this` in it.
Suppose we do regular parsing. THen we get lparam = statement, and parse to the `(`. Now we have to
compile the invocation of lparam, which can be thisObj.method or just method(). Unfortunately we
already compiled it and can't easily restore its type, so we have to parse it different way.
EBNF to parse term having lparam.
boundcall = "." , identifier, "("
We then call instance method bound to `lparam`.
call = "(', args, ")
we treat current lparam as callable and invoke it on the current context with current value of 'this.
Just traversing fields:
traverse = ".", not (identifier , ".")
Other cases to parse:
index = lparam, "[" , ilist , "]"
*/
/**
* Lower level of expr:
*
* assigning expressions:
*
* expr = expr: assignment
* ++expr, expr++, --expr, expr--,
*
* update-assigns:
* expr += expr, ...
*
* Dot!: expr , '.', ID
* Lambda: { <expr> }
* index: expr[ ilist ]
* call: <expr>( ilist )
* self updating: ++expr, expr++, --expr, expr--, expr+=<expr>,
* expr-=<expr>, expr*=<expr>, expr/=<expr>
* read expr: <expr>
*/
fun parseTerm3(cc: CompilerContext): Statement? {
var operand: Accessor? = null
while (true) {
val t = cc.next()
val startPos = t.pos
when (t.type) {
Token.Type.NEWLINE, Token.Type.SEMICOLON, Token.Type.EOF -> {
cc.previous()
return operand?.let { op -> statement(startPos) { op.getter(it) } }
}
Token.Type.DOT -> {
if (operand == null)
throw ScriptError(t.pos, "Expecting expression before dot")
continue
}
Token.Type.LPAREN -> {
operand?.let { left ->
// this is function call from <left>
operand = parseFunctionCall(
cc,
left,
thisObj = null,
)
} ?: run {
// Expression in parentheses
val statement = parseStatement(cc) ?: throw ScriptError(t.pos, "Expecting expression")
operand = Accessor {
statement.execute(it)
}
cc.skipTokenOfType(Token.Type.RPAREN, "missing ')'")
}
}
Token.Type.ID -> {
operand?.let { left ->
// selector: <lvalue>, '.' , <id>
// we replace operand with selector code, that
// is RW:
operand = Accessor({
it.pos = t.pos
left.getter(it).readField(it, t.value)
}) { cxt, newValue ->
cxt.pos = t.pos
left.getter(cxt).writeField(cxt, t.value, newValue)
}
} ?: run {
// variable to read or like
cc.previous()
operand = parseAccessor(cc)
}
}
Token.Type.PLUS2 -> {
// note: post-increment result is not assignable (truly lvalue)
operand?.let { left ->
// post increment
left.setter(startPos)
operand = Accessor({ ctx ->
left.getter(ctx).getAndIncrement(ctx)
})
} ?: run {
// no lvalue means pre-increment, expression to increment follows
val next = parseAccessor(cc) ?: throw ScriptError(t.pos, "Expecting expression")
operand = Accessor({ ctx -> next.getter(ctx).incrementAndGet(ctx) })
}
}
Token.Type.MINUS2 -> {
// note: post-decrement result is not assignable (truly lvalue)
operand?.let { left ->
// post decrement
left.setter(startPos)
operand = Accessor { ctx ->
left.getter(ctx).getAndDecrement(ctx)
}
}?: run {
// no lvalue means pre-decrement, expression to decrement follows
val next = parseAccessor(cc) ?: throw ScriptError(t.pos, "Expecting expression")
operand = Accessor { ctx -> next.getter(ctx).decrementAndGet(ctx) }
}
}
else -> {
cc.previous()
operand?.let { op ->
return statement(startPos) { op.getter(it) }
}
operand = parseAccessor(cc) ?: throw ScriptError(t.pos, "Expecting expression")
}
}
}
}
fun parseFunctionCall(cc: CompilerContext, left: Accessor, thisObj: Statement?): Accessor {
// insofar, functions always return lvalue
val args = mutableListOf<Arguments.Info>()
do {
val t = cc.next()
when (t.type) {
Token.Type.RPAREN, Token.Type.COMMA -> {}
else -> {
cc.previous()
parseStatement(cc)?.let { args += Arguments.Info(it, t.pos) }
?: throw ScriptError(t.pos, "Expecting arguments list")
}
}
} while (t.type != Token.Type.RPAREN)
return Accessor { context ->
val v = left.getter(context)
v.callOn(context.copy(
context.pos,
Arguments(
context.pos,
args.map { Arguments.Info((it.value as Statement).execute(context), it.pos) }
),
)
)
}
}
// fun parseReservedWord(word: String): Accessor? =
// when(word) {
// "true" -> Accessor { ObjBool(true) }
// "false" -> Accessor { ObjBool(false) }
// "void" -> Accessor { ObjVoid }
// "null" -> Accessor { ObjNull }
// else -> null
// }
fun parseAccessor(cc: CompilerContext): Accessor? {
// could be: literal
val t = cc.next()
return when (t.type) {
Token.Type.ID -> {
when (t.value) {
"void" -> statement(t.pos, true) { ObjVoid }
"null" -> statement(t.pos, true) { ObjNull }
"true" -> statement(t.pos, true) { ObjBool(true) }
"false" -> statement(t.pos, true) { ObjBool(false) }
else -> parseVarAccess(t, tokens)
}
Token.Type.INT, Token.Type.REAL, Token.Type.HEX -> {
cc.previous()
val n = parseNumber(true, cc)
Accessor({ n })
}
Token.Type.STRING -> statement(t.pos, true) { ObjString(t.value) }
Token.Type.LPAREN -> {
// ( subexpr )
parseExpression(tokens)?.also {
val tl = tokens.next()
if (tl.type != Token.Type.RPAREN)
throw ScriptError(t.pos, "unbalanced parenthesis: no ')' for it")
}
}
Token.Type.STRING -> Accessor({ ObjString(t.value) })
Token.Type.PLUS -> {
val n = parseNumber(true, tokens)
statement(t.pos, true) { n }
val n = parseNumber(true, cc)
Accessor { n }
}
Token.Type.MINUS -> {
val n = parseNumber(false, tokens)
statement(t.pos, true) { n }
val n = parseNumber(false, cc)
Accessor { n }
}
Token.Type.INT, Token.Type.REAL, Token.Type.HEX -> {
tokens.previous()
val n = parseNumber(true, tokens)
statement(t.pos, true) { n }
Token.Type.ID -> {
when (t.value) {
"void" -> Accessor { ObjVoid }
"null" -> Accessor { ObjNull }
"true" -> Accessor { ObjBool(true) }
"false" -> Accessor { ObjBool(false) }
else -> {
Accessor({
it.pos = t.pos
it.get(t.value)?.value ?: it.raiseError("symbol not defined: '${t.value}'")
}) { ctx, newValue ->
ctx.get(t.value)?.let { stored ->
ctx.pos = t.pos
if (stored.isMutable)
stored.value = newValue
else
ctx.raiseError("Cannot assign to immutable value")
} ?: ctx.raiseError("symbol not defined: '${t.value}'")
}
}
}
}
else -> null
}
}
fun parseVarAccess(id: Token, tokens: CompilerContext, path: List<String> = emptyList()): Statement {
val nt = tokens.next()
fun resolve(context: Context): Context {
var targetContext = context
for (n in path) {
val x = targetContext[n] ?: throw ScriptError(id.pos, "undefined symbol: $n")
(x.value as? ObjNamespace)?.let { targetContext = it.context }
?: throw ScriptError(id.pos, "Invalid symbolic path (wrong type of ${x.name}: ${x.value}")
}
return targetContext
}
return when (nt.type) {
Token.Type.DOT -> {
// selector
val t = tokens.next()
if (t.type == Token.Type.ID) {
parseVarAccess(t, tokens, path + id.value)
} else
throw ScriptError(t.pos, "Expected identifier after '.'")
}
Token.Type.PLUS2 -> {
statement(id.pos) { context ->
context.pos = id.pos
val v = resolve(context).get(id.value)
?: throw ScriptError(id.pos, "Undefined symbol: ${id.value}")
v.value?.getAndIncrement(context)
?: context.raiseNPE()
}
}
Token.Type.MINUS2 -> {
statement(id.pos) { context ->
context.pos = id.pos
val v = resolve(context).get(id.value)
?: throw ScriptError(id.pos, "Undefined symbol: ${id.value}")
v.value?.getAndDecrement(context)
?: context.raiseNPE()
}
}
Token.Type.LPAREN -> {
// function call
// Load arg list
val args = mutableListOf<Arguments.Info>()
do {
val t = tokens.next()
when (t.type) {
Token.Type.RPAREN, Token.Type.COMMA -> {}
else -> {
tokens.previous()
parseStatement(tokens)?.let { args += Arguments.Info(it, t.pos) }
?: throw ScriptError(t.pos, "Expecting arguments list")
}
}
} while (t.type != Token.Type.RPAREN)
statement(id.pos) { context ->
val v =
resolve(context).get(id.value) ?: throw ScriptError(id.pos, "Undefined function: ${id.value}")
(v.value as? Statement)?.execute(
context.copy(
id.pos,
Arguments(
nt.pos,
args.map { Arguments.Info((it.value as Statement).execute(context), it.pos) }
),
)
)
?: throw ScriptError(id.pos, "Variable $id is not callable ($id)")
}
}
Token.Type.LBRACKET -> {
TODO("indexing")
}
else -> {
// just access the var
tokens.previous()
// val t = tokens.next()
// fun parseTerm(tokens: CompilerContext): Statement? {
// // call op
// // index op
// // unary op
// // parenthesis
// // number or string
// val t = tokens.next()
// // todoL var?
// return when (t.type) {
// Token.Type.ID -> {
// when (t.value) {
// "void" -> statement(t.pos, true) { ObjVoid }
// "null" -> statement(t.pos, true) { ObjNull }
// "true" -> statement(t.pos, true) { ObjBool(true) }
// "false" -> statement(t.pos, true) { ObjBool(false) }
// else -> parseVarAccess(t, tokens)
// }
// }
//
// Token.Type.STRING -> statement(t.pos, true) { ObjString(t.value) }
//
// Token.Type.LPAREN -> {
// // ( subexpr )
// parseExpression(tokens)?.also {
// val tl = tokens.next()
// if (tl.type != Token.Type.RPAREN)
// throw ScriptError(t.pos, "unbalanced parenthesis: no ')' for it")
// }
// }
//
// Token.Type.PLUS -> {
// val n = parseNumber(true, tokens)
// statement(t.pos, true) { n }
// }
//
// Token.Type.MINUS -> {
// val n = parseNumber(false, tokens)
// statement(t.pos, true) { n }
// }
//
// Token.Type.INT, Token.Type.REAL, Token.Type.HEX -> {
// tokens.previous()
// println(t)
// if( path.isEmpty() ) {
// statement?
// tokens.previous()
// parseStatement(tokens) ?: throw ScriptError(id.pos, "Expecting expression/statement")
// } else
statement(id.pos) {
val v = resolve(it).get(id.value) ?: throw ScriptError(id.pos, "Undefined variable: ${id.value}")
v.value ?: throw ScriptError(id.pos, "Variable $id is not initialized")
}
}
}
}
// val n = parseNumber(true, tokens)
// statement(t.pos, true) { n }
// }
//
// else -> null
// }
//
// }
// fun parseVarAccess(id: Token, tokens: CompilerContext, path: List<String> = emptyList()): Statement {
// val nt = tokens.next()
//
// fun resolve(context: Context): Context {
// var targetContext = context
// for (n in path) {
// val x = targetContext[n] ?: throw ScriptError(id.pos, "undefined symbol: $n")
// (x.value as? ObjNamespace)?.let { targetContext = it.context }
// ?: throw ScriptError(id.pos, "Invalid symbolic path (wrong type of ${n}: ${x.value}")
// }
// return targetContext
// }
// return when (nt.type) {
// Token.Type.DOT -> {
// // selector
// val t = tokens.next()
// if (t.type == Token.Type.ID) {
// parseVarAccess(t, tokens, path + id.value)
// } else
// throw ScriptError(t.pos, "Expected identifier after '.'")
// }
//
// Token.Type.PLUS2 -> {
// statement(id.pos) { context ->
// context.pos = id.pos
// val v = resolve(context).get(id.value)
// ?: throw ScriptError(id.pos, "Undefined symbol: ${id.value}")
// v.value?.getAndIncrement(context)
// ?: context.raiseNPE()
// }
// }
//
// Token.Type.MINUS2 -> {
// statement(id.pos) { context ->
// context.pos = id.pos
// val v = resolve(context).get(id.value)
// ?: throw ScriptError(id.pos, "Undefined symbol: ${id.value}")
// v.value?.getAndDecrement(context)
// ?: context.raiseNPE()
// }
// }
//
// Token.Type.LPAREN -> {
// // function call
// // Load arg list
// val args = mutableListOf<Arguments.Info>()
// do {
// val t = tokens.next()
// when (t.type) {
// Token.Type.RPAREN, Token.Type.COMMA -> {}
// else -> {
// tokens.previous()
// parseStatement(tokens)?.let { args += Arguments.Info(it, t.pos) }
// ?: throw ScriptError(t.pos, "Expecting arguments list")
// }
// }
// } while (t.type != Token.Type.RPAREN)
//
// statement(id.pos) { context ->
// val v =
// resolve(context).get(id.value) ?: throw ScriptError(
// id.pos,
// "Undefined function: ${id.value}"
// )
// (v.value as? Statement)?.execute(
// context.copy(
// id.pos,
// Arguments(
// nt.pos,
// args.map { Arguments.Info((it.value as Statement).execute(context), it.pos) }
// ),
// )
// )
// ?: throw ScriptError(id.pos, "Variable $id is not callable ($id)")
// }
// }
//
// Token.Type.LBRACKET -> {
// TODO("indexing")
// }
//
// else -> {
// // just access the var
// tokens.previous()
//// val t = tokens.next()
//// tokens.previous()
//// println(t)
//// if( path.isEmpty() ) {
//// statement?
//// tokens.previous()
//// parseStatement(tokens) ?: throw ScriptError(id.pos, "Expecting expression/statement")
//// } else
// statement(id.pos) {
// val v =
// resolve(it).get(id.value) ?: throw ScriptError(id.pos, "Undefined variable: ${id.value}")
// v.value ?: throw ScriptError(id.pos, "Variable $id is not initialized")
// }
// }
// }
// }
//
fun parseNumber(isPlus: Boolean, tokens: CompilerContext): Obj {
val t = tokens.next()
@ -458,12 +747,14 @@ class Compiler {
val ifBody = parseStatement(tokens) ?: throw ScriptError(pos, "Bad if statement: expected statement")
tokens.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
// could be else block:
val t2 = tokens.next()
// we generate different statements: optimization
return if (t2.type == Token.Type.ID && t2.value == "else") {
val elseBody = parseStatement(tokens) ?: throw ScriptError(pos, "Bad else statement: expected statement")
val elseBody =
parseStatement(tokens) ?: throw ScriptError(pos, "Bad else statement: expected statement")
return statement(start) {
if (condition.execute(it).toBool())
ifBody.execute(it)
@ -626,13 +917,13 @@ class Compiler {
// bitwise or 2
// bitwise and 3
// equality/ne 4
LogicalOp(Token.Type.EQ, 4) { a, b -> a == b },
LogicalOp(Token.Type.NEQ, 4) { a, b -> a != b },
LogicalOp(Token.Type.EQ, 4) { c, a, b -> a.compareTo(c, b) == 0 },
LogicalOp(Token.Type.NEQ, 4) { c, a, b -> a.compareTo(c, b) != 0 },
// relational <=,... 5
LogicalOp(Token.Type.LTE, 5) { a, b -> a <= b },
LogicalOp(Token.Type.LT, 5) { a, b -> a < b },
LogicalOp(Token.Type.GTE, 5) { a, b -> a >= b },
LogicalOp(Token.Type.GT, 5) { a, b -> a > b },
LogicalOp(Token.Type.LTE, 5) { c, a, b -> a.compareTo(c, b) <= 0 },
LogicalOp(Token.Type.LT, 5) { c, a, b -> a.compareTo(c, b) < 0 },
LogicalOp(Token.Type.GTE, 5) { c, a, b -> a.compareTo(c, b) >= 0 },
LogicalOp(Token.Type.GT, 5) { c, a, b -> a.compareTo(c, b) > 0 },
// shuttle <=> 6
// bitshhifts 7
Operator(Token.Type.PLUS, 8, 2) { pos, a, b ->
@ -657,14 +948,17 @@ class Compiler {
suspend fun eval(code: String) = Compiler.compile(code).execute()
fun LogicalOp(tokenType: Token.Type, priority: Int, f: (Obj, Obj) -> Boolean) = Compiler.Operator(
fun LogicalOp(
tokenType: Token.Type, priority: Int,
f: suspend (Context, Obj, Obj) -> Boolean
) = Compiler.Operator(
tokenType,
priority,
2
) { pos, a, b ->
statement(pos) {
ObjBool(
f(a.execute(it), b.execute(it))
f(it, a.execute(it), b.execute(it))
)
}
}

View File

@ -32,13 +32,12 @@ class Context(
fun copy(pos: Pos, args: Arguments = Arguments.EMPTY): Context = Context(this, args, pos)
fun addItem(name: String, isMutable: Boolean, value: Obj?) {
objects.put(name, StoredObj(name, value, isMutable))
objects.put(name, StoredObj(value, isMutable))
}
fun getOrCreateNamespace(name: String) =
(objects.getOrPut(name) {
StoredObj(
name,
ObjNamespace(name, copy(pos)),
isMutable = false
)

View File

@ -8,41 +8,65 @@ import kotlin.math.floor
typealias InstanceMethod = (Context, Obj) -> Obj
data class Item<T>(var value: T, val isMutable: Boolean = false)
data class WithAccess<T>(var value: T, val isMutable: Boolean)
data class Accessor(
val getter: suspend (Context) -> Obj,
val setterOrNull: (suspend (Context, Obj) -> Unit)?
) {
constructor(getter: suspend (Context) -> Obj) : this(getter, null)
fun setter(pos: Pos) = setterOrNull ?: throw ScriptError(pos,"can't assign value")
}
@Serializable
sealed class ClassDef(
val className: String
) {
val baseClasses: List<ClassDef> get() = emptyList()
private val instanceMethods: MutableMap<String, Item<InstanceMethod>> get() = mutableMapOf()
protected val instanceMembers: MutableMap<String, WithAccess<Obj>> = mutableMapOf()
private val monitor = Mutex()
private val instanceLock = Mutex()
suspend fun addInstanceMethod(
context: Context,
name: String,
freeze: Boolean = false,
pos: Pos = Pos.builtIn,
body: InstanceMethod
isOpen: Boolean = false,
body: Obj
) {
instanceLock.withLock {
instanceMethods[name]?.let {
monitor.withLock {
instanceMembers[name]?.let {
if (!it.isMutable)
throw ScriptError(pos, "existing method $name is frozen and can't be updated")
context.raiseError("method $name is not open and can't be overridden")
it.value = body
} ?: instanceMethods.put(name, Item(body, freeze))
} ?: instanceMembers.put(name, WithAccess(body, isOpen))
}
}
//suspend fun callInstanceMethod(context: Context, self: Obj,args: Arguments): Obj {
//
// }
suspend fun getInstanceMethodOrNull(name: String): Obj? =
monitor.withLock { instanceMembers[name]?.value }
suspend fun getInstanceMethod(context: Context, name: String): Obj =
getInstanceMethodOrNull(name) ?: context.raiseError("no method found: $name")
// suspend fun callInstanceMethod(context: Context, name: String, self: Obj,args: Arguments): Obj {
// getInstanceMethod(context, name).invoke(context, self,args)
// }
}
object ObjClassDef : ClassDef("Obj")
@Serializable
sealed class Obj : Comparable<Obj> {
sealed class Obj {
open val classDef: ClassDef = ObjClassDef
var isFrozen: Boolean = false
protected val instanceMethods: Map<String, WithAccess<InstanceMethod>> = mutableMapOf()
private val monitor = Mutex()
open suspend fun compareTo(context: Context, other: Obj): Int {
context.raiseNotImplemented()
}
open val asStr: ObjString by lazy {
if (this is ObjString) this else ObjString(this.toString())
}
@ -53,39 +77,49 @@ sealed class Obj : Comparable<Obj> {
context.raiseNotImplemented()
}
open fun assign(context: Context, other: Obj): Obj {
context.raiseNotImplemented()
}
open fun plusAssign(context: Context, other: Obj): Obj {
assign(context, plus(context, other))
return this
}
open fun getAndIncrement(context: Context): Obj {
context.raiseNotImplemented()
}
open fun incrementAndGet(context: Context): Obj {
context.raiseNotImplemented()
}
open fun decrementAndGet(context: Context): Obj {
context.raiseNotImplemented()
}
open fun getAndDecrement(context: Context): Obj {
context.raiseNotImplemented()
}
@Suppress("unused")
enum class Type {
@SerialName("Void")
Void,
fun willMutate(context: Context) {
if (isFrozen) context.raiseError("attempt to mutate frozen object")
}
@SerialName("Null")
Null,
suspend fun getInstanceMember(context: Context, name: String): Obj? = definition.getInstanceMethodOrNull(name)
@SerialName("String")
String,
suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() }
@SerialName("Int")
Int,
open suspend fun readField(context: Context, name: String): Obj {
context.raiseNotImplemented()
}
@SerialName("Real")
Real,
open suspend fun writeField(context: Context,name: String, newValue: Obj) {
context.raiseNotImplemented()
}
@SerialName("Bool")
Bool,
@SerialName("Fn")
Fn,
@SerialName("Any")
Any,
open suspend fun callOn(context: Context): Obj {
context.raiseNotImplemented()
}
companion object {
@ -117,7 +151,7 @@ object ObjVoid : Obj() {
return other is ObjVoid || other is Unit
}
override fun compareTo(other: Obj): Int {
override suspend fun compareTo(context: Context, other: Obj): Int {
return if (other === this) 0 else -1
}
@ -127,7 +161,7 @@ object ObjVoid : Obj() {
@Serializable
@SerialName("null")
object ObjNull : Obj() {
override fun compareTo(other: Obj): Int {
override suspend fun compareTo(context: Context, other: Obj): Int {
return if (other === this) 0 else -1
}
@ -140,8 +174,8 @@ object ObjNull : Obj() {
@SerialName("string")
data class ObjString(val value: String) : Obj() {
override fun compareTo(other: Obj): Int {
if (other !is ObjString) throw IllegalArgumentException("cannot compare string with $other")
override suspend fun compareTo(context: Context, other: Obj): Int {
if (other !is ObjString) context.raiseError("cannot compare string with $other")
return this.value.compareTo(other.value)
}
@ -181,8 +215,8 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
override val toObjInt: ObjInt by lazy { ObjInt(longValue) }
override val toObjReal: ObjReal by lazy { ObjReal(value) }
override fun compareTo(other: Obj): Int {
if (other !is Numeric) throw IllegalArgumentException("cannot compare $this with $other")
override suspend fun compareTo(context: Context, other: Obj): Int {
if (other !is Numeric) context.raiseError("cannot compare $this with $other")
return value.compareTo(other.doubleValue)
}
@ -206,15 +240,16 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
return ObjInt(value).also { value-- }
}
// override fun plus(context: Context, other: Obj): Obj {
// if (other !is Numeric)
// context.raiseError("cannot add $this with $other")
// return if (other is ObjInt)
//
// }
override fun incrementAndGet(context: Context): Obj {
return ObjInt(++value)
}
override fun compareTo(other: Obj): Int {
if (other !is Numeric) throw IllegalArgumentException("cannot compare $this with $other")
override fun decrementAndGet(context: Context): Obj {
return ObjInt(--value)
}
override suspend fun compareTo(context: Context, other: Obj): Int {
if (other !is Numeric) context.raiseError("cannot compare $this with $other")
return value.compareTo(other.doubleValue)
}
@ -226,8 +261,8 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
data class ObjBool(val value: Boolean) : Obj() {
override val asStr by lazy { ObjString(value.toString()) }
override fun compareTo(other: Obj): Int {
if (other !is ObjBool) throw IllegalArgumentException("cannot compare $this with $other")
override suspend fun compareTo(context: Context, other: Obj): Int {
if (other !is ObjBool) context.raiseError("cannot compare $this with $other")
return value.compareTo(other.value)
}
@ -239,16 +274,25 @@ data class ObjNamespace(val name: String, val context: Context) : Obj() {
return "namespace ${name}"
}
override fun compareTo(other: Obj): Int {
throw IllegalArgumentException("cannot compare namespaces")
override suspend fun readField(callerContext: Context,name: String): Obj {
return context[name]?.value ?: callerContext.raiseError("not found: $name")
}
}
open class ObjError(val context: Context, val message: String) : Obj() {
override val asStr: ObjString by lazy { ObjString("Error: $message") }
override fun compareTo(other: Obj): Int {
if (other === this) return 0 else return -1
}
}
class ObjNullPointerError(context: Context) : ObjError(context, "object is null")
class ObjClass(override val definition: ClassDef) : Obj() {
override suspend fun compareTo(context: Context, other: Obj): Int {
// definition.callInstanceMethod(":compareTo", context, other)?.let {
// it(context, this)
// }
TODO("Not yet implemented")
}
}

View File

@ -51,19 +51,31 @@ private class Parser(fromPos: Pos) {
}
'+' -> {
if( currentChar == '+') {
advance()
Token("+", from, Token.Type.PLUS2)
when (currentChar) {
'+' -> {
advance()
Token("+", from, Token.Type.PLUS2)
}
'=' -> {
advance()
Token("+", from, Token.Type.PLUSASSIGN)
}
else ->
Token("+", from, Token.Type.PLUS)
}
else
Token("+", from, Token.Type.PLUS)
}
'-' -> {
if (currentChar == '-') {
advance()
Token("--", from, Token.Type.MINUS2)
} else
Token("-", from, Token.Type.MINUS)
when (currentChar) {
'-' -> {
advance()
Token("--", from, Token.Type.MINUS2)
}
'=' -> {
advance()
Token("-", from, Token.Type.MINUSASSIGN)
}
else -> Token("-", from, Token.Type.MINUS)
}
}
'*' -> Token("*", from, Token.Type.STAR)
'/' -> {
@ -285,6 +297,7 @@ private class Parser(fromPos: Pos) {
private fun skipws(): Char? {
while (!pos.end) {
val ch = pos.currentChar
if( ch == '\n') break
if (ch.isWhitespace())
advance()
else

View File

@ -4,7 +4,6 @@ package net.sergeych.ling
* Whatever [Obj] stored somewhere
*/
data class StoredObj(
val name: String,
var value: Obj?,
val isMutable: Boolean = false
)

View File

@ -7,10 +7,11 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
enum class Type {
ID, INT, REAL, HEX, STRING, LPAREN, RPAREN, LBRACE, RBRACE, LBRACKET, RBRACKET, COMMA,
SEMICOLON, COLON,
PLUS, MINUS, STAR, SLASH, ASSIGN,
PLUS, MINUS, STAR, SLASH, PERCENT,
ASSIGN, PLUSASSIGN, MINUSASSIGN, STARASSIGN, SLASHASSIGN, PERCENTASSIGN,
PLUS2, MINUS2,
EQ, NEQ, LT, LTE, GT, GTE,
AND, BITAND, OR, BITOR, NOT, DOT, ARROW, QUESTION, COLONCOLON, PERCENT,
AND, BITAND, OR, BITOR, NOT, DOT, ARROW, QUESTION, COLONCOLON,
SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
LABEL,ATLABEL, // label@ at@label
NEWLINE,

View File

@ -2,21 +2,30 @@ package net.sergeych.ling
fun String.toSource(name: String = "eval"): Source = Source(name, this)
sealed class ObjType {
object Any : ObjType()
object Int : ObjType()
}
@Suppress("unused")
abstract class Statement(
val isStaticConst: Boolean = false,
val isConst: Boolean = false,
val returnType: Type = Type.Any
val returnType: ObjType = ObjType.Any
) : Obj() {
abstract val pos: Pos
abstract suspend fun execute(context: Context): Obj
override fun compareTo(other: Obj): Int {
override suspend fun compareTo(context: Context,other: Obj): Int {
throw UnsupportedOperationException("not comparable")
}
override suspend fun callOn(context: Context): Obj {
return execute(context)
}
override fun toString(): String = "Callable@${this.hashCode()}"
}
@ -30,8 +39,8 @@ fun Statement.require(cond: Boolean, message: () -> String) {
if (!cond) raise(message())
}
fun statement(pos: Pos, isStaticConst: Boolean = false, f: suspend (Context) -> Obj): Statement =
object : Statement(isStaticConst) {
fun statement(pos: Pos, isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend (Context) -> Obj): Statement =
object : Statement(isStaticConst, isConst) {
override val pos: Pos = pos
override suspend fun execute(context: Context): Obj = f(context)
}

View File

@ -7,6 +7,24 @@ import kotlin.test.*
class ScriptTest {
@Test
fun parseNewlines() {
fun check(expected: String, type: Token.Type, row: Int, col: Int, src: String, offset: Int = 0) {
val source = src.toSource()
assertEquals(
Token(expected, source.posAt(row, col), type),
parseLing(source)[offset]
)
}
check("1", Token.Type.INT, 0, 0, "1 + x\n2", 0)
check("+", Token.Type.PLUS, 0, 2, "1 + x\n2", 1)
check("x", Token.Type.ID, 0, 4, "1 + x\n2", 2)
check("\n", Token.Type.NEWLINE, 0, 5, "1 + x\n2", 3)
// check("2", Token.Type.INT, 1, 0, "1 + x\n2", 4)
// check("", Token.Type.EOF, 1, 0, "1 + x\n2", 5)
}
@Test
fun parseNumbersTest() {
fun check(expected: String, type: Token.Type, row: Int, col: Int, src: String, offset: Int = 0) {
@ -69,7 +87,7 @@ class ScriptTest {
assertEquals(Token("(", src.posAt(0, 7), Token.Type.LPAREN), p.next())
assertEquals(Token("Hello", src.posAt(0, 8), Token.Type.STRING), p.next())
assertEquals(Token(")", src.posAt(0, 15), Token.Type.RPAREN), p.next())
assertEquals(Token("\n", src.posAt(0, 16), Token.Type.NEWLINE), p.next())
assertEquals(Token("println", src.posAt(1, 0), Token.Type.ID), p.next())
assertEquals(Token("(", src.posAt(1, 7), Token.Type.LPAREN), p.next())
assertEquals(Token("world", src.posAt(1, 9), Token.Type.STRING), p.next())
@ -290,7 +308,7 @@ class ScriptTest {
context.eval(
"""
fn test1(n) {
if( n >= 10 )
if( n >= 10 )
"enough"
else
"more"
@ -301,13 +319,13 @@ class ScriptTest {
assertEquals("more", context.eval("test1(1)").toString())
// if/else with blocks
context = Context(pos = Pos.builtIn )
context = Context(pos = Pos.builtIn)
context.eval(
"""
fn test1(n) {
if( n > 20 ) {
"too much"
} else if( n >= 10 ) {
} else if( n >= 10 ) {
"enough"
}
else {
@ -425,6 +443,7 @@ class ScriptTest {
"""
val count = 3
val res = if( count > 10 ) "too much" else "just " + count
println(res)
res
""".trimIndent()
)
@ -513,16 +532,21 @@ class ScriptTest {
@Test
fun testIncrAndDecr() = runTest {
val c = Context()
assertEquals( "8", c.eval("""
assertEquals(
"8", c.eval(
"""
var x = 5
x--
x--
x++
x * 2
""").toString())
"""
).toString()
)
assertEquals( "4", c.eval("x").toString())
assertEquals("4", c.eval("x").toString())
// assertEquals( "8", c.eval("x*2").toString())
// assertEquals( "4", c.eval("x+0").toString())
}
}