comparison operators + some optimizations
This commit is contained in:
parent
07a4c4c8d0
commit
95e68d6e2a
@ -115,7 +115,7 @@ class Compiler {
|
|||||||
while (true) {
|
while (true) {
|
||||||
|
|
||||||
val opToken = tokens.next()
|
val opToken = tokens.next()
|
||||||
val op = byLevel[level][opToken.value]
|
val op = byLevel[level][opToken.type]
|
||||||
if (op == null) {
|
if (op == null) {
|
||||||
tokens.previous()
|
tokens.previous()
|
||||||
break
|
break
|
||||||
@ -142,9 +142,12 @@ class Compiler {
|
|||||||
when (t.value) {
|
when (t.value) {
|
||||||
"void" -> statement(t.pos, true) { ObjVoid }
|
"void" -> statement(t.pos, true) { ObjVoid }
|
||||||
"null" -> statement(t.pos, true) { ObjNull }
|
"null" -> statement(t.pos, true) { ObjNull }
|
||||||
|
"true" -> statement(t.pos, true) { ObjBool(true) }
|
||||||
|
"false" -> statement(t.pos, true) { ObjBool(false) }
|
||||||
else -> parseVarAccess(t, tokens)
|
else -> parseVarAccess(t, tokens)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.LPAREN -> {
|
Token.Type.LPAREN -> {
|
||||||
// ( subexpr )
|
// ( subexpr )
|
||||||
parseExpression(tokens)?.also {
|
parseExpression(tokens)?.also {
|
||||||
@ -153,6 +156,7 @@ class Compiler {
|
|||||||
throw ScriptError(t.pos, "unbalanced parenthesis: no ')' for it")
|
throw ScriptError(t.pos, "unbalanced parenthesis: no ')' for it")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.PLUS -> {
|
Token.Type.PLUS -> {
|
||||||
val n = parseNumber(true, tokens)
|
val n = parseNumber(true, tokens)
|
||||||
statement(t.pos, true) { n }
|
statement(t.pos, true) { n }
|
||||||
@ -213,7 +217,8 @@ class Compiler {
|
|||||||
} while (t.type != Token.Type.RPAREN)
|
} while (t.type != Token.Type.RPAREN)
|
||||||
|
|
||||||
statement(id.pos) { context ->
|
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(
|
(v.value as? Statement)?.execute(
|
||||||
context.copy(
|
context.copy(
|
||||||
Arguments(
|
Arguments(
|
||||||
@ -325,7 +330,10 @@ class Compiler {
|
|||||||
d.name,
|
d.name,
|
||||||
false,
|
false,
|
||||||
d.defaultValue?.execute(context)
|
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,36 +395,43 @@ class Compiler {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
companion object {
|
|
||||||
data class Operator(
|
data class Operator(
|
||||||
val name: String,
|
val tokenType: Token.Type,
|
||||||
val priority: Int, val arity: Int,
|
val priority: Int, val arity: Int,
|
||||||
val generate: (Pos, Statement, Statement) -> Statement
|
val generate: (Pos, Statement, Statement) -> Statement
|
||||||
)
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
val allOps = listOf(
|
val allOps = listOf(
|
||||||
Operator("||", 0, 2) { pos, a, b -> LogicalOrStatement(pos, a, b) },
|
Operator(Token.Type.OR, 0, 2) { pos, a, b -> LogicalOrStatement(pos, a, b) },
|
||||||
Operator("&&", 1, 2) { pos, a, b -> LogicalAndStatement(pos, a, b) },
|
Operator(Token.Type.AND, 1, 2) { pos, a, b -> LogicalAndStatement(pos, a, b) },
|
||||||
// bitwise or 2
|
// bitwise or 2
|
||||||
// bitwise and 3
|
// bitwise and 3
|
||||||
// equality/ne 4
|
// equality/ne 4
|
||||||
|
LogicalOp(Token.Type.EQ, 4) { a, b -> a == b },
|
||||||
|
LogicalOp(Token.Type.NEQ, 4) { a, b -> a != b },
|
||||||
// relational <=,... 5
|
// 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
|
// shuttle <=> 6
|
||||||
// bitshhifts 7
|
// bitshhifts 7
|
||||||
Operator("+", 8, 2) { pos, a, b ->
|
Operator(Token.Type.PLUS, 8, 2) { pos, a, b ->
|
||||||
PlusStatement(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)
|
MinusStatement(pos, a, b)
|
||||||
},
|
},
|
||||||
Operator("*", 9, 2) { pos, a, b -> MulStatement(pos, a, b) },
|
Operator(Token.Type.STAR, 9, 2) { pos, a, b -> MulStatement(pos, a, b) },
|
||||||
Operator("/", 9, 2) { pos, a, b -> DivStatement(pos, a, b) },
|
Operator(Token.Type.SLASH, 9, 2) { pos, a, b -> DivStatement(pos, a, b) },
|
||||||
Operator("%", 9, 2) { pos, a, b -> ModStatement(pos, a, b) },
|
Operator(Token.Type.PERCENT, 9, 2) { pos, a, b -> ModStatement(pos, a, b) },
|
||||||
)
|
)
|
||||||
val lastLevel = 10
|
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 }
|
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))
|
fun compile(code: String): Script = Compiler().compile(Source("<eval>", code))
|
||||||
@ -424,3 +439,15 @@ class Compiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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,11 +5,33 @@ import kotlinx.serialization.Serializable
|
|||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
sealed class Obj {
|
sealed class Obj : Comparable<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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
companion object {
|
||||||
inline fun <reified T> from(obj: T): Obj {
|
inline fun <reified T> from(obj: T): Obj {
|
||||||
return when (obj) {
|
return when (obj) {
|
||||||
@ -38,11 +60,19 @@ object ObjVoid: Obj() {
|
|||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
return other is ObjVoid || other is Unit
|
return other is ObjVoid || other is Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun compareTo(other: Obj): Int {
|
||||||
|
return if (other === this) 0 else -1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("null")
|
@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 {
|
override fun equals(other: Any?): Boolean {
|
||||||
return other is ObjNull || other == null
|
return other is ObjNull || other == null
|
||||||
}
|
}
|
||||||
@ -51,6 +81,12 @@ object ObjNull: Obj() {
|
|||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("string")
|
@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
|
override fun toString(): String = value
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +110,8 @@ fun Obj.toLong(): Long =
|
|||||||
|
|
||||||
fun Obj.toInt(): Int = toLong().toInt()
|
fun Obj.toInt(): Int = toLong().toInt()
|
||||||
|
|
||||||
|
fun Obj.toBool(): Boolean = (this as? ObjBool)?.value ?: throw IllegalArgumentException("cannot convert to boolean ${this.type}:$this")
|
||||||
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("real")
|
@SerialName("real")
|
||||||
@ -83,6 +121,12 @@ data class ObjReal(val value: Double): Obj(), Numeric {
|
|||||||
override val doubleValue: Double by lazy { value }
|
override val doubleValue: Double by lazy { value }
|
||||||
override val toObjInt: ObjInt by lazy { ObjInt(longValue) }
|
override val toObjInt: ObjInt by lazy { ObjInt(longValue) }
|
||||||
override val toObjReal: ObjReal by lazy { ObjReal(value) }
|
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
|
@Serializable
|
||||||
@ -93,16 +137,31 @@ data class ObjInt(val value: Long): Obj(), Numeric {
|
|||||||
override val doubleValue: Double by lazy { value.toDouble() }
|
override val doubleValue: Double by lazy { value.toDouble() }
|
||||||
override val toObjInt: ObjInt by lazy { ObjInt(value) }
|
override val toObjInt: ObjInt by lazy { ObjInt(value) }
|
||||||
override val toObjReal: ObjReal by lazy { ObjReal(doubleValue) }
|
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
|
@Serializable
|
||||||
@SerialName("bool")
|
@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 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 {
|
override fun toString(): String {
|
||||||
return "namespace ${name}"
|
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.SLASH)
|
||||||
'%' -> Token("%", from, Token.Type.PERCENT)
|
'%' -> Token("%", from, Token.Type.PERCENT)
|
||||||
'.' -> Token(".", from, Token.Type.DOT)
|
'.' -> Token(".", from, Token.Type.DOT)
|
||||||
'<' -> Token("<", from, Token.Type.LT)
|
'<' -> {
|
||||||
'>' -> Token(">", from, Token.Type.GT)
|
if(currentChar == '=') {
|
||||||
'!' -> Token("!", from, Token.Type.NOT)
|
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 == '|') {
|
if (currentChar == '|') {
|
||||||
advance()
|
advance()
|
||||||
|
@ -16,7 +16,7 @@ class Script(
|
|||||||
return lastResult
|
return lastResult
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun execute() = execute(defaultContext)
|
suspend fun execute() = execute(defaultContext.copy())
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val defaultContext: Context = Context(null).apply {
|
val defaultContext: Context = Context(null).apply {
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
package net.sergeych.ling
|
package net.sergeych.ling
|
||||||
|
|
||||||
data class Token(val value: String, val pos: Pos, val type: Type) {
|
data class Token(val value: String, val pos: Pos, val type: Type) {
|
||||||
|
@Suppress("unused")
|
||||||
enum class Type {
|
enum class Type {
|
||||||
ID, INT, REAL, HEX, STRING, LPAREN, RPAREN, LBRACE, RBRACE, LBRACKET, RBRACKET, COMMA,
|
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,
|
SEMICOLON, COLON,
|
||||||
GTEQ, AND, BITAND, OR, BITOR, NOT, DOT, ARROW, QUESTION, COLONCOLON, PERCENT,
|
PLUS, MINUS, STAR, SLASH, ASSIGN,
|
||||||
|
EQ, NEQ, LT, LTE, GT, GTE,
|
||||||
|
AND, BITAND, OR, BITOR, NOT, DOT, ARROW, QUESTION, COLONCOLON, PERCENT,
|
||||||
EOF,
|
EOF,
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// fun eof(parser: Parser) = Token("", parser.currentPos, Type.EOF)
|
// 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)
|
fun String.toSource(name: String = "eval"): Source = Source(name, this)
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
abstract class Statement(
|
abstract class Statement(
|
||||||
@Suppress("unused") val isStaticConst: Boolean = false
|
val isStaticConst: Boolean = false,
|
||||||
|
val isConst: Boolean = false,
|
||||||
|
val returnType: Type = Type.Any
|
||||||
) : Obj() {
|
) : Obj() {
|
||||||
|
|
||||||
abstract val pos: Pos
|
abstract val pos: Pos
|
||||||
abstract suspend fun execute(context: Context): Obj
|
abstract suspend fun execute(context: Context): Obj
|
||||||
|
|
||||||
|
override fun compareTo(other: Obj): Int {
|
||||||
|
throw UnsupportedOperationException("not comparable")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Statement.raise(text: String): Nothing {
|
fun Statement.raise(text: String): Nothing {
|
||||||
|
@ -197,5 +197,36 @@ class ScriptTest {
|
|||||||
assertEquals(24, eval("(2 + 3) * 5 -1").toInt())
|
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