refactored operators for OO overloading (and faster), assignment is not expression and += *= /= -= %= added
This commit is contained in:
parent
72f637e7de
commit
9a9f712cec
112
docs/tutorial.md
112
docs/tutorial.md
@ -4,36 +4,38 @@ Ling is a very simple language, where we take only most important and popular fe
|
||||
other scripts and languages. In particular, we adopt _principle of minimal confusion_[^1].
|
||||
In other word, the code usually works as expected when you see it. So, nothing unusual.
|
||||
|
||||
# Expressions and blocks.
|
||||
# Expressions
|
||||
|
||||
Everything is an expression in Ling. Even an empty block:
|
||||
|
||||
{
|
||||
// empty block
|
||||
}
|
||||
>>> void
|
||||
|
||||
Block returns it last expression as "return value":
|
||||
any block also returns it's last expression:
|
||||
|
||||
{
|
||||
if( true ) {
|
||||
2 + 2
|
||||
3 + 3
|
||||
}
|
||||
>>> 6
|
||||
|
||||
Same is without block:
|
||||
|
||||
3 + 3
|
||||
>>> 6
|
||||
|
||||
If you don't want block to return anything, use `void`:
|
||||
|
||||
{
|
||||
3 + 4
|
||||
fn voidFunction() {
|
||||
3 + 4 // this will be ignored
|
||||
void
|
||||
}
|
||||
voidFunction()
|
||||
>>> void
|
||||
|
||||
otherwise, last expression will be returned:
|
||||
|
||||
fn normalize(value, minValue, maxValue) {
|
||||
(value - minValue) / (maxValue-minValue)
|
||||
}
|
||||
normalize( 4, 0.0, 10.0)
|
||||
>>> 0.4
|
||||
|
||||
Every construction is an expression that returns something (or `void`):
|
||||
|
||||
val x = 111 // or autotest will fail!
|
||||
@ -54,18 +56,47 @@ You can use blocks in if statement, as expected:
|
||||
|
||||
When putting multiple statments in the same line it is convenient and recommended to use `;`:
|
||||
|
||||
var from; var to;
|
||||
var from; var to
|
||||
from = 0; to = 100
|
||||
>>> void
|
||||
|
||||
Notice: returned value is `void` as assignment operator does not return its value. We might decide to change it.
|
||||
>>> 100
|
||||
|
||||
Notice: returned value is `100` as assignment operator returns its assigned value.
|
||||
Most often you can omit `;`, but improves readability and prevent some hardly seen bugs.
|
||||
|
||||
So the principles are:
|
||||
## Assignments
|
||||
|
||||
- everything is an expression returning its last calculated value or `void`
|
||||
- expression could be a `{ block }`
|
||||
Assignemnt is an expression that changes its lvalue and return assigned value:
|
||||
|
||||
var x = 100
|
||||
x = 20
|
||||
println(5 + (x=6)) // 11: x changes its value!
|
||||
x
|
||||
>>> 11
|
||||
>>> 6
|
||||
|
||||
As the assignment itself is an expression, you can use it in strange ways. Just remember
|
||||
to use parentheses as assignment operation insofar is left-associated and will not
|
||||
allow chained assignments (we might fix it later)
|
||||
|
||||
var x = 0
|
||||
var y = 0
|
||||
x = (y = 5)
|
||||
x + y
|
||||
>>> 10
|
||||
|
||||
## Modifying arithmetics
|
||||
|
||||
There is a set of assigning operations: `+=`, `-=`, `*=`, `/=` and even `%=`.
|
||||
|
||||
var x = 5
|
||||
assert( 25 == (x*=5) )
|
||||
assert( 25 == x)
|
||||
assert( 24 == (x-=1) )
|
||||
assert( 12 == (x/=2) )
|
||||
x
|
||||
>>> 12
|
||||
|
||||
Notice the parentheses here: the assignment has low priority!
|
||||
|
||||
## Expression details
|
||||
|
||||
@ -202,7 +233,7 @@ Regular pre-condition while loop, as expression, loop returns it's last line res
|
||||
|
||||
var count = 0
|
||||
while( count < 5 ) {
|
||||
count = count + 1
|
||||
count++
|
||||
count * 10
|
||||
}
|
||||
>>> 50
|
||||
@ -212,18 +243,17 @@ We can break as usual:
|
||||
var count = 0
|
||||
while( count < 5 ) {
|
||||
if( count < 5 ) break
|
||||
count = count + 1
|
||||
count * 10
|
||||
count = ++count * 10
|
||||
}
|
||||
>>> void
|
||||
|
||||
Why `void`? Because `break` drops out without the chute, not providing anything to return. Indeed, we should provide exit value in the case:
|
||||
|
||||
var count = 0
|
||||
while( count < 5 ) {
|
||||
while( count < 50 ) {
|
||||
if( count > 3 ) break "too much"
|
||||
count = count + 1
|
||||
count * 10
|
||||
count = ++count * 10
|
||||
"wrong "+count
|
||||
}
|
||||
>>> too much
|
||||
|
||||
@ -288,6 +318,38 @@ The label can be any valid identifier, even a keyword, labels exist in their own
|
||||
|
||||
Right now labels are implemented only for the while loop. It is intended to be implemented for all loops and returns.
|
||||
|
||||
# Self-assignments in expression
|
||||
|
||||
There are auto-increments and auto-decrements:
|
||||
|
||||
var counter = 0
|
||||
assert(counter++ * 100 == 0)
|
||||
assert(counter == 1)
|
||||
>>> void
|
||||
|
||||
but:
|
||||
|
||||
var counter = 0
|
||||
assert( ++counter * 100 == 100)
|
||||
assert(counter == 1)
|
||||
>>> void
|
||||
|
||||
The same with `--`:
|
||||
|
||||
var count = 100
|
||||
var sum = 0
|
||||
while( count > 0 ) sum = sum + count--
|
||||
sum
|
||||
>>> 5050
|
||||
|
||||
There are self-assigning version for operators too:
|
||||
|
||||
var count = 100
|
||||
var sum = 0
|
||||
while( count > 0 ) sum += count--
|
||||
sum
|
||||
>>> 5050
|
||||
|
||||
# Comments
|
||||
|
||||
// single line comment
|
||||
|
@ -29,20 +29,20 @@ class Compiler(
|
||||
return when (t.type) {
|
||||
Token.Type.ID -> {
|
||||
// could be keyword, assignment or just the expression
|
||||
val next = tokens.next()
|
||||
if (next.type == Token.Type.ASSIGN) {
|
||||
// val next = tokens.next()
|
||||
// if (next.type == Token.Type.ASSIGN) {
|
||||
// this _is_ assignment statement
|
||||
return AssignStatement(
|
||||
t.pos, t.value,
|
||||
parseStatement(tokens) ?: throw ScriptError(
|
||||
t.pos,
|
||||
"Expecting expression for assignment operator"
|
||||
)
|
||||
)
|
||||
}
|
||||
// return AssignStatement(
|
||||
// t.pos, t.value,
|
||||
// parseStatement(tokens) ?: throw ScriptError(
|
||||
// t.pos,
|
||||
// "Expecting expression for assignment operator"
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// not assignment, maybe keyword statement:
|
||||
// get back the token which is not '=':
|
||||
tokens.previous()
|
||||
// tokens.previous()
|
||||
// try keyword statement
|
||||
parseKeywordStatement(t, tokens)
|
||||
?: run {
|
||||
@ -84,10 +84,15 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseExpression(tokens: CompilerContext, level: Int = 0): Statement? {
|
||||
private fun parseExpression(tokens: CompilerContext): Statement? {
|
||||
val pos = tokens.currentPos()
|
||||
return parseExpressionLevel(tokens)?.let { a -> statement(pos) { a.getter(it) } }
|
||||
}
|
||||
|
||||
private fun parseExpressionLevel(tokens: CompilerContext, level: Int = 0): Accessor? {
|
||||
if (level == lastLevel)
|
||||
return parseTerm3(tokens)
|
||||
var lvalue = parseExpression(tokens, level + 1)
|
||||
var lvalue = parseExpressionLevel(tokens, level + 1)
|
||||
if (lvalue == null) return null
|
||||
|
||||
while (true) {
|
||||
@ -99,7 +104,7 @@ class Compiler(
|
||||
break
|
||||
}
|
||||
|
||||
val rvalue = parseExpression(tokens, level + 1)
|
||||
val rvalue = parseExpressionLevel(tokens, level + 1)
|
||||
?: throw ScriptError(opToken.pos, "Expecting expression")
|
||||
|
||||
lvalue = op.generate(opToken.pos, lvalue!!, rvalue)
|
||||
@ -167,7 +172,7 @@ class Compiler(
|
||||
* expr-=<expr>, expr*=<expr>, expr/=<expr>
|
||||
* read expr: <expr>
|
||||
*/
|
||||
private fun parseTerm3(cc: CompilerContext): Statement? {
|
||||
private fun parseTerm3(cc: CompilerContext): Accessor? {
|
||||
var operand: Accessor? = null
|
||||
|
||||
while (true) {
|
||||
@ -176,7 +181,13 @@ class Compiler(
|
||||
when (t.type) {
|
||||
Token.Type.NEWLINE, Token.Type.SEMICOLON, Token.Type.EOF -> {
|
||||
cc.previous()
|
||||
return operand?.let { op -> statement(startPos) { op.getter(it) } }
|
||||
return operand
|
||||
}
|
||||
|
||||
Token.Type.NOT -> {
|
||||
if( operand != null ) throw ScriptError(t.pos, "unexpected operator not '!'")
|
||||
val op = parseTerm3(cc) ?: throw ScriptError(t.pos, "Expecting expression")
|
||||
operand = Accessor { op.getter(it).logicalNot(it) }
|
||||
}
|
||||
|
||||
Token.Type.DOT -> {
|
||||
@ -233,11 +244,6 @@ class Compiler(
|
||||
Token.Type.ID -> {
|
||||
// there could be terminal operators or keywords:// variable to read or like
|
||||
when (t.value) {
|
||||
"else" -> {
|
||||
cc.previous()
|
||||
return operand?.let { op -> statement(startPos) { op.getter(it) } }
|
||||
}
|
||||
|
||||
"if", "when", "do", "while", "return" -> {
|
||||
if (operand != null) throw ScriptError(t.pos, "unexpected keyword")
|
||||
cc.previous()
|
||||
@ -245,9 +251,9 @@ class Compiler(
|
||||
operand = Accessor { s.execute(it) }
|
||||
}
|
||||
|
||||
"break", "continue" -> {
|
||||
"else", "break", "continue" -> {
|
||||
cc.previous()
|
||||
return operand?.let { op -> statement(startPos) { op.getter(it) } }
|
||||
return operand
|
||||
|
||||
}
|
||||
|
||||
@ -307,9 +313,7 @@ class Compiler(
|
||||
|
||||
else -> {
|
||||
cc.previous()
|
||||
operand?.let { op ->
|
||||
return statement(startPos) { op.getter(it) }
|
||||
}
|
||||
operand?.let { return it }
|
||||
operand = parseAccessor(cc) ?: throw ScriptError(t.pos, "Expecting expression")
|
||||
}
|
||||
}
|
||||
@ -713,53 +717,118 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
// fun parseStatement(parser: Parser): Statement? =
|
||||
// parser.withToken {
|
||||
// if (tokens.isEmpty()) null
|
||||
// else {
|
||||
// when (val token = tokens[0]) {
|
||||
// else -> {
|
||||
// rollback()
|
||||
// null
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
data class Operator(
|
||||
val tokenType: Token.Type,
|
||||
val priority: Int, val arity: Int,
|
||||
val generate: (Pos, Statement, Statement) -> Statement
|
||||
)
|
||||
val priority: Int, val arity: Int=2,
|
||||
val generate: (Pos, Accessor, Accessor) -> Accessor
|
||||
) {
|
||||
// fun isLeftAssociative() = tokenType != Token.Type.OR && tokenType != Token.Type.AND
|
||||
|
||||
companion object {
|
||||
fun simple(tokenType: Token.Type, priority: Int, f: suspend (Context, Obj, Obj) -> Obj): Operator =
|
||||
Operator(tokenType, priority, 2, { _: Pos, a: Accessor, b: Accessor ->
|
||||
Accessor { f(it, a.getter(it), b.getter(it)) }
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private var lastPrty = 0
|
||||
val allOps = listOf(
|
||||
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) },
|
||||
// assignments
|
||||
Operator(Token.Type.ASSIGN, lastPrty) { pos, a, b ->
|
||||
Accessor {
|
||||
val value = b.getter(it)
|
||||
a.setter(pos)(it, value)
|
||||
value
|
||||
}
|
||||
},
|
||||
Operator(Token.Type.PLUSASSIGN, lastPrty) { pos, a, b ->
|
||||
Accessor {
|
||||
val x = a.getter(it)
|
||||
val y = b.getter(it)
|
||||
x.plusAssign(it, y) ?: run {
|
||||
val result = x.plus(it, y)
|
||||
a.setter(pos)(it, result)
|
||||
result
|
||||
}
|
||||
}
|
||||
},
|
||||
Operator(Token.Type.MINUSASSIGN, lastPrty) { pos, a, b ->
|
||||
Accessor {
|
||||
val x = a.getter(it)
|
||||
val y = b.getter(it)
|
||||
x.minusAssign(it, y) ?: run {
|
||||
val result = x.minus(it, y)
|
||||
a.setter(pos)(it, result)
|
||||
result
|
||||
}
|
||||
}
|
||||
},
|
||||
Operator(Token.Type.STARASSIGN, lastPrty) { pos, a, b ->
|
||||
Accessor {
|
||||
val x = a.getter(it)
|
||||
val y = b.getter(it)
|
||||
x.mulAssign(it, y) ?: run {
|
||||
val result = x.mul(it, y)
|
||||
a.setter(pos)(it, result)
|
||||
result
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
Operator(Token.Type.SLASHASSIGN, lastPrty) { pos, a, b ->
|
||||
Accessor {
|
||||
val x = a.getter(it)
|
||||
val y = b.getter(it)
|
||||
x.divAssign(it, y) ?: run {
|
||||
val result = x.div(it, y)
|
||||
a.setter(pos)(it, result)
|
||||
result
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
Operator(Token.Type.PERCENTASSIGN, lastPrty) { pos, a, b ->
|
||||
Accessor {
|
||||
val x = a.getter(it)
|
||||
val y = b.getter(it)
|
||||
x.modAssign(it, y) ?: run {
|
||||
val result = x.mod(it, y)
|
||||
a.setter(pos)(it, result)
|
||||
result
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
// logical 1
|
||||
Operator.simple(Token.Type.OR, ++lastPrty) { ctx, a, b -> a.logicalOr(ctx,b) },
|
||||
// logical 2
|
||||
Operator.simple(Token.Type.AND, ++lastPrty) { ctx, a, b -> a.logicalAnd(ctx,b) },
|
||||
// bitwise or 2
|
||||
// bitwise and 3
|
||||
// equality/ne 4
|
||||
LogicalOp(Token.Type.EQ, 4) { c, a, b -> a.compareTo(c, b) == 0 },
|
||||
LogicalOp(Token.Type.NEQ, 4) { c, a, b -> a.compareTo(c, b) != 0 },
|
||||
Operator.simple(Token.Type.EQ, ++lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) == 0) },
|
||||
Operator.simple(Token.Type.NEQ, lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) != 0) },
|
||||
// relational <=,... 5
|
||||
LogicalOp(Token.Type.LTE, 5) { c, a, b -> a.compareTo(c, b) <= 0 },
|
||||
LogicalOp(Token.Type.LT, 5) { c, a, b -> a.compareTo(c, b) < 0 },
|
||||
LogicalOp(Token.Type.GTE, 5) { c, a, b -> a.compareTo(c, b) >= 0 },
|
||||
LogicalOp(Token.Type.GT, 5) { c, a, b -> a.compareTo(c, b) > 0 },
|
||||
Operator.simple(Token.Type.LTE, ++lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) <= 0) },
|
||||
Operator.simple(Token.Type.LT, lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) < 0) },
|
||||
Operator.simple(Token.Type.GTE, lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) >= 0) },
|
||||
Operator.simple(Token.Type.GT, lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) > 0) },
|
||||
// shuttle <=> 6
|
||||
// bitshhifts 7
|
||||
Operator(Token.Type.PLUS, 8, 2) { pos, a, b ->
|
||||
PlusStatement(pos, a, b)
|
||||
},
|
||||
Operator(Token.Type.MINUS, 8, 2) { pos, a, b ->
|
||||
MinusStatement(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) },
|
||||
// bit shifts 7
|
||||
Operator.simple(Token.Type.PLUS, ++lastPrty) { ctx, a, b -> a.plus(ctx,b) },
|
||||
Operator.simple(Token.Type.MINUS, lastPrty) { ctx, a, b -> a.minus(ctx,b) },
|
||||
|
||||
Operator.simple(Token.Type.STAR, ++lastPrty) { ctx, a, b -> a.mul(ctx,b) },
|
||||
Operator.simple(Token.Type.SLASH, lastPrty) { ctx, a, b -> a.div(ctx, b) },
|
||||
Operator.simple(Token.Type.PERCENT, lastPrty) { ctx, a, b -> a.mod(ctx, b) },
|
||||
)
|
||||
val lastLevel = 10
|
||||
|
||||
val lastLevel = lastPrty + 1
|
||||
|
||||
val byLevel: List<Map<Token.Type, Operator>> = (0..<lastLevel).map { l ->
|
||||
allOps.filter { it.priority == l }
|
||||
.map { it.tokenType to it }.toMap()
|
||||
@ -771,17 +840,3 @@ class Compiler(
|
||||
|
||||
suspend fun eval(code: String) = Compiler.compile(code).execute()
|
||||
|
||||
fun LogicalOp(
|
||||
tokenType: Token.Type, priority: Int,
|
||||
f: suspend (Context, Obj, Obj) -> Boolean
|
||||
) = Compiler.Operator(
|
||||
tokenType,
|
||||
priority,
|
||||
2
|
||||
) { pos, a, b ->
|
||||
statement(pos) {
|
||||
ObjBool(
|
||||
f(it, a.execute(it), b.execute(it))
|
||||
)
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ internal class CompilerContext(val tokens: List<Token>) : ListIterator<Token> by
|
||||
if (type != it.type) throw ScriptError(it.pos, message)
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun syntaxError(at: Pos, message: String = "Syntax error"): Nothing {
|
||||
throw ScriptError(at, message)
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ class Context(
|
||||
|
||||
fun raiseNotImplemented(): Nothing = raiseError("operation not implemented")
|
||||
|
||||
@Suppress("unused")
|
||||
fun raiseNPE(): Nothing = raiseError(ObjNullPointerError(this))
|
||||
|
||||
fun raiseError(message: String): Nothing {
|
||||
@ -73,17 +74,6 @@ class Context(
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T> addConstWithAliases(value: T, vararg names: String) {
|
||||
val obj = Obj.from(value)
|
||||
for (name in names) {
|
||||
addItem(
|
||||
name,
|
||||
false,
|
||||
obj
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun addConst(name: String,value: Obj) = addItem(name, false, value)
|
||||
|
||||
suspend fun eval(code: String): Obj =
|
||||
|
@ -7,7 +7,7 @@ import kotlinx.serialization.Serializable
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
typealias InstanceMethod = (Context, Obj) -> Obj
|
||||
//typealias InstanceMethod = (Context, Obj) -> Obj
|
||||
|
||||
data class WithAccess<T>(var value: T, val isMutable: Boolean)
|
||||
|
||||
@ -70,32 +70,70 @@ sealed class Obj {
|
||||
*/
|
||||
open val objClass: ObjClass by lazy { ObjClass("Obj") }
|
||||
|
||||
open fun plus(context: Context, other: Obj): Obj {
|
||||
open suspend fun plus(context: Context, other: Obj): Obj {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
open fun assign(context: Context, other: Obj): Obj {
|
||||
open suspend fun minus(context: Context, other: Obj): Obj {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
open fun plusAssign(context: Context, other: Obj): Obj {
|
||||
assign(context, plus(context, other))
|
||||
return this
|
||||
}
|
||||
|
||||
open fun getAndIncrement(context: Context): Obj {
|
||||
open suspend fun mul(context: Context, other: Obj): Obj {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
open fun incrementAndGet(context: Context): Obj {
|
||||
open suspend fun div(context: Context, other: Obj): Obj {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
open fun decrementAndGet(context: Context): Obj {
|
||||
open suspend fun mod(context: Context, other: Obj): Obj {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
open fun getAndDecrement(context: Context): Obj {
|
||||
open suspend fun logicalNot(context: Context): Obj {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
open suspend fun logicalAnd(context: Context, other: Obj): Obj {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
open suspend fun logicalOr(context: Context, other: Obj): Obj {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
open suspend fun assign(context: Context, other: Obj): Obj {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
/**
|
||||
* a += b
|
||||
* if( the operation is not defined, it returns null and the compiler would try
|
||||
* to generate it as 'this = this + other', reassigning its variable
|
||||
*/
|
||||
open suspend fun plusAssign(context: Context, other: Obj): Obj? = null
|
||||
|
||||
/**
|
||||
* `-=` operations, see [plusAssign]
|
||||
*/
|
||||
open suspend fun minusAssign(context: Context, other: Obj): Obj? = null
|
||||
open suspend fun mulAssign(context: Context, other: Obj): Obj? = null
|
||||
open suspend fun divAssign(context: Context, other: Obj): Obj? = null
|
||||
open suspend fun modAssign(context: Context, other: Obj): Obj? = null
|
||||
|
||||
open suspend fun getAndIncrement(context: Context): Obj {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
open suspend fun incrementAndGet(context: Context): Obj {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
open suspend fun decrementAndGet(context: Context): Obj {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
open suspend fun getAndDecrement(context: Context): Obj {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
@ -105,9 +143,9 @@ sealed class Obj {
|
||||
|
||||
suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() }
|
||||
|
||||
suspend fun readField(context: Context, name: String): Obj = getInstanceMember(context.pos, name)
|
||||
fun readField(context: Context, name: String): Obj = getInstanceMember(context.pos, name)
|
||||
|
||||
suspend fun writeField(context: Context, name: String, newValue: Obj) {
|
||||
fun writeField(context: Context, name: String, newValue: Obj) {
|
||||
willMutate(context)
|
||||
members[name]?.let { if (it.isMutable) it.value = newValue }
|
||||
?: context.raiseError("Can't reassign member: $name")
|
||||
@ -194,6 +232,10 @@ data class ObjString(val value: String) : Obj() {
|
||||
override val objClass: ObjClass
|
||||
get() = type
|
||||
|
||||
override suspend fun plus(context: Context, other: Obj): Obj {
|
||||
return ObjString(value + other.asStr.value)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("String")
|
||||
}
|
||||
@ -239,6 +281,21 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
||||
|
||||
override val objClass: ObjClass = type
|
||||
|
||||
override suspend fun plus(context: Context, other: Obj): Obj =
|
||||
ObjReal(this.value + other.toDouble())
|
||||
|
||||
override suspend fun minus(context: Context, other: Obj): Obj =
|
||||
ObjReal(this.value - other.toDouble())
|
||||
|
||||
override suspend fun mul(context: Context, other: Obj): Obj =
|
||||
ObjReal(this.value * other.toDouble())
|
||||
|
||||
override suspend fun div(context: Context, other: Obj): Obj =
|
||||
ObjReal(this.value / other.toDouble())
|
||||
|
||||
override suspend fun mod(context: Context, other: Obj): Obj =
|
||||
ObjReal(this.value % other.toDouble())
|
||||
|
||||
companion object {
|
||||
val type: ObjClass = ObjClass("Real").apply {
|
||||
createField(
|
||||
@ -258,19 +315,19 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
|
||||
override val toObjInt get() = this
|
||||
override val toObjReal = ObjReal(doubleValue)
|
||||
|
||||
override fun getAndIncrement(context: Context): Obj {
|
||||
override suspend fun getAndIncrement(context: Context): Obj {
|
||||
return ObjInt(value).also { value++ }
|
||||
}
|
||||
|
||||
override fun getAndDecrement(context: Context): Obj {
|
||||
override suspend fun getAndDecrement(context: Context): Obj {
|
||||
return ObjInt(value).also { value-- }
|
||||
}
|
||||
|
||||
override fun incrementAndGet(context: Context): Obj {
|
||||
override suspend fun incrementAndGet(context: Context): Obj {
|
||||
return ObjInt(++value)
|
||||
}
|
||||
|
||||
override fun decrementAndGet(context: Context): Obj {
|
||||
override suspend fun decrementAndGet(context: Context): Obj {
|
||||
return ObjInt(--value)
|
||||
}
|
||||
|
||||
@ -283,6 +340,33 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
|
||||
|
||||
override val objClass: ObjClass = type
|
||||
|
||||
override suspend fun plus(context: Context, other: Obj): Obj =
|
||||
if (other is ObjInt)
|
||||
ObjInt(this.value + other.value)
|
||||
else
|
||||
ObjReal(this.doubleValue + other.toDouble())
|
||||
|
||||
override suspend fun minus(context: Context, other: Obj): Obj =
|
||||
if (other is ObjInt)
|
||||
ObjInt(this.value - other.value)
|
||||
else
|
||||
ObjReal(this.doubleValue - other.toDouble())
|
||||
|
||||
override suspend fun mul(context: Context, other: Obj): Obj =
|
||||
if (other is ObjInt) {
|
||||
ObjInt(this.value * other.value)
|
||||
} else ObjReal(this.value * other.toDouble())
|
||||
|
||||
override suspend fun div(context: Context, other: Obj): Obj =
|
||||
if (other is ObjInt)
|
||||
ObjInt(this.value / other.value)
|
||||
else ObjReal(this.value / other.toDouble())
|
||||
|
||||
override suspend fun mod(context: Context, other: Obj): Obj =
|
||||
if (other is ObjInt)
|
||||
ObjInt(this.value % other.value)
|
||||
else ObjReal(this.value.toDouble() % other.toDouble())
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("Int")
|
||||
}
|
||||
@ -300,6 +384,12 @@ data class ObjBool(val value: Boolean) : Obj() {
|
||||
|
||||
override val objClass: ObjClass = type
|
||||
|
||||
override suspend fun logicalNot(context: Context): Obj = ObjBool(!value)
|
||||
|
||||
override suspend fun logicalAnd(context: Context, other: Obj): Obj = ObjBool(value && other.toBool())
|
||||
|
||||
override suspend fun logicalOr(context: Context, other: Obj): Obj = ObjBool(value || other.toBool())
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("Bool")
|
||||
}
|
||||
|
@ -56,62 +56,82 @@ private class Parser(fromPos: Pos) {
|
||||
advance()
|
||||
Token("+", from, Token.Type.PLUS2)
|
||||
}
|
||||
|
||||
'=' -> {
|
||||
advance()
|
||||
Token("+", from, Token.Type.PLUSASSIGN)
|
||||
}
|
||||
|
||||
else ->
|
||||
Token("+", from, Token.Type.PLUS)
|
||||
}
|
||||
}
|
||||
|
||||
'-' -> {
|
||||
when (currentChar) {
|
||||
'-' -> {
|
||||
advance()
|
||||
Token("--", from, Token.Type.MINUS2)
|
||||
}
|
||||
|
||||
'=' -> {
|
||||
advance()
|
||||
Token("-", from, Token.Type.MINUSASSIGN)
|
||||
}
|
||||
|
||||
else -> Token("-", from, Token.Type.MINUS)
|
||||
}
|
||||
}
|
||||
'*' -> Token("*", from, Token.Type.STAR)
|
||||
|
||||
'*' -> {
|
||||
if (currentChar == '=') {
|
||||
advance()
|
||||
Token("*=", from, Token.Type.STARASSIGN)
|
||||
} else
|
||||
Token("*", from, Token.Type.STAR)
|
||||
}
|
||||
|
||||
'/' -> when (currentChar) {
|
||||
'/' -> {
|
||||
if( currentChar == '/') {
|
||||
advance()
|
||||
Token(loadToEnd().trim(), from, Token.Type.SINLGE_LINE_COMMENT)
|
||||
}
|
||||
else
|
||||
Token("/", from, Token.Type.SLASH)
|
||||
'=' -> {
|
||||
advance()
|
||||
Token("/=", from, Token.Type.SLASHASSIGN)
|
||||
}
|
||||
'%' -> Token("%", from, Token.Type.PERCENT)
|
||||
else -> Token("/", from, Token.Type.SLASH)
|
||||
}
|
||||
|
||||
'%' -> when(currentChar) {
|
||||
'=' -> { advance(); Token("%=", from, Token.Type.PERCENTASSIGN) }
|
||||
else -> Token("%", from, Token.Type.PERCENT) }
|
||||
|
||||
'.' -> Token(".", from, Token.Type.DOT)
|
||||
'<' -> {
|
||||
if (currentChar == '=') {
|
||||
advance()
|
||||
Token("<=", from, Token.Type.LTE)
|
||||
}
|
||||
else
|
||||
} else
|
||||
Token("<", from, Token.Type.LT)
|
||||
}
|
||||
|
||||
'>' -> {
|
||||
if (currentChar == '=') {
|
||||
advance()
|
||||
Token(">=", from, Token.Type.GTE)
|
||||
}
|
||||
else
|
||||
} else
|
||||
Token(">", from, Token.Type.GT)
|
||||
}
|
||||
|
||||
'!' -> {
|
||||
if (currentChar == '=') {
|
||||
advance()
|
||||
Token("!=", from, Token.Type.NEQ)
|
||||
}
|
||||
else
|
||||
} else
|
||||
Token("!", from, Token.Type.NOT)
|
||||
}
|
||||
|
||||
'|' -> {
|
||||
if (currentChar == '|') {
|
||||
advance()
|
||||
@ -119,6 +139,7 @@ private class Parser(fromPos: Pos) {
|
||||
} else
|
||||
Token("|", from, Token.Type.BITOR)
|
||||
}
|
||||
|
||||
'&' -> {
|
||||
if (currentChar == '&') {
|
||||
advance()
|
||||
@ -126,19 +147,20 @@ private class Parser(fromPos: Pos) {
|
||||
} else
|
||||
Token("&", from, Token.Type.BITAND)
|
||||
}
|
||||
|
||||
'@' -> {
|
||||
val label = loadChars(idNextChars)
|
||||
if (label.isNotEmpty()) Token(label, from, Token.Type.ATLABEL)
|
||||
else raise("unexpected @ character")
|
||||
}
|
||||
|
||||
'\n' -> Token("\n", from, Token.Type.NEWLINE)
|
||||
|
||||
':' -> {
|
||||
if (currentChar == ':') {
|
||||
advance()
|
||||
Token("::", from, Token.Type.COLONCOLON)
|
||||
}
|
||||
else
|
||||
} else
|
||||
Token(":", from, Token.Type.COLON)
|
||||
}
|
||||
|
||||
@ -160,14 +182,11 @@ private class Parser(fromPos: Pos) {
|
||||
// break@label or like
|
||||
pos.back()
|
||||
Token(text, from, Token.Type.ID)
|
||||
}
|
||||
else
|
||||
} else
|
||||
Token(text, from, Token.Type.LABEL)
|
||||
}
|
||||
else
|
||||
} else
|
||||
Token(text, from, Token.Type.ID)
|
||||
}
|
||||
else
|
||||
} else
|
||||
raise("can't parse token")
|
||||
}
|
||||
}
|
||||
|
@ -31,17 +31,17 @@ class Script(
|
||||
addFn("floor") {
|
||||
val x = args.firstAndOnly()
|
||||
(if (x is ObjInt) x
|
||||
else ObjReal(floor(x.toDouble()))) as Obj
|
||||
else ObjReal(floor(x.toDouble())))
|
||||
}
|
||||
addFn("ceil") {
|
||||
val x = args.firstAndOnly()
|
||||
(if (x is ObjInt) x
|
||||
else ObjReal(ceil(x.toDouble()))) as Obj
|
||||
else ObjReal(ceil(x.toDouble())))
|
||||
}
|
||||
addFn("round") {
|
||||
val x = args.firstAndOnly()
|
||||
(if (x is ObjInt) x
|
||||
else ObjReal(round(x.toDouble()))) as Obj
|
||||
else ObjReal(round(x.toDouble())))
|
||||
}
|
||||
addFn("sin") {
|
||||
ObjReal(sin(args.firstAndOnly().toDouble()))
|
||||
|
@ -11,7 +11,7 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
|
||||
ASSIGN, PLUSASSIGN, MINUSASSIGN, STARASSIGN, SLASHASSIGN, PERCENTASSIGN,
|
||||
PLUS2, MINUS2,
|
||||
EQ, NEQ, LT, LTE, GT, GTE,
|
||||
AND, BITAND, OR, BITOR, NOT, DOT, ARROW, QUESTION, COLONCOLON,
|
||||
AND, BITAND, OR, BITOR, NOT, BITNOT, DOT, ARROW, QUESTION, COLONCOLON,
|
||||
SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
|
||||
LABEL,ATLABEL, // label@ at@label
|
||||
NEWLINE,
|
||||
|
@ -45,153 +45,4 @@ fun statement(pos: Pos, isStaticConst: Boolean = false, isConst: Boolean = false
|
||||
override suspend fun execute(context: Context): Obj = f(context)
|
||||
}
|
||||
|
||||
class LogicalAndStatement(
|
||||
override val pos: Pos,
|
||||
val left: Statement, val right: Statement
|
||||
) : Statement() {
|
||||
override suspend fun execute(context: Context): Obj {
|
||||
|
||||
val l = left.execute(context).let {
|
||||
(it as? ObjBool) ?: raise("left operand is not boolean: $it")
|
||||
}
|
||||
val r = right.execute(context).let {
|
||||
(it as? ObjBool) ?: raise("right operand is not boolean: $it")
|
||||
}
|
||||
return ObjBool(l.value && r.value)
|
||||
}
|
||||
}
|
||||
|
||||
class LogicalOrStatement(
|
||||
override val pos: Pos,
|
||||
val left: Statement, val right: Statement
|
||||
) : Statement() {
|
||||
override suspend fun execute(context: Context): Obj {
|
||||
|
||||
val l = left.execute(context).let {
|
||||
(it as? ObjBool) ?: raise("left operand is not boolean: $it")
|
||||
}
|
||||
val r = right.execute(context).let {
|
||||
(it as? ObjBool) ?: raise("right operand is not boolean: $it")
|
||||
}
|
||||
return ObjBool(l.value || r.value)
|
||||
}
|
||||
}
|
||||
|
||||
class PlusStatement(
|
||||
override val pos: Pos,
|
||||
val left: Statement, val right: Statement
|
||||
) : Statement() {
|
||||
override suspend fun execute(context: Context): Obj {
|
||||
// todo: implement also classes with 'plus' operator
|
||||
val l = left.execute(context)
|
||||
|
||||
if (l is ObjString)
|
||||
return ObjString(l.toString() + right.execute(context).asStr)
|
||||
|
||||
if (l !is Numeric)
|
||||
raise("left operand is not number: $l")
|
||||
|
||||
val r = right.execute(context)
|
||||
if (r !is Numeric)
|
||||
raise("right operand is not boolean: $r")
|
||||
|
||||
return if (l is ObjInt && r is ObjInt)
|
||||
ObjInt(l.longValue + r.longValue)
|
||||
else
|
||||
ObjReal(l.doubleValue + r.doubleValue)
|
||||
}
|
||||
}
|
||||
|
||||
class MinusStatement(
|
||||
override val pos: Pos,
|
||||
val left: Statement, val right: Statement
|
||||
) : Statement() {
|
||||
override suspend fun execute(context: Context): Obj {
|
||||
// todo: implement also classes with 'minus' operator
|
||||
val l = left.execute(context)
|
||||
if (l !is Numeric)
|
||||
raise("left operand is not number: $l")
|
||||
|
||||
val r = right.execute(context)
|
||||
if (r !is Numeric)
|
||||
raise("right operand is not number: $r")
|
||||
|
||||
return if (l is ObjInt && r is ObjInt)
|
||||
ObjInt(l.longValue - r.longValue)
|
||||
else
|
||||
ObjReal(l.doubleValue - r.doubleValue)
|
||||
}
|
||||
}
|
||||
|
||||
class MulStatement(
|
||||
override val pos: Pos,
|
||||
val left: Statement, val right: Statement
|
||||
) : Statement() {
|
||||
override suspend fun execute(context: Context): Obj {
|
||||
val l = left.execute(context)
|
||||
if (l !is Numeric)
|
||||
raise("left operand is not number: $l")
|
||||
|
||||
val r = right.execute(context)
|
||||
if (r !is Numeric)
|
||||
raise("right operand is not number: $r")
|
||||
|
||||
return if (l is ObjInt && r is ObjInt)
|
||||
ObjInt(l.longValue * r.longValue)
|
||||
else
|
||||
ObjReal(l.doubleValue * r.doubleValue)
|
||||
}
|
||||
}
|
||||
|
||||
class DivStatement(
|
||||
override val pos: Pos,
|
||||
val left: Statement, val right: Statement
|
||||
) : Statement() {
|
||||
override suspend fun execute(context: Context): Obj {
|
||||
val l = left.execute(context)
|
||||
if (l !is Numeric)
|
||||
raise("left operand is not number: $l")
|
||||
|
||||
val r = right.execute(context)
|
||||
if (r !is Numeric)
|
||||
raise("right operand is not number: $r")
|
||||
|
||||
return if (l is ObjInt && r is ObjInt)
|
||||
ObjInt(l.longValue / r.longValue)
|
||||
else
|
||||
ObjReal(l.doubleValue / r.doubleValue)
|
||||
}
|
||||
}
|
||||
|
||||
class ModStatement(
|
||||
override val pos: Pos,
|
||||
val left: Statement, val right: Statement
|
||||
) : Statement() {
|
||||
override suspend fun execute(context: Context): Obj {
|
||||
val l = left.execute(context)
|
||||
if (l !is Numeric)
|
||||
raise("left operand is not number: $l")
|
||||
|
||||
val r = right.execute(context)
|
||||
if (r !is Numeric)
|
||||
raise("right operand is not number: $r")
|
||||
|
||||
return if (l is ObjInt && r is ObjInt)
|
||||
ObjInt(l.longValue % r.longValue)
|
||||
else
|
||||
ObjReal(l.doubleValue % r.doubleValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class AssignStatement(override val pos: Pos, val name: String, val value: Statement) : Statement() {
|
||||
override suspend fun execute(context: Context): Obj {
|
||||
val variable = context[name] ?: raise("can't assign: variable does not exist: $name")
|
||||
if (!variable.isMutable)
|
||||
throw ScriptError(pos, "can't reassign val $name")
|
||||
variable.value = value.execute(context)
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -223,8 +223,29 @@ class ScriptTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun arithmeticParenthesisTest() = runTest {
|
||||
fun arithmetics() = runTest {
|
||||
// integer
|
||||
assertEquals(17, eval("2 + 3 * 5").toInt())
|
||||
assertEquals(4, eval("5-1").toInt())
|
||||
assertEquals(2, eval("8/4").toInt())
|
||||
assertEquals(2, eval("8 % 3").toInt())
|
||||
|
||||
// int-real
|
||||
assertEquals(9.5, eval("2 + 3 * 2.5").toDouble())
|
||||
assertEquals(4.5, eval("5 - 0.5").toDouble())
|
||||
assertEquals(2.5, eval("5 / 2.0").toDouble())
|
||||
assertEquals(2.5, eval("5.0 / 2.0").toDouble())
|
||||
|
||||
// real
|
||||
assertEquals(7.5, eval("2.5 + 5.0").toDouble())
|
||||
assertEquals(4.5, eval("5.0 - 0.5").toDouble())
|
||||
assertEquals(12.5, eval("5.0 * 2.5").toDouble())
|
||||
assertEquals(2.5, eval("5.0 / 2.0").toDouble())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun arithmeticParenthesisTest() = runTest {
|
||||
assertEquals(17, eval("2.0 + 3 * 5").toInt())
|
||||
assertEquals(17, eval("2 + (3 * 5)").toInt())
|
||||
assertEquals(25, eval("(2 + 3) * 5").toInt())
|
||||
assertEquals(24, eval("(2 + 3) * 5 -1").toInt())
|
||||
@ -252,6 +273,22 @@ class ScriptTest {
|
||||
assertTrue { eval("2 == 2 && 3 != 4").toBool() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun logicTest() = runTest {
|
||||
assertEquals(ObjBool(false), eval("true && false"))
|
||||
assertEquals(ObjBool(false), eval("false && false"))
|
||||
assertEquals(ObjBool(false), eval("false && true"))
|
||||
assertEquals(ObjBool(true), eval("true && true"))
|
||||
|
||||
assertEquals(ObjBool(true), eval("true || false"))
|
||||
assertEquals(ObjBool(false), eval("false || false"))
|
||||
assertEquals(ObjBool(true), eval("false || true"))
|
||||
assertEquals(ObjBool(true), eval("true || true"))
|
||||
|
||||
assertEquals(ObjBool(false), eval("!true"))
|
||||
assertEquals(ObjBool(true), eval("!false"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun gtLtTest() = runTest {
|
||||
assertTrue { eval("3 > 2").toBool() }
|
||||
@ -423,6 +460,7 @@ class ScriptTest {
|
||||
var t2 = 10
|
||||
while( t2 > 0 ) {
|
||||
t2 = t2 - 1
|
||||
println("t2 " + t2 + " t1 " + t1)
|
||||
if( t2 == 3 && t1 == 7) {
|
||||
break@outer "ok2:"+t2+":"+t1
|
||||
}
|
||||
@ -564,5 +602,35 @@ class ScriptTest {
|
||||
eval(src)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAssign1() = runTest {
|
||||
assertEquals(10, eval("var x = 5; x=10; x").toInt())
|
||||
val ctx = Context()
|
||||
ctx.eval("""
|
||||
var a = 1
|
||||
""".trimIndent())
|
||||
assertEquals(3, ctx.eval("a + a + 1").toInt())
|
||||
assertEquals(12, ctx.eval("a + (a = 10) + 1").toInt())
|
||||
assertEquals(10, ctx.eval("a").toInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAssign2() = runTest {
|
||||
val ctx = Context()
|
||||
ctx.eval("var x = 10")
|
||||
assertEquals(14, ctx.eval("x += 4").toInt())
|
||||
assertEquals(14, ctx.eval("x").toInt())
|
||||
assertEquals(12, ctx.eval("x -= 2").toInt())
|
||||
assertEquals(12, ctx.eval("x").toInt())
|
||||
|
||||
assertEquals(24, ctx.eval("x *= 2").toInt())
|
||||
assertEquals(24, ctx.eval("x").toInt())
|
||||
|
||||
assertEquals(12, ctx.eval("x /= 2").toInt())
|
||||
assertEquals(12, ctx.eval("x").toInt())
|
||||
|
||||
assertEquals(2, ctx.eval("x %= 5").toInt())
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user