refactored operators for OO overloading (and faster), assignment is not expression and += *= /= -= %= added
This commit is contained in:
parent
72f637e7de
commit
9a9f712cec
114
docs/tutorial.md
114
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].
|
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.
|
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:
|
Everything is an expression in Ling. Even an empty block:
|
||||||
|
|
||||||
{
|
// empty block
|
||||||
// empty block
|
|
||||||
}
|
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
Block returns it last expression as "return value":
|
any block also returns it's last expression:
|
||||||
|
|
||||||
{
|
if( true ) {
|
||||||
2 + 2
|
2 + 2
|
||||||
3 + 3
|
3 + 3
|
||||||
}
|
}
|
||||||
>>> 6
|
>>> 6
|
||||||
|
|
||||||
Same is without block:
|
|
||||||
|
|
||||||
3 + 3
|
|
||||||
>>> 6
|
|
||||||
|
|
||||||
If you don't want block to return anything, use `void`:
|
If you don't want block to return anything, use `void`:
|
||||||
|
|
||||||
{
|
fn voidFunction() {
|
||||||
3 + 4
|
3 + 4 // this will be ignored
|
||||||
void
|
void
|
||||||
}
|
}
|
||||||
|
voidFunction()
|
||||||
>>> void
|
>>> 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`):
|
Every construction is an expression that returns something (or `void`):
|
||||||
|
|
||||||
val x = 111 // or autotest will fail!
|
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 `;`:
|
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
|
from = 0; to = 100
|
||||||
>>> void
|
>>> 100
|
||||||
|
|
||||||
Notice: returned value is `void` as assignment operator does not return its value. We might decide to change it.
|
|
||||||
|
|
||||||
|
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.
|
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`
|
Assignemnt is an expression that changes its lvalue and return assigned value:
|
||||||
- expression could be a `{ block }`
|
|
||||||
|
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
|
## Expression details
|
||||||
|
|
||||||
@ -202,7 +233,7 @@ Regular pre-condition while loop, as expression, loop returns it's last line res
|
|||||||
|
|
||||||
var count = 0
|
var count = 0
|
||||||
while( count < 5 ) {
|
while( count < 5 ) {
|
||||||
count = count + 1
|
count++
|
||||||
count * 10
|
count * 10
|
||||||
}
|
}
|
||||||
>>> 50
|
>>> 50
|
||||||
@ -212,18 +243,17 @@ We can break as usual:
|
|||||||
var count = 0
|
var count = 0
|
||||||
while( count < 5 ) {
|
while( count < 5 ) {
|
||||||
if( count < 5 ) break
|
if( count < 5 ) break
|
||||||
count = count + 1
|
count = ++count * 10
|
||||||
count * 10
|
|
||||||
}
|
}
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
Why `void`? Because `break` drops out without the chute, not providing anything to return. Indeed, we should provide exit value in the case:
|
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
|
var count = 0
|
||||||
while( count < 5 ) {
|
while( count < 50 ) {
|
||||||
if( count > 3 ) break "too much"
|
if( count > 3 ) break "too much"
|
||||||
count = count + 1
|
count = ++count * 10
|
||||||
count * 10
|
"wrong "+count
|
||||||
}
|
}
|
||||||
>>> too much
|
>>> 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.
|
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
|
# Comments
|
||||||
|
|
||||||
// single line comment
|
// single line comment
|
||||||
|
@ -29,20 +29,20 @@ class Compiler(
|
|||||||
return when (t.type) {
|
return when (t.type) {
|
||||||
Token.Type.ID -> {
|
Token.Type.ID -> {
|
||||||
// could be keyword, assignment or just the expression
|
// could be keyword, assignment or just the expression
|
||||||
val next = tokens.next()
|
// val next = tokens.next()
|
||||||
if (next.type == Token.Type.ASSIGN) {
|
// if (next.type == Token.Type.ASSIGN) {
|
||||||
// this _is_ assignment statement
|
// this _is_ assignment statement
|
||||||
return AssignStatement(
|
// return AssignStatement(
|
||||||
t.pos, t.value,
|
// t.pos, t.value,
|
||||||
parseStatement(tokens) ?: throw ScriptError(
|
// parseStatement(tokens) ?: throw ScriptError(
|
||||||
t.pos,
|
// t.pos,
|
||||||
"Expecting expression for assignment operator"
|
// "Expecting expression for assignment operator"
|
||||||
)
|
// )
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
// not assignment, maybe keyword statement:
|
// not assignment, maybe keyword statement:
|
||||||
// get back the token which is not '=':
|
// get back the token which is not '=':
|
||||||
tokens.previous()
|
// tokens.previous()
|
||||||
// try keyword statement
|
// try keyword statement
|
||||||
parseKeywordStatement(t, tokens)
|
parseKeywordStatement(t, tokens)
|
||||||
?: run {
|
?: 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)
|
if (level == lastLevel)
|
||||||
return parseTerm3(tokens)
|
return parseTerm3(tokens)
|
||||||
var lvalue = parseExpression(tokens, level + 1)
|
var lvalue = parseExpressionLevel(tokens, level + 1)
|
||||||
if (lvalue == null) return null
|
if (lvalue == null) return null
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
@ -99,7 +104,7 @@ class Compiler(
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
val rvalue = parseExpression(tokens, level + 1)
|
val rvalue = parseExpressionLevel(tokens, level + 1)
|
||||||
?: throw ScriptError(opToken.pos, "Expecting expression")
|
?: throw ScriptError(opToken.pos, "Expecting expression")
|
||||||
|
|
||||||
lvalue = op.generate(opToken.pos, lvalue!!, rvalue)
|
lvalue = op.generate(opToken.pos, lvalue!!, rvalue)
|
||||||
@ -167,7 +172,7 @@ class Compiler(
|
|||||||
* expr-=<expr>, expr*=<expr>, expr/=<expr>
|
* expr-=<expr>, expr*=<expr>, expr/=<expr>
|
||||||
* read expr: <expr>
|
* read expr: <expr>
|
||||||
*/
|
*/
|
||||||
private fun parseTerm3(cc: CompilerContext): Statement? {
|
private fun parseTerm3(cc: CompilerContext): Accessor? {
|
||||||
var operand: Accessor? = null
|
var operand: Accessor? = null
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
@ -176,7 +181,13 @@ class Compiler(
|
|||||||
when (t.type) {
|
when (t.type) {
|
||||||
Token.Type.NEWLINE, Token.Type.SEMICOLON, Token.Type.EOF -> {
|
Token.Type.NEWLINE, Token.Type.SEMICOLON, Token.Type.EOF -> {
|
||||||
cc.previous()
|
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 -> {
|
Token.Type.DOT -> {
|
||||||
@ -233,11 +244,6 @@ class Compiler(
|
|||||||
Token.Type.ID -> {
|
Token.Type.ID -> {
|
||||||
// there could be terminal operators or keywords:// variable to read or like
|
// there could be terminal operators or keywords:// variable to read or like
|
||||||
when (t.value) {
|
when (t.value) {
|
||||||
"else" -> {
|
|
||||||
cc.previous()
|
|
||||||
return operand?.let { op -> statement(startPos) { op.getter(it) } }
|
|
||||||
}
|
|
||||||
|
|
||||||
"if", "when", "do", "while", "return" -> {
|
"if", "when", "do", "while", "return" -> {
|
||||||
if (operand != null) throw ScriptError(t.pos, "unexpected keyword")
|
if (operand != null) throw ScriptError(t.pos, "unexpected keyword")
|
||||||
cc.previous()
|
cc.previous()
|
||||||
@ -245,9 +251,9 @@ class Compiler(
|
|||||||
operand = Accessor { s.execute(it) }
|
operand = Accessor { s.execute(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
"break", "continue" -> {
|
"else", "break", "continue" -> {
|
||||||
cc.previous()
|
cc.previous()
|
||||||
return operand?.let { op -> statement(startPos) { op.getter(it) } }
|
return operand
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,9 +313,7 @@ class Compiler(
|
|||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
cc.previous()
|
cc.previous()
|
||||||
operand?.let { op ->
|
operand?.let { return it }
|
||||||
return statement(startPos) { op.getter(it) }
|
|
||||||
}
|
|
||||||
operand = parseAccessor(cc) ?: throw ScriptError(t.pos, "Expecting expression")
|
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(
|
data class Operator(
|
||||||
val tokenType: Token.Type,
|
val tokenType: Token.Type,
|
||||||
val priority: Int, val arity: Int,
|
val priority: Int, val arity: Int=2,
|
||||||
val generate: (Pos, Statement, Statement) -> Statement
|
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 {
|
companion object {
|
||||||
|
|
||||||
|
private var lastPrty = 0
|
||||||
val allOps = listOf(
|
val allOps = listOf(
|
||||||
Operator(Token.Type.OR, 0, 2) { pos, a, b -> LogicalOrStatement(pos, a, b) },
|
// assignments
|
||||||
Operator(Token.Type.AND, 1, 2) { pos, a, b -> LogicalAndStatement(pos, a, b) },
|
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 or 2
|
||||||
// bitwise and 3
|
// bitwise and 3
|
||||||
// equality/ne 4
|
// equality/ne 4
|
||||||
LogicalOp(Token.Type.EQ, 4) { c, a, b -> a.compareTo(c, b) == 0 },
|
Operator.simple(Token.Type.EQ, ++lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) == 0) },
|
||||||
LogicalOp(Token.Type.NEQ, 4) { c, a, b -> a.compareTo(c, b) != 0 },
|
Operator.simple(Token.Type.NEQ, lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) != 0) },
|
||||||
// relational <=,... 5
|
// relational <=,... 5
|
||||||
LogicalOp(Token.Type.LTE, 5) { c, a, b -> a.compareTo(c, b) <= 0 },
|
Operator.simple(Token.Type.LTE, ++lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) <= 0) },
|
||||||
LogicalOp(Token.Type.LT, 5) { c, a, b -> a.compareTo(c, b) < 0 },
|
Operator.simple(Token.Type.LT, lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) < 0) },
|
||||||
LogicalOp(Token.Type.GTE, 5) { c, a, b -> a.compareTo(c, b) >= 0 },
|
Operator.simple(Token.Type.GTE, lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) >= 0) },
|
||||||
LogicalOp(Token.Type.GT, 5) { c, a, b -> a.compareTo(c, b) > 0 },
|
Operator.simple(Token.Type.GT, lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) > 0) },
|
||||||
// shuttle <=> 6
|
// shuttle <=> 6
|
||||||
// bitshhifts 7
|
// bit shifts 7
|
||||||
Operator(Token.Type.PLUS, 8, 2) { pos, a, b ->
|
Operator.simple(Token.Type.PLUS, ++lastPrty) { ctx, a, b -> a.plus(ctx,b) },
|
||||||
PlusStatement(pos, a, b)
|
Operator.simple(Token.Type.MINUS, lastPrty) { ctx, a, b -> a.minus(ctx,b) },
|
||||||
},
|
|
||||||
Operator(Token.Type.MINUS, 8, 2) { pos, a, b ->
|
Operator.simple(Token.Type.STAR, ++lastPrty) { ctx, a, b -> a.mul(ctx,b) },
|
||||||
MinusStatement(pos, a, 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) },
|
||||||
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 lastLevel = lastPrty + 1
|
||||||
|
|
||||||
val byLevel: List<Map<Token.Type, 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.tokenType to it }.toMap()
|
.map { it.tokenType to it }.toMap()
|
||||||
@ -771,17 +840,3 @@ 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: 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)
|
if (type != it.type) throw ScriptError(it.pos, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
fun syntaxError(at: Pos, message: String = "Syntax error"): Nothing {
|
fun syntaxError(at: Pos, message: String = "Syntax error"): Nothing {
|
||||||
throw ScriptError(at, message)
|
throw ScriptError(at, message)
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ class Context(
|
|||||||
|
|
||||||
fun raiseNotImplemented(): Nothing = raiseError("operation not implemented")
|
fun raiseNotImplemented(): Nothing = raiseError("operation not implemented")
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
fun raiseNPE(): Nothing = raiseError(ObjNullPointerError(this))
|
fun raiseNPE(): Nothing = raiseError(ObjNullPointerError(this))
|
||||||
|
|
||||||
fun raiseError(message: String): Nothing {
|
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)
|
fun addConst(name: String,value: Obj) = addItem(name, false, value)
|
||||||
|
|
||||||
suspend fun eval(code: String): Obj =
|
suspend fun eval(code: String): Obj =
|
||||||
|
@ -7,7 +7,7 @@ import kotlinx.serialization.Serializable
|
|||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
import kotlin.math.roundToLong
|
import kotlin.math.roundToLong
|
||||||
|
|
||||||
typealias InstanceMethod = (Context, Obj) -> Obj
|
//typealias InstanceMethod = (Context, Obj) -> Obj
|
||||||
|
|
||||||
data class WithAccess<T>(var value: T, val isMutable: Boolean)
|
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 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()
|
context.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun assign(context: Context, other: Obj): Obj {
|
open suspend fun minus(context: Context, other: Obj): Obj {
|
||||||
context.raiseNotImplemented()
|
context.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun plusAssign(context: Context, other: Obj): Obj {
|
open suspend fun mul(context: Context, other: Obj): Obj {
|
||||||
assign(context, plus(context, other))
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun getAndIncrement(context: Context): Obj {
|
|
||||||
context.raiseNotImplemented()
|
context.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun incrementAndGet(context: Context): Obj {
|
open suspend fun div(context: Context, other: Obj): Obj {
|
||||||
context.raiseNotImplemented()
|
context.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun decrementAndGet(context: Context): Obj {
|
open suspend fun mod(context: Context, other: Obj): Obj {
|
||||||
context.raiseNotImplemented()
|
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()
|
context.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,9 +143,9 @@ sealed class Obj {
|
|||||||
|
|
||||||
suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() }
|
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)
|
willMutate(context)
|
||||||
members[name]?.let { if (it.isMutable) it.value = newValue }
|
members[name]?.let { if (it.isMutable) it.value = newValue }
|
||||||
?: context.raiseError("Can't reassign member: $name")
|
?: context.raiseError("Can't reassign member: $name")
|
||||||
@ -194,6 +232,10 @@ data class ObjString(val value: String) : Obj() {
|
|||||||
override val objClass: ObjClass
|
override val objClass: ObjClass
|
||||||
get() = type
|
get() = type
|
||||||
|
|
||||||
|
override suspend fun plus(context: Context, other: Obj): Obj {
|
||||||
|
return ObjString(value + other.asStr.value)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val type = ObjClass("String")
|
val type = ObjClass("String")
|
||||||
}
|
}
|
||||||
@ -239,6 +281,21 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
|||||||
|
|
||||||
override val objClass: ObjClass = type
|
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 {
|
companion object {
|
||||||
val type: ObjClass = ObjClass("Real").apply {
|
val type: ObjClass = ObjClass("Real").apply {
|
||||||
createField(
|
createField(
|
||||||
@ -258,19 +315,19 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
|
|||||||
override val toObjInt get() = this
|
override val toObjInt get() = this
|
||||||
override val toObjReal = ObjReal(doubleValue)
|
override val toObjReal = ObjReal(doubleValue)
|
||||||
|
|
||||||
override fun getAndIncrement(context: Context): Obj {
|
override suspend fun getAndIncrement(context: Context): Obj {
|
||||||
return ObjInt(value).also { value++ }
|
return ObjInt(value).also { value++ }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAndDecrement(context: Context): Obj {
|
override suspend fun getAndDecrement(context: Context): Obj {
|
||||||
return ObjInt(value).also { value-- }
|
return ObjInt(value).also { value-- }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun incrementAndGet(context: Context): Obj {
|
override suspend fun incrementAndGet(context: Context): Obj {
|
||||||
return ObjInt(++value)
|
return ObjInt(++value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun decrementAndGet(context: Context): Obj {
|
override suspend fun decrementAndGet(context: Context): Obj {
|
||||||
return ObjInt(--value)
|
return ObjInt(--value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,6 +340,33 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
|
|||||||
|
|
||||||
override val objClass: ObjClass = type
|
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 {
|
companion object {
|
||||||
val type = ObjClass("Int")
|
val type = ObjClass("Int")
|
||||||
}
|
}
|
||||||
@ -300,6 +384,12 @@ data class ObjBool(val value: Boolean) : Obj() {
|
|||||||
|
|
||||||
override val objClass: ObjClass = type
|
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 {
|
companion object {
|
||||||
val type = ObjClass("Bool")
|
val type = ObjClass("Bool")
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ package net.sergeych.ling
|
|||||||
val digitsSet = ('0'..'9').toSet()
|
val digitsSet = ('0'..'9').toSet()
|
||||||
val digits = { d: Char -> d in digitsSet }
|
val digits = { d: Char -> d in digitsSet }
|
||||||
val hexDigits = digitsSet + ('a'..'f') + ('A'..'F')
|
val hexDigits = digitsSet + ('a'..'f') + ('A'..'F')
|
||||||
val idNextChars = { d: Char -> d.isLetter() || d == '_' || d.isDigit()}
|
val idNextChars = { d: Char -> d.isLetter() || d == '_' || d.isDigit() }
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val idFirstChars = { d: Char -> d.isLetter() || d == '_' }
|
val idFirstChars = { d: Char -> d.isLetter() || d == '_' }
|
||||||
@ -56,62 +56,82 @@ private class Parser(fromPos: Pos) {
|
|||||||
advance()
|
advance()
|
||||||
Token("+", from, Token.Type.PLUS2)
|
Token("+", from, Token.Type.PLUS2)
|
||||||
}
|
}
|
||||||
|
|
||||||
'=' -> {
|
'=' -> {
|
||||||
advance()
|
advance()
|
||||||
Token("+", from, Token.Type.PLUSASSIGN)
|
Token("+", from, Token.Type.PLUSASSIGN)
|
||||||
}
|
}
|
||||||
|
|
||||||
else ->
|
else ->
|
||||||
Token("+", from, Token.Type.PLUS)
|
Token("+", from, Token.Type.PLUS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
'-' -> {
|
'-' -> {
|
||||||
when (currentChar) {
|
when (currentChar) {
|
||||||
'-' -> {
|
'-' -> {
|
||||||
advance()
|
advance()
|
||||||
Token("--", from, Token.Type.MINUS2)
|
Token("--", from, Token.Type.MINUS2)
|
||||||
}
|
}
|
||||||
|
|
||||||
'=' -> {
|
'=' -> {
|
||||||
advance()
|
advance()
|
||||||
Token("-", from, Token.Type.MINUSASSIGN)
|
Token("-", from, Token.Type.MINUSASSIGN)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> Token("-", from, Token.Type.MINUS)
|
else -> Token("-", from, Token.Type.MINUS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'*' -> Token("*", from, Token.Type.STAR)
|
|
||||||
'/' -> {
|
'*' -> {
|
||||||
if( currentChar == '/') {
|
if (currentChar == '=') {
|
||||||
|
advance()
|
||||||
|
Token("*=", from, Token.Type.STARASSIGN)
|
||||||
|
} else
|
||||||
|
Token("*", from, Token.Type.STAR)
|
||||||
|
}
|
||||||
|
|
||||||
|
'/' -> when (currentChar) {
|
||||||
|
'/' -> {
|
||||||
advance()
|
advance()
|
||||||
Token(loadToEnd().trim(), from, Token.Type.SINLGE_LINE_COMMENT)
|
Token(loadToEnd().trim(), from, Token.Type.SINLGE_LINE_COMMENT)
|
||||||
}
|
}
|
||||||
else
|
'=' -> {
|
||||||
Token("/", from, Token.Type.SLASH)
|
advance()
|
||||||
|
Token("/=", from, Token.Type.SLASHASSIGN)
|
||||||
|
}
|
||||||
|
else -> Token("/", from, Token.Type.SLASH)
|
||||||
}
|
}
|
||||||
'%' -> Token("%", from, Token.Type.PERCENT)
|
|
||||||
|
'%' -> when(currentChar) {
|
||||||
|
'=' -> { advance(); Token("%=", from, Token.Type.PERCENTASSIGN) }
|
||||||
|
else -> Token("%", from, Token.Type.PERCENT) }
|
||||||
|
|
||||||
'.' -> Token(".", from, Token.Type.DOT)
|
'.' -> Token(".", from, Token.Type.DOT)
|
||||||
'<' -> {
|
'<' -> {
|
||||||
if(currentChar == '=') {
|
if (currentChar == '=') {
|
||||||
advance()
|
advance()
|
||||||
Token("<=", from, Token.Type.LTE)
|
Token("<=", from, Token.Type.LTE)
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
Token("<", from, Token.Type.LT)
|
Token("<", from, Token.Type.LT)
|
||||||
}
|
}
|
||||||
|
|
||||||
'>' -> {
|
'>' -> {
|
||||||
if( currentChar == '=') {
|
if (currentChar == '=') {
|
||||||
advance()
|
advance()
|
||||||
Token(">=", from, Token.Type.GTE)
|
Token(">=", from, Token.Type.GTE)
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
Token(">", from, Token.Type.GT)
|
Token(">", from, Token.Type.GT)
|
||||||
}
|
}
|
||||||
|
|
||||||
'!' -> {
|
'!' -> {
|
||||||
if( currentChar == '=') {
|
if (currentChar == '=') {
|
||||||
advance()
|
advance()
|
||||||
Token("!=", from, Token.Type.NEQ)
|
Token("!=", from, Token.Type.NEQ)
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
Token("!", from, Token.Type.NOT)
|
Token("!", from, Token.Type.NOT)
|
||||||
}
|
}
|
||||||
|
|
||||||
'|' -> {
|
'|' -> {
|
||||||
if (currentChar == '|') {
|
if (currentChar == '|') {
|
||||||
advance()
|
advance()
|
||||||
@ -119,6 +139,7 @@ private class Parser(fromPos: Pos) {
|
|||||||
} else
|
} else
|
||||||
Token("|", from, Token.Type.BITOR)
|
Token("|", from, Token.Type.BITOR)
|
||||||
}
|
}
|
||||||
|
|
||||||
'&' -> {
|
'&' -> {
|
||||||
if (currentChar == '&') {
|
if (currentChar == '&') {
|
||||||
advance()
|
advance()
|
||||||
@ -126,19 +147,20 @@ private class Parser(fromPos: Pos) {
|
|||||||
} else
|
} else
|
||||||
Token("&", from, Token.Type.BITAND)
|
Token("&", from, Token.Type.BITAND)
|
||||||
}
|
}
|
||||||
|
|
||||||
'@' -> {
|
'@' -> {
|
||||||
val label = loadChars(idNextChars)
|
val label = loadChars(idNextChars)
|
||||||
if( label.isNotEmpty()) Token(label, from, Token.Type.ATLABEL)
|
if (label.isNotEmpty()) Token(label, from, Token.Type.ATLABEL)
|
||||||
else raise("unexpected @ character")
|
else raise("unexpected @ character")
|
||||||
}
|
}
|
||||||
|
|
||||||
'\n' -> Token("\n", from, Token.Type.NEWLINE)
|
'\n' -> Token("\n", from, Token.Type.NEWLINE)
|
||||||
|
|
||||||
':' -> {
|
':' -> {
|
||||||
if( currentChar == ':') {
|
if (currentChar == ':') {
|
||||||
advance()
|
advance()
|
||||||
Token("::", from, Token.Type.COLONCOLON)
|
Token("::", from, Token.Type.COLONCOLON)
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
Token(":", from, Token.Type.COLON)
|
Token(":", from, Token.Type.COLON)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,20 +176,17 @@ private class Parser(fromPos: Pos) {
|
|||||||
// statement@some: ID 'statement', LABEL 'some'!
|
// statement@some: ID 'statement', LABEL 'some'!
|
||||||
if (ch.isLetter() || ch == '_') {
|
if (ch.isLetter() || ch == '_') {
|
||||||
val text = ch + loadChars(idNextChars)
|
val text = ch + loadChars(idNextChars)
|
||||||
if( currentChar == '@') {
|
if (currentChar == '@') {
|
||||||
advance()
|
advance()
|
||||||
if( currentChar.isLetter()) {
|
if (currentChar.isLetter()) {
|
||||||
// break@label or like
|
// break@label or like
|
||||||
pos.back()
|
pos.back()
|
||||||
Token(text, from, Token.Type.ID)
|
Token(text, from, Token.Type.ID)
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
Token(text, from, Token.Type.LABEL)
|
Token(text, from, Token.Type.LABEL)
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
Token(text, from, Token.Type.ID)
|
Token(text, from, Token.Type.ID)
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
raise("can't parse token")
|
raise("can't parse token")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -209,7 +228,7 @@ private class Parser(fromPos: Pos) {
|
|||||||
// could be integer, also hex:
|
// could be integer, also hex:
|
||||||
if (currentChar == 'x' && p1 == "0") {
|
if (currentChar == 'x' && p1 == "0") {
|
||||||
advance()
|
advance()
|
||||||
Token(loadChars({ it in hexDigits}), start, Token.Type.HEX).also {
|
Token(loadChars({ it in hexDigits }), start, Token.Type.HEX).also {
|
||||||
if (currentChar.isLetter())
|
if (currentChar.isLetter())
|
||||||
raise("invalid hex literal")
|
raise("invalid hex literal")
|
||||||
}
|
}
|
||||||
@ -261,7 +280,7 @@ private class Parser(fromPos: Pos) {
|
|||||||
*
|
*
|
||||||
* @return the string of valid characters, could be empty
|
* @return the string of valid characters, could be empty
|
||||||
*/
|
*/
|
||||||
private fun loadChars(isValidChar: (Char)->Boolean): String {
|
private fun loadChars(isValidChar: (Char) -> Boolean): String {
|
||||||
val startLine = pos.line
|
val startLine = pos.line
|
||||||
val result = StringBuilder()
|
val result = StringBuilder()
|
||||||
while (!pos.end && pos.line == startLine) {
|
while (!pos.end && pos.line == startLine) {
|
||||||
@ -306,7 +325,7 @@ private class Parser(fromPos: Pos) {
|
|||||||
private fun skipws(): Char? {
|
private fun skipws(): Char? {
|
||||||
while (!pos.end) {
|
while (!pos.end) {
|
||||||
val ch = pos.currentChar
|
val ch = pos.currentChar
|
||||||
if( ch == '\n') break
|
if (ch == '\n') break
|
||||||
if (ch.isWhitespace())
|
if (ch.isWhitespace())
|
||||||
advance()
|
advance()
|
||||||
else
|
else
|
||||||
|
@ -31,17 +31,17 @@ class Script(
|
|||||||
addFn("floor") {
|
addFn("floor") {
|
||||||
val x = args.firstAndOnly()
|
val x = args.firstAndOnly()
|
||||||
(if (x is ObjInt) x
|
(if (x is ObjInt) x
|
||||||
else ObjReal(floor(x.toDouble()))) as Obj
|
else ObjReal(floor(x.toDouble())))
|
||||||
}
|
}
|
||||||
addFn("ceil") {
|
addFn("ceil") {
|
||||||
val x = args.firstAndOnly()
|
val x = args.firstAndOnly()
|
||||||
(if (x is ObjInt) x
|
(if (x is ObjInt) x
|
||||||
else ObjReal(ceil(x.toDouble()))) as Obj
|
else ObjReal(ceil(x.toDouble())))
|
||||||
}
|
}
|
||||||
addFn("round") {
|
addFn("round") {
|
||||||
val x = args.firstAndOnly()
|
val x = args.firstAndOnly()
|
||||||
(if (x is ObjInt) x
|
(if (x is ObjInt) x
|
||||||
else ObjReal(round(x.toDouble()))) as Obj
|
else ObjReal(round(x.toDouble())))
|
||||||
}
|
}
|
||||||
addFn("sin") {
|
addFn("sin") {
|
||||||
ObjReal(sin(args.firstAndOnly().toDouble()))
|
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,
|
ASSIGN, PLUSASSIGN, MINUSASSIGN, STARASSIGN, SLASHASSIGN, PERCENTASSIGN,
|
||||||
PLUS2, MINUS2,
|
PLUS2, MINUS2,
|
||||||
EQ, NEQ, LT, LTE, GT, GTE,
|
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,
|
SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
|
||||||
LABEL,ATLABEL, // label@ at@label
|
LABEL,ATLABEL, // label@ at@label
|
||||||
NEWLINE,
|
NEWLINE,
|
||||||
|
@ -45,153 +45,4 @@ fun statement(pos: Pos, isStaticConst: Boolean = false, isConst: Boolean = false
|
|||||||
override suspend fun execute(context: Context): Obj = f(context)
|
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
|
@Test
|
||||||
fun arithmeticParenthesisTest() = runTest {
|
fun arithmetics() = runTest {
|
||||||
|
// integer
|
||||||
assertEquals(17, eval("2 + 3 * 5").toInt())
|
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(17, eval("2 + (3 * 5)").toInt())
|
||||||
assertEquals(25, eval("(2 + 3) * 5").toInt())
|
assertEquals(25, eval("(2 + 3) * 5").toInt())
|
||||||
assertEquals(24, eval("(2 + 3) * 5 -1").toInt())
|
assertEquals(24, eval("(2 + 3) * 5 -1").toInt())
|
||||||
@ -252,6 +273,22 @@ class ScriptTest {
|
|||||||
assertTrue { eval("2 == 2 && 3 != 4").toBool() }
|
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
|
@Test
|
||||||
fun gtLtTest() = runTest {
|
fun gtLtTest() = runTest {
|
||||||
assertTrue { eval("3 > 2").toBool() }
|
assertTrue { eval("3 > 2").toBool() }
|
||||||
@ -423,6 +460,7 @@ class ScriptTest {
|
|||||||
var t2 = 10
|
var t2 = 10
|
||||||
while( t2 > 0 ) {
|
while( t2 > 0 ) {
|
||||||
t2 = t2 - 1
|
t2 = t2 - 1
|
||||||
|
println("t2 " + t2 + " t1 " + t1)
|
||||||
if( t2 == 3 && t1 == 7) {
|
if( t2 == 3 && t1 == 7) {
|
||||||
break@outer "ok2:"+t2+":"+t1
|
break@outer "ok2:"+t2+":"+t1
|
||||||
}
|
}
|
||||||
@ -564,5 +602,35 @@ class ScriptTest {
|
|||||||
eval(src)
|
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