Compare commits
2 Commits
b220299fc5
...
50986fbac5
Author | SHA1 | Date | |
---|---|---|---|
50986fbac5 | |||
1f2afbbe38 |
@ -1,10 +1,15 @@
|
|||||||
package net.sergeych.ling
|
package net.sergeych.ling
|
||||||
|
|
||||||
data class Arguments(val list: List<Statement> ) {
|
data class Arguments(val list: List<Obj> ) {
|
||||||
|
|
||||||
val size by list::size
|
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 {
|
companion object {
|
||||||
val EMPTY = Arguments(emptyList())
|
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
|
package net.sergeych.ling
|
||||||
|
|
||||||
import kotlin.math.pow
|
//sealed class ObjType(name: String, val defaultValue: Obj? = null) {
|
||||||
|
//
|
||||||
sealed class ObjType(name: String, val defaultValue: Obj? = null) {
|
// class Str : ObjType("string", ObjString(""))
|
||||||
|
// class Int : ObjType("real", ObjReal(0.0))
|
||||||
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.
|
||||||
* Descriptor for whatever object could be used as argument, return value,
|
// */
|
||||||
* field, etc.
|
//data class ObjDescriptor(
|
||||||
*/
|
// val type: ObjType,
|
||||||
data class ObjDescriptor(
|
// val mutable: Boolean,
|
||||||
val type: ObjType,
|
//)
|
||||||
val mutable: Boolean,
|
//
|
||||||
)
|
//data class MethodDescriptor(
|
||||||
|
// val args: Array<ObjDescriptor>,
|
||||||
data class MethodDescriptor(
|
// val result: ObjDescriptor
|
||||||
val args: Array<ObjDescriptor>,
|
//)
|
||||||
val result: ObjDescriptor
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Meta context contains map of symbols.
|
Meta context contains map of symbols.
|
||||||
@ -84,6 +82,7 @@ class Compiler {
|
|||||||
// try keyword statement
|
// try keyword statement
|
||||||
parseKeywordStatement(t, tokens)
|
parseKeywordStatement(t, tokens)
|
||||||
?: run {
|
?: run {
|
||||||
|
tokens.previous()
|
||||||
parseExpression(tokens)
|
parseExpression(tokens)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -130,6 +129,15 @@ class Compiler {
|
|||||||
val t = tokens.next()
|
val t = tokens.next()
|
||||||
// todoL var?
|
// todoL var?
|
||||||
return when (t.type) {
|
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(true,tokens) }??????
|
||||||
// "-" -> statement { parseNumber(false,tokens) }
|
// "-" -> statement { parseNumber(false,tokens) }
|
||||||
// "~" -> statement(t.pos) { ObjInt( parseLong(tokens)) }
|
// "~" -> statement(t.pos) { ObjInt( parseLong(tokens)) }
|
||||||
@ -155,16 +163,62 @@ class Compiler {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun parseVarAccess(id: Token, tokens: ListIterator<Token>,path: List<String> = emptyList()): Statement {
|
||||||
|
val nt = tokens.next()
|
||||||
|
|
||||||
fun parseLong(tokens: ListIterator<Token>): Long =
|
fun resolve(context: Context): Context {
|
||||||
// todo: hex notation?
|
var targetContext = context
|
||||||
getLong(tokens)
|
for( n in path) {
|
||||||
|
val x = targetContext[n] ?: throw ScriptError(id.pos, "undefined symbol: $n")
|
||||||
fun getLong(tokens: ListIterator<Token>): Long {
|
(x.value as? ObjNamespace )?.let { targetContext = it.context }
|
||||||
val t = tokens.next()
|
?: throw ScriptError(id.pos, "Invalid symbolic path (wrong type of ${x.name}: ${x.value}")
|
||||||
if (t.type != Token.Type.INT) throw ScriptError(t.pos, "expected number here")
|
|
||||||
return t.value.toLong()
|
|
||||||
}
|
}
|
||||||
|
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 {
|
fun parseNumber(isPlus: Boolean, tokens: ListIterator<Token>): Obj {
|
||||||
val t = tokens.next()
|
val t = tokens.next()
|
||||||
@ -189,6 +243,7 @@ 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? {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -243,22 +298,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()
|
suspend fun eval(code: String) = Compiler.compile(code).execute()
|
@ -1,15 +1,13 @@
|
|||||||
package net.sergeych.ling
|
package net.sergeych.ling
|
||||||
|
|
||||||
import kotlin.math.PI
|
|
||||||
|
|
||||||
class Context(
|
class Context(
|
||||||
val callerPos: Pos,
|
|
||||||
val parent: Context? = null,
|
val parent: Context? = null,
|
||||||
val args: Arguments = Arguments.EMPTY
|
val args: Arguments = Arguments.EMPTY
|
||||||
) {
|
) {
|
||||||
|
|
||||||
data class Item(
|
data class Item(
|
||||||
val name: String, var value: Obj?,
|
val name: String,
|
||||||
|
var value: Obj?,
|
||||||
val isMutable: Boolean = false
|
val isMutable: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,30 +15,51 @@ class Context(
|
|||||||
|
|
||||||
operator fun get(name: String): Item? = objects[name] ?: parent?.get(name)
|
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,
|
fun addItem(name: String, isMutable: Boolean, value: Obj?) {
|
||||||
object : Statement() {
|
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 val pos: Pos = Pos.builtIn
|
||||||
|
|
||||||
override suspend fun execute(context: Context): Obj {
|
override suspend fun execute(context: Context): Obj {
|
||||||
return try {
|
return try {
|
||||||
context.fn()
|
from(context.fn())
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
raise(e.message ?: "unexpected error")
|
raise(e.message ?: "unexpected error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
for (name in names) {
|
||||||
|
addItem(
|
||||||
|
name,
|
||||||
|
false,
|
||||||
|
newFn
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val basicContext = Context(Pos.builtIn).apply {
|
inline fun <reified T> addConst(value: T,vararg names: String) {
|
||||||
addFn("println") {
|
val obj = Obj.from(value)
|
||||||
require(args.size == 1)
|
for (name in names) {
|
||||||
println(args[0].execute(this).asStr.value)
|
addItem(
|
||||||
Void
|
name,
|
||||||
|
false,
|
||||||
|
obj
|
||||||
|
)
|
||||||
}
|
}
|
||||||
addFn("π") { ObjReal(PI) }
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
operator fun invoke() = Context()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -9,13 +9,39 @@ sealed class Obj {
|
|||||||
open val asStr: ObjString by lazy {
|
open val asStr: ObjString by lazy {
|
||||||
if( this is ObjString) this else ObjString(this.toString())
|
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
|
@Serializable
|
||||||
@SerialName("void")
|
@SerialName("void")
|
||||||
object Void: Obj() {
|
object ObjVoid: Obj() {
|
||||||
override fun equals(other: Any?): Boolean {
|
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
|
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
|
@Serializable
|
||||||
@SerialName("real")
|
@SerialName("real")
|
||||||
data class ObjReal(val value: Double): Obj(), Numeric {
|
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() {
|
data class ObjBool(val value: Boolean): Obj() {
|
||||||
override val asStr by lazy { ObjString(value.toString()) }
|
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()
|
pos.back()
|
||||||
decodeNumber(loadChars(digits), from)
|
decodeNumber(loadChars(digits), from)
|
||||||
}
|
}
|
||||||
in idFirstChars -> {
|
else -> {
|
||||||
// it includes first char, so from current position
|
if( ch.isLetter() || ch == '_' )
|
||||||
Token(ch + loadChars(idNextChars), from, Token.Type.ID)
|
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 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")
|
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 {
|
val end: Boolean get() = line >= source.lines.size
|
||||||
source.lines[line] + "\n" + "-".repeat(column - 1) + "^\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val builtIn = Pos(Source.builtIn, 0, 0)
|
val builtIn = Pos(Source.builtIn, 0, 0)
|
||||||
@ -33,11 +31,6 @@ class MutablePos(private val from: Pos) {
|
|||||||
val end: Boolean get() =
|
val end: Boolean get() =
|
||||||
line == lines.size
|
line == lines.size
|
||||||
|
|
||||||
fun reset(to: Pos) {
|
|
||||||
line = to.line
|
|
||||||
column = to.column
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toPos(): Pos = Pos(from.source, line, column)
|
fun toPos(): Pos = Pos(from.source, line, column)
|
||||||
|
|
||||||
fun advance(): Char? {
|
fun advance(): Char? {
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package net.sergeych.ling
|
package net.sergeych.ling
|
||||||
|
|
||||||
|
import kotlin.math.PI
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
class Script(
|
class Script(
|
||||||
override val pos: Pos,
|
override val pos: Pos,
|
||||||
private val statements: List<Statement> = emptyList(),
|
private val statements: List<Statement> = emptyList(),
|
||||||
@ -7,7 +10,7 @@ class Script(
|
|||||||
|
|
||||||
override suspend fun execute(context: Context): Obj {
|
override suspend fun execute(context: Context): Obj {
|
||||||
// todo: run script
|
// todo: run script
|
||||||
var lastResult: Obj = Void
|
var lastResult: Obj = ObjVoid
|
||||||
for (s in statements) {
|
for (s in statements) {
|
||||||
lastResult = s.execute(context)
|
lastResult = s.execute(context)
|
||||||
}
|
}
|
||||||
@ -17,6 +20,20 @@ class Script(
|
|||||||
suspend fun execute() = execute(defaultContext)
|
suspend fun execute() = execute(defaultContext)
|
||||||
|
|
||||||
companion object {
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,6 +12,7 @@ fun Statement.raise(text: String): Nothing {
|
|||||||
throw ScriptError(pos, text)
|
throw ScriptError(pos, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
fun Statement.require(cond: Boolean, message: () -> String) {
|
fun Statement.require(cond: Boolean, message: () -> String) {
|
||||||
if (!cond) raise(message())
|
if (!cond) raise(message())
|
||||||
}
|
}
|
||||||
@ -21,19 +22,6 @@ fun statement(pos: Pos, f: suspend (Context) -> Obj): Statement = object : State
|
|||||||
override suspend fun execute(context: Context): Obj = f(context)
|
override suspend fun execute(context: Context): Obj = f(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
class IfStatement(
|
|
||||||
override val pos: Pos,
|
|
||||||
val cond: Statement, val ifTrue: Statement, val ifFalse: Statement?
|
|
||||||
) : Statement() {
|
|
||||||
override suspend fun execute(context: Context): Obj {
|
|
||||||
val c = cond.execute(context)
|
|
||||||
if (c !is ObjBool)
|
|
||||||
raise("if: condition must me boolean, got: $c")
|
|
||||||
|
|
||||||
return if (c.value) ifTrue.execute(context) else ifFalse?.execute(context) ?: Void
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LogicalAndStatement(
|
class LogicalAndStatement(
|
||||||
override val pos: Pos,
|
override val pos: Pos,
|
||||||
val left: Statement, val right: Statement
|
val left: Statement, val right: Statement
|
||||||
@ -100,18 +88,7 @@ class AssignStatement(override val pos: Pos, val name: String, val value: Statem
|
|||||||
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")
|
||||||
variable.value = value.execute(context)
|
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 kotlinx.coroutines.test.runTest
|
||||||
import net.sergeych.ling.*
|
import net.sergeych.ling.*
|
||||||
|
import kotlin.math.PI
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertIs
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class ScriptTest {
|
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
|
@Test
|
||||||
fun parseNumbers() {
|
fun parseNumbers() {
|
||||||
fun check(expected: String, type: Token.Type, row: Int, col: Int, src: String, offset: Int = 0) {
|
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(3.14), eval("3.14"))
|
||||||
assertEquals(ObjReal(314.0), eval("3.14e2"))
|
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