int++ and int--

This commit is contained in:
Sergey Chernov 2025-05-21 13:40:32 +04:00
parent 9ae9752634
commit baf9eab3ba
8 changed files with 215 additions and 33 deletions

View File

@ -97,6 +97,10 @@ class Compiler {
parseExpression(tokens)
}
}
Token.Type.PLUS2, Token.Type.MINUS2 -> {
tokens.previous()
parseExpression(tokens)
}
Token.Type.LABEL -> continue
Token.Type.SINLGE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT -> continue
@ -222,6 +226,25 @@ class Compiler {
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
@ -243,10 +266,11 @@ class Compiler {
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)")
@ -501,8 +525,9 @@ class Compiler {
var closure: Context? = null
val fnBody = statement(t.pos) { callerContext ->
callerContext.pos = start
// restore closure where the function was defined:
val context = closure ?: Context()
val context = closure ?: callerContext.raiseError("bug: closure not set")
// load params from caller context
for ((i, d) in params.withIndex()) {
if (i < callerContext.args.size)
@ -539,7 +564,7 @@ class Compiler {
val block = parseScript(t.pos, tokens)
return statement(t.pos) {
// block run on inner context:
block.execute(it.copy())
block.execute(it.copy(t.pos))
}.also {
val t1 = tokens.next()
if (t1.type != Token.Type.RBRACE)

View File

@ -1,9 +1,27 @@
package net.sergeych.ling
class Context(
val parent: Context? = Script.defaultContext.copy(),
val args: Arguments = Arguments.EMPTY
val parent: Context?,
val args: Arguments = Arguments.EMPTY,
var pos: Pos = Pos.builtIn
) {
constructor(
args: Arguments = Arguments.EMPTY,
pos: Pos = Pos.builtIn
)
: this(Script.defaultContext, args, pos)
fun raiseNotImplemented(): Nothing = raiseError("operation not implemented")
fun raiseNPE(): Nothing = raiseError(ObjNullPointerError(this))
fun raiseError(message: String): Nothing {
throw ExecutionError(ObjError(this, message))
}
fun raiseError(obj: ObjError): Nothing {
throw ExecutionError(obj)
}
private val objects = mutableMapOf<String, StoredObj>()
@ -11,14 +29,20 @@ class Context(
objects[name]
?: parent?.get(name)
fun copy(args: Arguments = Arguments.EMPTY): Context = Context(this, args)
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))
}
fun getOrCreateNamespace(name: String) =
(objects.getOrPut(name) { StoredObj(name, ObjNamespace(name,copy()), isMutable = false) }.value as ObjNamespace)
(objects.getOrPut(name) {
StoredObj(
name,
ObjNamespace(name, copy(pos)),
isMutable = false
)
}.value as ObjNamespace)
.context
inline fun <reified T> addFn(vararg names: String, crossinline fn: suspend Context.() -> T) {
@ -43,7 +67,7 @@ class Context(
}
}
inline fun <reified T> addConst(value: T,vararg names: String) {
inline fun <reified T> addConst(value: T, vararg names: String) {
val obj = Obj.from(value)
for (name in names) {
addItem(
@ -59,8 +83,5 @@ class Context(
fun containsLocal(name: String): Boolean = name in objects
companion object {
operator fun invoke() = Context()
}
}

View File

@ -27,7 +27,7 @@ sealed class ClassDef(
) {
instanceLock.withLock {
instanceMethods[name]?.let {
if( !it.isMutable )
if (!it.isMutable)
throw ScriptError(pos, "existing method $name is frozen and can't be updated")
it.value = body
} ?: instanceMethods.put(name, Item(body, freeze))
@ -36,7 +36,7 @@ sealed class ClassDef(
//suspend fun callInstanceMethod(context: Context, self: Obj,args: Arguments): Obj {
//
// }
// }
}
object ObjClassDef : ClassDef("Obj")
@ -49,6 +49,18 @@ sealed class Obj : Comparable<Obj> {
open val definition: ClassDef = ObjClassDef
open fun plus(context: Context, other: Obj): Obj {
context.raiseNotImplemented()
}
open fun getAndIncrement(context: Context): Obj {
context.raiseNotImplemented()
}
open fun getAndDecrement(context: Context): Obj {
context.raiseNotImplemented()
}
@Suppress("unused")
enum class Type {
@SerialName("Void")
@ -179,12 +191,27 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
@Serializable
@SerialName("int")
data class ObjInt(val value: Long) : Obj(), Numeric {
override val asStr by lazy { ObjString(value.toString()) }
override val longValue: Long by lazy { value }
override val doubleValue: Double by lazy { value.toDouble() }
override val toObjInt: ObjInt by lazy { ObjInt(value) }
override val toObjReal: ObjReal by lazy { ObjReal(doubleValue) }
data class ObjInt(var value: Long) : Obj(), Numeric {
override val asStr get() = ObjString(value.toString())
override val longValue get() = value
override val doubleValue get() = value.toDouble()
override val toObjInt get() = this
override val toObjReal = ObjReal(doubleValue)
override fun getAndIncrement(context: Context): Obj {
return ObjInt(value).also { value++ }
}
override fun getAndDecrement(context: Context): Obj {
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 compareTo(other: Obj): Int {
if (other !is Numeric) throw IllegalArgumentException("cannot compare $this with $other")
@ -215,4 +242,13 @@ data class ObjNamespace(val name: String, val context: Context) : Obj() {
override fun compareTo(other: Obj): Int {
throw IllegalArgumentException("cannot compare namespaces")
}
}
}
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")

View File

@ -50,8 +50,21 @@ private class Parser(fromPos: Pos) {
Token("=", from, Token.Type.ASSIGN)
}
'+' -> Token("+", from, Token.Type.PLUS)
'-' -> Token("-", from, Token.Type.MINUS)
'+' -> {
if( currentChar == '+') {
advance()
Token("+", from, Token.Type.PLUS2)
}
else
Token("+", from, Token.Type.PLUS)
}
'-' -> {
if (currentChar == '-') {
advance()
Token("--", from, Token.Type.MINUS2)
} else
Token("-", from, Token.Type.MINUS)
}
'*' -> Token("*", from, Token.Type.STAR)
'/' -> {
if( currentChar == '/') {

View File

@ -15,10 +15,10 @@ class Script(
return lastResult
}
suspend fun execute() = execute(defaultContext.copy())
suspend fun execute() = execute(defaultContext.copy(pos))
companion object {
val defaultContext: Context = Context(null).apply {
val defaultContext: Context = Context().apply {
addFn("println") {
print("yn: ")
for( (i,a) in args.withIndex() ) {

View File

@ -1,9 +1,13 @@
@file:Suppress("CanBeParameter")
package net.sergeych.ling
class ScriptError(val pos: Pos, val errorMessage: String) : Exception(
open class ScriptError(val pos: Pos, val errorMessage: String) : Exception(
"""
$pos: Error: $errorMessage
${pos.currentLine}
${"-".repeat(pos.column)}^
""".trimIndent()
)
class ExecutionError(val errorObject: ObjError) : ScriptError(errorObject.context.pos, errorObject.message)

View File

@ -8,6 +8,7 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
ID, INT, REAL, HEX, STRING, LPAREN, RPAREN, LBRACE, RBRACE, LBRACKET, RBRACKET, COMMA,
SEMICOLON, COLON,
PLUS, MINUS, STAR, SLASH, ASSIGN,
PLUS2, MINUS2,
EQ, NEQ, LT, LTE, GT, GTE,
AND, BITAND, OR, BITOR, NOT, DOT, ARROW, QUESTION, COLONCOLON, PERCENT,
SINLGE_LINE_COMMENT, MULTILINE_COMMENT,

View File

@ -117,7 +117,7 @@ class ScriptTest {
@Test
fun varsAndConstsTest() = runTest {
val context = Context()
val context = Context(pos = Pos.builtIn)
assertEquals(
ObjVoid, context.eval(
"""
@ -137,7 +137,7 @@ class ScriptTest {
@Test
fun functionTest() = runTest {
val context = Context()
val context = Context(pos = Pos.builtIn)
context.eval(
"""
fun foo(a, b) {
@ -163,7 +163,7 @@ class ScriptTest {
@Test
fun simpleClosureTest() = runTest {
val context = Context()
val context = Context(pos = Pos.builtIn)
context.eval(
"""
var global = 10
@ -180,7 +180,7 @@ class ScriptTest {
@Test
fun nullAndVoidTest() = runTest {
val context = Context()
val context = Context(pos = Pos.builtIn)
assertEquals(ObjVoid, context.eval("void"))
assertEquals(ObjNull, context.eval("null"))
}
@ -252,7 +252,7 @@ class ScriptTest {
@Test
fun ifTest() = runTest {
// if - single line
var context = Context()
var context = Context(pos = Pos.builtIn)
context.eval(
"""
fn test1(n) {
@ -267,7 +267,7 @@ class ScriptTest {
assertEquals("more", context.eval("test1(1)").toString())
// if - multiline (block)
context = Context()
context = Context(pos = Pos.builtIn)
context.eval(
"""
fn test1(n) {
@ -286,7 +286,7 @@ class ScriptTest {
assertEquals("answer: more", context.eval("test1(1)").toString())
// else single line1
context = Context()
context = Context(pos = Pos.builtIn)
context.eval(
"""
fn test1(n) {
@ -301,7 +301,7 @@ class ScriptTest {
assertEquals("more", context.eval("test1(1)").toString())
// if/else with blocks
context = Context()
context = Context(pos = Pos.builtIn )
context.eval(
"""
fn test1(n) {
@ -443,4 +443,86 @@ class ScriptTest {
.toString()
)
}
@Test
fun testIncr() = runTest {
val c = Context()
c.eval("var x = 10")
assertEquals(10, c.eval("x++").toInt())
assertEquals(11, c.eval("x++").toInt())
assertEquals(12, c.eval("x").toInt())
assertEquals(12, c.eval("x").toInt())
assertEquals(12, c.eval("x").toInt())
}
@Test
fun testDecr() = runTest {
val c = Context()
c.eval("var x = 9")
assertEquals(9, c.eval("x--").toInt())
assertEquals(8, c.eval("x--").toInt())
assertEquals(7, c.eval("x--").toInt())
assertEquals(6, c.eval("x--").toInt())
assertEquals(5, c.eval("x").toInt())
}
@Test
fun testDecrIncr() = runTest {
val c = Context()
c.eval("var x = 9")
assertEquals(9, c.eval("x++").toInt())
assertEquals(10, c.eval("x++").toInt())
assertEquals(11, c.eval("x").toInt())
assertEquals(11, c.eval("x--").toInt())
assertEquals(10, c.eval("x--").toInt())
assertEquals(9, c.eval("x--").toInt())
assertEquals(8, c.eval("x--").toInt())
assertEquals(7, c.eval("x + 0").toInt())
}
@Test
fun testDecrIncr2() = runTest {
val c = Context()
c.eval("var x = 9")
assertEquals(9, c.eval("x--").toInt())
assertEquals(8, c.eval("x--").toInt())
assertEquals(7, c.eval("x--").toInt())
assertEquals(6, c.eval("x").toInt())
assertEquals(6, c.eval("x++").toInt())
assertEquals(7, c.eval("x++").toInt())
assertEquals(8, c.eval("x")
.also {
println("${it.toDouble()} ${it.toInt()} ${it.toLong()} ${it.toInt()}")
}
.toInt())
}
@Test
fun testDecrIncr3() = runTest {
val c = Context()
c.eval("var x = 9")
assertEquals(9, c.eval("x++").toInt())
assertEquals(10, c.eval("x++").toInt())
assertEquals(11, c.eval("x++").toInt())
assertEquals(12, c.eval("x").toInt())
assertEquals(12, c.eval("x--").toInt())
assertEquals(11, c.eval("x").toInt())
}
@Test
fun testIncrAndDecr() = runTest {
val c = Context()
assertEquals( "8", c.eval("""
var x = 5
x--
x--
x++
x * 2
""").toString())
assertEquals( "4", c.eval("x").toString())
// assertEquals( "8", c.eval("x*2").toString())
// assertEquals( "4", c.eval("x+0").toString())
}
}