+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? {
val t = tokens.next()
return when (t.type) {
Token.Type.ID -> {
// could be keyword, assignment or just the expression
val next = tokens.next()
if (next.type == Token.Type.EQ) {
// this _is_ assignment statement
AssignStatement(
t.pos, t.value,
parseExpression(tokens) ?: throw ScriptError(
t.pos,
"Expecting expression for assignment operator"
while(true) {
val t = tokens.next()
return when (t.type) {
Token.Type.ID -> {
// could be keyword, assignment or just the expression
val next = tokens.next()
if (next.type == Token.Type.ASSIGN) {
// this _is_ assignment statement
return AssignStatement(
t.pos, t.value,
parseExpression(tokens) ?: throw ScriptError(
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 -> {
// could be expression
tokens.previous()
parseExpression(tokens)
Token.Type.EOF -> null
else -> {
// could be expression
tokens.previous()
parseExpression(tokens)
}
}
}
}
@ -243,9 +247,37 @@ class Compiler {
* Parse keyword-starting statenment.
* @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? {
return null
private fun parseKeywordStatement(id: Token, tokens: ListIterator<Token>): Statement?
= when (id.value) {
"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? =

View File

@ -1,7 +1,7 @@
package net.sergeych.ling
class Context(
val parent: Context? = null,
val parent: Context? = Script.defaultContext.copy(),
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 {
operator fun invoke() = Context()
}

View File

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

View File

@ -41,7 +41,14 @@ private class Parser(fromPos: Pos) {
']' -> Token("]", from, Token.Type.RBRACKET)
',' -> Token(",", from, Token.Type.COMMA)
';' -> 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.MINUS)
'*' -> Token("*", from, Token.Type.STAR)

View File

@ -20,7 +20,7 @@ class Script(
suspend fun execute() = execute(defaultContext)
companion object {
val defaultContext: Context = Context().apply {
val defaultContext: Context = Context(null).apply {
addFn("println") {
require(args.size == 1)
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() {
override suspend fun execute(context: Context): Obj {
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)
return ObjVoid
}

View File

@ -3,10 +3,7 @@ package io.github.kotlin.fibonacci
import kotlinx.coroutines.test.runTest
import net.sergeych.ling.*
import kotlin.math.PI
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertTrue
import kotlin.test.*
class ScriptTest {
@ -109,4 +106,30 @@ class ScriptTest {
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())
// }
}