parboiled icon indicating copy to clipboard operation
parboiled copied to clipboard

"illegal writes to a local variable or parameter" in kotlin

Open tfij opened this issue 3 years ago • 0 comments

Hi, base on the CalculatorParser3 example I create a parser to evaluate expressions like

key=='abc'

It works fin in java. However, when I converted the code to kotlin, then I see an error:

Exception in thread "main" java.lang.RuntimeException: Error creating extended parser class: An ACTION or Var initializer in rule method 'Identifier' contains illegal writes to a local variable or parameter
	at org.parboiled.Parboiled.createParser(Parboiled.java:58)
	at pl.tfij.copy.CalculatorParser3copy$Companion.main(CalculatorParser3copy.kt:144)
	at pl.tfij.copy.CalculatorParser3copy.main(CalculatorParser3copy.kt)
Caused by: org.parboiled.errors.GrammarException: An ACTION or Var initializer in rule method 'Identifier' contains illegal writes to a local variable or parameter
	at org.parboiled.support.Checks.ensure(Checks.java:37)
	at org.parboiled.transform.InstructionGroupCreator.verify(InstructionGroupCreator.java:135)
	at org.parboiled.transform.InstructionGroupCreator.process(InstructionGroupCreator.java:58)
	at org.parboiled.transform.ParserTransformer.runMethodTransformers(ParserTransformer.java:62)
	at org.parboiled.transform.ParserTransformer.extendParserClass(ParserTransformer.java:45)
	at org.parboiled.transform.ParserTransformer.transformParser(ParserTransformer.java:39)
	at org.parboiled.Parboiled.createParser(Parboiled.java:54)
	... 2 more

Any idea why?

parboiled-java version : 1.4.1

My java code:

package pl.tfij;

import org.parboiled.BaseParser;
import org.parboiled.Parboiled;
import org.parboiled.Rule;
import org.parboiled.annotations.BuildParseTree;
import org.parboiled.parserunners.RecoveringParseRunner;
import org.parboiled.support.ParsingResult;
import org.parboiled.trees.ImmutableBinaryTreeNode;

import static org.parboiled.errors.ErrorUtils.printParseErrors;
import static pl.tfij.CalculatorParser3.CalcNode;

@BuildParseTree
public class CalculatorParser3 extends BaseParser<CalcNode> {

    public Rule InputLine() {
        return Sequence(Expression(), EOI);
    }

    Rule Expression() {
        return EqualityExpression();
    }

    Rule EqualityExpression() {
        return FirstOf(
                Sequence(
                        Identifier(),
                        "== ",
                        StringLiteral(),
                        push(new CalcNode("==", pop(1), pop()))
                ),
                Sequence(
                        Identifier(),
                        "!= ",
                        StringLiteral(),
                        push(new CalcNode("!=", pop(1), pop()))
                )
        );
    }

    Rule Identifier() {
        return Sequence(
            Sequence(
                OneOrMore(
                        Letter(),
                        ZeroOrMore(FirstOf(Letter(), Digit()))
                ),
                WhiteSpace()
            ),
            push(new CalcNode("Identifier", match().trim()))
        );
    }

    Rule Letter() {
        return FirstOf(CharRange('a', 'z'), CharRange('A', 'Z'), '_', '$');
    }

    Rule StringLiteral() {
        return Sequence("'", StringContent(), "'", WhiteSpace());
    }

    Rule StringContent() {
        return Sequence(
                ZeroOrMore(Sequence(TestNot(AnyOf("\r\n'")), FirstOf(EscapedChar(), ANY))),
                push(new CalcNode("String", escapeString(matchOrDefault(""))))
        );
    }

    protected String escapeString(String string) {
        StringBuilder result = new StringBuilder();
        var i = 0;
        while (i < string.length()) {
            if (string.charAt(i) == '\\') {
                i++;
            }
            result.append(string.charAt(i));
            i++;
        }
        return result.toString();
    }

    Rule EscapedChar() {
        return Sequence("\\", ANY);
    }

    Rule Digit() {
        return CharRange('0', '9');
    }

    Rule WhiteSpace() {
        return ZeroOrMore(AnyOf(" \t\f"));
    }

    // we redefine the rule creation for string literals to automatically match trailing whitespace if the string
    // literal ends with a space character, this way we don't have to insert extra whitespace() rules after each
    // character or string literal

    @Override
    protected Rule fromStringLiteral(String string) {
        return string.endsWith(" ") ?
                Sequence(String(string.substring(0, string.length() - 1)), WhiteSpace()) :
                String(string);
    }

    //****************************************************************

    /**
     * The AST node for the calculators. The type of the node is carried as a Character that can either contain
     * an operator char or be null. In the latter case the AST node is a leaf directly containing a value.
     */
    public static class CalcNode extends ImmutableBinaryTreeNode<CalcNode> {
        private Object value;
        private String type;

        public CalcNode(String type, Object value) {
            super(null, null);
            this.type = type;
            this.value = value;
        }

        public CalcNode(String type, CalcNode left, CalcNode right) {
            super(left, right);
            this.type = type;
        }

        public Object getValue() {
            switch (type) {
                case "==":
                    return left().getValue().equals(right().getValue());
                case "!=":
                    return !left().getValue().equals(right().getValue());
                case  "Identifier":
                    return "abc"; //TODO
                case "String":
                    return value;
                default:
                    throw new IllegalStateException(type);
            }
        }

        @Override
        public String toString() {
            return (type == null ? "Value " + value : "Operator '" + type + '\'') + " | " + getValue();
        }
    }

    //**************** MAIN ****************

    public static void main(String[] args) {
        CalculatorParser3 parser = Parboiled.createParser(CalculatorParser3.class);
        String input = "key=='abc'";
        ParsingResult<CalcNode> result = new RecoveringParseRunner<CalcNode>(parser.InputLine()).run(input);

        if (result.hasErrors()) {
            System.out.println("\nParse Errors:\n" + printParseErrors(result));
        }

        CalcNode value = result.resultValue;

        System.out.println("value: " + value.getValue());
    }
}

and Kotlin version:

package pl.tfij

import org.parboiled.BaseParser
import org.parboiled.Parboiled
import org.parboiled.Rule
import org.parboiled.annotations.BuildParseTree
import org.parboiled.errors.ErrorUtils
import org.parboiled.parserunners.RecoveringParseRunner
import org.parboiled.trees.ImmutableBinaryTreeNode
import pl.tfij.copy.CalculatorParser3copy

@BuildParseTree
open class CalculatorParser3copy : BaseParser<CalculatorParser3copy.CalcNode?>() {
    open fun InputLine(): Rule {
        return Sequence(Expression(), EOI)
    }

    open fun Expression(): Rule {
        return EqualityExpression()
    }

    open fun EqualityExpression(): Rule {
        return FirstOf(
            Sequence(
                Identifier(),
                "== ",
                StringLiteral(),
                push(CalcNode("==", pop(1), pop()))
            ),
            Sequence(
                Identifier(),
                "!= ",
                StringLiteral(),
                push(CalcNode("!=", pop(1), pop()))
            )
        )
    }

    open fun Identifier(): Rule {
        return Sequence(
            Sequence(
                OneOrMore(
                    Letter(),
                    ZeroOrMore(FirstOf(Letter(), Digit()))
                ),
                WhiteSpace()
            ),
            push(CalcNode("Identifier", match().trim()))
        )
    }

    open fun Letter(): Rule {
        return FirstOf(CharRange('a', 'z'), CharRange('A', 'Z'), '_', '$')
    }

    open fun StringLiteral(): Rule {
        return Sequence("'", StringContent(), "'", WhiteSpace())
    }

    open fun StringContent(): Rule {
        return Sequence(
            ZeroOrMore(Sequence(TestNot(AnyOf("\r\n'")), FirstOf(EscapedChar(), ANY))),
            push(CalcNode("String", escapeString(matchOrDefault(""))))
        )
    }

    protected fun escapeString(string: String): String {
        val result = StringBuilder()
        var i = 0
        while (i < string.length) {
            if (string[i] == '\\') {
                i++
            }
            result.append(string[i])
            i++
        }
        return result.toString()
    }

    open fun EscapedChar(): Rule {
        return Sequence("\\", ANY)
    }

    open fun Digit(): Rule {
        return CharRange('0', '9')
    }

    open fun WhiteSpace(): Rule {
        return ZeroOrMore(AnyOf(" \t\u000c"))
    }

    // we redefine the rule creation for string literals to automatically match trailing whitespace if the string
    // literal ends with a space character, this way we don't have to insert extra whitespace() rules after each
    // character or string literal
    override fun fromStringLiteral(string: String): Rule {
        return if (string.endsWith(" ")) Sequence(
            String(string.substring(0, string.length - 1)),
            WhiteSpace()
        ) else String(string)
    }
    //****************************************************************
    /**
     * The AST node for the calculators. The type of the node is carried as a Character that can either contain
     * an operator char or be null. In the latter case the AST node is a leaf directly containing a value.
     */
    class CalcNode : ImmutableBinaryTreeNode<CalcNode?> {
        private var value: Any? = null
        private var type: String?

        constructor(type: String?, value: Any?) : super(null, null) {
            this.type = type
            this.value = value
        }

        constructor(type: String?, left: CalcNode?, right: CalcNode?) : super(left, right) {
            this.type = type
        }

        fun getValue(): Any? {
            return when (type) {
                "==" -> left()!!.getValue() == right()!!.getValue()
                "!=" -> left()!!.getValue() != right()!!.getValue()
                "Identifier" -> "abc" //TODO
                "String" -> value
                else -> throw IllegalStateException(type)
            }
        }

        override fun toString(): String {
            return (if (type == null) "Value $value" else "Operator '$type'") + " | " + getValue()
        }
    }

    companion object {
        //**************** MAIN ****************
        @JvmStatic
        fun main(args: Array<String>) {
            val parser = Parboiled.createParser(
                CalculatorParser3copy::class.java
            )
            val input = "key=='abc'"
            val result = RecoveringParseRunner<CalcNode>(parser.InputLine()).run(input)
            if (result.hasErrors()) {
                println(
                    """
    Parse Errors:
    ${ErrorUtils.printParseErrors(result)}
    """.trimIndent()
                )
            }
            val value = result.resultValue
            println("value: " + value.getValue())
        }
    }
}

tfij avatar Apr 18 '22 20:04 tfij