From 382f1eefba451cc05062932df7b1d11eff5f1dfb Mon Sep 17 00:00:00 2001 From: rameel Date: Tue, 11 Feb 2025 02:39:45 +0500 Subject: [PATCH 1/9] Rename Before parser to ThenIgnore --- README.md | 2 +- samples/ExpressionParser.cs | 2 +- .../{Parser.Before.cs => Parser.ThenIgnore.cs} | 8 ++++---- ...{ParsersTests.Before.cs => ParsersTests.ThenIgnore.cs} | 4 ++-- tests/Ramstack.Parsing.Tests/Scenarios/SimpleCalcTests.cs | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) rename src/Ramstack.Parsing/{Parser.Before.cs => Parser.ThenIgnore.cs} (84%) rename tests/Ramstack.Parsing.Tests/{ParsersTests.Before.cs => ParsersTests.ThenIgnore.cs} (83%) diff --git a/README.md b/README.md index a593db9..37826e0 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ private static Parser CreateParser() S.Then(OneOf("+-")), (l, r, op) => op == '+' ? l + r : l - r); - return sum.Before(Eof); + return sum.ThenIgnore(Eof); } ``` diff --git a/samples/ExpressionParser.cs b/samples/ExpressionParser.cs index 66a643e..ac38da8 100644 --- a/samples/ExpressionParser.cs +++ b/samples/ExpressionParser.cs @@ -45,6 +45,6 @@ private static Parser CreateExpressionParser() S.Then(OneOf("+-")), (l, r, o) => o == '+' ? l + r : l - r); - return sum.Before(Eof); + return sum.ThenIgnore(Eof); } } diff --git a/src/Ramstack.Parsing/Parser.Before.cs b/src/Ramstack.Parsing/Parser.ThenIgnore.cs similarity index 84% rename from src/Ramstack.Parsing/Parser.Before.cs rename to src/Ramstack.Parsing/Parser.ThenIgnore.cs index 305b35a..45334c3 100644 --- a/src/Ramstack.Parsing/Parser.Before.cs +++ b/src/Ramstack.Parsing/Parser.ThenIgnore.cs @@ -14,8 +14,8 @@ partial class Parser /// A parser that sequentially applies the current parser and a specified second parser, /// returning the result of the first parser with ignoring the result of the second. /// - public static Parser Before(this Parser parser, Parser before) => - new BeforeParser(parser, before.Void()); + public static Parser ThenIgnore(this Parser parser, Parser before) => + new ThenIgnoreParser(parser, before.Void()); #region Inner type: BeforeParser @@ -26,7 +26,7 @@ public static Parser Before(this Parser parser, Pa /// The type of the value produced by the first parser. /// The initial parser whose result is returned. /// The subsequent parser, applied after the initial parser. - private sealed class BeforeParser(Parser parser, Parser before) : Parser + private sealed class ThenIgnoreParser(Parser parser, Parser before) : Parser { /// public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) @@ -50,7 +50,7 @@ public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out /// protected internal override Parser ToVoidParser() => - new BeforeParser(parser.Void(), before); + new ThenIgnoreParser(parser.Void(), before); } #endregion diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.Before.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.ThenIgnore.cs similarity index 83% rename from tests/Ramstack.Parsing.Tests/ParsersTests.Before.cs rename to tests/Ramstack.Parsing.Tests/ParsersTests.ThenIgnore.cs index 2aea69a..e8f1f69 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.Before.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.ThenIgnore.cs @@ -5,9 +5,9 @@ namespace Ramstack.Parsing; partial class ParsersTests { [Test] - public void BeforeTest() + public void ThenIgnoreTest() { - var parser = Any.Before(Any); + var parser = Any.ThenIgnore(Any); Assert.That(parser.Parse("12").Success, Is.True); Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("12").Value, Is.EqualTo((0, 1))); diff --git a/tests/Ramstack.Parsing.Tests/Scenarios/SimpleCalcTests.cs b/tests/Ramstack.Parsing.Tests/Scenarios/SimpleCalcTests.cs index 06b15ed..7895647 100644 --- a/tests/Ramstack.Parsing.Tests/Scenarios/SimpleCalcTests.cs +++ b/tests/Ramstack.Parsing.Tests/Scenarios/SimpleCalcTests.cs @@ -66,7 +66,7 @@ public void SimpleCalcTest(string text, double number, string error = "") sum.Parser = Seq(product, Seq(S, OneOf("+-"), product).Many()).Do(Add); // Sum S EOF - var expression = sum.Before(Seq(S, Eof)); + var expression = sum.ThenIgnore(Seq(S, Eof)); static double Multiply(double v, ArrayList<(Unit, char, double)> results) { From 4b196901cc1ac3f099fe448025f143d13ade45ad Mon Sep 17 00:00:00 2001 From: rameel Date: Tue, 11 Feb 2025 02:43:06 +0500 Subject: [PATCH 2/9] fix: Apply Before to ThenIgnore rename --- .../LiteralTests.UnicodeEscapeSequence.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Ramstack.Parsing.Tests/LiteralTests.UnicodeEscapeSequence.cs b/tests/Ramstack.Parsing.Tests/LiteralTests.UnicodeEscapeSequence.cs index f95f619..7f9681f 100644 --- a/tests/Ramstack.Parsing.Tests/LiteralTests.UnicodeEscapeSequence.cs +++ b/tests/Ramstack.Parsing.Tests/LiteralTests.UnicodeEscapeSequence.cs @@ -19,7 +19,7 @@ partial class LiteralTests [TestCase(@"\uDCBA", '\uDCBA')] public void UnicodeEscapeSequenceTest(string input, char symbol) { - var parser = UnicodeEscapeSequence.Before(Eof); + var parser = UnicodeEscapeSequence.ThenIgnore(Eof); Assert.That(parser.Parse(input).Success, Is.True); Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(input).Value, Is.EqualTo((0, 6))); Assert.That(parser.Parse(input).Value, Is.EqualTo(symbol)); @@ -42,7 +42,7 @@ public void UnicodeEscapeSequenceTest(string input, char symbol) [TestCase(@"\u000я")] public void UnicodeEscapeSequence_InvalidInput(string input) { - var parser = UnicodeEscapeSequence.Before(Eof); + var parser = UnicodeEscapeSequence.ThenIgnore(Eof); Assert.That(parser.Parse(input).Success, Is.False); } } From dcef5868bd9e75be0398f0f66d0b7d3a1da5f3de Mon Sep 17 00:00:00 2001 From: rameel Date: Tue, 11 Feb 2025 03:58:44 +0500 Subject: [PATCH 3/9] Add `Tiny-C` parser --- samples/TinyC/Node.cs | 124 ++++++++++++ samples/TinyC/README.md | 149 ++++++++++++++ samples/TinyC/TinyCParser.cs | 219 +++++++++++++++++++++ samples/TinyC/examples/example1.c | 14 ++ samples/TinyC/examples/example2.c | 11 ++ samples/TinyC/examples/example3.c | 26 +++ samples/TinyC/examples/example4.c | 31 +++ samples/Utilities/IndentedStringBuilder.cs | 109 ++++++++++ 8 files changed, 683 insertions(+) create mode 100644 samples/TinyC/Node.cs create mode 100644 samples/TinyC/README.md create mode 100644 samples/TinyC/TinyCParser.cs create mode 100644 samples/TinyC/examples/example1.c create mode 100644 samples/TinyC/examples/example2.c create mode 100644 samples/TinyC/examples/example3.c create mode 100644 samples/TinyC/examples/example4.c create mode 100644 samples/Utilities/IndentedStringBuilder.cs diff --git a/samples/TinyC/Node.cs b/samples/TinyC/Node.cs new file mode 100644 index 0000000..3f759ad --- /dev/null +++ b/samples/TinyC/Node.cs @@ -0,0 +1,124 @@ +using Samples.Utilities; + +namespace Samples.TinyC; + +public abstract record Node +{ + public static Node Empty() => new BlockNode([]); + public static Node Number(int value) => new NumberNode(value); + public static Node Variable(string name) => new VariableNode(name); + public static Node If(Node test, Node ifTrue, Node ifFalse) => new IfNode(test, Wrap(ifTrue), Wrap(ifFalse)); + public static Node Ternary(Node test, Node ifTrue, Node ifFalse) => new TernaryNode(test, ifTrue, ifFalse); + public static Node WhileLoop(Node test, Node body) => new WhileLoopNode(test, Wrap(body)); + public static Node DoWhileLoop(Node test, Node body) => new DoWhileLoopNode(test, Wrap(body)); + public static Node Unary(char op, Node operand) => new UnaryNode(op, operand); + public static Node Binary(string op, Node left, Node right) => new BinaryNode(op, left, right); + public static Node Assign(Node variable, Node value) => Binary("=", variable, value); + public static Node Block(IReadOnlyList statements) => new BlockNode(statements); + private static Node Wrap(Node statement) => statement is BlockNode ? statement : new BlockNode([statement]); + + public sealed override string ToString() + { + var sb = new IndentedStringBuilder(); + Print(this, sb); + return sb.ToString(); + + static void Print(Node node, IndentedStringBuilder sb) + { + switch (node) + { + case VariableNode(Name: var name): + sb.Append(name); + break; + + case NumberNode(Value: var number): + sb.Append(number.ToString()); + break; + + case IfNode c: + sb.Append("if ("); + Print(c.Test, sb); + sb.Append(')'); + sb.AppendLine(); + + Print(c.IfTrue, sb); + + if (c.IfFalse is BlockNode { Statements: [IfNode @else] }) + { + sb.Append("else "); + Print(@else, sb); + } + else if (c.IfFalse is not BlockNode([])) + { + sb.Append("else"); + sb.AppendLine(); + Print(c.IfFalse, sb); + } + break; + + case TernaryNode c: + Print(c.Test, sb); + sb.Append(" ? ("); + Print(c.IfTrue, sb); + sb.Append(") : ("); + Print(c.IfFalse, sb); + sb.Append(')'); + break; + + case WhileLoopNode w: + sb.Append("while ("); + Print(w.Test, sb); + sb.Append(')'); + sb.AppendLine(); + Print(w.Body, sb); + break; + + case DoWhileLoopNode d: + sb.AppendLine("do"); + Print(d.Body, sb); + sb.Append("while ("); + Print(d.Test, sb); + sb.AppendLine(");"); + break; + + case UnaryNode u: + sb.Append(u.Operator); + Print(u.Operand, sb); + break; + + case BinaryNode b: + sb.Append('('); + Print(b.Left, sb); + sb.Append($" {b.Operator} "); + Print(b.Right, sb); + sb.Append(')'); + break; + + case BlockNode block: + sb.AppendLine("{"); + sb.IncrementIndent(); + + foreach (var stmt in block.Statements) + { + Print(stmt, sb); + if (stmt is NumberNode or VariableNode or UnaryNode or BinaryNode) + sb.AppendLine(";"); + } + + sb.DecrementIndent(); + sb.AppendLine("}"); + break; + } + } + } + + public sealed record VariableNode(string Name) : Node; + public sealed record NumberNode(int Value) : Node; + public sealed record IfNode(Node Test, Node IfTrue, Node IfFalse) : Node; + public sealed record TernaryNode(Node Test, Node IfTrue, Node IfFalse) : Node; + public sealed record WhileLoopNode(Node Test, Node Body) : Node; + public sealed record DoWhileLoopNode(Node Test, Node Body) : Node; + public sealed record UnaryNode(char Operator, Node Operand) : Node; + public sealed record BinaryNode(string Operator, Node Left, Node Right) : Node; + public sealed record BlockNode(IReadOnlyList Statements) : Node; +} diff --git a/samples/TinyC/README.md b/samples/TinyC/README.md new file mode 100644 index 0000000..9c39e58 --- /dev/null +++ b/samples/TinyC/README.md @@ -0,0 +1,149 @@ +# Tiny C + +This project implements a parser for the [TinyC](http://www.iro.umontreal.ca/~felipe/IFT2030-Automne2002/Complements/tinyc.c) language, a highly simplified version of `C` designed as an educational tool for learning about compilers. + +All variables are predefined, of integer type, and initialized to zero. + +The main differences from the original `Tiny-C` are: +- Variable names are not limited to single letters. +- Additional operators are supported. + +## Tiny-C Grammar + +The grammar for `Tiny-C` is as follows: + +```sh +start: + = statement S EOF + ; + +keywords + = ("while" / "do" / "if" / "else") ![\w] + ; + +number + = [0-9]+; + +variable + = !keywords [a-zA-Z_][a-zA-Z0-9_]*; + +eq + = S "=" + ; + +S + = [ \t\n\r]* + ; + +EOF + = !. + ; + +var_expr + = S variable + ; + +number_expr + = S number + ; + +expr + = assigment_expr + ; + +assigment_expr + = var_expr EQ expr + / ternary_expr + ; + +ternary_expr + = or_expr (S "?" expr S ":" ternary_expr)? + ; + +or_expr + = and_expr (S "||" and_expr)* + ; + +and_expr + = inlcusive_or_expr (S "&&" inlcusive_or_expr)* + ; + +inlcusive_or_expr + = exlcusive_or_expr (S "|" exlcusive_or_expr)* + ; + +exlcusive_or_expr + = binary_and_expr (S "^" binary_and_expr)* + ; + +binary_and_expr + = eq_expr (S "&" eq_expr)* + ; + +eq_expr + = relational_expr (S ("==" / "!=") relational_expr)* + ; + +relational_expr + = shift_expr (S ("<" / "<=" / ">" / ">=") shift_expr)* + ; + +shift_expr + = sum_expr (S ("<<" / ">>") sum_expr)* + ; + +sum_expr + = mul_expr (S [+-] mul_expr)* + ; + +mul_expr + = unary_expr (S [*/%] unary_expr)* + ; + +unary_expr + = [-+~!]? primary_expr + ; + +primary_expr + = parenthesis + / var_expr + / number_expr + ; + +parenthesis + = S "(" expr S ")" + ; + +statement + = if_statement + / while_statement + / do_while_statement + / block_statement + / expr_statement + / empty_statement + ; + +if_statement + = S "if" S "(" expr S ")" statement (S "else" statement)? + ; + +while_statement + = S "while" S "(" expr S ")" statement + ; + +do_while_statement + = S "do" statement S "while" S "(" expr S ")" S ";" + ; + +block_statement + = S "{" statement* S "}" + ; + +expr_statement + = expr S ";" + ; + +empty_statement + = S ";" + ; +``` diff --git a/samples/TinyC/TinyCParser.cs b/samples/TinyC/TinyCParser.cs new file mode 100644 index 0000000..fe7fd86 --- /dev/null +++ b/samples/TinyC/TinyCParser.cs @@ -0,0 +1,219 @@ +using System.Diagnostics.CodeAnalysis; + +using Ramstack.Parsing; + +using static Ramstack.Parsing.Parser; + +namespace Samples.TinyC; + +public static class TinyCParser +{ + public static readonly Parser Parser = CreateParser(); + + [SuppressMessage("ReSharper", "InconsistentNaming")] + private static Parser CreateParser() + { + var keywords = Seq( + OneOf("while", "do", "if", "else"), + Not(Set("\\w"))); + + var number = Set("0-9") + .OneOrMore() + .Map(Node (m) => Node.Number(int.Parse(m))) + .As("number"); + + var variable = Seq( + Not(keywords), + Set("a-z"), + Set("a-zA-Z_0-9").ZeroOrMore() + ).Map(Identifier).As("variable"); + + var semicolon = + Seq(S, L(';')).Void(); + + var eq = + Seq(S, L('=')).Void(); + + var if_keyword = + Seq(S, L("if")).Void(); + + var else_keyword = + Seq(S, L("else")).Void(); + + var while_keyword = + Seq(S, L("while")).Void(); + + var do_keyword = + Seq(S, L("do")).Void(); + + var expr = Deferred(); + + var number_expr = + S.Then(number); + + var var_expr = + S.Then(variable); + + var parenthesis = + expr.Between( + Seq(S, L('(')), + Seq(S, L(')'))); + + var primary_expr = + Choice( + parenthesis, + number_expr, + var_expr); + + var unary_expr = Seq( + S, + OneOf("-+~!").Optional(), + primary_expr + ).Do(CreateUnary); + + var mul_expr = unary_expr.Fold( + S.Then(OneOf("*/%")), + CreateBinary); + + var sum_expr = mul_expr.Fold( + S.Then(OneOf("+-")), + CreateBinary); + + var shift_expr = sum_expr.Fold( + S.Then(OneOf("<<", ">>")), + (l, r, o) => Node.Binary(o, l, r)); + + var relational_expr = shift_expr.Fold( + S.Then(OneOf("<", "<=", ">", ">=")), + (l, r, o) => Node.Binary(o, l, r)); + + var eq_expr = relational_expr.Fold( + S.Then(OneOf("==", "!=")), + (l, r, o) => Node.Binary(o, l, r)); + + var binary_and_expr = eq_expr.Fold( + S.Then(L('&')), + (l, r, _) => Node.Binary("&", l, r)); + + var exclusive_or_expr = binary_and_expr.Fold( + S.Then(L('^')), + (l, r, _) => Node.Binary("^", l, r)); + + var inclusive_or_expr = exclusive_or_expr.Fold( + S.Then(L('|')), + (l, r, _) => Node.Binary("|", l, r)); + + var and_expr = inclusive_or_expr.Fold( + S.Then(L("&&")), + (l, r, _) => Node.Binary("&&", l, r)); + + var or_expr = and_expr.Fold( + S.Then(L("||")), + (l, r, _) => Node.Binary("||", l, r)); + + var ternary_expr = Deferred(); + ternary_expr.Parser = + Seq( + or_expr, + Seq( + S, L('?'), expr, + S, L(':'), ternary_expr + ).Optional()) + .Do(CreateTernary); + + var assignment_expr = + Choice( + Seq(var_expr, eq, expr).Do(CreateAssign), + ternary_expr); + + expr.Parser = assignment_expr; + + var statement = Deferred(); + + var else_clause = + Seq( + else_keyword, + statement + ).Do((_, s) => s).DefaultOnFail(Node.Empty()); + + var if_statement = + Seq( + if_keyword, + parenthesis, + statement, + else_clause + ).Do(CreateIf); + + var block_statement = + statement + .ZeroOrMore() + .Between( + Seq(S, L('{')), + Seq(S, L('}'))) + .Do(CreateBlock); + + var empty_statement = + Seq(S, L(';') + ).Map(_ => Node.Empty()); + + var while_statement = + Seq( + while_keyword, + parenthesis, + statement + ).Do(CreateWhile); + + var do_while_statement = + Seq( + do_keyword, + statement, + while_keyword, + parenthesis, + semicolon + ).Do(CreateDoWhile); + + var expr_statement = + expr.ThenIgnore(semicolon); + + statement.Parser = Choice( + if_statement, + while_statement, + do_while_statement, + block_statement, + expr_statement, + empty_statement + ); + + return statement + .ThenIgnore(Seq(S, Eof)); + + static Node Identifier(Match m) => + Node.Variable(m.ToString()); + + static Node CreateUnary(Unit _, OptionalValue u, Node operand) => + u.HasValue ? Node.Unary(u.Value, operand) : operand; + + static Node CreateBinary(Node l, Node r, char o) => + Node.Binary(new string(o, 1), l, r); + + static Node CreateAssign(Node variable, Unit _, Node value) => + Node.Assign(variable, value); + + static Node CreateTernary(Node expression, OptionalValue<(Unit _1, char _2, Node @true, Unit _4, char _5, Node @false)> optional) => + optional.HasValue + ? Node.Ternary(expression, optional.Value.@true, optional.Value.@false) + : expression; + + static Node CreateIf(Unit _, Node expression, Node @true, Node @false) => + Node.If(expression, @true, @false); + + static Node CreateWhile(Unit _, Node expression, Node body) => + Node.WhileLoop(expression, body); + + static Node CreateDoWhile(Unit _0, Node body, Unit _2, Node expression, Unit _4) => + Node.DoWhileLoop(expression, body); + + static Node CreateBlock(IReadOnlyList statements) => + Node.Block(statements); + } +} diff --git a/samples/TinyC/examples/example1.c b/samples/TinyC/examples/example1.c new file mode 100644 index 0000000..92d4f10 --- /dev/null +++ b/samples/TinyC/examples/example1.c @@ -0,0 +1,14 @@ +{ + x = 5; + fact = 1; + + if (x > 0) + { + do + { + fact = fact * x; + x = x - 1; + } + while (x != 0); + } +} diff --git a/samples/TinyC/examples/example2.c b/samples/TinyC/examples/example2.c new file mode 100644 index 0000000..2a097b0 --- /dev/null +++ b/samples/TinyC/examples/example2.c @@ -0,0 +1,11 @@ +{ + i = 100; + while (i) + { + i = i - 1; + } + + i = 0; + while (i * 2 < 100) + i = i + 1; +} diff --git a/samples/TinyC/examples/example3.c b/samples/TinyC/examples/example3.c new file mode 100644 index 0000000..3b76edf --- /dev/null +++ b/samples/TinyC/examples/example3.c @@ -0,0 +1,26 @@ +if (i > 100) +{ + if (j == 0) + a = 1; + else + a = 2; +} +else if (i < 100) +{ + if (j == 0) a = 10; + else if (j == 1) a = 20; + else if (j == 2) a = 30; + else if (j == 3) a = 40; + else if (j == 4) a = 50; + else if (j == 5) a = 60; + else a = 70; +} +else +{ + b = j == 0 ? 10 : + j == 1 ? 20 : + j == 2 ? 30 : + j == 3 ? 40 : + j == 4 ? 50 : + j == 5 ? 60 : 70; +} diff --git a/samples/TinyC/examples/example4.c b/samples/TinyC/examples/example4.c new file mode 100644 index 0000000..d614b20 --- /dev/null +++ b/samples/TinyC/examples/example4.c @@ -0,0 +1,31 @@ +{ + index = a = b = c = d = 0; + + { + d = 199; + + ; + ; + ; + } + + j = 128; + i = 0; + + if (j * 2 == 128 << 1) + { + do + { + i = i + 1; + j = j - 1; + } + while (i <= j); + } + + x = !0; + y = !1; + z = ~x + ~y ? a : b; + n = z + ? a < 0 ? b + 100 * c : c % 100 + 1 + : b > 0 ? x : (y = 23 & -7); +} diff --git a/samples/Utilities/IndentedStringBuilder.cs b/samples/Utilities/IndentedStringBuilder.cs new file mode 100644 index 0000000..37b9d70 --- /dev/null +++ b/samples/Utilities/IndentedStringBuilder.cs @@ -0,0 +1,109 @@ +using System.Text; + +namespace Samples.Utilities; + +internal sealed class IndentedStringBuilder +{ + private int _indent; + private bool _indentPending = true; + + private readonly StringBuilder _sb = new StringBuilder(); + + public int Length => _sb.Length; + + public IndentedStringBuilder Append(string value) + { + DoIndent(); + _sb.Append(value); + return this; + } + + public IndentedStringBuilder Append(FormattableString value) + { + DoIndent(); + _sb.Append(value); + return this; + } + + public IndentedStringBuilder Append(char value) + { + DoIndent(); + _sb.Append(value); + return this; + } + + public IndentedStringBuilder AppendLine() + { + AppendLine(string.Empty); + return this; + } + + public IndentedStringBuilder AppendLine(string value) + { + if (value.Length != 0) + DoIndent(); + + _sb.AppendLine(value); + _indentPending = true; + return this; + } + + public IndentedStringBuilder AppendLine(FormattableString value) + { + DoIndent(); + _sb.Append(value); + _indentPending = true; + return this; + } + + public IndentedStringBuilder Clear() + { + _sb.Clear(); + _indentPending = true; + _indent = 0; + + return this; + } + + public IndentedStringBuilder IncrementIndent() + { + _indent++; + return this; + } + + public IndentedStringBuilder DecrementIndent() + { + if (_indent > 0) + _indent--; + + return this; + } + + public IDisposable Indent() => + new Indenter(this); + + public override string ToString() => + _sb.ToString(); + + private void DoIndent() + { + if (_indentPending && _indent > 0) + _sb.Append(' ', _indent * 4); + + _indentPending = false; + } + + private sealed class Indenter : IDisposable + { + private readonly IndentedStringBuilder _sb; + + public Indenter(IndentedStringBuilder sb) + { + _sb = sb; + _sb.IncrementIndent(); + } + + public void Dispose() => + _sb.DecrementIndent(); + } +} From a72eac514a0d761c2a6d1662103094b883c7643c Mon Sep 17 00:00:00 2001 From: rameel Date: Tue, 11 Feb 2025 03:59:08 +0500 Subject: [PATCH 4/9] Clean up --- src/Ramstack.Parsing/DeferredParser.cs | 4 ++-- src/Ramstack.Parsing/Parser.Deferred.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Ramstack.Parsing/DeferredParser.cs b/src/Ramstack.Parsing/DeferredParser.cs index 92e0c56..8cf26a5 100644 --- a/src/Ramstack.Parsing/DeferredParser.cs +++ b/src/Ramstack.Parsing/DeferredParser.cs @@ -21,8 +21,8 @@ internal DeferredParser() => /// using the specified function to define the parser. /// /// A function that accepts a reference to this deferred parser and returns the resulting parser. - internal DeferredParser(Func, Parser> parser) => - Parser = parser(this); + internal DeferredParser(Func, Parser> parser) => + Parser = parser(this) ?? throw new InvalidOperationException("The deferred parser has not been initialized."); /// public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) => diff --git a/src/Ramstack.Parsing/Parser.Deferred.cs b/src/Ramstack.Parsing/Parser.Deferred.cs index 1c9f650..c58c831 100644 --- a/src/Ramstack.Parsing/Parser.Deferred.cs +++ b/src/Ramstack.Parsing/Parser.Deferred.cs @@ -20,6 +20,6 @@ public static DeferredParser Deferred() => /// /// A parser that can be recursively defined. /// - public static Parser Recursive(Func, Parser> parser) => + public static Parser Recursive(Func, Parser> parser) => new DeferredParser(parser); } From b1e54746e37228427bcda2f0d8cfcceeabddab64 Mon Sep 17 00:00:00 2001 From: rameel Date: Tue, 11 Feb 2025 03:59:25 +0500 Subject: [PATCH 5/9] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 37826e0..68b22b2 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ private static Parser CreateParser() // Primary : Parenthesis / Value // Parenthesis : S '(' Sum S ')' // Value : S Number - // S : Whitespace* + // S : [ \r\n\t]* var sum = Deferred(); var value = S.Then(Literal.Number()); From c9d96f6c26ec6fc317647021a7309e4460adbc8b Mon Sep 17 00:00:00 2001 From: rameel Date: Tue, 11 Feb 2025 04:06:44 +0500 Subject: [PATCH 6/9] Clean up --- samples/TinyC/README.md | 14 +++++--------- samples/TinyC/TinyCParser.cs | 4 ++-- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/samples/TinyC/README.md b/samples/TinyC/README.md index 9c39e58..67bc365 100644 --- a/samples/TinyC/README.md +++ b/samples/TinyC/README.md @@ -1,6 +1,6 @@ -# Tiny C +# Tiny-C -This project implements a parser for the [TinyC](http://www.iro.umontreal.ca/~felipe/IFT2030-Automne2002/Complements/tinyc.c) language, a highly simplified version of `C` designed as an educational tool for learning about compilers. +This project implements a parser for the [Tiny-C](http://www.iro.umontreal.ca/~felipe/IFT2030-Automne2002/Complements/tinyc.c) language, a highly simplified version of `C` designed as an educational tool for learning about compilers. All variables are predefined, of integer type, and initialized to zero. @@ -17,7 +17,7 @@ start: = statement S EOF ; -keywords +keyword = ("while" / "do" / "if" / "else") ![\w] ; @@ -25,11 +25,7 @@ number = [0-9]+; variable - = !keywords [a-zA-Z_][a-zA-Z0-9_]*; - -eq - = S "=" - ; + = !keyword [a-zA-Z_][a-zA-Z0-9_]*; S = [ \t\n\r]* @@ -52,7 +48,7 @@ expr ; assigment_expr - = var_expr EQ expr + = var_expr S "=" expr / ternary_expr ; diff --git a/samples/TinyC/TinyCParser.cs b/samples/TinyC/TinyCParser.cs index fe7fd86..95c55a2 100644 --- a/samples/TinyC/TinyCParser.cs +++ b/samples/TinyC/TinyCParser.cs @@ -13,7 +13,7 @@ public static class TinyCParser [SuppressMessage("ReSharper", "InconsistentNaming")] private static Parser CreateParser() { - var keywords = Seq( + var keyword = Seq( OneOf("while", "do", "if", "else"), Not(Set("\\w"))); @@ -23,7 +23,7 @@ private static Parser CreateParser() .As("number"); var variable = Seq( - Not(keywords), + Not(keyword), Set("a-z"), Set("a-zA-Z_0-9").ZeroOrMore() ).Map(Identifier).As("variable"); From f89e44095f37b0d5bf03500d99d0c80aa4b96ed0 Mon Sep 17 00:00:00 2001 From: rameel Date: Tue, 11 Feb 2025 04:47:55 +0500 Subject: [PATCH 7/9] Add README --- benchmarks/Parsers/RamstackParsers.cs | 4 +- samples/CalcExpr/ExpressionParser.cs | 49 ++++++++++++++++++ samples/CalcExpr/README.md | 43 ++++++++++++++++ samples/ExpressionParser.cs | 50 ------------------- samples/{ => Json}/JsonParser.cs | 27 +++++++--- samples/Json/README.md | 39 +++++++++++++++ samples/TinyC/README.md | 4 +- .../Scenarios/JsonTests.cs | 2 +- 8 files changed, 154 insertions(+), 64 deletions(-) create mode 100644 samples/CalcExpr/ExpressionParser.cs create mode 100644 samples/CalcExpr/README.md delete mode 100644 samples/ExpressionParser.cs rename samples/{ => Json}/JsonParser.cs (65%) create mode 100644 samples/Json/README.md diff --git a/benchmarks/Parsers/RamstackParsers.cs b/benchmarks/Parsers/RamstackParsers.cs index 04668d2..d7946d9 100644 --- a/benchmarks/Parsers/RamstackParsers.cs +++ b/benchmarks/Parsers/RamstackParsers.cs @@ -7,8 +7,8 @@ public static class RamstackParsers { public static readonly Parser EmailParser = CreateEmailParser(); public static readonly Parser EmailVoidParser = EmailParser.Void(); - public static readonly Parser ExpressionParser = Samples.ExpressionParser.Parser; - public static readonly Parser JsonParser = Samples.JsonParser.Parser; + public static readonly Parser ExpressionParser = Samples.CalcExpr.ExpressionParser.Parser; + public static readonly Parser JsonParser = Samples.Json.JsonParser.Parser; private static Parser CreateEmailParser() { diff --git a/samples/CalcExpr/ExpressionParser.cs b/samples/CalcExpr/ExpressionParser.cs new file mode 100644 index 0000000..c24f001 --- /dev/null +++ b/samples/CalcExpr/ExpressionParser.cs @@ -0,0 +1,49 @@ +using System.Diagnostics.CodeAnalysis; + +using Ramstack.Parsing; + +using static Ramstack.Parsing.Parser; + +namespace Samples.CalcExpr; + +public static class ExpressionParser +{ + public static readonly Parser Parser = CreateExpressionParser(); + + [SuppressMessage("ReSharper", "InconsistentNaming")] + private static Parser CreateExpressionParser() + { + var sum_expr = + Deferred(); + + var number_expr = + S.Then(Literal.Number("number")); + + var parenthesis_expr = + sum_expr.Between( + Seq(S, L('(')), + Seq(S, L(')'))); + + var primary_expr = + parenthesis_expr.Or(number_expr); + + var unary_expr = + Seq( + S, + L('-').Optional(), + primary_expr + ).Do((_, u, d) => u.HasValue ? -d : d); + + var mul_expr = + unary_expr.Fold( + S.Then(OneOf("*/")), + (l, r, o) => o == '*' ? l * r : l / r); + + sum_expr.Parser = + mul_expr.Fold( + S.Then(OneOf("+-")), + (l, r, o) => o == '+' ? l + r : l - r); + + return sum_expr.ThenIgnore(Eof); + } +} diff --git a/samples/CalcExpr/README.md b/samples/CalcExpr/README.md new file mode 100644 index 0000000..cebaa03 --- /dev/null +++ b/samples/CalcExpr/README.md @@ -0,0 +1,43 @@ +# Simple Calc + +This project implements a simple mathematical expression parser. + +## Simple Expression Grammar + +```sh +start + = sum_expr EOF + ; + +sum_expr + = mul_expr (S [+-] mul_expr)* + ; + +mul_expr + = unary_expr (S [*/] unary_expr)* + ; + +unary_expr + = S "-"? primary_expr + ; + +primary_expr + = parenthesis_expr / number_expr + ; + +parenthesis_expr + = S "(" Sum S ")" + ; + +number_expr + = S [0-9]+ + ; + +S + = [ \t\r\n]* + ; + +EOF: + = $ + ; +``` diff --git a/samples/ExpressionParser.cs b/samples/ExpressionParser.cs deleted file mode 100644 index ac38da8..0000000 --- a/samples/ExpressionParser.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Ramstack.Parsing; - -using static Ramstack.Parsing.Parser; - -namespace Samples; - -public static class ExpressionParser -{ - public static readonly Parser Parser = CreateExpressionParser(); - - private static Parser CreateExpressionParser() - { - // Grammar: - // ---------------------------------------- - // Start : Sum $ - // Sum : Product (S [+-] Product)* - // Product : Unary (S [*/] Unary)* - // Unary : S '-'? Primary - // Primary : Parenthesis / Value - // Parenthesis : S '(' Sum S ')' - // Value : S Number - // S : Whitespace* - - var sum = Deferred(); - var number = S.Then(Literal.Number("number")); - - var parenthesis = sum.Between( - Seq(S, L('(')), - Seq(S, L(')')) - ); - - var primary = parenthesis.Or(number); - - var unary = Seq( - S, - L('-').Optional(), - primary - ).Do((_, u, d) => u.HasValue ? -d : d); - - var product = unary.Fold( - S.Then(OneOf("*/")), - (l, r, o) => o == '*' ? l * r : l / r); - - sum.Parser = product.Fold( - S.Then(OneOf("+-")), - (l, r, o) => o == '+' ? l + r : l - r); - - return sum.ThenIgnore(Eof); - } -} diff --git a/samples/JsonParser.cs b/samples/Json/JsonParser.cs similarity index 65% rename from samples/JsonParser.cs rename to samples/Json/JsonParser.cs index aa2526a..3e141bd 100644 --- a/samples/JsonParser.cs +++ b/samples/Json/JsonParser.cs @@ -1,9 +1,9 @@ -using Ramstack.Parsing; +using Ramstack.Parsing; using static Ramstack.Parsing.Literal; using static Ramstack.Parsing.Parser; -namespace Samples; +namespace Samples.Json; public static class JsonParser { @@ -11,17 +11,20 @@ public static class JsonParser private static Parser CreateJsonParser() { - var value = Deferred(); + var value = + Deferred(); - var text = DoubleQuotedString.Do(object? (s) => s); - var number = Number().Do(object? (n) => n); + var @string = + DoubleQuotedString.Do(object? (s) => s); + + var number = + Number().Do(object? (n) => n); var primitive = OneOf(["true", "false", "null"]).Do(s => { object? r = null; if (s.Length != 0 && s[0] != 'n') r = s[0] == 't'; - return r; }); @@ -36,14 +39,22 @@ public static class JsonParser S, DoubleQuotedString, S, L(':'), value ).Do((_, name, _, _, v) => KeyValuePair.Create(name, v)); - var map = member + var @object = member .Separated(Seq(S, L(','))) .Between( L('{'), Seq(S, L('}'))) .Do(object? (members) => new Dictionary(members)); - value.Parser = S.Then(Choice(text, number, primitive, array, map)); + value.Parser = + S.Then( + Choice( + @string, + number, + primitive, + array, + @object)); + return value; } } diff --git a/samples/Json/README.md b/samples/Json/README.md new file mode 100644 index 0000000..d9aab8e --- /dev/null +++ b/samples/Json/README.md @@ -0,0 +1,39 @@ +# JSON Parser + +This project implements a simple JSON parser. + +## JSON Grammar + +```sh +start + = value $ + ; + +value + = S ( + object + / array + / string + / number + / "true" + / "false" + / "null" + ) + ; + +object + = S "{" (member (S "," S member)*)? S "}" + ; + +member + = string S ":" value + ; + +array + = S "[" (value (S "," value)*)? S "]" + ; + +S + = [ \t\r\n]* + ; +``` diff --git a/samples/TinyC/README.md b/samples/TinyC/README.md index 67bc365..6832472 100644 --- a/samples/TinyC/README.md +++ b/samples/TinyC/README.md @@ -10,8 +10,6 @@ The main differences from the original `Tiny-C` are: ## Tiny-C Grammar -The grammar for `Tiny-C` is as follows: - ```sh start: = statement S EOF @@ -32,7 +30,7 @@ S ; EOF - = !. + = $ ; var_expr diff --git a/tests/Ramstack.Parsing.Tests/Scenarios/JsonTests.cs b/tests/Ramstack.Parsing.Tests/Scenarios/JsonTests.cs index 88f7816..32c761c 100644 --- a/tests/Ramstack.Parsing.Tests/Scenarios/JsonTests.cs +++ b/tests/Ramstack.Parsing.Tests/Scenarios/JsonTests.cs @@ -1,6 +1,6 @@ using System.Text.Json; -using Samples; +using Samples.Json; namespace Ramstack.Parsing.Scenarios; From e5222e8b82c06e2a3139a96e4787bae8ae6dd336 Mon Sep 17 00:00:00 2001 From: rameel Date: Tue, 11 Feb 2025 05:43:50 +0500 Subject: [PATCH 8/9] Clean up --- README.md | 28 ++++---- samples/CalcExpr/ExpressionParser.cs | 16 ++--- samples/Json/JsonParser.cs | 34 ++++----- samples/Json/README.md | 16 ++--- samples/TinyC/README.md | 50 ++++++------- samples/TinyC/TinyCParser.cs | 54 +++++++------- .../Scenarios/SimpleCalcTests.cs | 72 ++++++++++++------- 7 files changed, 140 insertions(+), 130 deletions(-) diff --git a/README.md b/README.md index 68b22b2..d0c7dd8 100644 --- a/README.md +++ b/README.md @@ -22,40 +22,40 @@ private static Parser CreateParser() { // Grammar: // ---------------------------------------- - // Start : Sum $ - // Sum : Product (S [+-] Product)* - // Product : Unary (S [*/] Unary)* - // Unary : S '-'? Primary + // Start : S Sum $ + // Sum : Product ([+-] S Product)* + // Product : Unary ([*/] S Unary)* + // Unary : '-'? S Primary // Primary : Parenthesis / Value - // Parenthesis : S '(' Sum S ')' - // Value : S Number + // Parenthesis : '(' S Sum ')' S + // Value : Number S // S : [ \r\n\t]* var sum = Deferred(); - var value = S.Then(Literal.Number()); + var value = Literal.Number().ThenIgnore(S); var parenthesis = sum.Between( - Seq(S, L('(')), - Seq(S, L(')')) + Seq(L('('), S), + Seq(L(')'), S) ); var primary = parenthesis.Or(value); var unary = Seq( - S, L('-').Optional(), + S, primary - ).Do((_, u, d) => u.HasValue ? -d : d); + ).Do((u, _, d) => u.HasValue ? -d : d); var product = unary.Fold( - S.Then(OneOf("*/")), + OneOf("*/").ThenIgnore(S), (l, r, op) => op == '*' ? l * r : l / r); sum.Parser = product.Fold( - S.Then(OneOf("+-")), + OneOf("+-").ThenIgnore(S), (l, r, op) => op == '+' ? l + r : l - r); - return sum.ThenIgnore(Eof); + return sum.Between(S, Eof); } ``` diff --git a/samples/CalcExpr/ExpressionParser.cs b/samples/CalcExpr/ExpressionParser.cs index c24f001..0adc04c 100644 --- a/samples/CalcExpr/ExpressionParser.cs +++ b/samples/CalcExpr/ExpressionParser.cs @@ -17,33 +17,33 @@ private static Parser CreateExpressionParser() Deferred(); var number_expr = - S.Then(Literal.Number("number")); + Literal.Number("number").ThenIgnore(S); var parenthesis_expr = sum_expr.Between( - Seq(S, L('(')), - Seq(S, L(')'))); + Seq(L('('), S), + Seq(L(')'), S)); var primary_expr = parenthesis_expr.Or(number_expr); var unary_expr = Seq( - S, L('-').Optional(), + S, primary_expr - ).Do((_, u, d) => u.HasValue ? -d : d); + ).Do((u, _, d) => u.HasValue ? -d : d); var mul_expr = unary_expr.Fold( - S.Then(OneOf("*/")), + OneOf("*/").ThenIgnore(S), (l, r, o) => o == '*' ? l * r : l / r); sum_expr.Parser = mul_expr.Fold( - S.Then(OneOf("+-")), + OneOf("+-").ThenIgnore(S), (l, r, o) => o == '+' ? l + r : l - r); - return sum_expr.ThenIgnore(Eof); + return sum_expr.Between(S, Eof); } } diff --git a/samples/Json/JsonParser.cs b/samples/Json/JsonParser.cs index 3e141bd..2ade580 100644 --- a/samples/Json/JsonParser.cs +++ b/samples/Json/JsonParser.cs @@ -29,32 +29,32 @@ public static class JsonParser }); var array = value - .Separated(Seq(S, L(','))) + .Separated(Seq(L(','), S)) .Between( - L('['), - Seq(S, L(']'))) + Seq(L('['), S), + Seq(L(']'), S)) .Do(object? (list) => list); var member = Seq( - S, DoubleQuotedString, S, L(':'), value - ).Do((_, name, _, _, v) => KeyValuePair.Create(name, v)); + DoubleQuotedString, S, L(':'), S, value + ).Do((name, _, _, _, v) => KeyValuePair.Create(name, v)); var @object = member - .Separated(Seq(S, L(','))) + .Separated(Seq(L(','), S)) .Between( - L('{'), - Seq(S, L('}'))) + Seq(L('{'), S), + Seq(L('}'), S)) .Do(object? (members) => new Dictionary(members)); value.Parser = - S.Then( - Choice( - @string, - number, - primitive, - array, - @object)); - - return value; + Choice( + @string, + number, + primitive, + array, + @object + ).ThenIgnore(S); + + return S.Then(value); } } diff --git a/samples/Json/README.md b/samples/Json/README.md index d9aab8e..f6cf9ee 100644 --- a/samples/Json/README.md +++ b/samples/Json/README.md @@ -10,27 +10,19 @@ start ; value - = S ( - object - / array - / string - / number - / "true" - / "false" - / "null" - ) + = (object / array / string / number / "true" / "false" / "null") S ; object - = S "{" (member (S "," S member)*)? S "}" + = "{" S (member ("," S member)*)? "}" S ; member - = string S ":" value + = string ":" S value ; array - = S "[" (value (S "," value)*)? S "]" + = "[" S (value ("," S value)*)? "]" S ; S diff --git a/samples/TinyC/README.md b/samples/TinyC/README.md index 6832472..1493dc1 100644 --- a/samples/TinyC/README.md +++ b/samples/TinyC/README.md @@ -12,7 +12,7 @@ The main differences from the original `Tiny-C` are: ```sh start: - = statement S EOF + = S statement EOF ; keyword @@ -34,11 +34,11 @@ EOF ; var_expr - = S variable + = variable S ; number_expr - = S number + = number S ; expr @@ -46,56 +46,56 @@ expr ; assigment_expr - = var_expr S "=" expr + = var_expr "=" S expr / ternary_expr ; ternary_expr - = or_expr (S "?" expr S ":" ternary_expr)? + = or_expr ("?" S expr ":" S ternary_expr)? ; or_expr - = and_expr (S "||" and_expr)* + = and_expr ("||" S and_expr)* ; and_expr - = inlcusive_or_expr (S "&&" inlcusive_or_expr)* + = inclusive_or_expr ("&&" S inclusive_or_expr)* ; -inlcusive_or_expr - = exlcusive_or_expr (S "|" exlcusive_or_expr)* +inclusive_or_expr + = exclusive_or_expr ("|" S exclusive_or_expr)* ; -exlcusive_or_expr - = binary_and_expr (S "^" binary_and_expr)* +exclusive_or_expr + = binary_and_expr ("^" S binary_and_expr)* ; binary_and_expr - = eq_expr (S "&" eq_expr)* + = eq_expr ("&" S eq_expr)* ; eq_expr - = relational_expr (S ("==" / "!=") relational_expr)* + = relational_expr (("==" / "!=") S relational_expr)* ; relational_expr - = shift_expr (S ("<" / "<=" / ">" / ">=") shift_expr)* + = shift_expr (("<" / "<=" / ">" / ">=") S shift_expr)* ; shift_expr - = sum_expr (S ("<<" / ">>") sum_expr)* + = sum_expr (("<<" / ">>") S sum_expr)* ; sum_expr - = mul_expr (S [+-] mul_expr)* + = mul_expr ([+-] S mul_expr)* ; mul_expr - = unary_expr (S [*/%] unary_expr)* + = unary_expr ([*/%] S unary_expr)* ; unary_expr - = [-+~!]? primary_expr + = [-+~!]? S primary_expr ; primary_expr @@ -105,7 +105,7 @@ primary_expr ; parenthesis - = S "(" expr S ")" + = "(" S expr ")" S ; statement @@ -118,26 +118,26 @@ statement ; if_statement - = S "if" S "(" expr S ")" statement (S "else" statement)? + = "if" S "(" S expr ")" S statement ("else" S statement)? ; while_statement - = S "while" S "(" expr S ")" statement + = "while" S "(" S expr ")" S statement ; do_while_statement - = S "do" statement S "while" S "(" expr S ")" S ";" + = "do" S statement "while" S "(" S expr ")" S ";" S ; block_statement - = S "{" statement* S "}" + = "{" S statement* "}" S ; expr_statement - = expr S ";" + = expr ";" S ; empty_statement - = S ";" + = ";" S ; ``` diff --git a/samples/TinyC/TinyCParser.cs b/samples/TinyC/TinyCParser.cs index 95c55a2..d9a805c 100644 --- a/samples/TinyC/TinyCParser.cs +++ b/samples/TinyC/TinyCParser.cs @@ -29,35 +29,35 @@ private static Parser CreateParser() ).Map(Identifier).As("variable"); var semicolon = - Seq(S, L(';')).Void(); + Seq(L(';'), S).Void(); var eq = - Seq(S, L('=')).Void(); + Seq(L('='), S).Void(); var if_keyword = - Seq(S, L("if")).Void(); + Seq(L("if"), S).Void(); var else_keyword = - Seq(S, L("else")).Void(); + Seq(L("else"), S).Void(); var while_keyword = - Seq(S, L("while")).Void(); + Seq(L("while"), S).Void(); var do_keyword = - Seq(S, L("do")).Void(); + Seq(L("do"), S).Void(); var expr = Deferred(); var number_expr = - S.Then(number); + number.ThenIgnore(S); var var_expr = - S.Then(variable); + variable.ThenIgnore(S); var parenthesis = expr.Between( - Seq(S, L('(')), - Seq(S, L(')'))); + Seq(L('('), S), + Seq(L(')'), S)); var primary_expr = Choice( @@ -72,43 +72,43 @@ private static Parser CreateParser() ).Do(CreateUnary); var mul_expr = unary_expr.Fold( - S.Then(OneOf("*/%")), + OneOf("*/%").ThenIgnore(S), CreateBinary); var sum_expr = mul_expr.Fold( - S.Then(OneOf("+-")), + OneOf("+-").ThenIgnore(S), CreateBinary); var shift_expr = sum_expr.Fold( - S.Then(OneOf("<<", ">>")), + OneOf("<<", ">>").ThenIgnore(S), (l, r, o) => Node.Binary(o, l, r)); var relational_expr = shift_expr.Fold( - S.Then(OneOf("<", "<=", ">", ">=")), + OneOf("<", "<=", ">", ">=").ThenIgnore(S), (l, r, o) => Node.Binary(o, l, r)); var eq_expr = relational_expr.Fold( - S.Then(OneOf("==", "!=")), + OneOf("==", "!=").ThenIgnore(S), (l, r, o) => Node.Binary(o, l, r)); var binary_and_expr = eq_expr.Fold( - S.Then(L('&')), + L('&').ThenIgnore(S), (l, r, _) => Node.Binary("&", l, r)); var exclusive_or_expr = binary_and_expr.Fold( - S.Then(L('^')), + L('^').ThenIgnore(S), (l, r, _) => Node.Binary("^", l, r)); var inclusive_or_expr = exclusive_or_expr.Fold( - S.Then(L('|')), + L('|').ThenIgnore(S), (l, r, _) => Node.Binary("|", l, r)); var and_expr = inclusive_or_expr.Fold( - S.Then(L("&&")), + L("&&").ThenIgnore(S), (l, r, _) => Node.Binary("&&", l, r)); var or_expr = and_expr.Fold( - S.Then(L("||")), + L("||").ThenIgnore(S), (l, r, _) => Node.Binary("||", l, r)); var ternary_expr = Deferred(); @@ -116,8 +116,8 @@ private static Parser CreateParser() Seq( or_expr, Seq( - S, L('?'), expr, - S, L(':'), ternary_expr + L('?'), S, expr, + L(':'), S, ternary_expr ).Optional()) .Do(CreateTernary); @@ -148,12 +148,12 @@ private static Parser CreateParser() statement .ZeroOrMore() .Between( - Seq(S, L('{')), - Seq(S, L('}'))) + Seq(L('{'), S), + Seq(L('}'), S)) .Do(CreateBlock); var empty_statement = - Seq(S, L(';') + Seq(L(';'), S ).Map(_ => Node.Empty()); var while_statement = @@ -185,7 +185,7 @@ private static Parser CreateParser() ); return statement - .ThenIgnore(Seq(S, Eof)); + .Between(S, Eof); static Node Identifier(Match m) => Node.Variable(m.ToString()); @@ -199,7 +199,7 @@ static Node CreateBinary(Node l, Node r, char o) => static Node CreateAssign(Node variable, Unit _, Node value) => Node.Assign(variable, value); - static Node CreateTernary(Node expression, OptionalValue<(Unit _1, char _2, Node @true, Unit _4, char _5, Node @false)> optional) => + static Node CreateTernary(Node expression, OptionalValue<(char _1, Unit _2, Node @true, char _4, Unit _5, Node @false)> optional) => optional.HasValue ? Node.Ternary(expression, optional.Value.@true, optional.Value.@false) : expression; diff --git a/tests/Ramstack.Parsing.Tests/Scenarios/SimpleCalcTests.cs b/tests/Ramstack.Parsing.Tests/Scenarios/SimpleCalcTests.cs index 7895647..6e42a73 100644 --- a/tests/Ramstack.Parsing.Tests/Scenarios/SimpleCalcTests.cs +++ b/tests/Ramstack.Parsing.Tests/Scenarios/SimpleCalcTests.cs @@ -36,49 +36,67 @@ public class SimpleCalcTests [TestCase("()", 0, "(1:2) Expected [0-9] or '('")] public void SimpleCalcTest(string text, double number, string error = "") { - // Expr : Sum S EOF - // Sum : Product (S [+-] Product)* - // Product : Primary (S [*/] Primary)* + // Expr : S Sum EOF + // Sum : Product ([+-] S Product)* + // Product : Primary ([*/] S Primary)* // Primary : Parenthesis / Value - // Parenthesis : S '(' Sum S ')' - // Value : S Number + // Parenthesis : '(' S Sum ')' S + // Value : Number S var sum = Deferred(); - // S ['0'..'9']+ ('.' ['0'..'9']+)? - var digit = Set("0-9"); - var value = S.Then( + var digit = + Set("0-9"); + + var value = Seq( digit.OneOrMore(), - Seq(L('.'), digit.OneOrMore()).Optional()) - ).Map(m => double.Parse(m, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture)); - - // S '(' Sum S ')' - var parenthesis = sum.Between(Seq(S, L('(')), Seq(S, L(')'))); - - // value / parenthesis - var primary = Choice(value, parenthesis); + Seq(L('.'), digit.OneOrMore()).Optional() + ).ThenIgnore(S).Map(m => + double.Parse(m, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture)); - // Primary (S [*/] Primary)* - var product = Seq(primary, Seq(S, OneOf("*/"), primary).Many()).Do(Multiply); + var parenthesis = + sum.Between( + Seq(L('('), S), + Seq(L(')'), S)); - // Product (S [+-] Product)* - sum.Parser = Seq(product, Seq(S, OneOf("+-"), product).Many()).Do(Add); + var primary = + Choice(value, parenthesis); - // Sum S EOF - var expression = sum.ThenIgnore(Seq(S, Eof)); - - static double Multiply(double v, ArrayList<(Unit, char, double)> results) + var product = + Seq( + primary, + Seq( + OneOf("*/"), + S, + primary + ).Many() + ).Do(Multiply); + + sum.Parser = + Seq( + product, + Seq( + OneOf("+-"), + S, + product + ).Many() + ).Do(Add); + + var expression = + sum.Between(S, Eof); + + static double Multiply(double v, ArrayList<(char, Unit, double)> results) { - foreach (var (_, op, d) in results) + foreach (var (op, _, d) in results) v = op == '*' ? v * d : v / d; return v; } - static double Add(double v, ArrayList<(Unit, char, double)> results) + static double Add(double v, ArrayList<(char, Unit, double)> results) { - foreach (var (_, op, d) in results) + foreach (var (op, _, d) in results) v = op == '+' ? v + d : v - d; return v; From 58528c194172661fd697a30fce2591628a8131bb Mon Sep 17 00:00:00 2001 From: rameel Date: Tue, 11 Feb 2025 06:02:44 +0500 Subject: [PATCH 9/9] Clean up --- samples/TinyC/Node.cs | 2 ++ samples/TinyC/TinyCParser.cs | 10 ++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/samples/TinyC/Node.cs b/samples/TinyC/Node.cs index 3f759ad..709c864 100644 --- a/samples/TinyC/Node.cs +++ b/samples/TinyC/Node.cs @@ -83,7 +83,9 @@ static void Print(Node node, IndentedStringBuilder sb) case UnaryNode u: sb.Append(u.Operator); + sb.Append('('); Print(u.Operand, sb); + sb.Append(')'); break; case BinaryNode b: diff --git a/samples/TinyC/TinyCParser.cs b/samples/TinyC/TinyCParser.cs index d9a805c..a8990e1 100644 --- a/samples/TinyC/TinyCParser.cs +++ b/samples/TinyC/TinyCParser.cs @@ -66,8 +66,8 @@ private static Parser CreateParser() var_expr); var unary_expr = Seq( - S, OneOf("-+~!").Optional(), + S, primary_expr ).Do(CreateUnary); @@ -126,9 +126,11 @@ private static Parser CreateParser() Seq(var_expr, eq, expr).Do(CreateAssign), ternary_expr); - expr.Parser = assignment_expr; + expr.Parser = + assignment_expr; - var statement = Deferred(); + var statement = + Deferred(); var else_clause = Seq( @@ -190,7 +192,7 @@ private static Parser CreateParser() static Node Identifier(Match m) => Node.Variable(m.ToString()); - static Node CreateUnary(Unit _, OptionalValue u, Node operand) => + static Node CreateUnary(OptionalValue u, Unit _, Node operand) => u.HasValue ? Node.Unary(u.Value, operand) : operand; static Node CreateBinary(Node l, Node r, char o) =>