comparison operators + some optimizations
This commit is contained in:
parent
07a4c4c8d0
commit
95e68d6e2a
@ -115,7 +115,7 @@ class Compiler {
|
||||
while (true) {
|
||||
|
||||
val opToken = tokens.next()
|
||||
val op = byLevel[level][opToken.value]
|
||||
val op = byLevel[level][opToken.type]
|
||||
if (op == null) {
|
||||
tokens.previous()
|
||||
break
|
||||
@ -142,17 +142,21 @@ class Compiler {
|
||||
when (t.value) {
|
||||
"void" -> statement(t.pos, true) { ObjVoid }
|
||||
"null" -> statement(t.pos, true) { ObjNull }
|
||||
"true" -> statement(t.pos, true) { ObjBool(true) }
|
||||
"false" -> statement(t.pos, true) { ObjBool(false) }
|
||||
else -> parseVarAccess(t, tokens)
|
||||
}
|
||||
}
|
||||
|
||||
Token.Type.LPAREN -> {
|
||||
// ( subexpr )
|
||||
parseExpression(tokens)?.also {
|
||||
val tl = tokens.next()
|
||||
if( tl.type != Token.Type.RPAREN )
|
||||
if (tl.type != Token.Type.RPAREN)
|
||||
throw ScriptError(t.pos, "unbalanced parenthesis: no ')' for it")
|
||||
}
|
||||
}
|
||||
|
||||
Token.Type.PLUS -> {
|
||||
val n = parseNumber(true, tokens)
|
||||
statement(t.pos, true) { n }
|
||||
@ -213,7 +217,8 @@ class Compiler {
|
||||
} while (t.type != Token.Type.RPAREN)
|
||||
|
||||
statement(id.pos) { context ->
|
||||
val v = resolve(context).get(id.value) ?: throw ScriptError(id.pos, "Undefined function: ${id.value}")
|
||||
val v =
|
||||
resolve(context).get(id.value) ?: throw ScriptError(id.pos, "Undefined function: ${id.value}")
|
||||
(v.value as? Statement)?.execute(
|
||||
context.copy(
|
||||
Arguments(
|
||||
@ -325,7 +330,10 @@ class Compiler {
|
||||
d.name,
|
||||
false,
|
||||
d.defaultValue?.execute(context)
|
||||
?: throw ScriptError(context.args.callerPos, "missing required argument #${1+i}: ${d.name}")
|
||||
?: throw ScriptError(
|
||||
context.args.callerPos,
|
||||
"missing required argument #${1 + i}: ${d.name}"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -387,40 +395,59 @@ class Compiler {
|
||||
// }
|
||||
// }
|
||||
|
||||
data class Operator(
|
||||
val tokenType: Token.Type,
|
||||
val priority: Int, val arity: Int,
|
||||
val generate: (Pos, Statement, Statement) -> Statement
|
||||
)
|
||||
|
||||
companion object {
|
||||
data class Operator(
|
||||
val name: String,
|
||||
val priority: Int, val arity: Int,
|
||||
val generate: (Pos, Statement, Statement) -> Statement
|
||||
)
|
||||
|
||||
val allOps = listOf(
|
||||
Operator("||", 0, 2) { pos, a, b -> LogicalOrStatement(pos, a, b) },
|
||||
Operator("&&", 1, 2) { pos, a, b -> LogicalAndStatement(pos, a, b) },
|
||||
Operator(Token.Type.OR, 0, 2) { pos, a, b -> LogicalOrStatement(pos, a, b) },
|
||||
Operator(Token.Type.AND, 1, 2) { pos, a, b -> LogicalAndStatement(pos, a, b) },
|
||||
// bitwise or 2
|
||||
// bitwise and 3
|
||||
// equality/ne 4
|
||||
LogicalOp(Token.Type.EQ, 4) { a, b -> a == b },
|
||||
LogicalOp(Token.Type.NEQ, 4) { a, b -> a != b },
|
||||
// relational <=,... 5
|
||||
LogicalOp(Token.Type.LTE, 5) { a, b -> a <= b },
|
||||
LogicalOp(Token.Type.LT, 5) { a, b -> a < b },
|
||||
LogicalOp(Token.Type.GTE, 5) { a, b -> a >= b },
|
||||
LogicalOp(Token.Type.GT, 5) { a, b -> a > b },
|
||||
// shuttle <=> 6
|
||||
// bitshhifts 7
|
||||
Operator("+", 8, 2) { pos, a, b ->
|
||||
Operator(Token.Type.PLUS, 8, 2) { pos, a, b ->
|
||||
PlusStatement(pos, a, b)
|
||||
},
|
||||
Operator("-", 8, 2) { pos, a, b ->
|
||||
Operator(Token.Type.MINUS, 8, 2) { pos, a, b ->
|
||||
MinusStatement(pos, a, b)
|
||||
},
|
||||
Operator("*", 9, 2) { pos, a, b -> MulStatement(pos, a, b) },
|
||||
Operator("/", 9, 2) { pos, a, b -> DivStatement(pos, a, b) },
|
||||
Operator("%", 9, 2) { pos, a, b -> ModStatement(pos, a, b) },
|
||||
Operator(Token.Type.STAR, 9, 2) { pos, a, b -> MulStatement(pos, a, b) },
|
||||
Operator(Token.Type.SLASH, 9, 2) { pos, a, b -> DivStatement(pos, a, b) },
|
||||
Operator(Token.Type.PERCENT, 9, 2) { pos, a, b -> ModStatement(pos, a, b) },
|
||||
)
|
||||
val lastLevel = 10
|
||||
val byLevel: List<Map<String, Operator>> = (0..< lastLevel).map { l ->
|
||||
val byLevel: List<Map<Token.Type, Operator>> = (0..<lastLevel).map { l ->
|
||||
allOps.filter { it.priority == l }
|
||||
.map { it.name to it }.toMap()
|
||||
.map { it.tokenType to it }.toMap()
|
||||
}
|
||||
|
||||
fun compile(code: String): Script = Compiler().compile(Source("<eval>", code))
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun eval(code: String) = Compiler.compile(code).execute()
|
||||
suspend fun eval(code: String) = Compiler.compile(code).execute()
|
||||
|
||||
fun LogicalOp(tokenType: Token.Type, priority: Int, f: (Obj, Obj) -> Boolean) = Compiler.Operator(
|
||||
tokenType,
|
||||
priority,
|
||||
2
|
||||
) { pos, a, b ->
|
||||
statement(pos) {
|
||||
ObjBool(
|
||||
f(a.execute(it), b.execute(it))
|
||||
)
|
||||
}
|
||||
}
|
@ -5,14 +5,36 @@ import kotlinx.serialization.Serializable
|
||||
import kotlin.math.floor
|
||||
|
||||
@Serializable
|
||||
sealed class Obj {
|
||||
sealed class Obj : Comparable<Obj> {
|
||||
open val asStr: ObjString by lazy {
|
||||
if( this is ObjString) this else ObjString(this.toString())
|
||||
if (this is ObjString) this else ObjString(this.toString())
|
||||
}
|
||||
|
||||
open val type: Type = Type.Any
|
||||
|
||||
@Suppress("unused")
|
||||
enum class Type {
|
||||
@SerialName("Void")
|
||||
Void,
|
||||
@SerialName("Null")
|
||||
Null,
|
||||
@SerialName("String")
|
||||
String,
|
||||
@SerialName("Int")
|
||||
Int,
|
||||
@SerialName("Real")
|
||||
Real,
|
||||
@SerialName("Bool")
|
||||
Bool,
|
||||
@SerialName("Fn")
|
||||
Fn,
|
||||
@SerialName("Any")
|
||||
Any,
|
||||
}
|
||||
|
||||
companion object {
|
||||
inline fun <reified T> from(obj: T): Obj {
|
||||
return when(obj) {
|
||||
return when (obj) {
|
||||
is Obj -> obj
|
||||
is Double -> ObjReal(obj)
|
||||
is Float -> ObjReal(obj.toDouble())
|
||||
@ -34,15 +56,23 @@ inline fun <reified T> T.toObj(): Obj = Obj.from(this)
|
||||
|
||||
@Serializable
|
||||
@SerialName("void")
|
||||
object ObjVoid: Obj() {
|
||||
object ObjVoid : Obj() {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is ObjVoid || other is Unit
|
||||
}
|
||||
|
||||
override fun compareTo(other: Obj): Int {
|
||||
return if (other === this) 0 else -1
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("null")
|
||||
object ObjNull: Obj() {
|
||||
object ObjNull : Obj() {
|
||||
override fun compareTo(other: Obj): Int {
|
||||
return if (other === this) 0 else -1
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is ObjNull || other == null
|
||||
}
|
||||
@ -50,7 +80,13 @@ object ObjNull: Obj() {
|
||||
|
||||
@Serializable
|
||||
@SerialName("string")
|
||||
data class ObjString(val value: String): Obj() {
|
||||
data class ObjString(val value: String) : Obj() {
|
||||
|
||||
override fun compareTo(other: Obj): Int {
|
||||
if (other !is ObjString) throw IllegalArgumentException("cannot compare string with $other")
|
||||
return this.value.compareTo(other.value)
|
||||
}
|
||||
|
||||
override fun toString(): String = value
|
||||
}
|
||||
|
||||
@ -74,35 +110,58 @@ fun Obj.toLong(): Long =
|
||||
|
||||
fun Obj.toInt(): Int = toLong().toInt()
|
||||
|
||||
fun Obj.toBool(): Boolean = (this as? ObjBool)?.value ?: throw IllegalArgumentException("cannot convert to boolean ${this.type}:$this")
|
||||
|
||||
|
||||
@Serializable
|
||||
@SerialName("real")
|
||||
data class ObjReal(val value: Double): Obj(), Numeric {
|
||||
data class ObjReal(val value: Double) : Obj(), Numeric {
|
||||
override val asStr by lazy { ObjString(value.toString()) }
|
||||
override val longValue: Long by lazy { floor(value).toLong() }
|
||||
override val doubleValue: Double by lazy { value }
|
||||
override val toObjInt: ObjInt by lazy { ObjInt(longValue) }
|
||||
override val toObjReal: ObjReal by lazy { ObjReal(value) }
|
||||
|
||||
override fun compareTo(other: Obj): Int {
|
||||
if( other !is Numeric) throw IllegalArgumentException("cannot compare $this with $other")
|
||||
return value.compareTo(other.doubleValue)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("int")
|
||||
data class ObjInt(val value: Long): Obj(), Numeric {
|
||||
data class ObjInt(val value: Long) : Obj(), Numeric {
|
||||
override val asStr by lazy { ObjString(value.toString()) }
|
||||
override val longValue: Long by lazy { value }
|
||||
override val doubleValue: Double by lazy { value.toDouble() }
|
||||
override val toObjInt: ObjInt by lazy { ObjInt(value) }
|
||||
override val toObjReal: ObjReal by lazy { ObjReal(doubleValue) }
|
||||
|
||||
override fun compareTo(other: Obj): Int {
|
||||
if( other !is Numeric) throw IllegalArgumentException("cannot compare $this with $other")
|
||||
return value.compareTo(other.doubleValue)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("bool")
|
||||
data class ObjBool(val value: Boolean): Obj() {
|
||||
data class ObjBool(val value: Boolean) : Obj() {
|
||||
override val asStr by lazy { ObjString(value.toString()) }
|
||||
|
||||
override fun compareTo(other: Obj): Int {
|
||||
if( other !is ObjBool) throw IllegalArgumentException("cannot compare $this with $other")
|
||||
return value.compareTo(other.value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class ObjNamespace(val name: String,val context: Context): Obj() {
|
||||
data class ObjNamespace(val name: String, val context: Context) : Obj() {
|
||||
override fun toString(): String {
|
||||
return "namespace ${name}"
|
||||
}
|
||||
|
||||
override fun compareTo(other: Obj): Int {
|
||||
throw IllegalArgumentException("cannot compare namespaces")
|
||||
}
|
||||
}
|
@ -55,9 +55,30 @@ private class Parser(fromPos: Pos) {
|
||||
'/' -> Token("/", from, Token.Type.SLASH)
|
||||
'%' -> Token("%", from, Token.Type.PERCENT)
|
||||
'.' -> Token(".", from, Token.Type.DOT)
|
||||
'<' -> Token("<", from, Token.Type.LT)
|
||||
'>' -> Token(">", from, Token.Type.GT)
|
||||
'!' -> Token("!", from, Token.Type.NOT)
|
||||
'<' -> {
|
||||
if(currentChar == '=') {
|
||||
advance()
|
||||
Token("<=", from, Token.Type.LTE)
|
||||
}
|
||||
else
|
||||
Token("<", from, Token.Type.LT)
|
||||
}
|
||||
'>' -> {
|
||||
if( currentChar == '=') {
|
||||
advance()
|
||||
Token(">=", from, Token.Type.GTE)
|
||||
}
|
||||
else
|
||||
Token(">", from, Token.Type.GT)
|
||||
}
|
||||
'!' -> {
|
||||
if( currentChar == '=') {
|
||||
advance()
|
||||
Token("!=", from, Token.Type.NEQ)
|
||||
}
|
||||
else
|
||||
Token("!", from, Token.Type.NOT)
|
||||
}
|
||||
'|' -> {
|
||||
if (currentChar == '|') {
|
||||
advance()
|
||||
|
@ -16,7 +16,7 @@ class Script(
|
||||
return lastResult
|
||||
}
|
||||
|
||||
suspend fun execute() = execute(defaultContext)
|
||||
suspend fun execute() = execute(defaultContext.copy())
|
||||
|
||||
companion object {
|
||||
val defaultContext: Context = Context(null).apply {
|
||||
|
@ -1,12 +1,16 @@
|
||||
package net.sergeych.ling
|
||||
|
||||
data class Token(val value: String, val pos: Pos, val type: Type) {
|
||||
@Suppress("unused")
|
||||
enum class Type {
|
||||
ID, INT, REAL, HEX, STRING, LPAREN, RPAREN, LBRACE, RBRACE, LBRACKET, RBRACKET, COMMA,
|
||||
SEMICOLON, COLON, EQ, PLUS, MINUS, STAR, SLASH, ASSIGN, EQEQ, NEQ, LT, LTEQ, GT,
|
||||
GTEQ, AND, BITAND, OR, BITOR, NOT, DOT, ARROW, QUESTION, COLONCOLON, PERCENT,
|
||||
SEMICOLON, COLON,
|
||||
PLUS, MINUS, STAR, SLASH, ASSIGN,
|
||||
EQ, NEQ, LT, LTE, GT, GTE,
|
||||
AND, BITAND, OR, BITOR, NOT, DOT, ARROW, QUESTION, COLONCOLON, PERCENT,
|
||||
EOF,
|
||||
}
|
||||
|
||||
companion object {
|
||||
// fun eof(parser: Parser) = Token("", parser.currentPos, Type.EOF)
|
||||
}
|
||||
|
@ -3,11 +3,20 @@ package net.sergeych.ling
|
||||
fun String.toSource(name: String = "eval"): Source = Source(name, this)
|
||||
|
||||
|
||||
@Suppress("unused")
|
||||
abstract class Statement(
|
||||
@Suppress("unused") val isStaticConst: Boolean = false
|
||||
val isStaticConst: Boolean = false,
|
||||
val isConst: Boolean = false,
|
||||
val returnType: Type = Type.Any
|
||||
) : Obj() {
|
||||
|
||||
abstract val pos: Pos
|
||||
abstract suspend fun execute(context: Context): Obj
|
||||
|
||||
override fun compareTo(other: Obj): Int {
|
||||
throw UnsupportedOperationException("not comparable")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun Statement.raise(text: String): Nothing {
|
||||
|
@ -197,5 +197,36 @@ class ScriptTest {
|
||||
assertEquals(24, eval("(2 + 3) * 5 -1").toInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEqNeq() = runTest {
|
||||
assertEquals(ObjBool(true), eval("val x = 2; x == 2"))
|
||||
assertEquals(ObjBool(false), eval("val x = 3; x == 2"))
|
||||
assertEquals(ObjBool(true), eval("val x = 3; x != 2"))
|
||||
assertEquals(ObjBool(false), eval("val x = 3; x != 3"))
|
||||
|
||||
assertTrue { eval("1 == 1").toBool() }
|
||||
assertTrue { eval("true == true").toBool() }
|
||||
assertTrue { eval("true != false").toBool() }
|
||||
assertFalse { eval("true == false").toBool() }
|
||||
assertFalse { eval("false != false").toBool() }
|
||||
|
||||
assertTrue { eval("2 == 2 && 3 != 4").toBool() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGtLt() = runTest {
|
||||
assertTrue { eval("3 > 2").toBool() }
|
||||
assertFalse { eval("3 > 3").toBool() }
|
||||
assertTrue { eval("3 >= 2").toBool() }
|
||||
assertFalse { eval("3 >= 4").toBool() }
|
||||
assertFalse { eval("3 < 2").toBool() }
|
||||
assertFalse { eval("3 <= 2").toBool() }
|
||||
assertTrue { eval("3 <= 3").toBool()}
|
||||
assertTrue { eval("3 <= 4").toBool()}
|
||||
assertTrue { eval("3 < 4").toBool()}
|
||||
assertFalse { eval("4 < 3").toBool()}
|
||||
assertFalse { eval("4 <= 3").toBool()}
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user