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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions