refactored operators for OO overloading (and faster), assignment is not expression and += *= /= -= %= added

This commit is contained in:
Sergey Chernov 2025-05-28 19:09:41 +04:00
parent 72f637e7de
commit 9a9f712cec
10 changed files with 454 additions and 318 deletions

View File

@ -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
}
// 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

View File

@ -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) {
// this _is_ assignment statement
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()
// 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"
// )
// )
// }
// not assignment, maybe keyword statement:
// get back the token which is not '=':
// 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))
)
}
}

View File

@ -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)
}

View File

@ -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 =

View File

@ -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")
}

View File

@ -3,7 +3,7 @@ package net.sergeych.ling
val digitsSet = ('0'..'9').toSet()
val digits = { d: Char -> d in digitsSet }
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")
val idFirstChars = { d: Char -> d.isLetter() || d == '_' }
@ -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 == '/') {
'*' -> {
if (currentChar == '=') {
advance()
Token("*=", from, Token.Type.STARASSIGN)
} else
Token("*", from, Token.Type.STAR)
}
'/' -> when (currentChar) {
'/' -> {
advance()
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)
'<' -> {
if(currentChar == '=') {
if (currentChar == '=') {
advance()
Token("<=", from, Token.Type.LTE)
}
else
} else
Token("<", from, Token.Type.LT)
}
'>' -> {
if( currentChar == '=') {
if (currentChar == '=') {
advance()
Token(">=", from, Token.Type.GTE)
}
else
} else
Token(">", from, Token.Type.GT)
}
'!' -> {
if( currentChar == '=') {
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)
if (label.isNotEmpty()) Token(label, from, Token.Type.ATLABEL)
else raise("unexpected @ character")
}
'\n' -> Token("\n", from, Token.Type.NEWLINE)
':' -> {
if( currentChar == ':') {
if (currentChar == ':') {
advance()
Token("::", from, Token.Type.COLONCOLON)
}
else
} else
Token(":", from, Token.Type.COLON)
}
@ -154,20 +176,17 @@ private class Parser(fromPos: Pos) {
// statement@some: ID 'statement', LABEL 'some'!
if (ch.isLetter() || ch == '_') {
val text = ch + loadChars(idNextChars)
if( currentChar == '@') {
if (currentChar == '@') {
advance()
if( currentChar.isLetter()) {
if (currentChar.isLetter()) {
// 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")
}
}
@ -209,7 +228,7 @@ private class Parser(fromPos: Pos) {
// could be integer, also hex:
if (currentChar == 'x' && p1 == "0") {
advance()
Token(loadChars({ it in hexDigits}), start, Token.Type.HEX).also {
Token(loadChars({ it in hexDigits }), start, Token.Type.HEX).also {
if (currentChar.isLetter())
raise("invalid hex literal")
}
@ -261,7 +280,7 @@ private class Parser(fromPos: Pos) {
*
* @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 result = StringBuilder()
while (!pos.end && pos.line == startLine) {
@ -306,7 +325,7 @@ private class Parser(fromPos: Pos) {
private fun skipws(): Char? {
while (!pos.end) {
val ch = pos.currentChar
if( ch == '\n') break
if (ch == '\n') break
if (ch.isWhitespace())
advance()
else

View File

@ -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()))

View File

@ -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,

View File

@ -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
}
}

View File

@ -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())
}
}