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

How to make Positioned work with ChainOperator? #171

Open
miRoox opened this issue Apr 23, 2021 · 0 comments
Open

How to make Positioned work with ChainOperator? #171

miRoox opened this issue Apr 23, 2021 · 0 comments

Comments

@miRoox
Copy link

miRoox commented Apr 23, 2021

If I call Positioned after ChainOperator, it will only work for the top-level node and may overwrite the position of the operand when there is no operation in the input.

For example (see the end for the full example code):

static readonly Parser<Node> parserExpression =
            Parse.ChainOperator(parserOperator, parserInt, (op, lhs, rhs) => new BinaryExpression(op, lhs, rhs))
            .Positioned(); // only work for top-level

input: 1 + 2 - 3 + 4

expected output:

1 + 2 - 3 + 4 ( Pos 0, Line 1, Column 1, Length 13 )
├─1 + 2 - 3 ( Pos 0, Line 1, Column 1, Length 9 )
│ ├─1 + 2 ( Pos 0, Line 1, Column 1, Length 5 )
│ │ ├─1 ( Pos 0, Line 1, Column 1, Length 1 )
│ │ └─2 ( Pos 4, Line 1, Column 5, Length 1 )
│ └─3 ( Pos 8, Line 1, Column 9, Length 1 )
└─4 ( Pos 12, Line 1, Column 13, Length 1 )

actual output:

1 + 2 - 3 + 4 ( Pos 0, Line 1, Column 1, Length 13 )
├─1 + 2 - 3
│ ├─1 + 2
│ │ ├─1 ( Pos 0, Line 1, Column 1, Length 1 )
│ │ └─2 ( Pos 4, Line 1, Column 5, Length 1 )
│ └─3 ( Pos 8, Line 1, Column 9, Length 1 )
└─4 ( Pos 12, Line 1, Column 13, Length 1 )

input:  1  (1 surrounded by space characters)

expected output::

1 ( Pos 1, Line 1, Column 2, Length 1 )

actual output:

1 ( Pos 0, Line 1, Column 1, Length 3 )

Is there a proper way to obtain the position information of the intermediate node?

Full example code

node.cs

using Sprache;
using System.Collections.Generic;

namespace SphracheTest
{
    public abstract class Node : IPositionAware<Node>
    {
        public abstract IEnumerable<Node> Children { get; }

        public Position? StartPos { get; private set; }

        public int Length { get; private set; }

        public Node SetPos(Position startPos, int length)
        {
            StartPos = startPos;
            Length = length;
            return this;
        }
    }
}

literal.cs

using Sprache;
using System;
using System.Collections.Generic;

namespace SphracheTest
{
    public class Literal<T> : Node, IPositionAware<Literal<T>>
    {
        public T Value { get; }

        public Literal(T value) => Value = value;

        public override IEnumerable<Node> Children => Array.Empty<Node>();

        public override string? ToString() => Value?.ToString();

        Literal<T> IPositionAware<Literal<T>>.SetPos(Position startPos, int length) => (Literal<T>)SetPos(startPos, length);
    }
}

binaryexpression.cs

using Sprache;
using System;
using System.Collections.Generic;

namespace SphracheTest
{
    public enum BinaryOperator
    {
        Plus,
        Subtract,
    }

    public class BinaryExpression : Node, IPositionAware<BinaryExpression>
    {
        public override IEnumerable<Node> Children => new Node[] { Left, Right };

        public BinaryOperator Operator { get; }

        public Node Left { get; }

        public Node Right { get; }

        public BinaryExpression(BinaryOperator op, Node lhs, Node rhs)
        {
            Operator = op;
            Left = lhs;
            Right = rhs;
        }

        public override string? ToString() => string.Join(GetOperatorString(), Left, Right);

        private string GetOperatorString() => Operator switch
        {
            BinaryOperator.Plus => " + ",
            BinaryOperator.Subtract => " - ",
            _ => throw new ArgumentException("Invalid operator", nameof(Operator)),
        };

        BinaryExpression IPositionAware<BinaryExpression>.SetPos(Position startPos, int length) => (BinaryExpression)SetPos(startPos, length);
    }
}

nodeextension.cs

using System.Text;

namespace SphracheTest
{
    public static class NodeExtension
    {
        public static string ToTreeForm(this Node node) => node.ToTreeFormImpl(new StringBuilder().AppendTreeNode(node), prefix: string.Empty).ToString();

        private static StringBuilder ToTreeFormImpl(this Node node, StringBuilder acc, string prefix)
        {
            var iter = node.Children.GetEnumerator();
            if (!iter.MoveNext())
                return acc;
            while (true)
            {
                var child = iter.Current;
                acc.Append(prefix);
                if (iter.MoveNext())
                {
                    child.ToTreeFormImpl(acc.Append("├─").AppendTreeNode(child), prefix + "│ ");
                }
                else
                {
                    return child.ToTreeFormImpl(acc.Append("└─").AppendTreeNode(child), prefix + "  ");
                }
            }
        }

        private static StringBuilder AppendTreeNode(this StringBuilder acc, Node node)
        {
            acc.Append(node.ToString());
            if (node.StartPos != null)
            {
                acc
                    .Append(" ( Pos ")
                    .Append(node.StartPos.Pos)
                    .Append(", Line ")
                    .Append(node.StartPos.Line)
                    .Append(", Column ")
                    .Append(node.StartPos.Column)
                    .Append(", Length ")
                    .Append(node.Length)
                    .Append(" )");
            }
            return acc.AppendLine();
        }
    }
}

program.cs

using Sprache;
using System;

namespace SphracheTest
{
    static class Program
    {
        static readonly Parser<Node> parserInt =
            (from n in Parse.Number select new Literal<int>(int.Parse(n)))
            .Positioned()
            .Token();

        static readonly Parser<BinaryOperator> parserOperatorPlus =
            Parse.Char('+').Return(BinaryOperator.Plus);

        static readonly Parser<BinaryOperator> parserOperatorSubtract =
            Parse.Char('-').Return(BinaryOperator.Subtract);

        static readonly Parser<BinaryOperator> parserOperator =
            parserOperatorPlus.XOr(parserOperatorSubtract).Token();

        static readonly Parser<Node> parserExpression =
            Parse.ChainOperator(parserOperator, parserInt, (op, lhs, rhs) => new BinaryExpression(op, lhs, rhs))
            .Positioned(); // only work for top-level

        static void Main(string[] args)
        {
            Console.WriteLine(parserExpression.Parse(Console.ReadLine()).ToTreeForm());
        }
    }
}
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