+var and val

This commit is contained in:
Sergey Chernov 2025-05-18 12:49:45 +04:00
parent 50986fbac5
commit 408543d191
7 changed files with 108 additions and 38 deletions

View File

@ -61,38 +61,42 @@ class Compiler {
} }
private fun parseStatement(tokens: ListIterator<Token>): Statement? { private fun parseStatement(tokens: ListIterator<Token>): Statement? {
val t = tokens.next() while(true) {
return when (t.type) { val t = tokens.next()
Token.Type.ID -> { return when (t.type) {
// could be keyword, assignment or just the expression Token.Type.ID -> {
val next = tokens.next() // could be keyword, assignment or just the expression
if (next.type == Token.Type.EQ) { val next = tokens.next()
// this _is_ assignment statement if (next.type == Token.Type.ASSIGN) {
AssignStatement( // this _is_ assignment statement
t.pos, t.value, return AssignStatement(
parseExpression(tokens) ?: throw ScriptError( t.pos, t.value,
t.pos, parseExpression(tokens) ?: throw ScriptError(
"Expecting expression for assignment operator" t.pos,
"Expecting expression for assignment operator"
)
) )
)
}
// not assignment, maybe keyword statement:
// get back the token which is not '=':
tokens.previous()
// try keyword statement
parseKeywordStatement(t, tokens)
?: run {
tokens.previous()
parseExpression(tokens)
} }
} // not assignment, maybe keyword statement:
// get back the token which is not '=':
tokens.previous()
// try keyword statement
parseKeywordStatement(t, tokens)
?: run {
tokens.previous()
parseExpression(tokens)
}
}
Token.Type.EOF -> null Token.Type.SEMICOLON -> continue
else -> { Token.Type.EOF -> null
// could be expression
tokens.previous() else -> {
parseExpression(tokens) // could be expression
tokens.previous()
parseExpression(tokens)
}
} }
} }
} }
@ -243,9 +247,37 @@ class Compiler {
* Parse keyword-starting statenment. * Parse keyword-starting statenment.
* @return parsed statement or null if, for example. [id] is not among keywords * @return parsed statement or null if, for example. [id] is not among keywords
*/ */
@Suppress("UNUSED_PARAMETER") private fun parseKeywordStatement(id: Token, tokens: ListIterator<Token>): Statement?
private fun parseKeywordStatement(id: Token, tokens: ListIterator<Token>): Statement? { = when (id.value) {
return null "val" -> parseVarDeclaration(id.value, false, tokens)
"var" -> parseVarDeclaration(id.value, true, tokens)
else -> null
}
private fun parseVarDeclaration(kind: String, mutable: Boolean, tokens: ListIterator<Token>): Statement {
val nameToken = tokens.next()
if( nameToken.type != Token.Type.ID)
throw ScriptError(nameToken.pos, "Expected identifier after '$kind'")
val name = nameToken.value
val eqToken = tokens.next()
var setNull = false
if( eqToken.type != Token.Type.ASSIGN) {
if( !mutable )
throw ScriptError(eqToken.pos, "Expected initializator: '=' after '$kind ${name}'")
else {
tokens.previous()
setNull = true
}
}
val initialExpression = if( setNull ) null else parseExpression(tokens)
?: throw ScriptError(eqToken.pos, "Expected initializer expression")
return statement(nameToken.pos) { context ->
if( context.containsLocal(name) )
throw ScriptError(nameToken.pos, "Variable $name is already defined")
val initValue = initialExpression?.execute(context) ?: ObjNull
context.addItem(name, mutable, initValue)
ObjVoid
}
} }
// fun parseStatement(parser: Parser): Statement? = // fun parseStatement(parser: Parser): Statement? =

View File

@ -1,7 +1,7 @@
package net.sergeych.ling package net.sergeych.ling
class Context( class Context(
val parent: Context? = null, val parent: Context? = Script.defaultContext.copy(),
val args: Arguments = Arguments.EMPTY val args: Arguments = Arguments.EMPTY
) { ) {
@ -58,6 +58,11 @@ class Context(
} }
} }
suspend fun eval(code: String): Obj =
Compiler().compile(code.toSource()).execute(this)
fun containsLocal(name: String): Boolean = name in objects
companion object { companion object {
operator fun invoke() = Context() operator fun invoke() = Context()
} }

View File

@ -69,6 +69,7 @@ fun Obj.toLong(): Long =
?: (this as? ObjString)?.value?.toLong() ?: (this as? ObjString)?.value?.toLong()
?: throw IllegalArgumentException("cannot convert to double $this") ?: throw IllegalArgumentException("cannot convert to double $this")
fun Obj.toInt(): Int = toLong().toInt()
@Serializable @Serializable

View File

@ -41,7 +41,14 @@ private class Parser(fromPos: Pos) {
']' -> Token("]", from, Token.Type.RBRACKET) ']' -> Token("]", from, Token.Type.RBRACKET)
',' -> Token(",", from, Token.Type.COMMA) ',' -> Token(",", from, Token.Type.COMMA)
';' -> Token(";", from, Token.Type.SEMICOLON) ';' -> Token(";", from, Token.Type.SEMICOLON)
'=' -> Token("=", from, Token.Type.ASSIGN) '=' -> {
if( pos.currentChar == '=') {
advance()
Token("==", from, Token.Type.EQ)
}
else
Token("=", from, Token.Type.ASSIGN)
}
'+' -> Token("+", from, Token.Type.PLUS) '+' -> Token("+", from, Token.Type.PLUS)
'-' -> Token("-", from, Token.Type.MINUS) '-' -> Token("-", from, Token.Type.MINUS)
'*' -> Token("*", from, Token.Type.STAR) '*' -> Token("*", from, Token.Type.STAR)

View File

@ -20,7 +20,7 @@ class Script(
suspend fun execute() = execute(defaultContext) suspend fun execute() = execute(defaultContext)
companion object { companion object {
val defaultContext: Context = Context().apply { val defaultContext: Context = Context(null).apply {
addFn("println") { addFn("println") {
require(args.size == 1) require(args.size == 1)
println(args[0].asStr.value) println(args[0].asStr.value)

View File

@ -87,6 +87,8 @@ class MinusStatement(
class AssignStatement(override val pos: Pos, val name: String, val value: Statement) : Statement() { class AssignStatement(override val pos: Pos, val name: String, val value: Statement) : Statement() {
override suspend fun execute(context: Context): Obj { override suspend fun execute(context: Context): Obj {
val variable = context[name] ?: raise("can't assign: variable does not exist: $name") val variable = context[name] ?: raise("can't assign: variable does not exist: $name")
if( !variable.isMutable )
throw ScriptError(pos,"can't reassign val $name")
variable.value = value.execute(context) variable.value = value.execute(context)
return ObjVoid return ObjVoid
} }

View File

@ -3,10 +3,7 @@ package io.github.kotlin.fibonacci
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import net.sergeych.ling.* import net.sergeych.ling.*
import kotlin.math.PI import kotlin.math.PI
import kotlin.test.Test import kotlin.test.*
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertTrue
class ScriptTest { class ScriptTest {
@ -109,4 +106,30 @@ class ScriptTest {
assertTrue(eval("sin(π)").toDouble() - 1 < 0.000001) assertTrue(eval("sin(π)").toDouble() - 1 < 0.000001)
} }
@Test
fun varsAndConsts() = runTest {
val context = Context()
assertEquals(ObjVoid,context.eval("""
val a = 17
var b = 3
""".trimIndent()))
assertEquals(17, context.eval("a").toInt())
assertEquals(20, context.eval("b + a").toInt())
assertFailsWith<ScriptError> {
context.eval("a = 10")
}
assertEquals(10, context.eval("b = a - 3 - 4; b").toInt())
assertEquals(10, context.eval("b").toInt())
}
// @Test
// fun functionTest() = runTest {
// val context = Context()
// context.eval("""
// fun foo(a, b) {
// a + b
// }
// """.trimIndent())
// }
} }