+var and val
This commit is contained in:
parent
50986fbac5
commit
408543d191
@ -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? =
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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())
|
||||
// }
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user