Compare commits
No commits in common. "50986fbac5badd7dd899219dae51b3781a0cd531" and "b220299fc536d3731f3aed6925dbc3be179aa1ed" have entirely different histories.
50986fbac5
...
b220299fc5
@ -1,15 +1,10 @@
|
|||||||
package net.sergeych.ling
|
package net.sergeych.ling
|
||||||
|
|
||||||
data class Arguments(val list: List<Obj> ) {
|
data class Arguments(val list: List<Statement> ) {
|
||||||
|
|
||||||
val size by list::size
|
val size by list::size
|
||||||
|
|
||||||
operator fun get(index: Int): Obj = list[index]
|
operator fun get(index: Int): Statement = 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())
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
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,25 +1,27 @@
|
|||||||
package net.sergeych.ling
|
package net.sergeych.ling
|
||||||
|
|
||||||
//sealed class ObjType(name: String, val defaultValue: Obj? = null) {
|
import kotlin.math.pow
|
||||||
//
|
|
||||||
// class Str : ObjType("string", ObjString(""))
|
sealed class ObjType(name: String, val defaultValue: Obj? = null) {
|
||||||
// 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,
|
||||||
//data class ObjDescriptor(
|
* field, etc.
|
||||||
// val type: ObjType,
|
*/
|
||||||
// val mutable: Boolean,
|
data class ObjDescriptor(
|
||||||
//)
|
val type: ObjType,
|
||||||
//
|
val mutable: Boolean,
|
||||||
//data class MethodDescriptor(
|
)
|
||||||
// val args: Array<ObjDescriptor>,
|
|
||||||
// val result: ObjDescriptor
|
data class MethodDescriptor(
|
||||||
//)
|
val args: Array<ObjDescriptor>,
|
||||||
|
val result: ObjDescriptor
|
||||||
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Meta context contains map of symbols.
|
Meta context contains map of symbols.
|
||||||
@ -82,7 +84,6 @@ class Compiler {
|
|||||||
// try keyword statement
|
// try keyword statement
|
||||||
parseKeywordStatement(t, tokens)
|
parseKeywordStatement(t, tokens)
|
||||||
?: run {
|
?: run {
|
||||||
tokens.previous()
|
|
||||||
parseExpression(tokens)
|
parseExpression(tokens)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -129,15 +130,6 @@ 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)) }
|
||||||
@ -163,63 +155,17 @@ class Compiler {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parseVarAccess(id: Token, tokens: ListIterator<Token>,path: List<String> = emptyList()): Statement {
|
|
||||||
val nt = tokens.next()
|
|
||||||
|
|
||||||
fun resolve(context: Context): Context {
|
fun parseLong(tokens: ListIterator<Token>): Long =
|
||||||
var targetContext = context
|
// todo: hex notation?
|
||||||
for( n in path) {
|
getLong(tokens)
|
||||||
val x = targetContext[n] ?: throw ScriptError(id.pos, "undefined symbol: $n")
|
|
||||||
(x.value as? ObjNamespace )?.let { targetContext = it.context }
|
fun getLong(tokens: ListIterator<Token>): Long {
|
||||||
?: throw ScriptError(id.pos, "Invalid symbolic path (wrong type of ${x.name}: ${x.value}")
|
val t = tokens.next()
|
||||||
}
|
if (t.type != Token.Type.INT) throw ScriptError(t.pos, "expected number here")
|
||||||
return targetContext
|
return t.value.toLong()
|
||||||
}
|
|
||||||
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()
|
||||||
return when (t.type) {
|
return when (t.type) {
|
||||||
@ -243,7 +189,6 @@ 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
|
||||||
}
|
}
|
||||||
@ -298,4 +243,22 @@ 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,13 +1,15 @@
|
|||||||
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,
|
val name: String, var value: Obj?,
|
||||||
var value: Obj?,
|
|
||||||
val isMutable: Boolean = false
|
val isMutable: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,51 +17,30 @@ 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(args: Arguments = Arguments.EMPTY): Context = Context(this, args)
|
fun copy(from: Pos, args: Arguments = Arguments.EMPTY): Context = Context(from, this, args)
|
||||||
|
|
||||||
fun addItem(name: String, isMutable: Boolean, value: Obj?) {
|
fun addFn(name: String, fn: suspend Context.() -> Obj) = objects.put(name, Item(name,
|
||||||
objects.put(name, Item(name, value, isMutable))
|
object : Statement() {
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
||||||
from(context.fn())
|
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,39 +9,13 @@ 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 ObjVoid: Obj() {
|
object Void: Obj() {
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
return other is ObjVoid || other is Unit
|
return other is Void || other is Unit
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("null")
|
|
||||||
object ObjNull: Obj() {
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
return other is ObjNull || other == null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,19 +32,6 @@ 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 {
|
||||||
@ -96,9 +57,3 @@ 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,12 +55,11 @@ private class Parser(fromPos: Pos) {
|
|||||||
pos.back()
|
pos.back()
|
||||||
decodeNumber(loadChars(digits), from)
|
decodeNumber(loadChars(digits), from)
|
||||||
}
|
}
|
||||||
else -> {
|
in idFirstChars -> {
|
||||||
if( ch.isLetter() || ch == '_' )
|
// it includes first char, so from current position
|
||||||
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,9 +10,11 @@ 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() = if( end ) "EOF" else source.lines[line]
|
val currentLine: String get() = source.lines[line]
|
||||||
|
|
||||||
val end: Boolean get() = line >= source.lines.size
|
val showSource: String by lazy {
|
||||||
|
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)
|
||||||
@ -31,6 +33,11 @@ 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,8 +1,5 @@
|
|||||||
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(),
|
||||||
@ -10,7 +7,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 = ObjVoid
|
var lastResult: Obj = Void
|
||||||
for (s in statements) {
|
for (s in statements) {
|
||||||
lastResult = s.execute(context)
|
lastResult = s.execute(context)
|
||||||
}
|
}
|
||||||
@ -20,20 +17,6 @@ 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(Pos.builtIn)
|
||||||
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,7 +12,6 @@ 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())
|
||||||
}
|
}
|
||||||
@ -22,6 +21,19 @@ 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
|
||||||
@ -88,7 +100,18 @@ 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 ObjVoid
|
return Void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,14 +2,28 @@ 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) {
|
||||||
@ -93,20 +107,6 @@ 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