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

Parsing Function Call with empty parameters fails #18

Closed
mustik22 opened this issue Dec 8, 2018 · 5 comments
Closed

Parsing Function Call with empty parameters fails #18

mustik22 opened this issue Dec 8, 2018 · 5 comments

Comments

@mustik22
Copy link
Contributor

mustik22 commented Dec 8, 2018

Parsing Function Call with empty parameters fails

I have been able to create a reasonably complex expression evaluator. The only issue I am having is that when the object text contains a function with no arguments it fails (spinning in DLL) ... suspect Between not working with no arguments.

Expression that fails:
TEST()

Expression that works fine:
TEST(X)
TEST(X,X ...)

Parsing logic (directly from your test application)

private static Parser<char, T> Parenthesised(Parser<char, T> parser)
=> parser.Between(Tok("("), Tok(")"));
...
var call = Parenthesised(Rec(() => expr).Separated(Tok(",")))
.Select<Func<IExpr, IExpr>>(args => method => new Call(method, args.ToImmutableArray()))
.Labelled("function call");

Unfortunately, I have not been able to debug the DLL so I cant give you more details.

Thanks in advance.

@mustik22
Copy link
Contributor Author

mustik22 commented Dec 9, 2018

That being said ... the following works fine (using between)
""
("")

Parsing logic

    private static readonly Parser<char, char> Quote = Char('"');

    private static readonly Parser<char, string> String =
        Token(c => c != '"')
            .ManyString()
            .Between(Quote);

    // Quoted string
    // (results in Literal)
    private static readonly Parser<char, IExpr> LiteralString
        = Tok(String)
            .Select<IExpr>(value => new Literal(value))
            .Labelled("string literal");

@benjamin-hodgson
Copy link
Owner

I suspect you have an infinite loop in your recursive expr call. Could you supply your full code please?

@mustik22
Copy link
Contributor Author

mustik22 commented Dec 9, 2018

Thanks again for getting back to me so quickly.

Happy to supply the code (see below), but the loop is in the Parser, with the code I supplied. I have a number of functions that work great, the only failure is in the TEST() permutation.

Examples of Functions that work
Result = eval.ParseString("INT(123)", 123);
Result = eval.ParseString("INT("123")", 123);
Result = eval.ParseString("STR(123)", "123");
Result = eval.ParseString("IF(1==1,"123","456")", "123");
Result = eval.ParseString("IF(1⩵1,"123","456")", "123");
Result = eval.ParseString("IF(1!=1,"123","456")", "456");
Result = eval.ParseString("IF(1≠1,"123","456")", "456");
Result = eval.ParseString("SUBSTRING("ABCDEF",2,3)", "CDE");
Result = eval.ParseString("SUBSTRING("ABCDEF",2)", "CDEF");
Result = eval.ParseString("SUM(123,"456",100)", 679);

TEST() never makes it out of the parser.

Any thoughts would be greatly appreciated

Parser code

public static class ExprParser
{
private static Parser<char, T> Tok(Parser<char, T> token)
=> Try(token).Before(SkipWhitespaces);
private static Parser<char, string> Tok(string token)
=> Tok(String(token));

    private static Parser<char, T> Parenthesised<T>(Parser<char, T> parser)
        => parser.Between(Tok("("), Tok(")"));

    private static Parser<char, Func<IExpr, IExpr, IExpr>> Binary(Parser<char, BinaryOperatorType> op)
        => op.Select<Func<IExpr, IExpr, IExpr>>(type => (l, r) => new BinaryOp(type, l, r));
    private static Parser<char, Func<IExpr, IExpr>> Unary(Parser<char, UnaryOperatorType> op)
        => op.Select<Func<IExpr, IExpr>>(type => o => new UnaryOp(type, o));

    private static readonly Parser<char, Func<IExpr, IExpr, IExpr>> Plus
        = Binary(Tok("+").ThenReturn(BinaryOperatorType.Plus));
    private static readonly Parser<char, Func<IExpr, IExpr, IExpr>> Minus
        = Binary(Tok("-").ThenReturn(BinaryOperatorType.Minus));
    private static readonly Parser<char, Func<IExpr, IExpr, IExpr>> Multiply
        = Binary(Tok("*").ThenReturn(BinaryOperatorType.Multiply));
    private static readonly Parser<char, Func<IExpr, IExpr, IExpr>> Divide
        = Binary(Tok("/").ThenReturn(BinaryOperatorType.Divide));
    private static readonly Parser<char, Func<IExpr, IExpr, IExpr>> EqualTo
       = Binary(Tok("=").Then(String("=")).ThenReturn(BinaryOperatorType.EqualTo));        // "=="
    private static readonly Parser<char, Func<IExpr, IExpr, IExpr>> EqualToA
    //    //= Binary(Tok("=").ThenReturn(BinaryOperatorType.EqualTo));                       // 'FULLWIDTH EQUALS SIGN' (U+FF1D)
        = Binary(Tok("⩵").ThenReturn(BinaryOperatorType.EqualTo));                           // Two Consecutive Equals Signs (U+2A75)
    private static readonly Parser<char, Func<IExpr, IExpr, IExpr>> NotEqualTo
        = Binary(Tok("!").Then(String("=")).ThenReturn(BinaryOperatorType.NotEqualTo));     // "!="
    private static readonly Parser<char, Func<IExpr, IExpr, IExpr>> NotEqualToA
        = Binary(Tok("≠").ThenReturn(BinaryOperatorType.NotEqualTo));                       // 'NOT EQUAL TO' (U+2260)
    private static readonly Parser<char, Func<IExpr, IExpr, IExpr>> AssignTo
        = Binary(Tok(":").Then(String("=")).ThenReturn(BinaryOperatorType.AssignTo));       // ":="
    private static readonly Parser<char, Func<IExpr, IExpr, IExpr>> AssignToA
        = Binary(Tok("≔").ThenReturn(BinaryOperatorType.AssignTo));                         // 'COLON EQUALS' (U+2254)
    private static readonly Parser<char, Func<IExpr, IExpr>> Neg
        = Unary(Tok("-").ThenReturn(UnaryOperatorType.Neg));
    private static readonly Parser<char, Func<IExpr, IExpr>> UPlus
        = Unary(Tok("+").ThenReturn(UnaryOperatorType.UPlus));
    private static readonly Parser<char, Func<IExpr, IExpr>> Complement
        = Unary(Tok("~").ThenReturn(UnaryOperatorType.Complement));

    private static readonly Parser<char, IExpr> Identifier
        = Tok(Letter.Then(LetterOrDigit.ManyString(), (h, t) => h + t))
            .Select<IExpr>(name => new Identifier(name))
            .Labelled("identifier");
    private static readonly Parser<char, IExpr> Literal
        = Tok(Num)
            .Select<IExpr>(value => new Literal(value))
            .Labelled("integer literal");

    // Floating point number
    // (not working)
    // private static readonly Parser<char, char> Period = Char('.');
    // private static readonly Parser<char, string> LiteralFloat
    //     = Try(FloatString, LiteralFloat)
    //         .Select<IExpr>(value => new Literal(value))
    //         .Labelled("float literal");

    // private static readonly Parser<char, string> FloatString =
    //     Digit.ManyString().Then(Period).Then(Digit).ManyString();

    private static readonly Parser<char, char> Quote = Char('"');

    private static readonly Parser<char, string> String =
        Token(c => c != '"')
            .ManyString()
            .Between(Quote);

    // Quoted string
    // (results in Literal)
    private static readonly Parser<char, IExpr> LiteralString
        = Tok(String)
            .Select<IExpr>(value => new Literal(value))
            .Labelled("string literal");

    private static Parser<char, IExpr> BuildExpressionParser()
    {
        Parser<char, IExpr> expr = null;

        var term = OneOf(
            Identifier,
            Literal,
            LiteralString,
            //LiteralFloat,
            Parenthesised(Rec(() => expr)).Labelled("parenthesised expression")
        );

        var call = Parenthesised(Rec(() => expr).Separated(Tok(",")))
            .Select<Func<IExpr, IExpr>>(args => method => new Call(method, args.ToImmutableArray()))
            .Labelled("function call");

        // IEnumerable<OperatorTableRow<char, IExpr>>OperTable=  new[]
        // {
        //     Operator.PostfixChainable(call),
        //     Operator.Prefix(Neg).And(Operator.Prefix(Complement)).And(Operator.Prefix(UPlus)),
        //     Operator.InfixL(Multiply).And(Operator.InfixL(Divide)),
        //     Operator.InfixL(Plus).And(Operator.InfixL(Minus)),
        //     Operator.InfixL(EqualTo).And(Operator.InfixL(NotEqualTo))
        // };

        expr = ExpressionParser.Build(
            term,
            new[]
            {
                Operator.PostfixChainable(call),
                Operator.Prefix(Neg).And(Operator.Prefix(Complement)).And(Operator.Prefix(UPlus)),
                //Operator.InfixL(Multiply).And(Operator.InfixL(Divide)).And(Operator.InfixL(Plus)).And(Operator.InfixL(Minus)),
                Operator.InfixL(Multiply).And(Operator.InfixL(Divide)),
			    Operator.InfixL(Plus).And(Operator.InfixL(Minus)),
                Operator.InfixL(EqualTo).And(Operator.InfixL(EqualToA))
                    .And(Operator.InfixL(NotEqualTo)).And(Operator.InfixL(NotEqualToA))
                    .And(Operator.InfixL(AssignTo)).And(Operator.InfixL(AssignToA))
                //Operator.InfixL(EqualToA).And(Operator.InfixL(NotEqualToA)).And(Operator.InfixL(AssignToA))
                //Operator.InfixL(EqualTo).And(Operator.InfixL(NotEqualTo)),
                //Operator.InfixL(AssignTo)
            }
        ).Labelled("expression");

        return expr;
    }

    private static readonly Parser<char, IExpr> Expr = BuildExpressionParser();

    public static IExpr ParseOrThrow(string input)
    {
        try
        {
            return Expr.ParseOrThrow(input);
        }
        catch(Exception ex)
        {
            AppLog.LogException(ex, $"Expression parser {input.Truncate(50)}");
            return null;
        }
    }
        //=> Expr.ParseOrThrow(input);
}

@benjamin-hodgson
Copy link
Owner

I can't reproduce the issue using the code you supplied. ParseOrThrow("TEST()") correctly returns new Call(new Identifier("TEST"), ImmutableArray<IExpr>.Empty).

@mustik22
Copy link
Contributor Author

My apologizes, I had not integrated with the latest version. Corrected and verified.

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

2 participants