add: basic grammar parser including expression and program.
This commit is contained in:
parent
fee62ec289
commit
bdcc59a2ab
201
CanonSharp.Pascal/Parser/GrammarParser.cs
Normal file
201
CanonSharp.Pascal/Parser/GrammarParser.cs
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
using CanonSharp.Combinator.Abstractions;
|
||||||
|
using CanonSharp.Combinator.Extensions;
|
||||||
|
using static CanonSharp.Combinator.ParserBuilder;
|
||||||
|
using CanonSharp.Pascal.Scanner;
|
||||||
|
using CanonSharp.Pascal.SyntaxTree;
|
||||||
|
|
||||||
|
namespace CanonSharp.Pascal.Parser;
|
||||||
|
|
||||||
|
public sealed class GrammarParser : GrammarParserBuilder
|
||||||
|
{
|
||||||
|
public static IParser<LexicalToken, SyntaxNodeBase> FactorParser()
|
||||||
|
{
|
||||||
|
// factor -> true | false | num
|
||||||
|
IParser<LexicalToken, SyntaxNodeBase> trueParser = from _ in Keyword("true")
|
||||||
|
select new BooleanValueNode(true);
|
||||||
|
|
||||||
|
IParser<LexicalToken, SyntaxNodeBase> falseParser = from _ in Keyword("false")
|
||||||
|
select new BooleanValueNode(false);
|
||||||
|
|
||||||
|
IParser<LexicalToken, SyntaxNodeBase> integerParser =
|
||||||
|
from token in Satisfy<LexicalToken>(x => x.TokenType == LexicalTokenType.ConstInteger)
|
||||||
|
select new IntegerValueNode(int.Parse(token.LiteralValue));
|
||||||
|
|
||||||
|
IParser<LexicalToken, SyntaxNodeBase> floatParser =
|
||||||
|
from token in Satisfy<LexicalToken>(x => x.TokenType == LexicalTokenType.ConstFloat)
|
||||||
|
select new FloatValueNode(double.Parse(token.LiteralValue));
|
||||||
|
|
||||||
|
// factor -> - factor | + factor
|
||||||
|
IParser<LexicalToken, SyntaxNodeBase> minusParser =
|
||||||
|
from _ in Operator("-")
|
||||||
|
from node in FactorParser()
|
||||||
|
select new UnaryOperatorNode(UnaryOperatorType.Minus, node);
|
||||||
|
|
||||||
|
IParser<LexicalToken, SyntaxNodeBase> plusParser =
|
||||||
|
from _ in Operator("+")
|
||||||
|
from node in FactorParser()
|
||||||
|
select new UnaryOperatorNode(UnaryOperatorType.Plus, node);
|
||||||
|
|
||||||
|
IParser<LexicalToken, SyntaxNodeBase> notParser =
|
||||||
|
from _ in Keyword("not")
|
||||||
|
from node in FactorParser()
|
||||||
|
select new UnaryOperatorNode(UnaryOperatorType.Not, node);
|
||||||
|
|
||||||
|
IParser<LexicalToken, SyntaxNodeBase> parenthesisParser =
|
||||||
|
from _1 in Delimiter("(")
|
||||||
|
from node in ExpressionParser()
|
||||||
|
from _2 in Delimiter(")")
|
||||||
|
select node;
|
||||||
|
|
||||||
|
return Choice(
|
||||||
|
trueParser,
|
||||||
|
falseParser,
|
||||||
|
integerParser,
|
||||||
|
floatParser,
|
||||||
|
minusParser,
|
||||||
|
plusParser,
|
||||||
|
notParser,
|
||||||
|
VariableParser(),
|
||||||
|
parenthesisParser
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IParser<LexicalToken, SyntaxNodeBase> TermRecursively(SyntaxNodeBase left)
|
||||||
|
{
|
||||||
|
// MultiplyOperator -> * | / | div | mod | and
|
||||||
|
IParser<LexicalToken, BinaryOperatorType> multiplyOperator = Choice(
|
||||||
|
from _ in Operator("*")
|
||||||
|
select BinaryOperatorType.Multiply,
|
||||||
|
from _ in Operator("/")
|
||||||
|
select BinaryOperatorType.Divide,
|
||||||
|
from _ in Keyword("div")
|
||||||
|
select BinaryOperatorType.IntegerDivide,
|
||||||
|
from _ in Keyword("mod")
|
||||||
|
select BinaryOperatorType.Mod,
|
||||||
|
from _ in Keyword("and")
|
||||||
|
select BinaryOperatorType.And);
|
||||||
|
|
||||||
|
return multiplyOperator.Next(op =>
|
||||||
|
{
|
||||||
|
return FactorParser().Map(right => new BinaryOperatorNode(op, left, right))
|
||||||
|
.Bind(TermRecursively);
|
||||||
|
}, left);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IParser<LexicalToken, SyntaxNodeBase> TermParser()
|
||||||
|
{
|
||||||
|
// Term -> Factor | Term MultiplyOperator Factor
|
||||||
|
// 消除左递归为
|
||||||
|
// Term -> Factor Term'
|
||||||
|
// Term' -> MultiplyOperator Factor Term' | ε
|
||||||
|
return FactorParser().Bind(TermRecursively);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IParser<LexicalToken, SyntaxNodeBase> SimpleExpressionRecursively(SyntaxNodeBase left)
|
||||||
|
{
|
||||||
|
// AddOperator -> + | - | or
|
||||||
|
IParser<LexicalToken, BinaryOperatorType> addOperator = Choice(
|
||||||
|
from _ in Operator("+")
|
||||||
|
select BinaryOperatorType.Add,
|
||||||
|
from _ in Operator("-")
|
||||||
|
select BinaryOperatorType.Subtract,
|
||||||
|
from _ in Keyword("or")
|
||||||
|
select BinaryOperatorType.Or);
|
||||||
|
|
||||||
|
return addOperator.Next(op =>
|
||||||
|
{
|
||||||
|
return TermParser().Map(right => new BinaryOperatorNode(op, left, right))
|
||||||
|
.Bind(SimpleExpressionRecursively);
|
||||||
|
}, left);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IParser<LexicalToken, SyntaxNodeBase> SimpleExpressionParser()
|
||||||
|
{
|
||||||
|
// SimpleExpression -> Term | SimpleExpression AddOperator Term
|
||||||
|
// 消除左递归为
|
||||||
|
// SimpleExpression -> Term SimpleExpression'
|
||||||
|
// SimpleExpression' -> AddOperator Term SimpleExpression' | ε
|
||||||
|
return TermParser().Bind(SimpleExpressionRecursively);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IParser<LexicalToken, SyntaxNodeBase> ExpressionParser()
|
||||||
|
{
|
||||||
|
// RelationOperator -> = | <> | < | <= | > | >=
|
||||||
|
IParser<LexicalToken, BinaryOperatorType> relationOperator = Choice(
|
||||||
|
from _ in Operator("=")
|
||||||
|
select BinaryOperatorType.Equal,
|
||||||
|
from _ in Operator("<>")
|
||||||
|
select BinaryOperatorType.NotEqual,
|
||||||
|
from _ in Operator("<")
|
||||||
|
select BinaryOperatorType.Less,
|
||||||
|
from _ in Operator("<=")
|
||||||
|
select BinaryOperatorType.LessEqual,
|
||||||
|
from _ in Operator(">")
|
||||||
|
select BinaryOperatorType.Greater,
|
||||||
|
from _ in Operator(">=")
|
||||||
|
select BinaryOperatorType.GreaterEqual);
|
||||||
|
|
||||||
|
// Expression -> SimpleExpression | SimpleExpression RelationOperator SimpleExpression
|
||||||
|
return Choice(
|
||||||
|
from left in SimpleExpressionParser()
|
||||||
|
from op in relationOperator
|
||||||
|
from right in SimpleExpressionParser()
|
||||||
|
select new BinaryOperatorNode(op, left, right),
|
||||||
|
SimpleExpressionParser()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ExpressionList Parser
|
||||||
|
/// ExpressionList -> Expression | ExpressionList , Expression
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IParser<LexicalToken, IEnumerable<SyntaxNodeBase>> ExpressionsParser()
|
||||||
|
=> ExpressionParser().SeparatedBy1(Delimiter(","));
|
||||||
|
|
||||||
|
public static IParser<LexicalToken, VariableNode> VariableParser()
|
||||||
|
{
|
||||||
|
return from token in Satisfy<LexicalToken>(token => token.TokenType == LexicalTokenType.Identifier)
|
||||||
|
select new VariableNode(token.LiteralValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IParser<LexicalToken, SyntaxNodeBase> StatementParser()
|
||||||
|
{
|
||||||
|
return Choice(
|
||||||
|
from variable in VariableParser()
|
||||||
|
from _ in Operator(":=")
|
||||||
|
from expression in ExpressionParser()
|
||||||
|
select new AssignNode(variable, expression)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IParser<LexicalToken, BlockNode> CompoundStatementParser()
|
||||||
|
{
|
||||||
|
return from _1 in Keyword("begin")
|
||||||
|
from statements in StatementParser().SeparatedOrEndBy(Delimiter(";"))
|
||||||
|
from _2 in Keyword("end")
|
||||||
|
select new BlockNode(statements);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IParser<LexicalToken, ProgramBody> ProgramBodyParser()
|
||||||
|
{
|
||||||
|
return from block in CompoundStatementParser()
|
||||||
|
select new ProgramBody(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IParser<LexicalToken, ProgramHead> ProgramHeadParser()
|
||||||
|
{
|
||||||
|
return from _ in Keyword("program")
|
||||||
|
from token in Satisfy<LexicalToken>(token => token.TokenType == LexicalTokenType.Identifier)
|
||||||
|
select new ProgramHead(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IParser<LexicalToken, Program> ProgramParser()
|
||||||
|
{
|
||||||
|
return from head in ProgramHeadParser()
|
||||||
|
from _1 in Delimiter(";")
|
||||||
|
from body in ProgramBodyParser()
|
||||||
|
from _2 in Delimiter(".")
|
||||||
|
select new Program(head, body);
|
||||||
|
}
|
||||||
|
}
|
17
CanonSharp.Pascal/Parser/GrammarParserBase.cs
Normal file
17
CanonSharp.Pascal/Parser/GrammarParserBase.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
using CanonSharp.Combinator.Abstractions;
|
||||||
|
using CanonSharp.Pascal.Scanner;
|
||||||
|
using static CanonSharp.Combinator.ParserBuilder;
|
||||||
|
|
||||||
|
namespace CanonSharp.Pascal.Parser;
|
||||||
|
|
||||||
|
public abstract class GrammarParserBuilder
|
||||||
|
{
|
||||||
|
protected static IParser<LexicalToken, LexicalToken> Keyword(string value)
|
||||||
|
=> Satisfy<LexicalToken>(token => token.TokenType == LexicalTokenType.Keyword && token.LiteralValue == value);
|
||||||
|
|
||||||
|
protected static IParser<LexicalToken, LexicalToken> Operator(string value)
|
||||||
|
=> Satisfy<LexicalToken>(token => token.TokenType == LexicalTokenType.Operator && token.LiteralValue == value);
|
||||||
|
|
||||||
|
protected static IParser<LexicalToken, LexicalToken> Delimiter(string value)
|
||||||
|
=> Satisfy<LexicalToken>(token => token.TokenType == LexicalTokenType.Delimiter && token.LiteralValue == value);
|
||||||
|
}
|
53
CanonSharp.Pascal/Parser/LexicalTokenReadState.cs
Normal file
53
CanonSharp.Pascal/Parser/LexicalTokenReadState.cs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
using CanonSharp.Combinator.Abstractions;
|
||||||
|
using CanonSharp.Pascal.Scanner;
|
||||||
|
|
||||||
|
namespace CanonSharp.Pascal.Parser;
|
||||||
|
|
||||||
|
public sealed class LexicalTokenReadState : IReadState<LexicalToken, LexicalTokenReadState>
|
||||||
|
{
|
||||||
|
private readonly List<LexicalToken> _tokens;
|
||||||
|
private readonly int _pos;
|
||||||
|
|
||||||
|
public LexicalToken Current => _tokens[_pos];
|
||||||
|
|
||||||
|
public bool HasValue => _pos < _tokens.Count;
|
||||||
|
|
||||||
|
public LexicalTokenReadState Next => new(_tokens, _pos + 1);
|
||||||
|
|
||||||
|
private LexicalTokenReadState(List<LexicalToken> tokens, int pos)
|
||||||
|
{
|
||||||
|
_tokens = tokens;
|
||||||
|
_pos = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LexicalTokenReadState(IEnumerable<LexicalToken> tokens)
|
||||||
|
{
|
||||||
|
_tokens = tokens.ToList();
|
||||||
|
_pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(LexicalTokenReadState? other)
|
||||||
|
{
|
||||||
|
if (other is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_tokens.Count != other._tokens.Count)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ((LexicalToken first, LexicalToken second) in _tokens.Zip(other._tokens))
|
||||||
|
{
|
||||||
|
if (!first.Equals(second))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _pos == other._pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => HasValue ? Current.ToString() : "End of input stream.";
|
||||||
|
}
|
10
CanonSharp.Pascal/SyntaxTree/AssignNode.cs
Normal file
10
CanonSharp.Pascal/SyntaxTree/AssignNode.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace CanonSharp.Pascal.SyntaxTree;
|
||||||
|
|
||||||
|
public sealed class AssignNode(VariableNode variable, SyntaxNodeBase expression) : SyntaxNodeBase
|
||||||
|
{
|
||||||
|
public override SyntaxNodeType NodeType => SyntaxNodeType.Assign;
|
||||||
|
|
||||||
|
public VariableNode Variable => variable;
|
||||||
|
|
||||||
|
public SyntaxNodeBase Expression => expression;
|
||||||
|
}
|
33
CanonSharp.Pascal/SyntaxTree/BinaryOperatorNode.cs
Normal file
33
CanonSharp.Pascal/SyntaxTree/BinaryOperatorNode.cs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
namespace CanonSharp.Pascal.SyntaxTree;
|
||||||
|
|
||||||
|
public enum BinaryOperatorType
|
||||||
|
{
|
||||||
|
Add,
|
||||||
|
Subtract,
|
||||||
|
Multiply,
|
||||||
|
|
||||||
|
// Pascal特有的整数除法关键词div
|
||||||
|
IntegerDivide,
|
||||||
|
Divide,
|
||||||
|
Mod,
|
||||||
|
And,
|
||||||
|
Or,
|
||||||
|
Equal,
|
||||||
|
NotEqual,
|
||||||
|
Greater,
|
||||||
|
GreaterEqual,
|
||||||
|
Less,
|
||||||
|
LessEqual
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class BinaryOperatorNode(BinaryOperatorType operatorType, SyntaxNodeBase left, SyntaxNodeBase right)
|
||||||
|
: SyntaxNodeBase
|
||||||
|
{
|
||||||
|
public override SyntaxNodeType NodeType => SyntaxNodeType.BinaryOperator;
|
||||||
|
|
||||||
|
public BinaryOperatorType OperatorType => operatorType;
|
||||||
|
|
||||||
|
public SyntaxNodeBase Left => left;
|
||||||
|
|
||||||
|
public SyntaxNodeBase Right => right;
|
||||||
|
}
|
8
CanonSharp.Pascal/SyntaxTree/BlockNode.cs
Normal file
8
CanonSharp.Pascal/SyntaxTree/BlockNode.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace CanonSharp.Pascal.SyntaxTree;
|
||||||
|
|
||||||
|
public sealed class BlockNode(IEnumerable<SyntaxNodeBase> statements) : SyntaxNodeBase
|
||||||
|
{
|
||||||
|
public override SyntaxNodeType NodeType => SyntaxNodeType.Block;
|
||||||
|
|
||||||
|
public IList<SyntaxNodeBase> Statements => statements.ToList();
|
||||||
|
}
|
8
CanonSharp.Pascal/SyntaxTree/BooleanValueNode.cs
Normal file
8
CanonSharp.Pascal/SyntaxTree/BooleanValueNode.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace CanonSharp.Pascal.SyntaxTree;
|
||||||
|
|
||||||
|
public sealed class BooleanValueNode(bool value) : SyntaxNodeBase
|
||||||
|
{
|
||||||
|
public override SyntaxNodeType NodeType => SyntaxNodeType.BooleanValue;
|
||||||
|
|
||||||
|
public bool Value => value;
|
||||||
|
}
|
8
CanonSharp.Pascal/SyntaxTree/FloatValueNode.cs
Normal file
8
CanonSharp.Pascal/SyntaxTree/FloatValueNode.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace CanonSharp.Pascal.SyntaxTree;
|
||||||
|
|
||||||
|
public sealed class FloatValueNode(double value) : SyntaxNodeBase
|
||||||
|
{
|
||||||
|
public override SyntaxNodeType NodeType => SyntaxNodeType.FloatValue;
|
||||||
|
|
||||||
|
public double Value => value;
|
||||||
|
}
|
8
CanonSharp.Pascal/SyntaxTree/IntegerValueNode.cs
Normal file
8
CanonSharp.Pascal/SyntaxTree/IntegerValueNode.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace CanonSharp.Pascal.SyntaxTree;
|
||||||
|
|
||||||
|
public sealed class IntegerValueNode(int value) : SyntaxNodeBase
|
||||||
|
{
|
||||||
|
public override SyntaxNodeType NodeType => SyntaxNodeType.IntegerValue;
|
||||||
|
|
||||||
|
public int Value => value;
|
||||||
|
}
|
10
CanonSharp.Pascal/SyntaxTree/Program.cs
Normal file
10
CanonSharp.Pascal/SyntaxTree/Program.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace CanonSharp.Pascal.SyntaxTree;
|
||||||
|
|
||||||
|
public sealed class Program(ProgramHead head, ProgramBody body) : SyntaxNodeBase
|
||||||
|
{
|
||||||
|
public override SyntaxNodeType NodeType => SyntaxNodeType.Program;
|
||||||
|
|
||||||
|
public ProgramHead Head => head;
|
||||||
|
|
||||||
|
public ProgramBody Body => body;
|
||||||
|
}
|
8
CanonSharp.Pascal/SyntaxTree/ProgramBody.cs
Normal file
8
CanonSharp.Pascal/SyntaxTree/ProgramBody.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace CanonSharp.Pascal.SyntaxTree;
|
||||||
|
|
||||||
|
public sealed class ProgramBody(BlockNode block) : SyntaxNodeBase
|
||||||
|
{
|
||||||
|
public override SyntaxNodeType NodeType => SyntaxNodeType.ProgramBody;
|
||||||
|
|
||||||
|
public BlockNode MainBlock => block;
|
||||||
|
}
|
10
CanonSharp.Pascal/SyntaxTree/ProgramHead.cs
Normal file
10
CanonSharp.Pascal/SyntaxTree/ProgramHead.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
using CanonSharp.Pascal.Scanner;
|
||||||
|
|
||||||
|
namespace CanonSharp.Pascal.SyntaxTree;
|
||||||
|
|
||||||
|
public sealed class ProgramHead(LexicalToken programName) : SyntaxNodeBase
|
||||||
|
{
|
||||||
|
public override SyntaxNodeType NodeType => SyntaxNodeType.ProgramHead;
|
||||||
|
|
||||||
|
public LexicalToken ProgramName => programName;
|
||||||
|
}
|
31
CanonSharp.Pascal/SyntaxTree/SyntaxNodeBase.cs
Normal file
31
CanonSharp.Pascal/SyntaxTree/SyntaxNodeBase.cs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
namespace CanonSharp.Pascal.SyntaxTree;
|
||||||
|
|
||||||
|
public enum SyntaxNodeType
|
||||||
|
{
|
||||||
|
BinaryOperator,
|
||||||
|
UnaryOperator,
|
||||||
|
IntegerValue,
|
||||||
|
FloatValue,
|
||||||
|
BooleanValue,
|
||||||
|
Variable,
|
||||||
|
Assign,
|
||||||
|
Block,
|
||||||
|
ProgramBody,
|
||||||
|
ProgramHead,
|
||||||
|
Program
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class SyntaxNodeBase
|
||||||
|
{
|
||||||
|
public abstract SyntaxNodeType NodeType { get; }
|
||||||
|
|
||||||
|
public T Convert<T>() where T : SyntaxNodeBase
|
||||||
|
{
|
||||||
|
if (this is not T result)
|
||||||
|
{
|
||||||
|
throw new InvalidCastException($"Can't convert {NodeType} to target node.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
17
CanonSharp.Pascal/SyntaxTree/UnaryOperatorNode.cs
Normal file
17
CanonSharp.Pascal/SyntaxTree/UnaryOperatorNode.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
namespace CanonSharp.Pascal.SyntaxTree;
|
||||||
|
|
||||||
|
public enum UnaryOperatorType
|
||||||
|
{
|
||||||
|
Plus,
|
||||||
|
Minus,
|
||||||
|
Not
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class UnaryOperatorNode(UnaryOperatorType operatorType, SyntaxNodeBase node) : SyntaxNodeBase
|
||||||
|
{
|
||||||
|
public override SyntaxNodeType NodeType => SyntaxNodeType.UnaryOperator;
|
||||||
|
|
||||||
|
public UnaryOperatorType OperatorType => operatorType;
|
||||||
|
|
||||||
|
public SyntaxNodeBase Node => node;
|
||||||
|
}
|
8
CanonSharp.Pascal/SyntaxTree/VariableNode.cs
Normal file
8
CanonSharp.Pascal/SyntaxTree/VariableNode.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace CanonSharp.Pascal.SyntaxTree;
|
||||||
|
|
||||||
|
public sealed class VariableNode(string name) : SyntaxNodeBase
|
||||||
|
{
|
||||||
|
public override SyntaxNodeType NodeType => SyntaxNodeType.Variable;
|
||||||
|
|
||||||
|
public string IdentifierName => name;
|
||||||
|
}
|
245
CanonSharp.Tests/ParserTests/ExpressionParserTests.cs
Normal file
245
CanonSharp.Tests/ParserTests/ExpressionParserTests.cs
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
using CanonSharp.Pascal.Parser;
|
||||||
|
using CanonSharp.Pascal.SyntaxTree;
|
||||||
|
using CanonSharp.Tests.Utils;
|
||||||
|
|
||||||
|
namespace CanonSharp.Tests.ParserTests;
|
||||||
|
|
||||||
|
public sealed class ExpressionParserTests : GrammarParserTestBase
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void BoolTest()
|
||||||
|
{
|
||||||
|
BooleanValueNode node = RunParser<BooleanValueNode>(GrammarParser.FactorParser(), "false");
|
||||||
|
Assert.False(node.Value);
|
||||||
|
|
||||||
|
node = RunParser<BooleanValueNode>(GrammarParser.FactorParser(), "true");
|
||||||
|
Assert.True(node.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NumberTest()
|
||||||
|
{
|
||||||
|
IntegerValueNode integerValueNode = RunParser<IntegerValueNode>(GrammarParser.FactorParser(), "123456");
|
||||||
|
Assert.Equal(123456, integerValueNode.Value);
|
||||||
|
|
||||||
|
FloatValueNode floatValueNode = RunParser<FloatValueNode>(GrammarParser.FactorParser(), "123.456");
|
||||||
|
Assert.Equal(123.456, floatValueNode.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UnaryOperatorTest()
|
||||||
|
{
|
||||||
|
UnaryOperatorNode node = RunParser<UnaryOperatorNode>(GrammarParser.FactorParser(), "- 123");
|
||||||
|
Assert.Equal(UnaryOperatorType.Minus, node.OperatorType);
|
||||||
|
Assert.Equal(123, node.Node.Convert<IntegerValueNode>().Value);
|
||||||
|
|
||||||
|
node = RunParser<UnaryOperatorNode>(GrammarParser.FactorParser(), "+ 100.5");
|
||||||
|
Assert.Equal(UnaryOperatorType.Plus, node.OperatorType);
|
||||||
|
Assert.Equal(100.5, node.Node.Convert<FloatValueNode>().Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IdentifierTest()
|
||||||
|
{
|
||||||
|
VariableNode node = RunParser<VariableNode>(GrammarParser.FactorParser(), "temp");
|
||||||
|
Assert.Equal("temp", node.IdentifierName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SingleTermTest()
|
||||||
|
{
|
||||||
|
VariableNode node = RunParser<VariableNode>(GrammarParser.TermParser(), "temp");
|
||||||
|
Assert.Equal("temp", node.IdentifierName);
|
||||||
|
|
||||||
|
UnaryOperatorNode unaryOperatorNode = RunParser<UnaryOperatorNode>(GrammarParser.TermParser(), "- 123");
|
||||||
|
Assert.Equal(UnaryOperatorType.Minus, unaryOperatorNode.OperatorType);
|
||||||
|
Assert.Equal(123, unaryOperatorNode.Node.Convert<IntegerValueNode>().Value);
|
||||||
|
|
||||||
|
unaryOperatorNode = RunParser<UnaryOperatorNode>(GrammarParser.TermParser(), "+ 100.5");
|
||||||
|
Assert.Equal(UnaryOperatorType.Plus, unaryOperatorNode.OperatorType);
|
||||||
|
Assert.Equal(100.5, unaryOperatorNode.Node.Convert<FloatValueNode>().Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MultiplyTermTest1()
|
||||||
|
{
|
||||||
|
BinaryOperatorNode node = RunParser<BinaryOperatorNode>(GrammarParser.TermParser(), "10 / 2");
|
||||||
|
|
||||||
|
Assert.Equal(BinaryOperatorType.Divide, node.OperatorType);
|
||||||
|
Assert.Equal(10, node.Left.Convert<IntegerValueNode>().Value);
|
||||||
|
Assert.Equal(2, node.Right.Convert<IntegerValueNode>().Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MultiplyTermTest2()
|
||||||
|
{
|
||||||
|
BinaryOperatorNode node = RunParser<BinaryOperatorNode>(GrammarParser.TermParser(), "10 div 2");
|
||||||
|
|
||||||
|
Assert.Equal(BinaryOperatorType.IntegerDivide, node.OperatorType);
|
||||||
|
Assert.Equal(10, node.Left.Convert<IntegerValueNode>().Value);
|
||||||
|
Assert.Equal(2, node.Right.Convert<IntegerValueNode>().Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MultiplyTermTest3()
|
||||||
|
{
|
||||||
|
BinaryOperatorNode node = RunParser<BinaryOperatorNode>(GrammarParser.TermParser(), "temp * 2 div 3");
|
||||||
|
|
||||||
|
Assert.Equal(BinaryOperatorType.IntegerDivide, node.OperatorType);
|
||||||
|
Assert.Equal(3, node.Right.Convert<IntegerValueNode>().Value);
|
||||||
|
BinaryOperatorNode leftNode = node.Left.Convert<BinaryOperatorNode>();
|
||||||
|
Assert.Equal(BinaryOperatorType.Multiply, leftNode.OperatorType);
|
||||||
|
Assert.Equal("temp", leftNode.Left.Convert<VariableNode>().IdentifierName);
|
||||||
|
Assert.Equal(2, leftNode.Right.Convert<IntegerValueNode>().Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SimpleExpressionTest1()
|
||||||
|
{
|
||||||
|
VariableNode node = RunParser<VariableNode>(GrammarParser.SimpleExpressionParser(), "temp");
|
||||||
|
Assert.Equal("temp", node.IdentifierName);
|
||||||
|
|
||||||
|
UnaryOperatorNode unaryOperatorNode =
|
||||||
|
RunParser<UnaryOperatorNode>(GrammarParser.SimpleExpressionParser(), "- 123");
|
||||||
|
Assert.Equal(UnaryOperatorType.Minus, unaryOperatorNode.OperatorType);
|
||||||
|
Assert.Equal(123, unaryOperatorNode.Node.Convert<IntegerValueNode>().Value);
|
||||||
|
|
||||||
|
unaryOperatorNode = RunParser<UnaryOperatorNode>(GrammarParser.SimpleExpressionParser(), "+ 100.5");
|
||||||
|
Assert.Equal(UnaryOperatorType.Plus, unaryOperatorNode.OperatorType);
|
||||||
|
Assert.Equal(100.5, unaryOperatorNode.Node.Convert<FloatValueNode>().Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SimpleExpressionTest2()
|
||||||
|
{
|
||||||
|
BinaryOperatorNode node = RunParser<BinaryOperatorNode>(GrammarParser.SimpleExpressionParser(), "1 + 1");
|
||||||
|
|
||||||
|
Assert.Equal(BinaryOperatorType.Add, node.OperatorType);
|
||||||
|
Assert.Equal(1, node.Left.Convert<IntegerValueNode>().Value);
|
||||||
|
Assert.Equal(1, node.Right.Convert<IntegerValueNode>().Value);
|
||||||
|
|
||||||
|
node = RunParser<BinaryOperatorNode>(GrammarParser.SimpleExpressionParser(), "1 + 1 - 2");
|
||||||
|
|
||||||
|
Assert.Equal(BinaryOperatorType.Subtract, node.OperatorType);
|
||||||
|
Assert.Equal(2, node.Right.Convert<IntegerValueNode>().Value);
|
||||||
|
|
||||||
|
BinaryOperatorNode leftNode = node.Left.Convert<BinaryOperatorNode>();
|
||||||
|
Assert.Equal(BinaryOperatorType.Add, leftNode.OperatorType);
|
||||||
|
Assert.Equal(1, leftNode.Left.Convert<IntegerValueNode>().Value);
|
||||||
|
Assert.Equal(1, leftNode.Right.Convert<IntegerValueNode>().Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SimpleExpressionTest3()
|
||||||
|
{
|
||||||
|
BinaryOperatorNode node = RunParser<BinaryOperatorNode>(GrammarParser.SimpleExpressionParser(), "1 - 2 * 5");
|
||||||
|
|
||||||
|
Assert.Equal(BinaryOperatorType.Subtract, node.OperatorType);
|
||||||
|
Assert.Equal(1, node.Left.Convert<IntegerValueNode>().Value);
|
||||||
|
|
||||||
|
BinaryOperatorNode rightNode = node.Right.Convert<BinaryOperatorNode>();
|
||||||
|
Assert.Equal(BinaryOperatorType.Multiply, rightNode.OperatorType);
|
||||||
|
Assert.Equal(2, rightNode.Left.Convert<IntegerValueNode>().Value);
|
||||||
|
Assert.Equal(5, rightNode.Right.Convert<IntegerValueNode>().Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SimpleExpressionTest4()
|
||||||
|
{
|
||||||
|
BinaryOperatorNode node = RunParser<BinaryOperatorNode>(GrammarParser.SimpleExpressionParser(), "1 + +1");
|
||||||
|
|
||||||
|
Assert.Equal(BinaryOperatorType.Add, node.OperatorType);
|
||||||
|
Assert.Equal(1, node.Left.Convert<IntegerValueNode>().Value);
|
||||||
|
UnaryOperatorNode unaryOperatorNode = node.Right.Convert<UnaryOperatorNode>();
|
||||||
|
Assert.Equal(UnaryOperatorType.Plus, unaryOperatorNode.OperatorType);
|
||||||
|
Assert.Equal(1, unaryOperatorNode.Node.Convert<IntegerValueNode>().Value);
|
||||||
|
|
||||||
|
node = RunParser<BinaryOperatorNode>(GrammarParser.SimpleExpressionParser(), "1 - -1");
|
||||||
|
|
||||||
|
Assert.Equal(BinaryOperatorType.Subtract, node.OperatorType);
|
||||||
|
Assert.Equal(1, node.Left.Convert<IntegerValueNode>().Value);
|
||||||
|
unaryOperatorNode = node.Right.Convert<UnaryOperatorNode>();
|
||||||
|
Assert.Equal(UnaryOperatorType.Minus, unaryOperatorNode.OperatorType);
|
||||||
|
Assert.Equal(1, unaryOperatorNode.Node.Convert<IntegerValueNode>().Value);
|
||||||
|
|
||||||
|
node = RunParser<BinaryOperatorNode>(GrammarParser.SimpleExpressionParser(), "+ 1 - - 1");
|
||||||
|
|
||||||
|
Assert.Equal(BinaryOperatorType.Subtract, node.OperatorType);
|
||||||
|
unaryOperatorNode = node.Left.Convert<UnaryOperatorNode>();
|
||||||
|
Assert.Equal(UnaryOperatorType.Plus, unaryOperatorNode.OperatorType);
|
||||||
|
Assert.Equal(1, unaryOperatorNode.Node.Convert<IntegerValueNode>().Value);
|
||||||
|
unaryOperatorNode = node.Right.Convert<UnaryOperatorNode>();
|
||||||
|
Assert.Equal(UnaryOperatorType.Minus, unaryOperatorNode.OperatorType);
|
||||||
|
Assert.Equal(1, unaryOperatorNode.Node.Convert<IntegerValueNode>().Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SimpleExpressionTest5()
|
||||||
|
{
|
||||||
|
BinaryOperatorNode node = RunParser<BinaryOperatorNode>(GrammarParser.SimpleExpressionParser(),
|
||||||
|
"true and temp or temp and false");
|
||||||
|
|
||||||
|
Assert.Equal(BinaryOperatorType.Or, node.OperatorType);
|
||||||
|
|
||||||
|
BinaryOperatorNode left = node.Left.Convert<BinaryOperatorNode>();
|
||||||
|
Assert.Equal(BinaryOperatorType.And, left.OperatorType);
|
||||||
|
BinaryOperatorNode right = node.Right.Convert<BinaryOperatorNode>();
|
||||||
|
Assert.Equal(BinaryOperatorType.And, right.OperatorType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExpressionTest1()
|
||||||
|
{
|
||||||
|
BinaryOperatorNode node = RunParser<BinaryOperatorNode>(GrammarParser.ExpressionParser(),
|
||||||
|
"true and temp or temp and false");
|
||||||
|
|
||||||
|
Assert.Equal(BinaryOperatorType.Or, node.OperatorType);
|
||||||
|
|
||||||
|
BinaryOperatorNode left = node.Left.Convert<BinaryOperatorNode>();
|
||||||
|
Assert.Equal(BinaryOperatorType.And, left.OperatorType);
|
||||||
|
BinaryOperatorNode right = node.Right.Convert<BinaryOperatorNode>();
|
||||||
|
Assert.Equal(BinaryOperatorType.And, right.OperatorType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExpressionTest2()
|
||||||
|
{
|
||||||
|
BinaryOperatorNode node = RunParser<BinaryOperatorNode>(GrammarParser.ExpressionParser(), "2 >= 1");
|
||||||
|
Assert.Equal(BinaryOperatorType.GreaterEqual, node.OperatorType);
|
||||||
|
Assert.Equal(2, node.Left.Convert<IntegerValueNode>().Value);
|
||||||
|
Assert.Equal(1, node.Right.Convert<IntegerValueNode>().Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExpressionTest3()
|
||||||
|
{
|
||||||
|
BinaryOperatorNode node = RunParser<BinaryOperatorNode>(GrammarParser.ExpressionParser(), "(1 + 1) * 2");
|
||||||
|
|
||||||
|
Assert.Equal(BinaryOperatorType.Multiply, node.OperatorType);
|
||||||
|
Assert.Equal(2, node.Right.Convert<IntegerValueNode>().Value);
|
||||||
|
|
||||||
|
node = node.Left.Convert<BinaryOperatorNode>();
|
||||||
|
Assert.Equal(BinaryOperatorType.Add, node.OperatorType);
|
||||||
|
Assert.Equal(1, node.Left.Convert<IntegerValueNode>().Value);
|
||||||
|
Assert.Equal(1, node.Right.Convert<IntegerValueNode>().Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExpressionsTest1()
|
||||||
|
{
|
||||||
|
List<BinaryOperatorNode> nodes =
|
||||||
|
RunParser<BinaryOperatorNode>(GrammarParser.ExpressionsParser(), "1 + 1, 2 * 3");
|
||||||
|
|
||||||
|
Assert.Equal(BinaryOperatorType.Add, nodes[0].OperatorType);
|
||||||
|
Assert.Equal(BinaryOperatorType.Multiply, nodes[1].OperatorType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void VariableTest1()
|
||||||
|
{
|
||||||
|
VariableNode node = RunParser<VariableNode>(GrammarParser.VariableParser(), "temp");
|
||||||
|
Assert.Equal("temp", node.IdentifierName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
41
CanonSharp.Tests/ParserTests/ProgramParserTests.cs
Normal file
41
CanonSharp.Tests/ParserTests/ProgramParserTests.cs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
using CanonSharp.Pascal.SyntaxTree;
|
||||||
|
using CanonSharp.Tests.Utils;
|
||||||
|
|
||||||
|
namespace CanonSharp.Tests.ParserTests;
|
||||||
|
|
||||||
|
public class ProgramParserTests : GrammarParserTestBase
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ProgramParseTest1()
|
||||||
|
{
|
||||||
|
Program program = ProgramParse("""
|
||||||
|
program main;
|
||||||
|
begin
|
||||||
|
end.
|
||||||
|
""");
|
||||||
|
|
||||||
|
Assert.Equal("main", program.Head.ProgramName.LiteralValue);
|
||||||
|
Assert.Empty(program.Body.MainBlock.Statements);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ProgramParseTest2()
|
||||||
|
{
|
||||||
|
Program program = ProgramParse("""
|
||||||
|
program main;
|
||||||
|
begin
|
||||||
|
temp := 1 + 1;
|
||||||
|
end.
|
||||||
|
""");
|
||||||
|
|
||||||
|
Assert.Equal("main", program.Head.ProgramName.LiteralValue);
|
||||||
|
|
||||||
|
AssignNode assignNode = program.Body.MainBlock.Statements[0].Convert<AssignNode>();
|
||||||
|
Assert.Equal("temp", assignNode.Variable.IdentifierName);
|
||||||
|
|
||||||
|
BinaryOperatorNode binaryOperatorNode = assignNode.Expression.Convert<BinaryOperatorNode>();
|
||||||
|
Assert.Equal(BinaryOperatorType.Add, binaryOperatorNode.OperatorType);
|
||||||
|
Assert.Equal(1, binaryOperatorNode.Left.Convert<IntegerValueNode>().Value);
|
||||||
|
Assert.Equal(1, binaryOperatorNode.Right.Convert<IntegerValueNode>().Value);
|
||||||
|
}
|
||||||
|
}
|
82
CanonSharp.Tests/ParserTests/StatementParserTests.cs
Normal file
82
CanonSharp.Tests/ParserTests/StatementParserTests.cs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
using CanonSharp.Pascal.Parser;
|
||||||
|
using CanonSharp.Pascal.SyntaxTree;
|
||||||
|
using CanonSharp.Tests.Utils;
|
||||||
|
|
||||||
|
namespace CanonSharp.Tests.ParserTests;
|
||||||
|
|
||||||
|
public class StatementParserTests : GrammarParserTestBase
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void StatementTest1()
|
||||||
|
{
|
||||||
|
AssignNode node = RunParser<AssignNode>(GrammarParser.StatementParser(), "temp := 1");
|
||||||
|
|
||||||
|
Assert.Equal("temp", node.Variable.IdentifierName);
|
||||||
|
Assert.Equal(1, node.Expression.Convert<IntegerValueNode>().Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("""
|
||||||
|
begin
|
||||||
|
temp := 1 + 1;
|
||||||
|
flag := true and false;
|
||||||
|
end
|
||||||
|
""")]
|
||||||
|
[InlineData("""
|
||||||
|
begin
|
||||||
|
temp := 1 + 1;
|
||||||
|
flag := true and false
|
||||||
|
end
|
||||||
|
""")]
|
||||||
|
public void CompoundStatementTest1(string input)
|
||||||
|
{
|
||||||
|
BlockNode node = RunParser<BlockNode>(GrammarParser.CompoundStatementParser(), input);
|
||||||
|
|
||||||
|
Assert.Equal(2, node.Statements.Count);
|
||||||
|
AssignNode assignNode = node.Statements[0].Convert<AssignNode>();
|
||||||
|
Assert.Equal("temp", assignNode.Variable.IdentifierName);
|
||||||
|
|
||||||
|
assignNode = node.Statements[1].Convert<AssignNode>();
|
||||||
|
Assert.Equal("flag", assignNode.Variable.IdentifierName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CompoundStatementTest2()
|
||||||
|
{
|
||||||
|
BlockNode node = RunParser<BlockNode>(GrammarParser.CompoundStatementParser(), "begin end");
|
||||||
|
Assert.Empty(node.Statements);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ProgramHeadTest1()
|
||||||
|
{
|
||||||
|
ProgramHead head = RunParser<ProgramHead>(GrammarParser.ProgramHeadParser(), "program main");
|
||||||
|
|
||||||
|
Assert.Equal("main", head.ProgramName.LiteralValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("""
|
||||||
|
begin
|
||||||
|
temp := 1 + 1;
|
||||||
|
flag := true and false;
|
||||||
|
end
|
||||||
|
""")]
|
||||||
|
[InlineData("""
|
||||||
|
begin
|
||||||
|
temp := 1 + 1;
|
||||||
|
flag := true and false
|
||||||
|
end
|
||||||
|
""")]
|
||||||
|
public void ProgramBodyTest1(string input)
|
||||||
|
{
|
||||||
|
BlockNode node = RunParser<ProgramBody>(GrammarParser.ProgramBodyParser(), input).MainBlock;
|
||||||
|
|
||||||
|
Assert.Equal(2, node.Statements.Count);
|
||||||
|
AssignNode assignNode = node.Statements[0].Convert<AssignNode>();
|
||||||
|
Assert.Equal("temp", assignNode.Variable.IdentifierName);
|
||||||
|
|
||||||
|
assignNode = node.Statements[1].Convert<AssignNode>();
|
||||||
|
Assert.Equal("flag", assignNode.Variable.IdentifierName);
|
||||||
|
}
|
||||||
|
}
|
31
CanonSharp.Tests/Utils/GrammarParserTestBase.cs
Normal file
31
CanonSharp.Tests/Utils/GrammarParserTestBase.cs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
using CanonSharp.Combinator.Abstractions;
|
||||||
|
using CanonSharp.Pascal.Parser;
|
||||||
|
using CanonSharp.Pascal.Scanner;
|
||||||
|
using CanonSharp.Pascal.SyntaxTree;
|
||||||
|
|
||||||
|
namespace CanonSharp.Tests.Utils;
|
||||||
|
|
||||||
|
public abstract class GrammarParserTestBase
|
||||||
|
{
|
||||||
|
protected static T RunParser<T>(IParser<LexicalToken, SyntaxNodeBase> parser, string input) where T : SyntaxNodeBase
|
||||||
|
{
|
||||||
|
LexicalScanner scanner = new();
|
||||||
|
LexicalTokenReadState state = new(scanner.Tokenize(new StringReadState(input)));
|
||||||
|
IParseResult<LexicalToken, SyntaxNodeBase> parseResult = parser.Parse(state);
|
||||||
|
|
||||||
|
return parseResult.Value.Convert<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static List<T> RunParser<T>(IParser<LexicalToken, IEnumerable<SyntaxNodeBase>> parser,
|
||||||
|
string input) where T : SyntaxNodeBase
|
||||||
|
{
|
||||||
|
LexicalScanner scanner = new();
|
||||||
|
LexicalTokenReadState state = new(scanner.Tokenize(new StringReadState(input)));
|
||||||
|
IParseResult<LexicalToken, IEnumerable<SyntaxNodeBase>> parseResult = parser.Parse(state);
|
||||||
|
|
||||||
|
return parseResult.Value.Select(node => node.Convert<T>()).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Program ProgramParse(string input)
|
||||||
|
=> RunParser<Program>(GrammarParser.ProgramParser(), input);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user