Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
tfij opened this issue Apr 18, 2022 · 0 comments
Open

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

tfij opened this issue Apr 18, 2022 · 0 comments

Comments

@tfij
Copy link

tfij commented Apr 18, 2022

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())
        }
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant