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].
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
@ -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))
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -16,6 +16,7 @@ internal class CompilerContext(val tokens: List<Token>) : ListIterator<Token> by
 | 
			
		||||
            if (type != it.type) throw ScriptError(it.pos, message)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    @Suppress("unused")
 | 
			
		||||
    fun syntaxError(at: Pos, message: String = "Syntax error"): Nothing {
 | 
			
		||||
        throw ScriptError(at, message)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -14,6 +14,7 @@ class Context(
 | 
			
		||||
 | 
			
		||||
    fun raiseNotImplemented(): Nothing = raiseError("operation not implemented")
 | 
			
		||||
 | 
			
		||||
    @Suppress("unused")
 | 
			
		||||
    fun raiseNPE(): Nothing = raiseError(ObjNullPointerError(this))
 | 
			
		||||
 | 
			
		||||
    fun raiseError(message: String): Nothing {
 | 
			
		||||
@ -73,17 +74,6 @@ class Context(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    inline fun <reified T> addConstWithAliases(value: T, vararg names: String) {
 | 
			
		||||
        val obj = Obj.from(value)
 | 
			
		||||
        for (name in names) {
 | 
			
		||||
            addItem(
 | 
			
		||||
                name,
 | 
			
		||||
                false,
 | 
			
		||||
                obj
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun addConst(name: String,value: Obj) = addItem(name, false, value)
 | 
			
		||||
 | 
			
		||||
    suspend fun eval(code: String): Obj =
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,7 @@ import kotlinx.serialization.Serializable
 | 
			
		||||
import kotlin.math.floor
 | 
			
		||||
import kotlin.math.roundToLong
 | 
			
		||||
 | 
			
		||||
typealias InstanceMethod = (Context, Obj) -> Obj
 | 
			
		||||
//typealias InstanceMethod = (Context, Obj) -> Obj
 | 
			
		||||
 | 
			
		||||
data class WithAccess<T>(var value: T, val isMutable: Boolean)
 | 
			
		||||
 | 
			
		||||
@ -70,32 +70,70 @@ sealed class Obj {
 | 
			
		||||
     */
 | 
			
		||||
    open val objClass: ObjClass by lazy { ObjClass("Obj") }
 | 
			
		||||
 | 
			
		||||
    open fun plus(context: Context, other: Obj): Obj {
 | 
			
		||||
    open suspend fun plus(context: Context, other: Obj): Obj {
 | 
			
		||||
        context.raiseNotImplemented()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open fun assign(context: Context, other: Obj): Obj {
 | 
			
		||||
    open suspend fun minus(context: Context, other: Obj): Obj {
 | 
			
		||||
        context.raiseNotImplemented()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open fun plusAssign(context: Context, other: Obj): Obj {
 | 
			
		||||
        assign(context, plus(context, other))
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open fun getAndIncrement(context: Context): Obj {
 | 
			
		||||
    open suspend fun mul(context: Context, other: Obj): Obj {
 | 
			
		||||
        context.raiseNotImplemented()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open fun incrementAndGet(context: Context): Obj {
 | 
			
		||||
    open suspend fun div(context: Context, other: Obj): Obj {
 | 
			
		||||
        context.raiseNotImplemented()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open fun decrementAndGet(context: Context): Obj {
 | 
			
		||||
    open suspend fun mod(context: Context, other: Obj): Obj {
 | 
			
		||||
        context.raiseNotImplemented()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open fun getAndDecrement(context: Context): Obj {
 | 
			
		||||
    open suspend fun logicalNot(context: Context): Obj {
 | 
			
		||||
        context.raiseNotImplemented()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open suspend fun logicalAnd(context: Context, other: Obj): Obj {
 | 
			
		||||
        context.raiseNotImplemented()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open suspend fun logicalOr(context: Context, other: Obj): Obj {
 | 
			
		||||
        context.raiseNotImplemented()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open suspend fun assign(context: Context, other: Obj): Obj {
 | 
			
		||||
        context.raiseNotImplemented()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * a += b
 | 
			
		||||
     * if( the operation is not defined, it returns null and the compiler would try
 | 
			
		||||
     * to generate it as 'this = this + other', reassigning its variable
 | 
			
		||||
     */
 | 
			
		||||
    open suspend fun plusAssign(context: Context, other: Obj): Obj? = null
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * `-=` operations, see [plusAssign]
 | 
			
		||||
     */
 | 
			
		||||
    open suspend fun minusAssign(context: Context, other: Obj): Obj? = null
 | 
			
		||||
    open suspend fun mulAssign(context: Context, other: Obj): Obj? = null
 | 
			
		||||
    open suspend fun divAssign(context: Context, other: Obj): Obj? = null
 | 
			
		||||
    open suspend fun modAssign(context: Context, other: Obj): Obj? = null
 | 
			
		||||
 | 
			
		||||
    open suspend fun getAndIncrement(context: Context): Obj {
 | 
			
		||||
        context.raiseNotImplemented()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open suspend fun incrementAndGet(context: Context): Obj {
 | 
			
		||||
        context.raiseNotImplemented()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open suspend fun decrementAndGet(context: Context): Obj {
 | 
			
		||||
        context.raiseNotImplemented()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open suspend fun getAndDecrement(context: Context): Obj {
 | 
			
		||||
        context.raiseNotImplemented()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -105,9 +143,9 @@ sealed class Obj {
 | 
			
		||||
 | 
			
		||||
    suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() }
 | 
			
		||||
 | 
			
		||||
    suspend fun readField(context: Context, name: String): Obj = getInstanceMember(context.pos, name)
 | 
			
		||||
    fun readField(context: Context, name: String): Obj = getInstanceMember(context.pos, name)
 | 
			
		||||
 | 
			
		||||
    suspend fun writeField(context: Context, name: String, newValue: Obj) {
 | 
			
		||||
    fun writeField(context: Context, name: String, newValue: Obj) {
 | 
			
		||||
        willMutate(context)
 | 
			
		||||
        members[name]?.let { if (it.isMutable) it.value = newValue }
 | 
			
		||||
            ?: context.raiseError("Can't reassign member: $name")
 | 
			
		||||
@ -194,6 +232,10 @@ data class ObjString(val value: String) : Obj() {
 | 
			
		||||
    override val objClass: ObjClass
 | 
			
		||||
        get() = type
 | 
			
		||||
 | 
			
		||||
    override suspend fun plus(context: Context, other: Obj): Obj {
 | 
			
		||||
        return ObjString(value + other.asStr.value)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val type = ObjClass("String")
 | 
			
		||||
    }
 | 
			
		||||
@ -239,6 +281,21 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
 | 
			
		||||
 | 
			
		||||
    override val objClass: ObjClass = type
 | 
			
		||||
 | 
			
		||||
    override suspend fun plus(context: Context, other: Obj): Obj =
 | 
			
		||||
        ObjReal(this.value + other.toDouble())
 | 
			
		||||
 | 
			
		||||
    override suspend fun minus(context: Context, other: Obj): Obj =
 | 
			
		||||
        ObjReal(this.value - other.toDouble())
 | 
			
		||||
 | 
			
		||||
    override suspend fun mul(context: Context, other: Obj): Obj =
 | 
			
		||||
        ObjReal(this.value * other.toDouble())
 | 
			
		||||
 | 
			
		||||
    override suspend fun div(context: Context, other: Obj): Obj =
 | 
			
		||||
        ObjReal(this.value / other.toDouble())
 | 
			
		||||
 | 
			
		||||
    override suspend fun mod(context: Context, other: Obj): Obj =
 | 
			
		||||
        ObjReal(this.value % other.toDouble())
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val type: ObjClass = ObjClass("Real").apply {
 | 
			
		||||
            createField(
 | 
			
		||||
@ -258,19 +315,19 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
 | 
			
		||||
    override val toObjInt get() = this
 | 
			
		||||
    override val toObjReal = ObjReal(doubleValue)
 | 
			
		||||
 | 
			
		||||
    override fun getAndIncrement(context: Context): Obj {
 | 
			
		||||
    override suspend fun getAndIncrement(context: Context): Obj {
 | 
			
		||||
        return ObjInt(value).also { value++ }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getAndDecrement(context: Context): Obj {
 | 
			
		||||
    override suspend fun getAndDecrement(context: Context): Obj {
 | 
			
		||||
        return ObjInt(value).also { value-- }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun incrementAndGet(context: Context): Obj {
 | 
			
		||||
    override suspend fun incrementAndGet(context: Context): Obj {
 | 
			
		||||
        return ObjInt(++value)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun decrementAndGet(context: Context): Obj {
 | 
			
		||||
    override suspend fun decrementAndGet(context: Context): Obj {
 | 
			
		||||
        return ObjInt(--value)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -283,6 +340,33 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
 | 
			
		||||
 | 
			
		||||
    override val objClass: ObjClass = type
 | 
			
		||||
 | 
			
		||||
    override suspend fun plus(context: Context, other: Obj): Obj =
 | 
			
		||||
        if (other is ObjInt)
 | 
			
		||||
            ObjInt(this.value + other.value)
 | 
			
		||||
        else
 | 
			
		||||
            ObjReal(this.doubleValue + other.toDouble())
 | 
			
		||||
 | 
			
		||||
    override suspend fun minus(context: Context, other: Obj): Obj =
 | 
			
		||||
        if (other is ObjInt)
 | 
			
		||||
            ObjInt(this.value - other.value)
 | 
			
		||||
        else
 | 
			
		||||
            ObjReal(this.doubleValue - other.toDouble())
 | 
			
		||||
 | 
			
		||||
    override suspend fun mul(context: Context, other: Obj): Obj =
 | 
			
		||||
        if (other is ObjInt) {
 | 
			
		||||
            ObjInt(this.value * other.value)
 | 
			
		||||
        } else ObjReal(this.value * other.toDouble())
 | 
			
		||||
 | 
			
		||||
    override suspend fun div(context: Context, other: Obj): Obj =
 | 
			
		||||
        if (other is ObjInt)
 | 
			
		||||
            ObjInt(this.value / other.value)
 | 
			
		||||
        else ObjReal(this.value / other.toDouble())
 | 
			
		||||
 | 
			
		||||
    override suspend fun mod(context: Context, other: Obj): Obj =
 | 
			
		||||
        if (other is ObjInt)
 | 
			
		||||
            ObjInt(this.value % other.value)
 | 
			
		||||
        else ObjReal(this.value.toDouble() % other.toDouble())
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val type = ObjClass("Int")
 | 
			
		||||
    }
 | 
			
		||||
@ -300,6 +384,12 @@ data class ObjBool(val value: Boolean) : Obj() {
 | 
			
		||||
 | 
			
		||||
    override val objClass: ObjClass = type
 | 
			
		||||
 | 
			
		||||
    override suspend fun logicalNot(context: Context): Obj = ObjBool(!value)
 | 
			
		||||
 | 
			
		||||
    override suspend fun logicalAnd(context: Context, other: Obj): Obj = ObjBool(value && other.toBool())
 | 
			
		||||
 | 
			
		||||
    override suspend fun logicalOr(context: Context, other: Obj): Obj = ObjBool(value || other.toBool())
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val type = ObjClass("Bool")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -31,17 +31,17 @@ class Script(
 | 
			
		||||
            addFn("floor") {
 | 
			
		||||
                val x = args.firstAndOnly()
 | 
			
		||||
                (if (x is ObjInt) x
 | 
			
		||||
                else ObjReal(floor(x.toDouble()))) as Obj
 | 
			
		||||
                else ObjReal(floor(x.toDouble())))
 | 
			
		||||
            }
 | 
			
		||||
            addFn("ceil") {
 | 
			
		||||
                val x = args.firstAndOnly()
 | 
			
		||||
                (if (x is ObjInt) x
 | 
			
		||||
                else ObjReal(ceil(x.toDouble()))) as Obj
 | 
			
		||||
                else ObjReal(ceil(x.toDouble())))
 | 
			
		||||
            }
 | 
			
		||||
            addFn("round") {
 | 
			
		||||
                val x = args.firstAndOnly()
 | 
			
		||||
                (if (x is ObjInt) x
 | 
			
		||||
                else ObjReal(round(x.toDouble()))) as Obj
 | 
			
		||||
                else ObjReal(round(x.toDouble())))
 | 
			
		||||
            }
 | 
			
		||||
            addFn("sin") {
 | 
			
		||||
                ObjReal(sin(args.firstAndOnly().toDouble()))
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
 | 
			
		||||
        ASSIGN, PLUSASSIGN, MINUSASSIGN, STARASSIGN, SLASHASSIGN, PERCENTASSIGN,
 | 
			
		||||
        PLUS2, MINUS2,
 | 
			
		||||
        EQ, NEQ, LT, LTE, GT, GTE,
 | 
			
		||||
        AND, BITAND, OR, BITOR, NOT, DOT, ARROW, QUESTION, COLONCOLON,
 | 
			
		||||
        AND, BITAND, OR, BITOR, NOT, BITNOT, DOT, ARROW, QUESTION, COLONCOLON,
 | 
			
		||||
        SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
 | 
			
		||||
        LABEL,ATLABEL, // label@ at@label
 | 
			
		||||
        NEWLINE,
 | 
			
		||||
 | 
			
		||||
@ -45,153 +45,4 @@ fun statement(pos: Pos, isStaticConst: Boolean = false, isConst: Boolean = false
 | 
			
		||||
        override suspend fun execute(context: Context): Obj = f(context)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
class LogicalAndStatement(
 | 
			
		||||
    override val pos: Pos,
 | 
			
		||||
    val left: Statement, val right: Statement
 | 
			
		||||
) : Statement() {
 | 
			
		||||
    override suspend fun execute(context: Context): Obj {
 | 
			
		||||
 | 
			
		||||
        val l = left.execute(context).let {
 | 
			
		||||
            (it as? ObjBool) ?: raise("left operand is not boolean: $it")
 | 
			
		||||
        }
 | 
			
		||||
        val r = right.execute(context).let {
 | 
			
		||||
            (it as? ObjBool) ?: raise("right operand is not boolean: $it")
 | 
			
		||||
        }
 | 
			
		||||
        return ObjBool(l.value && r.value)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class LogicalOrStatement(
 | 
			
		||||
    override val pos: Pos,
 | 
			
		||||
    val left: Statement, val right: Statement
 | 
			
		||||
) : Statement() {
 | 
			
		||||
    override suspend fun execute(context: Context): Obj {
 | 
			
		||||
 | 
			
		||||
        val l = left.execute(context).let {
 | 
			
		||||
            (it as? ObjBool) ?: raise("left operand is not boolean: $it")
 | 
			
		||||
        }
 | 
			
		||||
        val r = right.execute(context).let {
 | 
			
		||||
            (it as? ObjBool) ?: raise("right operand is not boolean: $it")
 | 
			
		||||
        }
 | 
			
		||||
        return ObjBool(l.value || r.value)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class PlusStatement(
 | 
			
		||||
    override val pos: Pos,
 | 
			
		||||
    val left: Statement, val right: Statement
 | 
			
		||||
) : Statement() {
 | 
			
		||||
    override suspend fun execute(context: Context): Obj {
 | 
			
		||||
        // todo: implement also classes with 'plus' operator
 | 
			
		||||
        val l = left.execute(context)
 | 
			
		||||
 | 
			
		||||
        if (l is ObjString)
 | 
			
		||||
            return ObjString(l.toString() + right.execute(context).asStr)
 | 
			
		||||
 | 
			
		||||
        if (l !is Numeric)
 | 
			
		||||
            raise("left operand is not number: $l")
 | 
			
		||||
 | 
			
		||||
        val r = right.execute(context)
 | 
			
		||||
        if (r !is Numeric)
 | 
			
		||||
            raise("right operand is not boolean: $r")
 | 
			
		||||
 | 
			
		||||
        return if (l is ObjInt && r is ObjInt)
 | 
			
		||||
            ObjInt(l.longValue + r.longValue)
 | 
			
		||||
        else
 | 
			
		||||
            ObjReal(l.doubleValue + r.doubleValue)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class MinusStatement(
 | 
			
		||||
    override val pos: Pos,
 | 
			
		||||
    val left: Statement, val right: Statement
 | 
			
		||||
) : Statement() {
 | 
			
		||||
    override suspend fun execute(context: Context): Obj {
 | 
			
		||||
        // todo: implement also classes with 'minus' operator
 | 
			
		||||
        val l = left.execute(context)
 | 
			
		||||
        if (l !is Numeric)
 | 
			
		||||
            raise("left operand is not number: $l")
 | 
			
		||||
 | 
			
		||||
        val r = right.execute(context)
 | 
			
		||||
        if (r !is Numeric)
 | 
			
		||||
            raise("right operand is not number: $r")
 | 
			
		||||
 | 
			
		||||
        return if (l is ObjInt && r is ObjInt)
 | 
			
		||||
            ObjInt(l.longValue - r.longValue)
 | 
			
		||||
        else
 | 
			
		||||
            ObjReal(l.doubleValue - r.doubleValue)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class MulStatement(
 | 
			
		||||
    override val pos: Pos,
 | 
			
		||||
    val left: Statement, val right: Statement
 | 
			
		||||
) : Statement() {
 | 
			
		||||
    override suspend fun execute(context: Context): Obj {
 | 
			
		||||
        val l = left.execute(context)
 | 
			
		||||
        if (l !is Numeric)
 | 
			
		||||
            raise("left operand is not number: $l")
 | 
			
		||||
 | 
			
		||||
        val r = right.execute(context)
 | 
			
		||||
        if (r !is Numeric)
 | 
			
		||||
            raise("right operand is not number: $r")
 | 
			
		||||
 | 
			
		||||
        return if (l is ObjInt && r is ObjInt)
 | 
			
		||||
            ObjInt(l.longValue * r.longValue)
 | 
			
		||||
        else
 | 
			
		||||
            ObjReal(l.doubleValue * r.doubleValue)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class DivStatement(
 | 
			
		||||
    override val pos: Pos,
 | 
			
		||||
    val left: Statement, val right: Statement
 | 
			
		||||
) : Statement() {
 | 
			
		||||
    override suspend fun execute(context: Context): Obj {
 | 
			
		||||
        val l = left.execute(context)
 | 
			
		||||
        if (l !is Numeric)
 | 
			
		||||
            raise("left operand is not number: $l")
 | 
			
		||||
 | 
			
		||||
        val r = right.execute(context)
 | 
			
		||||
        if (r !is Numeric)
 | 
			
		||||
            raise("right operand is not number: $r")
 | 
			
		||||
 | 
			
		||||
        return if (l is ObjInt && r is ObjInt)
 | 
			
		||||
            ObjInt(l.longValue / r.longValue)
 | 
			
		||||
        else
 | 
			
		||||
            ObjReal(l.doubleValue / r.doubleValue)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ModStatement(
 | 
			
		||||
    override val pos: Pos,
 | 
			
		||||
    val left: Statement, val right: Statement
 | 
			
		||||
) : Statement() {
 | 
			
		||||
    override suspend fun execute(context: Context): Obj {
 | 
			
		||||
        val l = left.execute(context)
 | 
			
		||||
        if (l !is Numeric)
 | 
			
		||||
            raise("left operand is not number: $l")
 | 
			
		||||
 | 
			
		||||
        val r = right.execute(context)
 | 
			
		||||
        if (r !is Numeric)
 | 
			
		||||
            raise("right operand is not number: $r")
 | 
			
		||||
 | 
			
		||||
        return if (l is ObjInt && r is ObjInt)
 | 
			
		||||
            ObjInt(l.longValue % r.longValue)
 | 
			
		||||
        else
 | 
			
		||||
            ObjReal(l.doubleValue % r.doubleValue)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AssignStatement(override val pos: Pos, val name: String, val value: Statement) : Statement() {
 | 
			
		||||
    override suspend fun execute(context: Context): Obj {
 | 
			
		||||
        val variable = context[name] ?: raise("can't assign: variable does not exist: $name")
 | 
			
		||||
        if (!variable.isMutable)
 | 
			
		||||
            throw ScriptError(pos, "can't reassign val $name")
 | 
			
		||||
        variable.value = value.execute(context)
 | 
			
		||||
        return ObjVoid
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -223,8 +223,29 @@ class ScriptTest {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun arithmeticParenthesisTest() = runTest {
 | 
			
		||||
    fun arithmetics() = runTest {
 | 
			
		||||
        // integer
 | 
			
		||||
        assertEquals(17, eval("2 + 3 * 5").toInt())
 | 
			
		||||
        assertEquals(4, eval("5-1").toInt())
 | 
			
		||||
        assertEquals(2, eval("8/4").toInt())
 | 
			
		||||
        assertEquals(2, eval("8 % 3").toInt())
 | 
			
		||||
 | 
			
		||||
        // int-real
 | 
			
		||||
        assertEquals(9.5, eval("2 + 3 * 2.5").toDouble())
 | 
			
		||||
        assertEquals(4.5, eval("5 - 0.5").toDouble())
 | 
			
		||||
        assertEquals(2.5, eval("5 / 2.0").toDouble())
 | 
			
		||||
        assertEquals(2.5, eval("5.0 / 2.0").toDouble())
 | 
			
		||||
 | 
			
		||||
        // real
 | 
			
		||||
        assertEquals(7.5, eval("2.5 + 5.0").toDouble())
 | 
			
		||||
        assertEquals(4.5, eval("5.0 - 0.5").toDouble())
 | 
			
		||||
        assertEquals(12.5, eval("5.0 * 2.5").toDouble())
 | 
			
		||||
        assertEquals(2.5, eval("5.0 / 2.0").toDouble())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun arithmeticParenthesisTest() = runTest {
 | 
			
		||||
        assertEquals(17, eval("2.0 + 3 * 5").toInt())
 | 
			
		||||
        assertEquals(17, eval("2 + (3 * 5)").toInt())
 | 
			
		||||
        assertEquals(25, eval("(2 + 3) * 5").toInt())
 | 
			
		||||
        assertEquals(24, eval("(2 + 3) * 5 -1").toInt())
 | 
			
		||||
@ -252,6 +273,22 @@ class ScriptTest {
 | 
			
		||||
        assertTrue { eval("2 == 2 && 3 != 4").toBool() }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun logicTest() = runTest {
 | 
			
		||||
        assertEquals(ObjBool(false), eval("true && false"))
 | 
			
		||||
        assertEquals(ObjBool(false), eval("false && false"))
 | 
			
		||||
        assertEquals(ObjBool(false), eval("false && true"))
 | 
			
		||||
        assertEquals(ObjBool(true), eval("true && true"))
 | 
			
		||||
 | 
			
		||||
        assertEquals(ObjBool(true), eval("true || false"))
 | 
			
		||||
        assertEquals(ObjBool(false), eval("false || false"))
 | 
			
		||||
        assertEquals(ObjBool(true), eval("false || true"))
 | 
			
		||||
        assertEquals(ObjBool(true), eval("true || true"))
 | 
			
		||||
 | 
			
		||||
        assertEquals(ObjBool(false), eval("!true"))
 | 
			
		||||
        assertEquals(ObjBool(true), eval("!false"))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun gtLtTest() = runTest {
 | 
			
		||||
        assertTrue { eval("3 > 2").toBool() }
 | 
			
		||||
@ -423,6 +460,7 @@ class ScriptTest {
 | 
			
		||||
                var t2 = 10
 | 
			
		||||
                while( t2 > 0 ) {
 | 
			
		||||
                    t2 = t2 - 1
 | 
			
		||||
                    println("t2 " + t2 + " t1 " + t1)
 | 
			
		||||
                    if( t2 == 3 && t1 == 7) {
 | 
			
		||||
                        break@outer "ok2:"+t2+":"+t1
 | 
			
		||||
                    }
 | 
			
		||||
@ -564,5 +602,35 @@ class ScriptTest {
 | 
			
		||||
        eval(src)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testAssign1() = runTest {
 | 
			
		||||
        assertEquals(10, eval("var x = 5; x=10; x").toInt())
 | 
			
		||||
        val ctx = Context()
 | 
			
		||||
        ctx.eval("""
 | 
			
		||||
            var a = 1
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
        assertEquals(3, ctx.eval("a + a + 1").toInt())
 | 
			
		||||
        assertEquals(12, ctx.eval("a + (a = 10) + 1").toInt())
 | 
			
		||||
        assertEquals(10, ctx.eval("a").toInt())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testAssign2() = runTest {
 | 
			
		||||
        val ctx = Context()
 | 
			
		||||
        ctx.eval("var x = 10")
 | 
			
		||||
        assertEquals(14, ctx.eval("x += 4").toInt())
 | 
			
		||||
        assertEquals(14, ctx.eval("x").toInt())
 | 
			
		||||
        assertEquals(12, ctx.eval("x -= 2").toInt())
 | 
			
		||||
        assertEquals(12, ctx.eval("x").toInt())
 | 
			
		||||
 | 
			
		||||
        assertEquals(24, ctx.eval("x *= 2").toInt())
 | 
			
		||||
        assertEquals(24, ctx.eval("x").toInt())
 | 
			
		||||
 | 
			
		||||
        assertEquals(12, ctx.eval("x /= 2").toInt())
 | 
			
		||||
        assertEquals(12, ctx.eval("x").toInt())
 | 
			
		||||
 | 
			
		||||
        assertEquals(2, ctx.eval("x %= 5").toInt())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user