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]. 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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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