+namespaces with direct resolution
+national chars in ids
This commit is contained in:
parent
b220299fc5
commit
1f2afbbe38
@ -1,10 +1,15 @@
|
||||
package net.sergeych.ling
|
||||
|
||||
data class Arguments(val list: List<Statement> ) {
|
||||
data class Arguments(val list: List<Obj> ) {
|
||||
|
||||
val size by list::size
|
||||
|
||||
operator fun get(index: Int): Statement = list[index]
|
||||
operator fun get(index: Int): Obj = list[index]
|
||||
|
||||
fun firstAndOnly(): Obj {
|
||||
if( list.size != 1 ) throw IllegalArgumentException("Expected one argument, got ${list.size}")
|
||||
return list.first()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val EMPTY = Arguments(emptyList())
|
||||
|
@ -0,0 +1,19 @@
|
||||
package net.sergeych.ling
|
||||
|
||||
//fun buildDoubleFromParts(
|
||||
// integerPart: Long,
|
||||
// decimalPart: Long,
|
||||
// exponent: Int
|
||||
//): Double {
|
||||
// // Handle zero decimal case efficiently
|
||||
// val numDecimalDigits = if (decimalPart == 0L) 0 else decimalPart.toString().length
|
||||
//
|
||||
// // Calculate decimal multiplier (10^-digits)
|
||||
// val decimalMultiplier = 10.0.pow(-numDecimalDigits)
|
||||
//
|
||||
// // Combine integer and decimal parts
|
||||
// val baseValue = integerPart.toDouble() + decimalPart.toDouble() * decimalMultiplier
|
||||
//
|
||||
// // Apply exponent
|
||||
// return baseValue * 10.0.pow(exponent)
|
||||
//}
|
@ -1,27 +1,25 @@
|
||||
package net.sergeych.ling
|
||||
|
||||
import kotlin.math.pow
|
||||
|
||||
sealed class ObjType(name: String, val defaultValue: Obj? = null) {
|
||||
|
||||
class Str : ObjType("string", ObjString(""))
|
||||
class Int : ObjType("real", ObjReal(0.0))
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Descriptor for whatever object could be used as argument, return value,
|
||||
* field, etc.
|
||||
*/
|
||||
data class ObjDescriptor(
|
||||
val type: ObjType,
|
||||
val mutable: Boolean,
|
||||
)
|
||||
|
||||
data class MethodDescriptor(
|
||||
val args: Array<ObjDescriptor>,
|
||||
val result: ObjDescriptor
|
||||
)
|
||||
//sealed class ObjType(name: String, val defaultValue: Obj? = null) {
|
||||
//
|
||||
// class Str : ObjType("string", ObjString(""))
|
||||
// class Int : ObjType("real", ObjReal(0.0))
|
||||
//
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * Descriptor for whatever object could be used as argument, return value,
|
||||
// * field, etc.
|
||||
// */
|
||||
//data class ObjDescriptor(
|
||||
// val type: ObjType,
|
||||
// val mutable: Boolean,
|
||||
//)
|
||||
//
|
||||
//data class MethodDescriptor(
|
||||
// val args: Array<ObjDescriptor>,
|
||||
// val result: ObjDescriptor
|
||||
//)
|
||||
|
||||
/*
|
||||
Meta context contains map of symbols.
|
||||
@ -84,6 +82,7 @@ class Compiler {
|
||||
// try keyword statement
|
||||
parseKeywordStatement(t, tokens)
|
||||
?: run {
|
||||
tokens.previous()
|
||||
parseExpression(tokens)
|
||||
}
|
||||
}
|
||||
@ -130,6 +129,15 @@ class Compiler {
|
||||
val t = tokens.next()
|
||||
// todoL var?
|
||||
return when (t.type) {
|
||||
Token.Type.ID -> {
|
||||
parseVarAccess(t, tokens)
|
||||
}
|
||||
// todoL: check if it's a function call
|
||||
// todoL: check if it's a field access
|
||||
// todoL: check if it's a var
|
||||
// todoL: check if it's a const
|
||||
// todoL: check if it's a type
|
||||
|
||||
// "+" -> statement { parseNumber(true,tokens) }??????
|
||||
// "-" -> statement { parseNumber(false,tokens) }
|
||||
// "~" -> statement(t.pos) { ObjInt( parseLong(tokens)) }
|
||||
@ -155,17 +163,63 @@ class Compiler {
|
||||
|
||||
}
|
||||
|
||||
fun parseVarAccess(id: Token, tokens: ListIterator<Token>,path: List<String> = emptyList()): Statement {
|
||||
val nt = tokens.next()
|
||||
|
||||
fun parseLong(tokens: ListIterator<Token>): Long =
|
||||
// todo: hex notation?
|
||||
getLong(tokens)
|
||||
|
||||
fun getLong(tokens: ListIterator<Token>): Long {
|
||||
val t = tokens.next()
|
||||
if (t.type != Token.Type.INT) throw ScriptError(t.pos, "expected number here")
|
||||
return t.value.toLong()
|
||||
fun resolve(context: Context): Context {
|
||||
var targetContext = context
|
||||
for( n in path) {
|
||||
val x = targetContext[n] ?: throw ScriptError(id.pos, "undefined symbol: $n")
|
||||
(x.value as? ObjNamespace )?.let { targetContext = it.context }
|
||||
?: throw ScriptError(id.pos, "Invalid symbolic path (wrong type of ${x.name}: ${x.value}")
|
||||
}
|
||||
return targetContext
|
||||
}
|
||||
return when(nt.type) {
|
||||
Token.Type.DOT -> {
|
||||
// selector
|
||||
val t = tokens.next()
|
||||
if( t.type== Token.Type.ID) {
|
||||
parseVarAccess(t,tokens,path+id.value)
|
||||
}
|
||||
else
|
||||
throw ScriptError(t.pos,"Expected identifier after '.'")
|
||||
}
|
||||
Token.Type.LPAREN -> {
|
||||
// Load arg list
|
||||
val args = mutableListOf<Statement>()
|
||||
do {
|
||||
val t = tokens.next()
|
||||
when(t.type) {
|
||||
Token.Type.RPAREN, Token.Type.COMMA -> {}
|
||||
else -> {
|
||||
tokens.previous()
|
||||
parseExpression(tokens)?.let { args += it }
|
||||
?: throw ScriptError(t.pos, "Expecting arguments list")
|
||||
}
|
||||
}
|
||||
} while (t.type != Token.Type.RPAREN)
|
||||
statement(id.pos) { context ->
|
||||
val v = resolve(context).get(id.value) ?: throw ScriptError(id.pos, "Undefined variable: $id")
|
||||
(v.value as? Statement)?.execute(context.copy(Arguments(args.map { it.execute(context) })))
|
||||
?: throw ScriptError(id.pos, "Variable $id is not callable ($id)")
|
||||
}
|
||||
}
|
||||
Token.Type.LBRACKET -> {
|
||||
TODO("indexing")
|
||||
}
|
||||
else -> {
|
||||
// just access the var
|
||||
tokens.previous()
|
||||
statement(id.pos) {
|
||||
val v = resolve(it).get(id.value) ?: throw ScriptError(id.pos, "Undefined variable: $id")
|
||||
v.value ?: throw ScriptError(id.pos, "Variable $id is not initialized")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun parseNumber(isPlus: Boolean, tokens: ListIterator<Token>): Obj {
|
||||
val t = tokens.next()
|
||||
return when (t.type) {
|
||||
@ -243,22 +297,4 @@ class Compiler {
|
||||
}
|
||||
}
|
||||
|
||||
fun buildDoubleFromParts(
|
||||
integerPart: Long,
|
||||
decimalPart: Long,
|
||||
exponent: Int
|
||||
): Double {
|
||||
// Handle zero decimal case efficiently
|
||||
val numDecimalDigits = if (decimalPart == 0L) 0 else decimalPart.toString().length
|
||||
|
||||
// Calculate decimal multiplier (10^-digits)
|
||||
val decimalMultiplier = 10.0.pow(-numDecimalDigits)
|
||||
|
||||
// Combine integer and decimal parts
|
||||
val baseValue = integerPart.toDouble() + decimalPart.toDouble() * decimalMultiplier
|
||||
|
||||
// Apply exponent
|
||||
return baseValue * 10.0.pow(exponent)
|
||||
}
|
||||
|
||||
suspend fun eval(code: String) = Compiler.compile(code).execute()
|
@ -1,15 +1,13 @@
|
||||
package net.sergeych.ling
|
||||
|
||||
import kotlin.math.PI
|
||||
|
||||
class Context(
|
||||
val callerPos: Pos,
|
||||
val parent: Context? = null,
|
||||
val args: Arguments = Arguments.EMPTY
|
||||
) {
|
||||
|
||||
data class Item(
|
||||
val name: String, var value: Obj?,
|
||||
val name: String,
|
||||
var value: Obj?,
|
||||
val isMutable: Boolean = false
|
||||
)
|
||||
|
||||
@ -17,30 +15,51 @@ class Context(
|
||||
|
||||
operator fun get(name: String): Item? = objects[name] ?: parent?.get(name)
|
||||
|
||||
fun copy(from: Pos, args: Arguments = Arguments.EMPTY): Context = Context(from, this, args)
|
||||
fun copy(args: Arguments = Arguments.EMPTY): Context = Context(this, args)
|
||||
|
||||
fun addFn(name: String, fn: suspend Context.() -> Obj) = objects.put(name, Item(name,
|
||||
object : Statement() {
|
||||
fun addItem(name: String, isMutable: Boolean, value: Obj?) {
|
||||
objects.put(name, Item(name, value, isMutable))
|
||||
}
|
||||
|
||||
fun getOrCreateNamespace(name: String) =
|
||||
(objects.getOrPut(name) { Item(name, ObjNamespace(name,copy()), isMutable = false) }.value as ObjNamespace)
|
||||
.context
|
||||
|
||||
inline fun <reified T> addFn(vararg names: String, crossinline fn: suspend Context.() -> T) {
|
||||
val newFn = object : Statement() {
|
||||
override val pos: Pos = Pos.builtIn
|
||||
|
||||
override suspend fun execute(context: Context): Obj {
|
||||
return try {
|
||||
context.fn()
|
||||
from(context.fn())
|
||||
|
||||
} catch (e: Exception) {
|
||||
raise(e.message ?: "unexpected error")
|
||||
}
|
||||
}
|
||||
}
|
||||
for (name in names) {
|
||||
addItem(
|
||||
name,
|
||||
false,
|
||||
newFn
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
)
|
||||
inline fun <reified T> addConst(value: T,vararg names: String) {
|
||||
val obj = Obj.from(value)
|
||||
for (name in names) {
|
||||
addItem(
|
||||
name,
|
||||
false,
|
||||
obj
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
operator fun invoke() = Context()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val basicContext = Context(Pos.builtIn).apply {
|
||||
addFn("println") {
|
||||
require(args.size == 1)
|
||||
println(args[0].execute(this).asStr.value)
|
||||
Void
|
||||
}
|
||||
addFn("π") { ObjReal(PI) }
|
||||
}
|
@ -9,13 +9,39 @@ sealed class Obj {
|
||||
open val asStr: ObjString by lazy {
|
||||
if( this is ObjString) this else ObjString(this.toString())
|
||||
}
|
||||
|
||||
companion object {
|
||||
inline fun <reified T> from(obj: T): Obj {
|
||||
return when(obj) {
|
||||
is Obj -> obj
|
||||
is Double -> ObjReal(obj)
|
||||
is Float -> ObjReal(obj.toDouble())
|
||||
is Int -> ObjInt(obj.toLong())
|
||||
is Long -> ObjInt(obj)
|
||||
is String -> ObjString(obj)
|
||||
is CharSequence -> ObjString(obj.toString())
|
||||
is Boolean -> ObjBool(obj)
|
||||
Unit -> ObjVoid
|
||||
null -> ObjNull
|
||||
else -> throw IllegalArgumentException("cannot convert to Obj: $obj")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("void")
|
||||
object Void: Obj() {
|
||||
object ObjVoid: Obj() {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is Void || other is Unit
|
||||
return other is ObjVoid || other is Unit
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("null")
|
||||
object ObjNull: Obj() {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is ObjNull || other == null
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,6 +58,19 @@ interface Numeric {
|
||||
val toObjReal: ObjReal
|
||||
}
|
||||
|
||||
fun Obj.toDouble(): Double =
|
||||
(this as? Numeric)?.doubleValue
|
||||
?: (this as? ObjString)?.value?.toDouble()
|
||||
?: throw IllegalArgumentException("cannot convert to double $this")
|
||||
|
||||
@Suppress("unused")
|
||||
fun Obj.toLong(): Long =
|
||||
(this as? Numeric)?.longValue
|
||||
?: (this as? ObjString)?.value?.toLong()
|
||||
?: throw IllegalArgumentException("cannot convert to double $this")
|
||||
|
||||
|
||||
|
||||
@Serializable
|
||||
@SerialName("real")
|
||||
data class ObjReal(val value: Double): Obj(), Numeric {
|
||||
@ -57,3 +96,9 @@ data class ObjInt(val value: Long): Obj(), Numeric {
|
||||
data class ObjBool(val value: Boolean): Obj() {
|
||||
override val asStr by lazy { ObjString(value.toString()) }
|
||||
}
|
||||
|
||||
data class ObjNamespace(val name: String,val context: Context): Obj() {
|
||||
override fun toString(): String {
|
||||
return "namespace ${name}"
|
||||
}
|
||||
}
|
@ -55,11 +55,12 @@ private class Parser(fromPos: Pos) {
|
||||
pos.back()
|
||||
decodeNumber(loadChars(digits), from)
|
||||
}
|
||||
in idFirstChars -> {
|
||||
// it includes first char, so from current position
|
||||
Token(ch + loadChars(idNextChars), from, Token.Type.ID)
|
||||
else -> {
|
||||
if( ch.isLetter() || ch == '_' )
|
||||
Token(ch + loadChars(idNextChars), from, Token.Type.ID)
|
||||
else
|
||||
raise("can't parse token")
|
||||
}
|
||||
else -> raise("can't parse token")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,11 +10,9 @@ data class Pos(val source: Source, val line: Int, val column: Int) {
|
||||
else if( line > 0) Pos(source, line-1, source.lines[line-1].length - 1)
|
||||
else throw IllegalStateException("can't go back from line 0, column 0")
|
||||
|
||||
val currentLine: String get() = source.lines[line]
|
||||
val currentLine: String get() = if( end ) "EOF" else source.lines[line]
|
||||
|
||||
val showSource: String by lazy {
|
||||
source.lines[line] + "\n" + "-".repeat(column - 1) + "^\n"
|
||||
}
|
||||
val end: Boolean get() = line >= source.lines.size
|
||||
|
||||
companion object {
|
||||
val builtIn = Pos(Source.builtIn, 0, 0)
|
||||
@ -33,11 +31,6 @@ class MutablePos(private val from: Pos) {
|
||||
val end: Boolean get() =
|
||||
line == lines.size
|
||||
|
||||
fun reset(to: Pos) {
|
||||
line = to.line
|
||||
column = to.column
|
||||
}
|
||||
|
||||
fun toPos(): Pos = Pos(from.source, line, column)
|
||||
|
||||
fun advance(): Char? {
|
||||
|
@ -1,5 +1,8 @@
|
||||
package net.sergeych.ling
|
||||
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.sin
|
||||
|
||||
class Script(
|
||||
override val pos: Pos,
|
||||
private val statements: List<Statement> = emptyList(),
|
||||
@ -7,7 +10,7 @@ class Script(
|
||||
|
||||
override suspend fun execute(context: Context): Obj {
|
||||
// todo: run script
|
||||
var lastResult: Obj = Void
|
||||
var lastResult: Obj = ObjVoid
|
||||
for (s in statements) {
|
||||
lastResult = s.execute(context)
|
||||
}
|
||||
@ -17,6 +20,20 @@ class Script(
|
||||
suspend fun execute() = execute(defaultContext)
|
||||
|
||||
companion object {
|
||||
val defaultContext: Context = Context(Pos.builtIn)
|
||||
val defaultContext: Context = Context().apply {
|
||||
addFn("println") {
|
||||
require(args.size == 1)
|
||||
println(args[0].asStr.value)
|
||||
ObjVoid
|
||||
}
|
||||
addFn("sin") {
|
||||
sin(args.firstAndOnly().toDouble())
|
||||
}
|
||||
val pi = ObjReal(PI)
|
||||
addConst(pi, "π")
|
||||
getOrCreateNamespace("Math").also { ns ->
|
||||
ns.addConst(pi, "PI")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -30,7 +30,7 @@ class IfStatement(
|
||||
if (c !is ObjBool)
|
||||
raise("if: condition must me boolean, got: $c")
|
||||
|
||||
return if (c.value) ifTrue.execute(context) else ifFalse?.execute(context) ?: Void
|
||||
return if (c.value) ifTrue.execute(context) else ifFalse?.execute(context) ?: ObjVoid
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,18 +100,7 @@ class AssignStatement(override val pos: Pos, val name: String, val value: Statem
|
||||
override suspend fun execute(context: Context): Obj {
|
||||
val variable = context[name] ?: raise("can't assign: variable does not exist: $name")
|
||||
variable.value = value.execute(context)
|
||||
return Void
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
|
||||
class CallStatement(
|
||||
override val pos: Pos,
|
||||
val name: String,
|
||||
val args: Arguments = Arguments.EMPTY
|
||||
) : Statement() {
|
||||
override suspend fun execute(context: Context): Obj {
|
||||
val callee = context[name] ?: raise("Call: unknown name: $name")
|
||||
return (callee.value as? Statement)?.execute(context.copy(pos, args))
|
||||
?: raise("Call: not a callable object: $callee")
|
||||
}
|
||||
}
|
@ -2,28 +2,14 @@ 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
|
||||
|
||||
class ScriptTest {
|
||||
|
||||
@Test
|
||||
fun level0() = runTest {
|
||||
val s = Script(
|
||||
Pos.builtIn,
|
||||
listOf(
|
||||
CallStatement(
|
||||
Pos.builtIn, "println",
|
||||
Arguments(listOf(CallStatement(Pos.builtIn, "π", Arguments.EMPTY)))
|
||||
)
|
||||
)
|
||||
)
|
||||
s.execute(basicContext)
|
||||
}
|
||||
|
||||
fun parseFirst(str: String): Token =
|
||||
parseLing(str.toSource()).firstOrNull()!!
|
||||
|
||||
@Test
|
||||
fun parseNumbers() {
|
||||
fun check(expected: String, type: Token.Type, row: Int, col: Int, src: String, offset: Int = 0) {
|
||||
@ -107,6 +93,20 @@ class ScriptTest {
|
||||
|
||||
// assertEquals(ObjReal(3.14), eval("3.14"))
|
||||
assertEquals(ObjReal(314.0), eval("3.14e2"))
|
||||
assertEquals(ObjReal(314.0), eval("100 3.14e2"))
|
||||
assertEquals(ObjReal(314.0), eval("100\n 3.14e2"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun compileBuiltinCalls() = runTest {
|
||||
// println(eval("π"))
|
||||
val pi = eval("Math.PI")
|
||||
assertIs<ObjReal>(pi)
|
||||
assertTrue(pi.value - PI < 0.000001)
|
||||
assertTrue(eval("Math.PI+1").toDouble() - PI - 1.0 < 0.000001)
|
||||
|
||||
assertTrue(eval("sin(Math.PI)").toDouble() - 1 < 0.000001)
|
||||
assertTrue(eval("sin(π)").toDouble() - 1 < 0.000001)
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user