tevelee / Eval

Eval is a lightweight interpreter framework written in Swift, evaluating expressions at runtime
https://tevelee.github.io/Eval/
Apache License 2.0
178 stars 7 forks source link

Possible parser bug #3

Closed nicklockwood closed 6 years ago

nicklockwood commented 6 years ago

With the following configuration, Eval is producing nil for the 4th expression. I'm not sure how to dig further into where the bug lies, but it's possibly confused by the nested function calls?

let number = DataType(type: Double.self, literals: [
    Literal<Double> { value, _ in Double(value) },
]) { arg, _ in "\(arg)" }

let addition = Function<Double>(Variable<Double>("lhs") + Keyword("+") +
    Variable<Double>("rhs")) { arguments, _, _  in
    guard let lhs = arguments["lhs"] as? Double,
        let rhs = arguments["rhs"] as? Double else { return nil }
    return lhs + rhs
}

let foo = Function<Double>(Keyword("foo") + Keyword("(") + Variable<Double>("lhs") + Keyword(",") + Variable<Double>("rhs") + Keyword(")")) { arguments, _, _  in
    guard let lhs = arguments["lhs"] as? Double,
        let rhs = arguments["rhs"] as? Double else { return nil }
    return lhs - rhs
}

let bar = Function<Double>(Keyword("bar") + Keyword("(") + Variable<Double>("value") + Keyword(")")) { arguments, _, _  in
    guard let value = arguments["value"] as? Double else { return nil }
    return value - 2
}

let interpreter = TypedInterpreter(dataTypes: [number], functions: [addition, foo, bar])

print(interpreter.evaluate("5 + 6") as? Double ?? "nil") // 11.0
print(interpreter.evaluate("foo(5, 6)") as? Double ?? "nil") // -1.0
print(interpreter.evaluate("bar(6)") as? Double ?? "nil") // 4.0
print(interpreter.evaluate("foo(5, 6 + bar(6))") as? Double ?? "nil") // nil
tevelee commented 6 years ago

Hi @nicklockwood,

Thanks very much for your contribution! I'm really grateful that you're experimenting with the framework. πŸŽ‰

Parentheses nesting is a really tricky topic, but fortunately there is a solution to that. Instead of simply providing Keyword("(") and Keyword(")") keywords, you can use the OpenKeyword("(") and CloseKeyword(")") for adding some extra semantics to the framework that you are using ( and ) as nested pairs.

In your example:

let foo = Function<Double>(Keyword("foo") + OpenKeyword("(") + Variable<Double>("lhs") + Keyword(",") + Variable<Double>("rhs") + CloseKeyword(")")) { arguments, _, _  in
    guard let lhs = arguments["lhs"] as? Double,
        let rhs = arguments["rhs"] as? Double else { return nil }
    return lhs - rhs
}

let bar = Function<Double>(Keyword("bar") + OpenKeyword("(") + Variable<Double>("value") + CloseKeyword(")")) { arguments, _, _  in
    guard let value = arguments["value"] as? Double else { return nil }
    return value - 2
}

This will provide the expected output for print(interpreter.evaluate("foo(5, 6 + bar(6))") as? Double ?? "nil"), which is -5.0.

In order to debug expressions like these, I suggest using the context argument of the expression. It's a mutable instance, which contains a debugInfo field that may give you a deeper understanding of how the framework interpreted your expression.

let context = Context()
let result = interpreter.evaluate("foo(5, 6 + bar(6))", context: context)
print(context.debugInfo)

This is going to provide the following output:

[
"bar(6)": 
    Eval.ExpressionInfo(input: "bar(6)", output: 4.0, pattern: "bar ( {value} )", variables: ["value": 6.0]), 
"6 + bar(6)": 
    Eval.ExpressionInfo(input: "6 + bar(6)", output: 10.0, pattern: "{lhs} + {rhs}", variables: ["rhs": 4.0, "lhs": 6.0]), 
"foo(5, 6 + bar(6))": 
    Eval.ExpressionInfo(input: "foo(5, 6 + bar(6))", output: -5.0, pattern: "foo ( {lhs} , {rhs} )", variables: ["rhs": 10.0, "lhs": 5.0])
]

Here you can see, that first, it interprets bar(6) as the deepest argument. Then, it proceeds with 6 + bar(6), and finally, the whole expression, which provides the expected output. You can also inspect the output and the variables used in each step.

For future reference, I also wrote a few lines about this phenomenon here, in the Use OpenKeyword and CloseKeyword for embedding parentheses section.

I hope it answers your question! Let me know if you have anything else in your mind.

Keep up the good stuff! πŸŽ‰ Cheers, Laszlo

nicklockwood commented 6 years ago

@tevelee thanks, that makes sense πŸ‘