Skip to content

How to make Positioned work with ChainOperator? #171

@miRoox

Description

@miRoox

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions