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