+var and val
This commit is contained in:
parent
50986fbac5
commit
408543d191
@ -61,14 +61,15 @@ class Compiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseStatement(tokens: ListIterator<Token>): Statement? {
|
private fun parseStatement(tokens: ListIterator<Token>): Statement? {
|
||||||
|
while(true) {
|
||||||
val t = tokens.next()
|
val t = tokens.next()
|
||||||
return when (t.type) {
|
return when (t.type) {
|
||||||
Token.Type.ID -> {
|
Token.Type.ID -> {
|
||||||
// could be keyword, assignment or just the expression
|
// could be keyword, assignment or just the expression
|
||||||
val next = tokens.next()
|
val next = tokens.next()
|
||||||
if (next.type == Token.Type.EQ) {
|
if (next.type == Token.Type.ASSIGN) {
|
||||||
// this _is_ assignment statement
|
// this _is_ assignment statement
|
||||||
AssignStatement(
|
return AssignStatement(
|
||||||
t.pos, t.value,
|
t.pos, t.value,
|
||||||
parseExpression(tokens) ?: throw ScriptError(
|
parseExpression(tokens) ?: throw ScriptError(
|
||||||
t.pos,
|
t.pos,
|
||||||
@ -87,6 +88,8 @@ class Compiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Token.Type.SEMICOLON -> continue
|
||||||
|
|
||||||
Token.Type.EOF -> null
|
Token.Type.EOF -> null
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
@ -96,6 +99,7 @@ class Compiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun parseExpression(tokens: ListIterator<Token>, level: Int = 0): Statement? {
|
private fun parseExpression(tokens: ListIterator<Token>, level: Int = 0): Statement? {
|
||||||
if (level == lastLevel)
|
if (level == lastLevel)
|
||||||
@ -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? =
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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())
|
||||||
|
// }
|
||||||
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user