From 7f5c489266be1590b79502da42c296bc90b9476d Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Tue, 27 Aug 2024 13:47:02 +0200 Subject: [PATCH 01/42] feat(sample-rock): Add Initial Work For Poetic Literals --- .../Sample.Rockstar/Evaluation/EvaluationVisitor.cs | 5 ++--- Source/Samples/Sample.Rockstar/Evaluation/Scope.cs | 2 +- .../Matchers/AliasedBooleanMatcher.cs | 5 ++--- .../Sample.Rockstar/Matchers/MappingMatcher.cs | 5 ++--- .../Parselets/AliasedBooleanParselet.cs | 7 +++---- .../Sample.Rockstar/Parselets/AssignmentParselet.cs | 5 ++--- .../Sample.Rockstar/Parselets/MappingParselet.cs | 5 ++--- .../Sample.Rockstar/Parselets/PrintParselet.cs | 3 +-- Source/Samples/Sample.Rockstar/Program.cs | 2 +- Source/Samples/Sample.Rockstar/Repl.cs | 6 +++--- Source/Samples/Sample.Rockstar/RockstarCallbacks.cs | 2 +- Source/Samples/Sample.Rockstar/RockstarGrammar.cs | 13 ++++++++----- .../Samples/Sample.Rockstar/RockstarNameAdvancer.cs | 3 +-- 13 files changed, 29 insertions(+), 34 deletions(-) diff --git a/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs b/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs index 0d39afc..44225d4 100644 --- a/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs +++ b/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs @@ -1,9 +1,8 @@ -using Silverfly; -using Silverfly.Generator; +using Silverfly.Generator; using Silverfly.Nodes; using Silverfly.Nodes.Operators; -namespace Sample.Rockstar.Evaluation; +namespace Silverfly.Sample.Rockstar.Evaluation; [Visitor] public partial class EvaluationVisitor : TaggedNodeVisitor diff --git a/Source/Samples/Sample.Rockstar/Evaluation/Scope.cs b/Source/Samples/Sample.Rockstar/Evaluation/Scope.cs index d811b65..ca4ff18 100644 --- a/Source/Samples/Sample.Rockstar/Evaluation/Scope.cs +++ b/Source/Samples/Sample.Rockstar/Evaluation/Scope.cs @@ -1,4 +1,4 @@ -namespace Sample.Rockstar.Evaluation; +namespace Silverfly.Sample.Rockstar.Evaluation; public class Scope(bool isRoot = false) { diff --git a/Source/Samples/Sample.Rockstar/Matchers/AliasedBooleanMatcher.cs b/Source/Samples/Sample.Rockstar/Matchers/AliasedBooleanMatcher.cs index 1891ded..fc8af38 100644 --- a/Source/Samples/Sample.Rockstar/Matchers/AliasedBooleanMatcher.cs +++ b/Source/Samples/Sample.Rockstar/Matchers/AliasedBooleanMatcher.cs @@ -1,7 +1,6 @@ -using Silverfly; -using Silverfly.Lexing; +using Silverfly.Lexing; -namespace Sample.Rockstar.Matchers; +namespace Silverfly.Sample.Rockstar.Matchers; public class AliasedBooleanMatcher : IMatcher { diff --git a/Source/Samples/Sample.Rockstar/Matchers/MappingMatcher.cs b/Source/Samples/Sample.Rockstar/Matchers/MappingMatcher.cs index 9ae02d2..ec5eeb0 100644 --- a/Source/Samples/Sample.Rockstar/Matchers/MappingMatcher.cs +++ b/Source/Samples/Sample.Rockstar/Matchers/MappingMatcher.cs @@ -1,7 +1,6 @@ -using Silverfly; -using Silverfly.Lexing; +using Silverfly.Lexing; -namespace Sample.Rockstar.Matchers; +namespace Silverfly.Sample.Rockstar.Matchers; public class MappingMatcher(Symbol type, string[] aliases) : IMatcher { diff --git a/Source/Samples/Sample.Rockstar/Parselets/AliasedBooleanParselet.cs b/Source/Samples/Sample.Rockstar/Parselets/AliasedBooleanParselet.cs index 405dcca..5f39a19 100644 --- a/Source/Samples/Sample.Rockstar/Parselets/AliasedBooleanParselet.cs +++ b/Source/Samples/Sample.Rockstar/Parselets/AliasedBooleanParselet.cs @@ -1,9 +1,8 @@ -using Sample.Rockstar.Matchers; -using Silverfly; -using Silverfly.Nodes; +using Silverfly.Nodes; using Silverfly.Parselets; +using Silverfly.Sample.Rockstar.Matchers; -namespace Sample.Rockstar.Parselets; +namespace Silverfly.Sample.Rockstar.Parselets; public class AliasedBooleanParselet : IPrefixParselet { diff --git a/Source/Samples/Sample.Rockstar/Parselets/AssignmentParselet.cs b/Source/Samples/Sample.Rockstar/Parselets/AssignmentParselet.cs index 703ea9c..0c0ff0e 100644 --- a/Source/Samples/Sample.Rockstar/Parselets/AssignmentParselet.cs +++ b/Source/Samples/Sample.Rockstar/Parselets/AssignmentParselet.cs @@ -1,9 +1,8 @@ -using Silverfly; -using Silverfly.Nodes; +using Silverfly.Nodes; using Silverfly.Nodes.Operators; using Silverfly.Parselets; -namespace Sample.Rockstar.Parselets; +namespace Silverfly.Sample.Rockstar.Parselets; public class AssignmentParselet : IPrefixParselet { diff --git a/Source/Samples/Sample.Rockstar/Parselets/MappingParselet.cs b/Source/Samples/Sample.Rockstar/Parselets/MappingParselet.cs index 344f237..13b88dd 100644 --- a/Source/Samples/Sample.Rockstar/Parselets/MappingParselet.cs +++ b/Source/Samples/Sample.Rockstar/Parselets/MappingParselet.cs @@ -1,8 +1,7 @@ -using Silverfly; -using Silverfly.Nodes; +using Silverfly.Nodes; using Silverfly.Parselets; -namespace Sample.Rockstar.Parselets; +namespace Silverfly.Sample.Rockstar.Parselets; public class MappingParselet(object Value) : IPrefixParselet { diff --git a/Source/Samples/Sample.Rockstar/Parselets/PrintParselet.cs b/Source/Samples/Sample.Rockstar/Parselets/PrintParselet.cs index bfc120e..148576a 100644 --- a/Source/Samples/Sample.Rockstar/Parselets/PrintParselet.cs +++ b/Source/Samples/Sample.Rockstar/Parselets/PrintParselet.cs @@ -1,9 +1,8 @@ using System.Collections.Immutable; -using Silverfly; using Silverfly.Nodes; using Silverfly.Parselets; -namespace Sample.Rockstar.Parselets; +namespace Silverfly.Sample.Rockstar.Parselets; public class PrintParselet : IPrefixParselet { diff --git a/Source/Samples/Sample.Rockstar/Program.cs b/Source/Samples/Sample.Rockstar/Program.cs index 60fe72f..fb07506 100644 --- a/Source/Samples/Sample.Rockstar/Program.cs +++ b/Source/Samples/Sample.Rockstar/Program.cs @@ -1,4 +1,4 @@ -namespace Sample.Rockstar; +namespace Silverfly.Sample.Rockstar; class Program { diff --git a/Source/Samples/Sample.Rockstar/Repl.cs b/Source/Samples/Sample.Rockstar/Repl.cs index 9938af3..eab91d2 100644 --- a/Source/Samples/Sample.Rockstar/Repl.cs +++ b/Source/Samples/Sample.Rockstar/Repl.cs @@ -1,7 +1,7 @@ -using Sample.Rockstar.Evaluation; -using Silverfly.Repl; +using Silverfly.Repl; +using Silverfly.Sample.Rockstar.Evaluation; -namespace Sample.Rockstar; +namespace Silverfly.Sample.Rockstar; public class Repl : ReplInstance { diff --git a/Source/Samples/Sample.Rockstar/RockstarCallbacks.cs b/Source/Samples/Sample.Rockstar/RockstarCallbacks.cs index 3a49c15..068574d 100644 --- a/Source/Samples/Sample.Rockstar/RockstarCallbacks.cs +++ b/Source/Samples/Sample.Rockstar/RockstarCallbacks.cs @@ -4,7 +4,7 @@ using PrettyPrompt.Highlighting; using Silverfly.Repl; -namespace Sample.Rockstar; +namespace Silverfly.Sample.Rockstar; public class RockstarCallbacks : ReplPromptCallbacks { diff --git a/Source/Samples/Sample.Rockstar/RockstarGrammar.cs b/Source/Samples/Sample.Rockstar/RockstarGrammar.cs index 1f8f687..12dc9c8 100644 --- a/Source/Samples/Sample.Rockstar/RockstarGrammar.cs +++ b/Source/Samples/Sample.Rockstar/RockstarGrammar.cs @@ -1,12 +1,11 @@ -using Sample.Rockstar.Matchers; -using Sample.Rockstar.Parselets; -using Silverfly; -using Silverfly.Lexing.IgnoreMatcher.Comments; +using Silverfly.Lexing.IgnoreMatcher.Comments; using Silverfly.Parselets; using Silverfly.Parselets.Literals; +using Silverfly.Sample.Rockstar.Matchers; +using Silverfly.Sample.Rockstar.Parselets; using static Silverfly.PredefinedSymbols; -namespace Sample.Rockstar; +namespace Silverfly.Sample.Rockstar; public class RockstarGrammar : Parser { @@ -18,11 +17,13 @@ protected override void InitLexer(LexerConfig lexer) var emptyStringMatcher = new MappingMatcher("#empty_string", ["empty", "silent", "silence"]); var nullStringMatcher = new MappingMatcher("#null", ["null", "nothing", "nowhere", "nobody", "gone"]); var pronounMatcher = new MappingMatcher("#pronoun", ["it", "he", "she", "him", "her", "they", "them", "ze", "hir", "zie", "zir", "xe", "xem", "ve", "ver"]); + var poeticLiteralMatcher = new MappingMatcher("#poetic", ["is", "are", "was", "were"]); lexer.AddKeywords(AliasedBooleanMatcher.TrueAliases); lexer.AddKeywords(AliasedBooleanMatcher.FalseAliases); lexer.AddKeywords(emptyStringMatcher.Aliases); lexer.AddKeywords(pronounMatcher.Aliases); + lexer.AddKeywords(poeticLiteralMatcher.Aliases); lexer.AddKeywords(PrintParselet.Aliases); lexer.AddKeywords("let", "be", "put", "into"); @@ -34,6 +35,7 @@ protected override void InitLexer(LexerConfig lexer) lexer.AddMatcher(emptyStringMatcher); lexer.AddMatcher(nullStringMatcher); lexer.AddMatcher(pronounMatcher); + lexer.AddMatcher(poeticLiteralMatcher); lexer.IgnoreWhitespace(); lexer.Ignore(new MultiLineCommentIgnoreMatcher("(", ")")); @@ -49,6 +51,7 @@ protected override void InitParser(ParserDefinition def) def.Register("#pronoun", new MappingParselet(null)); def.Register(new AssignmentParselet(), "let", "put"); + def.InfixLeft("#poetic", "Sum"); def.Register(Name, new NameParselet()); def.Register(Number, new NumberParselet()); diff --git a/Source/Samples/Sample.Rockstar/RockstarNameAdvancer.cs b/Source/Samples/Sample.Rockstar/RockstarNameAdvancer.cs index 40fc4df..a7b744e 100644 --- a/Source/Samples/Sample.Rockstar/RockstarNameAdvancer.cs +++ b/Source/Samples/Sample.Rockstar/RockstarNameAdvancer.cs @@ -1,7 +1,6 @@ -using Silverfly; using Silverfly.Lexing; -namespace Sample.Rockstar; +namespace Silverfly.Sample.Rockstar; public class RockstarNameAdvancer : INameAdvancer { From 0cdc64b9ecb4b9b26bd7c444e260c421106954dd Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Tue, 27 Aug 2024 18:41:45 +0200 Subject: [PATCH 02/42] feat(sample-rockstar): Implement Poetic Numbers --- .../Parselets/PoeticLiteralParselet.cs | 75 +++++++++++++++++++ .../Sample.Rockstar/RockstarGrammar.cs | 6 +- Source/Silverfly/Parser.cs | 2 +- 3 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 Source/Samples/Sample.Rockstar/Parselets/PoeticLiteralParselet.cs diff --git a/Source/Samples/Sample.Rockstar/Parselets/PoeticLiteralParselet.cs b/Source/Samples/Sample.Rockstar/Parselets/PoeticLiteralParselet.cs new file mode 100644 index 0000000..4743bc4 --- /dev/null +++ b/Source/Samples/Sample.Rockstar/Parselets/PoeticLiteralParselet.cs @@ -0,0 +1,75 @@ +using Silverfly.Nodes; +using Silverfly.Nodes.Operators; +using Silverfly.Parselets; + +namespace Silverfly.Sample.Rockstar.Parselets; + +public class PoeticLiteralParselet : IInfixParselet +{ + public AstNode Parse(Parser parser, AstNode left, Token token) + { + AstNode value; + if (parser.IsMatch(PredefinedSymbols.Name)) + { + List tmp = []; + while (!parser.Match(PredefinedSymbols.EOL, PredefinedSymbols.EOF)) + { + tmp.AddRange(parser.Consume().Text.ToString().Split(' ')); + } + + var numValue = ConvertPoeticNumber(tmp); + + value = new LiteralNode(numValue, token); + } + else + { + value = parser.ParseExpression(); + } + + return new BinaryOperatorNode(left, token.Rewrite("="), value); + } + + private static double ConvertPoeticNumber(List tmp) + { + var numValue = 0.0; + var decimalPointEncountered = false; + var decimalMultiplier = 0.1; + + // Iterate over words after the variable name and 'is/was/are/were' + for (int i = 0; i < tmp.Count; i++) + { + var word = tmp[i]; + + // Remove non-alphabetical characters + var cleanedWord = new string(word.Where(char.IsLetter).ToArray()); + + if (cleanedWord.Length > 0) + { + // Handle the period (decimal point) + if (word.Contains('.')) + { + decimalPointEncountered = true; + continue; + } + + // Calculate the digit + var digit = cleanedWord.Length % 10; + + // Append the digit to the number + if (decimalPointEncountered) + { + numValue += digit * decimalMultiplier; + decimalMultiplier *= 0.1; + } + else + { + numValue = numValue * 10 + digit; + } + } + } + + return numValue; + } + + public int GetBindingPower() => 100; +} diff --git a/Source/Samples/Sample.Rockstar/RockstarGrammar.cs b/Source/Samples/Sample.Rockstar/RockstarGrammar.cs index 12dc9c8..ce664d9 100644 --- a/Source/Samples/Sample.Rockstar/RockstarGrammar.cs +++ b/Source/Samples/Sample.Rockstar/RockstarGrammar.cs @@ -27,6 +27,8 @@ protected override void InitLexer(LexerConfig lexer) lexer.AddKeywords(PrintParselet.Aliases); lexer.AddKeywords("let", "be", "put", "into"); + lexer.AddSymbol(Environment.NewLine); + lexer.MatchNumber(false,false); lexer.UseNameAdvancer(new RockstarNameAdvancer()); @@ -37,7 +39,7 @@ protected override void InitLexer(LexerConfig lexer) lexer.AddMatcher(pronounMatcher); lexer.AddMatcher(poeticLiteralMatcher); - lexer.IgnoreWhitespace(); + lexer.Ignore(" "); lexer.Ignore(new MultiLineCommentIgnoreMatcher("(", ")")); } @@ -51,7 +53,7 @@ protected override void InitParser(ParserDefinition def) def.Register("#pronoun", new MappingParselet(null)); def.Register(new AssignmentParselet(), "let", "put"); - def.InfixLeft("#poetic", "Sum"); + def.Register("#poetic", new PoeticLiteralParselet()); def.Register(Name, new NameParselet()); def.Register(Number, new NumberParselet()); diff --git a/Source/Silverfly/Parser.cs b/Source/Silverfly/Parser.cs index b9e5020..279499e 100644 --- a/Source/Silverfly/Parser.cs +++ b/Source/Silverfly/Parser.cs @@ -211,7 +211,7 @@ public bool Match(Symbol expected) /// /// An array of expected symbols to match. /// True if any of the expected symbols match; otherwise, false. - public bool Match(Symbol[] expected) + public bool Match(params Symbol[] expected) { foreach (var symbol in expected) { From e48b09b32882f5f05632aafbc8e2407829e292f5 Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Tue, 27 Aug 2024 22:07:50 +0200 Subject: [PATCH 03/42] fix(sample-rockstar): Print --- Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs | 2 +- Source/Samples/Sample.Rockstar/Parselets/PrintParselet.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs b/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs index 44225d4..88838a4 100644 --- a/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs +++ b/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs @@ -19,7 +19,7 @@ private object VisitAssignment(BinaryOperatorNode node, Scope scope) private object VisitCall(CallNode call, Scope scope) { - if (call.FunctionExpr is NameNode name && name.Token == "say") + if (call.FunctionExpr is NameNode name && name.Token == "print") { Console.WriteLine(Visit(call.Arguments[0], scope)); } diff --git a/Source/Samples/Sample.Rockstar/Parselets/PrintParselet.cs b/Source/Samples/Sample.Rockstar/Parselets/PrintParselet.cs index 148576a..17d6c81 100644 --- a/Source/Samples/Sample.Rockstar/Parselets/PrintParselet.cs +++ b/Source/Samples/Sample.Rockstar/Parselets/PrintParselet.cs @@ -9,7 +9,7 @@ public class PrintParselet : IPrefixParselet public static readonly string[] Aliases = ["say", "shout", "whisper", "scream"]; public AstNode Parse(Parser parser, Token token) { - var func = new NameNode(token); + var func = new NameNode(token.Rewrite("print")); ImmutableList args = [parser.ParseExpression()]; return new CallNode(func, args).WithRange(token, parser.LookAhead(0)); From e33e9ec86e644bcab320ca841a12d066156c309f Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Wed, 28 Aug 2024 12:43:56 +0200 Subject: [PATCH 04/42] refactor(sample-rockstar): Some Stuff --- .../Sample.Rockstar/Evaluation/EvaluationVisitor.cs | 13 +++++++++++-- Source/Samples/Sample.Rockstar/RockstarGrammar.cs | 5 ++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs b/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs index 88838a4..7a8e708 100644 --- a/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs +++ b/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs @@ -1,6 +1,7 @@ using Silverfly.Generator; using Silverfly.Nodes; using Silverfly.Nodes.Operators; +using Silverfly.Text; namespace Silverfly.Sample.Rockstar.Evaluation; @@ -12,7 +13,7 @@ private object VisitAssignment(BinaryOperatorNode node, Scope scope) { if (node.LeftExpr is not NameNode name) return null; - scope.Define(name.Token.Text.ToString(), Visit(node.RightExpr, scope)); + scope.Define(name.Token.Text.Trim().ToString(), Visit(node.RightExpr, scope)); return null; } @@ -21,7 +22,15 @@ private object VisitCall(CallNode call, Scope scope) { if (call.FunctionExpr is NameNode name && name.Token == "print") { - Console.WriteLine(Visit(call.Arguments[0], scope)); + var arg = Visit(call.Arguments[0], scope); + + if (arg is null && call.Arguments[0] is NameNode n) + { + call.Arguments[0].AddMessage(MessageSeverity.Error, $"Variable '{n.Token.Text}' is not defined"); + return null; + } + + Console.WriteLine(arg); } return null; diff --git a/Source/Samples/Sample.Rockstar/RockstarGrammar.cs b/Source/Samples/Sample.Rockstar/RockstarGrammar.cs index ce664d9..4461b67 100644 --- a/Source/Samples/Sample.Rockstar/RockstarGrammar.cs +++ b/Source/Samples/Sample.Rockstar/RockstarGrammar.cs @@ -26,7 +26,8 @@ protected override void InitLexer(LexerConfig lexer) lexer.AddKeywords(poeticLiteralMatcher.Aliases); lexer.AddKeywords(PrintParselet.Aliases); lexer.AddKeywords("let", "be", "put", "into"); - + + lexer.AddSymbol(Environment.NewLine + Environment.NewLine); //blank lines lexer.AddSymbol(Environment.NewLine); lexer.MatchNumber(false,false); @@ -59,5 +60,7 @@ protected override void InitParser(ParserDefinition def) def.Register(Number, new NumberParselet()); def.Register(new PrintParselet(), PrintParselet.Aliases); + + def.Block("if", "#line"); } } From e4095053719922843a7a38a12d24c31b62edd81e Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Sat, 31 Aug 2024 14:35:31 +0200 Subject: [PATCH 05/42] feat(sample-brainf*ck): Add Initial Work --- .../Samples/Sample.Brainfuck/EvalVisitor.cs | 68 +++++++++++++++++++ .../Sample.Brainfuck/Nodes/OperationNode.cs | 9 +++ .../Parselets/OperationParselet.cs | 14 ++++ Source/Samples/Sample.Brainfuck/Program.cs | 9 +++ Source/Samples/Sample.Brainfuck/Repl.cs | 12 ++++ Source/Silverfly.sln | 7 ++ 6 files changed, 119 insertions(+) create mode 100644 Source/Samples/Sample.Brainfuck/EvalVisitor.cs create mode 100644 Source/Samples/Sample.Brainfuck/Nodes/OperationNode.cs create mode 100644 Source/Samples/Sample.Brainfuck/Parselets/OperationParselet.cs create mode 100644 Source/Samples/Sample.Brainfuck/Program.cs create mode 100644 Source/Samples/Sample.Brainfuck/Repl.cs diff --git a/Source/Samples/Sample.Brainfuck/EvalVisitor.cs b/Source/Samples/Sample.Brainfuck/EvalVisitor.cs new file mode 100644 index 0000000..1b79ada --- /dev/null +++ b/Source/Samples/Sample.Brainfuck/EvalVisitor.cs @@ -0,0 +1,68 @@ +using Sample.Brainfuck.Nodes; +using Silverfly; +using Silverfly.Generator; +using Silverfly.Nodes; + +namespace Sample.Brainfuck; + +[Visitor] +public partial class EvalVisitor : NodeVisitor +{ + private int pointer = 0; + char[] cells = new char[100]; + + [VisitorCondition("_.Token == '.'")] + void Print(OperationNode node) + { + Console.WriteLine(cells[pointer]); + } + + [VisitorCondition("_.Token == ','")] + void Read(OperationNode node) + { + cells[pointer] = Console.ReadKey().KeyChar; + } + + [VisitorCondition("_.Token == '<'")] + void Decrement(OperationNode node) + { + pointer--; + } + + [VisitorCondition("_.Token == '>'")] + void Increment(OperationNode node) + { + pointer++; + } + + [VisitorCondition("_.Token == '+'")] + void IncrementCell(OperationNode node) + { + cells[pointer]++; + } + + [VisitorCondition("_.Token == '-'")] + void DecrementCell(OperationNode node) + { + cells[pointer]--; + } + + [VisitorCondition("_.LeftSymbol == '['")] + void Loop(GroupNode node) + { + while (cells[pointer] != 0) { + Visit(); + } + } +} + +/* +> ptr++; inkrementiert den Zeiger +< ptr--; dekrementiert den Zeiger ++ cell[ptr]++; inkrementiert den aktuellen Zellenwert +− cell[ptr]--; dekrementiert den aktuellen Zellenwert +. putchar (cell[ptr]); Gibt den aktuellen Zellenwert als ASCII-Zeichen auf der Standardausgabe aus +, cell[ptr] = getchar(); Liest ein Zeichen von der Standardeingabe und speichert dessen ASCII-Wert in der aktuellen Zelle +[ while (cell[ptr]) { Springt nach vorne, hinter den passenden ]-Befehl, wenn der aktuelle Zellenwert 0 ist +] } Springt zurück, hinter den passenden [-Befehl, wenn der aktuelle Zellenwert nicht 0 ist +*/ diff --git a/Source/Samples/Sample.Brainfuck/Nodes/OperationNode.cs b/Source/Samples/Sample.Brainfuck/Nodes/OperationNode.cs new file mode 100644 index 0000000..6532f24 --- /dev/null +++ b/Source/Samples/Sample.Brainfuck/Nodes/OperationNode.cs @@ -0,0 +1,9 @@ +using Silverfly; +using Silverfly.Nodes; + +namespace Sample.Brainfuck.Nodes; + +public record OperationNode(Token Token) : AstNode +{ + +} diff --git a/Source/Samples/Sample.Brainfuck/Parselets/OperationParselet.cs b/Source/Samples/Sample.Brainfuck/Parselets/OperationParselet.cs new file mode 100644 index 0000000..871b67f --- /dev/null +++ b/Source/Samples/Sample.Brainfuck/Parselets/OperationParselet.cs @@ -0,0 +1,14 @@ +using Sample.Brainfuck.Nodes; +using Silverfly; +using Silverfly.Nodes; +using Silverfly.Parselets; + +namespace Sample.Brainfuck.Parselets; + +public class OperationParselet : IPrefixParselet +{ + public AstNode Parse(Parser parser, Token token) + { + return new OperationNode(token).WithRange(token); + } +} diff --git a/Source/Samples/Sample.Brainfuck/Program.cs b/Source/Samples/Sample.Brainfuck/Program.cs new file mode 100644 index 0000000..0bb4abf --- /dev/null +++ b/Source/Samples/Sample.Brainfuck/Program.cs @@ -0,0 +1,9 @@ +namespace Sample.Brainfuck; + +public static class Program +{ + public static async Task Main(string[] args) + { + new Repl().Run(); + } +} diff --git a/Source/Samples/Sample.Brainfuck/Repl.cs b/Source/Samples/Sample.Brainfuck/Repl.cs new file mode 100644 index 0000000..ef8fd71 --- /dev/null +++ b/Source/Samples/Sample.Brainfuck/Repl.cs @@ -0,0 +1,12 @@ +using Silverfly.Repl; + +namespace Sample.Brainfuck; + +public class Repl : ReplInstance +{ + protected override void Evaluate(string input) + { + var parsed = Parser.Parse(input); + parsed.Tree.Accept(new EvalVisitor()); + } +} diff --git a/Source/Silverfly.sln b/Source/Silverfly.sln index 905aca0..addedbf 100644 --- a/Source/Silverfly.sln +++ b/Source/Silverfly.sln @@ -23,6 +23,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silverfly.Repl", "Silverfly EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.Rockstar", "Samples\Sample.Rockstar\Sample.Rockstar.csproj", "{554CFF94-2F55-4E55-A176-A05283BF1063}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.Brainfuck", "Samples\Sample.Brainfuck\Sample.Brainfuck.csproj", "{C43A7724-4D0D-4600-9778-E479F8BD946B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -65,6 +67,10 @@ Global {554CFF94-2F55-4E55-A176-A05283BF1063}.Debug|Any CPU.Build.0 = Debug|Any CPU {554CFF94-2F55-4E55-A176-A05283BF1063}.Release|Any CPU.ActiveCfg = Release|Any CPU {554CFF94-2F55-4E55-A176-A05283BF1063}.Release|Any CPU.Build.0 = Release|Any CPU + {C43A7724-4D0D-4600-9778-E479F8BD946B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C43A7724-4D0D-4600-9778-E479F8BD946B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C43A7724-4D0D-4600-9778-E479F8BD946B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C43A7724-4D0D-4600-9778-E479F8BD946B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -76,5 +82,6 @@ Global {48BC421F-8E9A-467D-922F-601EA7B09B06} = {DBC8A4C9-DBD3-4397-9EDC-CE9363947718} {AF32C673-CC70-4012-89D9-0249EC74ACB6} = {DBC8A4C9-DBD3-4397-9EDC-CE9363947718} {554CFF94-2F55-4E55-A176-A05283BF1063} = {DBC8A4C9-DBD3-4397-9EDC-CE9363947718} + {C43A7724-4D0D-4600-9778-E479F8BD946B} = {DBC8A4C9-DBD3-4397-9EDC-CE9363947718} EndGlobalSection EndGlobal From 9940a831762cfd34617333c6d6a449a4abb86696 Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Sat, 31 Aug 2024 14:36:24 +0200 Subject: [PATCH 06/42] feat(lexer): Add Ability To Set TokenType For A Symbol --- Source/Silverfly/Lexer.cs | 9 +++++---- Source/Silverfly/LexerConfig.cs | 10 ++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Source/Silverfly/Lexer.cs b/Source/Silverfly/Lexer.cs index 08ab6fb..343693a 100644 --- a/Source/Silverfly/Lexer.cs +++ b/Source/Silverfly/Lexer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Text.RegularExpressions; using Silverfly.Text; @@ -178,7 +179,7 @@ private bool InvokeSymbols(out Token token) continue; } - token = LexSymbol(symbol.Key, Document); + token = LexSymbol(symbol, Document); return true; } @@ -216,13 +217,13 @@ private bool AdvanceIgnoreMatcher(char c) return false; } - private Token LexSymbol(string punctuatorKey, SourceDocument document) + private Token LexSymbol(KeyValuePair punctuatorKey, SourceDocument document) { var oldColumn = _column; - Advance(punctuatorKey.Length); + Advance(punctuatorKey.Key.Length); - return new(punctuatorKey, punctuatorKey.AsMemory(), _line, oldColumn, document); + return new(punctuatorKey.Value, punctuatorKey.Key.AsMemory(), _line, oldColumn, document); } private Token LexName(SourceDocument document) diff --git a/Source/Silverfly/LexerConfig.cs b/Source/Silverfly/LexerConfig.cs index 633219b..9ddd4fa 100644 --- a/Source/Silverfly/LexerConfig.cs +++ b/Source/Silverfly/LexerConfig.cs @@ -50,6 +50,16 @@ public void AddSymbol(string symbol) Symbols.Add(symbol, PredefinedSymbols.Pool.Get(symbol)); } + public void AddSymbol(string symbol, string type) + { + if (Symbols.ContainsKey(symbol)) + { + return; + } + + Symbols.Add(symbol, type); + } + /// /// Adds a keyword to the Keywords collection and Symbols dictionary. /// From 640b51f10cf1ef72550b294ad7681a13567e75dd Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Sat, 31 Aug 2024 14:36:57 +0200 Subject: [PATCH 07/42] feat(parser): Better Error Messages --- Source/Silverfly/Parser.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Silverfly/Parser.cs b/Source/Silverfly/Parser.cs index 279499e..a2abfad 100644 --- a/Source/Silverfly/Parser.cs +++ b/Source/Silverfly/Parser.cs @@ -132,7 +132,7 @@ public AstNode Parse(int precedence) if (!ParserDefinition._prefixParselets.TryGetValue(token.Type, out var prefix)) { - token.Document.AddMessage(MessageSeverity.Error, "Could not parse prefix \"" + token.Type + "\".", token.GetRange()); + token.Document.AddMessage(MessageSeverity.Error, $"Failed to parse token '{token.Text}'", token.GetRange()); return new InvalidNode(token).WithRange(token); } @@ -146,7 +146,9 @@ public AstNode Parse(int precedence) if (!ParserDefinition._infixParselets.TryGetValue(token.Type, out var infix)) { token.Document.Messages.Add( - Message.Error("Could not parse \"" + token.Text + "\".", token.GetRange())); + Message.Error($"Failed to parse token '{token.Text}'", token.GetRange())); + + return new InvalidNode(token).WithRange(token); } left = infix!.Parse(this, left, token); From 11c58cdf5bdeaed3d7dac50a67ba226f488aa26f Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Sat, 31 Aug 2024 14:37:24 +0200 Subject: [PATCH 08/42] feat(generator): Change Visibility --- Source/Silverfly.Generator/Definition/DefinitionGrammar.cs | 2 +- Source/Silverfly.Generator/Definition/GeneratorVisitor.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Silverfly.Generator/Definition/DefinitionGrammar.cs b/Source/Silverfly.Generator/Definition/DefinitionGrammar.cs index e54e8c6..6f4715f 100644 --- a/Source/Silverfly.Generator/Definition/DefinitionGrammar.cs +++ b/Source/Silverfly.Generator/Definition/DefinitionGrammar.cs @@ -3,7 +3,7 @@ namespace Silverfly.Generator.Definition; -public class DefinitionGrammar : Parser +internal class DefinitionGrammar : Parser { protected override void InitLexer(LexerConfig lexer) { diff --git a/Source/Silverfly.Generator/Definition/GeneratorVisitor.cs b/Source/Silverfly.Generator/Definition/GeneratorVisitor.cs index eb65a57..24497ff 100644 --- a/Source/Silverfly.Generator/Definition/GeneratorVisitor.cs +++ b/Source/Silverfly.Generator/Definition/GeneratorVisitor.cs @@ -4,7 +4,7 @@ namespace Silverfly.Generator.Definition; -public class GeneratorVisitor : NodeVisitor +internal class GeneratorVisitor : NodeVisitor { private const string IndentationString = " "; // 4 spaces for each indentation level private readonly StringBuilder _builder; From 454bfacd3a5ff1b36f3196e927a44ab514856651 Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Sat, 31 Aug 2024 14:37:58 +0200 Subject: [PATCH 09/42] feat(sample-brainf*ck): Add Brainfuck Parser --- .../Sample.Brainfuck/BrainfuckParser.cs | 19 +++++++++++++++++++ .../Sample.Brainfuck/Sample.Brainfuck.csproj | 17 +++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 Source/Samples/Sample.Brainfuck/BrainfuckParser.cs create mode 100644 Source/Samples/Sample.Brainfuck/Sample.Brainfuck.csproj diff --git a/Source/Samples/Sample.Brainfuck/BrainfuckParser.cs b/Source/Samples/Sample.Brainfuck/BrainfuckParser.cs new file mode 100644 index 0000000..209359d --- /dev/null +++ b/Source/Samples/Sample.Brainfuck/BrainfuckParser.cs @@ -0,0 +1,19 @@ +using Sample.Brainfuck.Parselets; +using Silverfly; + +namespace Sample.Brainfuck; + +public class BrainfuckParser : Parser +{ + protected override void InitLexer(LexerConfig lexer) + { + + } + + protected override void InitParser(ParserDefinition def) + { + def.Group("[", "]"); + + def.Register(new OperationParselet(), "+", "-", "<", ">", ".", ","); + } +} diff --git a/Source/Samples/Sample.Brainfuck/Sample.Brainfuck.csproj b/Source/Samples/Sample.Brainfuck/Sample.Brainfuck.csproj new file mode 100644 index 0000000..6e433ac --- /dev/null +++ b/Source/Samples/Sample.Brainfuck/Sample.Brainfuck.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + latest + enable + enable + Exe + + + + + + + + + From fbd51d8f6856d280c246dd6c06cb1b89293a9066 Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Sun, 1 Sep 2024 09:59:24 +0200 Subject: [PATCH 10/42] feat(sample-brainfuck): Finalization --- .../Sample.Brainfuck/BrainfuckParser.cs | 9 ++-- .../Samples/Sample.Brainfuck/EvalVisitor.cs | 48 ++++++++++--------- .../Parselets/LoopParselet.cs | 18 +++++++ Source/Samples/Sample.Brainfuck/Repl.cs | 22 ++++++++- 4 files changed, 70 insertions(+), 27 deletions(-) create mode 100644 Source/Samples/Sample.Brainfuck/Parselets/LoopParselet.cs diff --git a/Source/Samples/Sample.Brainfuck/BrainfuckParser.cs b/Source/Samples/Sample.Brainfuck/BrainfuckParser.cs index 209359d..4ef773d 100644 --- a/Source/Samples/Sample.Brainfuck/BrainfuckParser.cs +++ b/Source/Samples/Sample.Brainfuck/BrainfuckParser.cs @@ -1,5 +1,6 @@ using Sample.Brainfuck.Parselets; using Silverfly; +using Silverfly.Lexing.IgnoreMatcher.Comments; namespace Sample.Brainfuck; @@ -7,13 +8,15 @@ public class BrainfuckParser : Parser { protected override void InitLexer(LexerConfig lexer) { - + lexer.IgnoreWhitespace(); + lexer.Ignore(new SingleLineCommentIgnoreMatcher("#")); } protected override void InitParser(ParserDefinition def) { - def.Group("[", "]"); - def.Register(new OperationParselet(), "+", "-", "<", ">", ".", ","); + def.Register("[", new LoopParselet()); + + def.Block(PredefinedSymbols.SOF, PredefinedSymbols.EOF); } } diff --git a/Source/Samples/Sample.Brainfuck/EvalVisitor.cs b/Source/Samples/Sample.Brainfuck/EvalVisitor.cs index 1b79ada..fadcb31 100644 --- a/Source/Samples/Sample.Brainfuck/EvalVisitor.cs +++ b/Source/Samples/Sample.Brainfuck/EvalVisitor.cs @@ -8,61 +8,63 @@ namespace Sample.Brainfuck; [Visitor] public partial class EvalVisitor : NodeVisitor { - private int pointer = 0; - char[] cells = new char[100]; + private int _pointer = 0; + readonly char[] _cells = new char[100]; [VisitorCondition("_.Token == '.'")] void Print(OperationNode node) { - Console.WriteLine(cells[pointer]); + Console.WriteLine(_cells[_pointer]); } [VisitorCondition("_.Token == ','")] void Read(OperationNode node) { - cells[pointer] = Console.ReadKey().KeyChar; + _cells[_pointer] = Console.ReadKey().KeyChar; } [VisitorCondition("_.Token == '<'")] void Decrement(OperationNode node) { - pointer--; + _pointer--; } [VisitorCondition("_.Token == '>'")] void Increment(OperationNode node) { - pointer++; + _pointer++; } [VisitorCondition("_.Token == '+'")] void IncrementCell(OperationNode node) { - cells[pointer]++; + _cells[_pointer]++; } [VisitorCondition("_.Token == '-'")] void DecrementCell(OperationNode node) { - cells[pointer]--; + _cells[_pointer]--; } - [VisitorCondition("_.LeftSymbol == '['")] - void Loop(GroupNode node) + [VisitorCondition("_.Tag == 'loop'")] + void Loop(BlockNode node) { - while (cells[pointer] != 0) { - Visit(); + while (_cells[_pointer] != '\0') + { + foreach (var child in node.Children) + { + Visit(child); + } } } -} -/* -> ptr++; inkrementiert den Zeiger -< ptr--; dekrementiert den Zeiger -+ cell[ptr]++; inkrementiert den aktuellen Zellenwert -− cell[ptr]--; dekrementiert den aktuellen Zellenwert -. putchar (cell[ptr]); Gibt den aktuellen Zellenwert als ASCII-Zeichen auf der Standardausgabe aus -, cell[ptr] = getchar(); Liest ein Zeichen von der Standardeingabe und speichert dessen ASCII-Wert in der aktuellen Zelle -[ while (cell[ptr]) { Springt nach vorne, hinter den passenden ]-Befehl, wenn der aktuelle Zellenwert 0 ist -] } Springt zurück, hinter den passenden [-Befehl, wenn der aktuelle Zellenwert nicht 0 ist -*/ + [VisitorCondition("_.Tag == null")] + void Block(BlockNode node) + { + foreach (var child in node.Children) + { + Visit(child); + } + } +} diff --git a/Source/Samples/Sample.Brainfuck/Parselets/LoopParselet.cs b/Source/Samples/Sample.Brainfuck/Parselets/LoopParselet.cs new file mode 100644 index 0000000..85c387f --- /dev/null +++ b/Source/Samples/Sample.Brainfuck/Parselets/LoopParselet.cs @@ -0,0 +1,18 @@ +using Silverfly; +using Silverfly.Nodes; +using Silverfly.Parselets; + +namespace Sample.Brainfuck.Parselets; + +public class LoopParselet : IPrefixParselet +{ + public AstNode Parse(Parser parser, Token token) + { + var instructions = parser.ParseList(terminators: "]"); + + return new BlockNode(null, "]") + .WithChildren(instructions) + .WithTag("loop") + .WithRange(token, parser.LookAhead(0)); + } +} diff --git a/Source/Samples/Sample.Brainfuck/Repl.cs b/Source/Samples/Sample.Brainfuck/Repl.cs index ef8fd71..4d79bbe 100644 --- a/Source/Samples/Sample.Brainfuck/Repl.cs +++ b/Source/Samples/Sample.Brainfuck/Repl.cs @@ -6,7 +6,27 @@ public class Repl : ReplInstance { protected override void Evaluate(string input) { - var parsed = Parser.Parse(input); + var helloWorld = """ + ++++++++++ + [ + >+++++++>++++++++++>+++>+<<<<- + ] + >++. #'H' + >+. #'e' + +++++++. #'l' + . #'l' + +++. #'o' + >++. #Space + <<+++++++++++++++. #'W' + >. #'o' + +++. #'r' + ------. #'l' + --------. #'d' + >+. #'!' + >. + +++. +"""; + var parsed = Parser.Parse(helloWorld); parsed.Tree.Accept(new EvalVisitor()); } } From 45e28bb5c40f186673cde592ba69743fb0eab876 Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Sun, 1 Sep 2024 09:59:56 +0200 Subject: [PATCH 11/42] feat: Add tag to all nodes --- Source/Silverfly/Nodes/AstNode.cs | 12 ++++++++++++ .../Silverfly/Nodes/Operators/PostfixOperatorNode.cs | 2 +- .../Silverfly/Nodes/Operators/PrefixOperatorNode.cs | 2 +- .../Parselets/Operators/PostfixOperatorParselet.cs | 5 +++-- .../Parselets/Operators/PrefixOperatorParselet.cs | 5 +++-- 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Source/Silverfly/Nodes/AstNode.cs b/Source/Silverfly/Nodes/AstNode.cs index 612b4e7..fe07f58 100644 --- a/Source/Silverfly/Nodes/AstNode.cs +++ b/Source/Silverfly/Nodes/AstNode.cs @@ -14,6 +14,11 @@ public abstract record AstNode /// public SourceRange Range { get; set; } + /// + /// A property to store extra information + /// + public object Tag { get; set; } + /// /// Gets or sets the parent node of this AST node. /// @@ -107,4 +112,11 @@ public void AddMessage(MessageSeverity severity, string message, Token token) { Range.Document.Messages.Add(new Message(severity, message, token.GetRange())); } + + public AstNode WithTag(object tag) + { + Tag = tag; + + return this; + } } diff --git a/Source/Silverfly/Nodes/Operators/PostfixOperatorNode.cs b/Source/Silverfly/Nodes/Operators/PostfixOperatorNode.cs index 842d0c4..7f3aab3 100644 --- a/Source/Silverfly/Nodes/Operators/PostfixOperatorNode.cs +++ b/Source/Silverfly/Nodes/Operators/PostfixOperatorNode.cs @@ -3,6 +3,6 @@ namespace Silverfly.Nodes.Operators; /// /// A postfix unary arithmetic expression like "a!" /// -public record PostfixOperatorNode(AstNode Expr, Token Operator, string Tag) : AstNode +public record PostfixOperatorNode(AstNode Expr, Token Operator) : AstNode { } diff --git a/Source/Silverfly/Nodes/Operators/PrefixOperatorNode.cs b/Source/Silverfly/Nodes/Operators/PrefixOperatorNode.cs index 2c4f9eb..63ff8b0 100644 --- a/Source/Silverfly/Nodes/Operators/PrefixOperatorNode.cs +++ b/Source/Silverfly/Nodes/Operators/PrefixOperatorNode.cs @@ -3,6 +3,6 @@ namespace Silverfly.Nodes.Operators; /// /// A prefix unary arithmetic expression like "!a" or "-b". /// -public record PrefixOperatorNode(Token Operator, AstNode Expr, string Tag) : AstNode +public record PrefixOperatorNode(Token Operator, AstNode Expr) : AstNode { } diff --git a/Source/Silverfly/Parselets/Operators/PostfixOperatorParselet.cs b/Source/Silverfly/Parselets/Operators/PostfixOperatorParselet.cs index 0daf6e9..b30feeb 100644 --- a/Source/Silverfly/Parselets/Operators/PostfixOperatorParselet.cs +++ b/Source/Silverfly/Parselets/Operators/PostfixOperatorParselet.cs @@ -11,8 +11,9 @@ public class PostfixOperatorParselet(int bindingPower, string tag) : IInfixParse public AstNode Parse(Parser parser, AstNode left, Token token) { - var node = new PostfixOperatorNode(left, token, tag) - .WithRange(left.Range.Document, left.Range.Start, token.GetSourceSpanEnd()); + var node = new PostfixOperatorNode(left, token) + .WithTag(tag) + .WithRange(left.Range.Document, left.Range.Start, token.GetSourceSpanEnd()); left.WithParent(node); diff --git a/Source/Silverfly/Parselets/Operators/PrefixOperatorParselet.cs b/Source/Silverfly/Parselets/Operators/PrefixOperatorParselet.cs index 9c35d08..840720a 100644 --- a/Source/Silverfly/Parselets/Operators/PrefixOperatorParselet.cs +++ b/Source/Silverfly/Parselets/Operators/PrefixOperatorParselet.cs @@ -16,8 +16,9 @@ public AstNode Parse(Parser parser, Token token) // take *this* parselet's result as its left-hand argument. var right = parser.Parse(bindingPower); - var node = new PrefixOperatorNode(token, right, tag) - .WithRange(token.Document, token.GetSourceSpanStart(), right.Range.End); + var node = new PrefixOperatorNode(token, right) + .WithTag(tag) + .WithRange(token.Document, token.GetSourceSpanStart(), right.Range.End); right.WithParent(node); From 79c1ce6be0166b9a6ecac907a107c35e197b887f Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Sun, 1 Sep 2024 11:44:30 +0200 Subject: [PATCH 12/42] fix(sample-brainfuck): Wrong Console Write --- Source/Samples/Sample.Brainfuck/EvalVisitor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Samples/Sample.Brainfuck/EvalVisitor.cs b/Source/Samples/Sample.Brainfuck/EvalVisitor.cs index fadcb31..5de7c56 100644 --- a/Source/Samples/Sample.Brainfuck/EvalVisitor.cs +++ b/Source/Samples/Sample.Brainfuck/EvalVisitor.cs @@ -14,7 +14,7 @@ public partial class EvalVisitor : NodeVisitor [VisitorCondition("_.Token == '.'")] void Print(OperationNode node) { - Console.WriteLine(_cells[_pointer]); + Console.Write(_cells[_pointer]); } [VisitorCondition("_.Token == ','")] From 24b9f5468538e91fd08c433b64fed92d41281a1a Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 4 Sep 2024 08:45:57 +0200 Subject: [PATCH 13/42] feat(lexer): Add AdvanceIfMatch --- Source/Silverfly/Lexer.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Source/Silverfly/Lexer.cs b/Source/Silverfly/Lexer.cs index 343693a..9172f59 100644 --- a/Source/Silverfly/Lexer.cs +++ b/Source/Silverfly/Lexer.cs @@ -261,6 +261,17 @@ public void Advance(int distance = 1) _column += distance; } + /// + /// Advances the current position in the document if a symbol matches. + /// + public void AdvanceIfMatch(string symbol) + { + if (lexer.IsMatch(symbol, Config.IgnoreCasing)) + { + lexer.Advance(symbol.Length); + } + } + /// /// Determines whether the specified token name is a punctuator. /// From 2c7025d7a4b3443503eee7d20875a1f15e0a9565 Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Wed, 4 Sep 2024 20:05:12 +0200 Subject: [PATCH 14/42] refactor: Cleanup --- Source/Silverfly/Lexer.cs | 4 ++-- Source/Silverfly/LexerConfig.cs | 4 ++-- Source/Silverfly/Lexing/Matcher/BooleanMatcher.cs | 15 ++++----------- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/Source/Silverfly/Lexer.cs b/Source/Silverfly/Lexer.cs index 9172f59..7be120c 100644 --- a/Source/Silverfly/Lexer.cs +++ b/Source/Silverfly/Lexer.cs @@ -266,9 +266,9 @@ public void Advance(int distance = 1) /// public void AdvanceIfMatch(string symbol) { - if (lexer.IsMatch(symbol, Config.IgnoreCasing)) + if (IsMatch(symbol, Config.IgnoreCasing)) { - lexer.Advance(symbol.Length); + Advance(symbol.Length); } } diff --git a/Source/Silverfly/LexerConfig.cs b/Source/Silverfly/LexerConfig.cs index 9ddd4fa..82180ba 100644 --- a/Source/Silverfly/LexerConfig.cs +++ b/Source/Silverfly/LexerConfig.cs @@ -181,9 +181,9 @@ public void AddSymbols(params string[] symbols) /// Adds a matcher to identify boolean values ('true' and 'false'). /// /// Flag indicating whether casing should be ignored when matching. - public void MatchBoolean(bool ignoreCasing = false) + public void MatchBoolean() { - AddMatcher(new BooleanMatcher(ignoreCasing)); + AddMatcher(new BooleanMatcher()); } /// diff --git a/Source/Silverfly/Lexing/Matcher/BooleanMatcher.cs b/Source/Silverfly/Lexing/Matcher/BooleanMatcher.cs index 1dec995..70e39b3 100644 --- a/Source/Silverfly/Lexing/Matcher/BooleanMatcher.cs +++ b/Source/Silverfly/Lexing/Matcher/BooleanMatcher.cs @@ -3,8 +3,7 @@ /// /// Represents a matcher that identifies boolean literals ("true" or "false") in the lexer input. /// -/// Determines whether the casing of the boolean literals should be ignored. -public class BooleanMatcher(bool ignoreCasing = false) : IMatcher +public class BooleanMatcher : IMatcher { /// /// Determines whether the current lexer position matches a boolean literal ("true" or "false"). @@ -16,7 +15,7 @@ public class BooleanMatcher(bool ignoreCasing = false) : IMatcher /// public bool Match(Lexer lexer, char c) { - return lexer.IsMatch("true", ignoreCasing) || lexer.IsMatch("false", ignoreCasing); + return lexer.IsMatch("true", lexer.Config.IgnoreCasing) || lexer.IsMatch("false", lexer.Config.IgnoreCasing); } /// @@ -34,14 +33,8 @@ public Token Build(Lexer lexer, ref int index, ref int column, ref int line) var oldColumn = column; var oldIndex = index; - if (lexer.IsMatch("true", ignoreCasing)) - { - lexer.Advance("true".Length); - } - else if (lexer.IsMatch("false", ignoreCasing)) - { - lexer.Advance("false".Length); - } + lexer.AdvanceIfMatch("true"); + lexer.AdvanceIfMatch("false"); return new(PredefinedSymbols.Boolean, lexer.Document.Source[oldIndex..index], line, oldColumn, lexer.Document); } From 7472b6b3c03f311db25181f5de9774be07ddf20d Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Sun, 8 Sep 2024 18:35:38 +0200 Subject: [PATCH 15/42] refactor: Cleanup --- Source/TestProject/TestParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/TestProject/TestParser.cs b/Source/TestProject/TestParser.cs index 78b1007..a00fc25 100644 --- a/Source/TestProject/TestParser.cs +++ b/Source/TestProject/TestParser.cs @@ -37,7 +37,7 @@ protected override void InitLexer(LexerConfig lexer) lexer.IgnoreWhitespace(); lexer.Ignore("\r", "\r\n"); - lexer.MatchBoolean(ignoreCasing: true); + lexer.MatchBoolean(); lexer.MatchString("'", "'"); lexer.MatchNumber(allowHex: true, allowBin: true); From 1ff04e26c1ca26f08f398a46e6eb51de5d28802b Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Sun, 8 Sep 2024 18:37:06 +0200 Subject: [PATCH 16/42] feat(sample-rock): Add if and while --- .../Samples/Sample.Rockstar/Nodes/IfNode.cs | 7 ++++++ .../Samples/Sample.Rockstar/Nodes/LoopNode.cs | 6 +++++ .../Sample.Rockstar/Parselets/IfParselet.cs | 16 ++++++++++++++ .../Sample.Rockstar/Parselets/LoopParselet.cs | 22 +++++++++++++++++++ 4 files changed, 51 insertions(+) create mode 100644 Source/Samples/Sample.Rockstar/Nodes/IfNode.cs create mode 100644 Source/Samples/Sample.Rockstar/Nodes/LoopNode.cs create mode 100644 Source/Samples/Sample.Rockstar/Parselets/IfParselet.cs create mode 100644 Source/Samples/Sample.Rockstar/Parselets/LoopParselet.cs diff --git a/Source/Samples/Sample.Rockstar/Nodes/IfNode.cs b/Source/Samples/Sample.Rockstar/Nodes/IfNode.cs new file mode 100644 index 0000000..4df387f --- /dev/null +++ b/Source/Samples/Sample.Rockstar/Nodes/IfNode.cs @@ -0,0 +1,7 @@ +using System.Collections.Immutable; +using Silverfly.Nodes; + +namespace Silverfly.Sample.Rockstar.Nodes; + +public record IfNode(AstNode Condition, ImmutableList TruePart, ImmutableList FalsePart) + : StatementNode; diff --git a/Source/Samples/Sample.Rockstar/Nodes/LoopNode.cs b/Source/Samples/Sample.Rockstar/Nodes/LoopNode.cs new file mode 100644 index 0000000..d388442 --- /dev/null +++ b/Source/Samples/Sample.Rockstar/Nodes/LoopNode.cs @@ -0,0 +1,6 @@ +using System.Collections.Immutable; +using Silverfly.Nodes; + +namespace Silverfly.Sample.Rockstar.Nodes; + +public record LoopNode(AstNode Condition, ImmutableList Body) : StatementNode; diff --git a/Source/Samples/Sample.Rockstar/Parselets/IfParselet.cs b/Source/Samples/Sample.Rockstar/Parselets/IfParselet.cs new file mode 100644 index 0000000..212dd6e --- /dev/null +++ b/Source/Samples/Sample.Rockstar/Parselets/IfParselet.cs @@ -0,0 +1,16 @@ +using Silverfly.Nodes; +using Silverfly.Parselets; +using Silverfly.Sample.Rockstar.Nodes; + +namespace Silverfly.Sample.Rockstar.Parselets; + +public class IfParselet : IStatementParselet +{ + public AstNode Parse(Parser parser, Token token) + { + var condition = parser.ParseExpression(); + var body = parser.ParseList(PredefinedSymbols.EOF, Environment.NewLine + Environment.NewLine); + + return new IfNode(condition, body, []); + } +} diff --git a/Source/Samples/Sample.Rockstar/Parselets/LoopParselet.cs b/Source/Samples/Sample.Rockstar/Parselets/LoopParselet.cs new file mode 100644 index 0000000..3901748 --- /dev/null +++ b/Source/Samples/Sample.Rockstar/Parselets/LoopParselet.cs @@ -0,0 +1,22 @@ +using Silverfly.Nodes; +using Silverfly.Parselets; +using Silverfly.Sample.Rockstar.Nodes; + +namespace Silverfly.Sample.Rockstar.Parselets; + +public class LoopParselet : IStatementParselet +{ + public AstNode Parse(Parser parser, Token token) + { + var condition = parser.ParseExpression(); + + if (parser.IsMatch(",") || parser.IsMatch("\n")) + { + parser.Consume(); + } + + var body = parser.ParseList(PredefinedSymbols.EOF, "\n\n", "."); + + return new LoopNode(condition, body); + } +} From abe06beb1aef7b30016389c27fff636f60fe8197 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 10 Sep 2024 10:15:38 +0200 Subject: [PATCH 17/42] feat(lexer): AdvanceIfMatch returns boolean --- Source/Silverfly/Lexer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Silverfly/Lexer.cs b/Source/Silverfly/Lexer.cs index 7be120c..e60565c 100644 --- a/Source/Silverfly/Lexer.cs +++ b/Source/Silverfly/Lexer.cs @@ -264,12 +264,16 @@ public void Advance(int distance = 1) /// /// Advances the current position in the document if a symbol matches. /// - public void AdvanceIfMatch(string symbol) + public bool AdvanceIfMatch(string symbol) { if (IsMatch(symbol, Config.IgnoreCasing)) { Advance(symbol.Length); + + return true; } + + return false; } /// From f5a7bac136973e6b22cd74b237b400c378ce3c79 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 10 Sep 2024 11:42:28 +0000 Subject: [PATCH 18/42] feat(repl): Add Number Highlighting --- Source/Silverfly.Repl/ReplPromptCallbacks.cs | 21 ++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Source/Silverfly.Repl/ReplPromptCallbacks.cs b/Source/Silverfly.Repl/ReplPromptCallbacks.cs index 9afc6e0..ed6f788 100644 --- a/Source/Silverfly.Repl/ReplPromptCallbacks.cs +++ b/Source/Silverfly.Repl/ReplPromptCallbacks.cs @@ -21,6 +21,7 @@ protected override Task> HighlightCallbackAsync( var spans = GetKeywordSpans(text, keywords) .Concat(brackets) + .Concat(GetNumberSpans(text)) .Concat(GetStringsSpans(text)) .ToList(); @@ -109,4 +110,24 @@ private static IEnumerable GetStringsSpans(string text) offset = endIndex + 1; } } + + private static IEnumerable GetNumberSpans(string text) + { + int offset = 0; + + while (offset < text.Length) + { + if (char.IsDigit(text[offset])) + { + int startIndex = offset; + + while (char.IsDigit(text[offset])) + { + offset++; + } + + yield return new FormatSpan(startIndex, offset, ToAnsi(MessageFormatter.Theme.Number)); + } + } + } } From a88f340e2b659e6278a33e05645b8083db41bf76 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 11 Sep 2024 06:15:25 +0000 Subject: [PATCH 19/42] feat(lexer): Add StringSyntaxAttribute to autocomplete patterns --- Source/Silverfly/LexerConfig.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Source/Silverfly/LexerConfig.cs b/Source/Silverfly/LexerConfig.cs index 82180ba..6594e8c 100644 --- a/Source/Silverfly/LexerConfig.cs +++ b/Source/Silverfly/LexerConfig.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text.RegularExpressions; using Silverfly.Lexing; @@ -70,14 +71,14 @@ public void AddKeyword(string keyword) Symbols.TryAdd(keyword, keyword); } - + /// /// Adds multiple keywords to the Keywords collection and Symbols dictionary. /// /// An array of keywords to be added. public void AddKeywords(params string[] keywords) { - this.Keywords.AddRange(keywords); + Keywords.AddRange(keywords); foreach (var keyword in keywords) { @@ -136,7 +137,7 @@ public void MatchNumber(bool allowHex, bool allowBin, Symbol floatingPointSymbol AddMatcher(new NumberMatcher(allowHex, allowBin, floatingPointSymbol ?? PredefinedSymbols.Dot, separatorSymbol ?? PredefinedSymbols.Underscore)); } - + /// /// Adds a new regular expression matcher for a specified symbol type. /// @@ -150,7 +151,7 @@ public void MatchPattern(Symbol type, Regex regex) { AddMatcher(new RegexMatcher(type, regex)); } - + /// /// Adds a new regular expression matcher for a specified symbol type. /// @@ -160,7 +161,7 @@ public void MatchPattern(Symbol type, Regex regex) /// This method creates a new instance of with the given symbol type /// and regular expression pattern, and adds it to the matchers collection. /// - public void MatchPattern(Symbol type, string pattern) + public void MatchPattern(Symbol type, [StringSyntax(StringSyntaxAttribute.Regex)] string pattern) { MatchPattern(type, new Regex(pattern)); } @@ -203,7 +204,7 @@ public void Ignore(char c) /// This method converts the provided pattern into a object and /// calls the method to handle the exclusion. /// - public void IgnorePattern(string pattern) + public void IgnorePattern([StringSyntax(StringSyntaxAttribute.Regex)] string pattern) { IgnorePattern(new Regex(pattern)); } From 2c59fbe67686655395b1da6016a3e44b37bcb44e Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Tue, 27 Aug 2024 13:47:02 +0200 Subject: [PATCH 20/42] feat(sample-rock): Add Initial Work For Poetic Literals --- .../Sample.Rockstar/Evaluation/EvaluationVisitor.cs | 5 ++--- .../Matchers/AliasedBooleanMatcher.cs | 5 ++--- .../Sample.Rockstar/Matchers/MappingMatcher.cs | 5 ++--- .../Parselets/AliasedBooleanParselet.cs | 7 +++---- .../Sample.Rockstar/Parselets/AssignmentParselet.cs | 5 ++--- .../Sample.Rockstar/Parselets/MappingParselet.cs | 5 ++--- .../Sample.Rockstar/Parselets/PrintParselet.cs | 3 +-- Source/Samples/Sample.Rockstar/Program.cs | 2 +- Source/Samples/Sample.Rockstar/RockstarCallbacks.cs | 2 +- Source/Samples/Sample.Rockstar/RockstarGrammar.cs | 13 ++++++++----- .../Samples/Sample.Rockstar/RockstarNameAdvancer.cs | 3 +-- 11 files changed, 25 insertions(+), 30 deletions(-) diff --git a/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs b/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs index ece22a9..328911d 100644 --- a/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs +++ b/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs @@ -1,10 +1,9 @@ -using Silverfly; -using Silverfly.Generator; +using Silverfly.Generator; using Silverfly.Nodes; using Silverfly.Nodes.Operators; using Silverfly.Sample.Rockstar.Evaluation; -namespace Sample.Rockstar.Evaluation; +namespace Silverfly.Sample.Rockstar.Evaluation; [Visitor] public partial class EvaluationVisitor : TaggedNodeVisitor diff --git a/Source/Samples/Sample.Rockstar/Matchers/AliasedBooleanMatcher.cs b/Source/Samples/Sample.Rockstar/Matchers/AliasedBooleanMatcher.cs index 1891ded..fc8af38 100644 --- a/Source/Samples/Sample.Rockstar/Matchers/AliasedBooleanMatcher.cs +++ b/Source/Samples/Sample.Rockstar/Matchers/AliasedBooleanMatcher.cs @@ -1,7 +1,6 @@ -using Silverfly; -using Silverfly.Lexing; +using Silverfly.Lexing; -namespace Sample.Rockstar.Matchers; +namespace Silverfly.Sample.Rockstar.Matchers; public class AliasedBooleanMatcher : IMatcher { diff --git a/Source/Samples/Sample.Rockstar/Matchers/MappingMatcher.cs b/Source/Samples/Sample.Rockstar/Matchers/MappingMatcher.cs index 9ae02d2..ec5eeb0 100644 --- a/Source/Samples/Sample.Rockstar/Matchers/MappingMatcher.cs +++ b/Source/Samples/Sample.Rockstar/Matchers/MappingMatcher.cs @@ -1,7 +1,6 @@ -using Silverfly; -using Silverfly.Lexing; +using Silverfly.Lexing; -namespace Sample.Rockstar.Matchers; +namespace Silverfly.Sample.Rockstar.Matchers; public class MappingMatcher(Symbol type, string[] aliases) : IMatcher { diff --git a/Source/Samples/Sample.Rockstar/Parselets/AliasedBooleanParselet.cs b/Source/Samples/Sample.Rockstar/Parselets/AliasedBooleanParselet.cs index 405dcca..5f39a19 100644 --- a/Source/Samples/Sample.Rockstar/Parselets/AliasedBooleanParselet.cs +++ b/Source/Samples/Sample.Rockstar/Parselets/AliasedBooleanParselet.cs @@ -1,9 +1,8 @@ -using Sample.Rockstar.Matchers; -using Silverfly; -using Silverfly.Nodes; +using Silverfly.Nodes; using Silverfly.Parselets; +using Silverfly.Sample.Rockstar.Matchers; -namespace Sample.Rockstar.Parselets; +namespace Silverfly.Sample.Rockstar.Parselets; public class AliasedBooleanParselet : IPrefixParselet { diff --git a/Source/Samples/Sample.Rockstar/Parselets/AssignmentParselet.cs b/Source/Samples/Sample.Rockstar/Parselets/AssignmentParselet.cs index 703ea9c..0c0ff0e 100644 --- a/Source/Samples/Sample.Rockstar/Parselets/AssignmentParselet.cs +++ b/Source/Samples/Sample.Rockstar/Parselets/AssignmentParselet.cs @@ -1,9 +1,8 @@ -using Silverfly; -using Silverfly.Nodes; +using Silverfly.Nodes; using Silverfly.Nodes.Operators; using Silverfly.Parselets; -namespace Sample.Rockstar.Parselets; +namespace Silverfly.Sample.Rockstar.Parselets; public class AssignmentParselet : IPrefixParselet { diff --git a/Source/Samples/Sample.Rockstar/Parselets/MappingParselet.cs b/Source/Samples/Sample.Rockstar/Parselets/MappingParselet.cs index 344f237..13b88dd 100644 --- a/Source/Samples/Sample.Rockstar/Parselets/MappingParselet.cs +++ b/Source/Samples/Sample.Rockstar/Parselets/MappingParselet.cs @@ -1,8 +1,7 @@ -using Silverfly; -using Silverfly.Nodes; +using Silverfly.Nodes; using Silverfly.Parselets; -namespace Sample.Rockstar.Parselets; +namespace Silverfly.Sample.Rockstar.Parselets; public class MappingParselet(object Value) : IPrefixParselet { diff --git a/Source/Samples/Sample.Rockstar/Parselets/PrintParselet.cs b/Source/Samples/Sample.Rockstar/Parselets/PrintParselet.cs index bfc120e..148576a 100644 --- a/Source/Samples/Sample.Rockstar/Parselets/PrintParselet.cs +++ b/Source/Samples/Sample.Rockstar/Parselets/PrintParselet.cs @@ -1,9 +1,8 @@ using System.Collections.Immutable; -using Silverfly; using Silverfly.Nodes; using Silverfly.Parselets; -namespace Sample.Rockstar.Parselets; +namespace Silverfly.Sample.Rockstar.Parselets; public class PrintParselet : IPrefixParselet { diff --git a/Source/Samples/Sample.Rockstar/Program.cs b/Source/Samples/Sample.Rockstar/Program.cs index 60fe72f..fb07506 100644 --- a/Source/Samples/Sample.Rockstar/Program.cs +++ b/Source/Samples/Sample.Rockstar/Program.cs @@ -1,4 +1,4 @@ -namespace Sample.Rockstar; +namespace Silverfly.Sample.Rockstar; class Program { diff --git a/Source/Samples/Sample.Rockstar/RockstarCallbacks.cs b/Source/Samples/Sample.Rockstar/RockstarCallbacks.cs index 3a49c15..068574d 100644 --- a/Source/Samples/Sample.Rockstar/RockstarCallbacks.cs +++ b/Source/Samples/Sample.Rockstar/RockstarCallbacks.cs @@ -4,7 +4,7 @@ using PrettyPrompt.Highlighting; using Silverfly.Repl; -namespace Sample.Rockstar; +namespace Silverfly.Sample.Rockstar; public class RockstarCallbacks : ReplPromptCallbacks { diff --git a/Source/Samples/Sample.Rockstar/RockstarGrammar.cs b/Source/Samples/Sample.Rockstar/RockstarGrammar.cs index 1f8f687..12dc9c8 100644 --- a/Source/Samples/Sample.Rockstar/RockstarGrammar.cs +++ b/Source/Samples/Sample.Rockstar/RockstarGrammar.cs @@ -1,12 +1,11 @@ -using Sample.Rockstar.Matchers; -using Sample.Rockstar.Parselets; -using Silverfly; -using Silverfly.Lexing.IgnoreMatcher.Comments; +using Silverfly.Lexing.IgnoreMatcher.Comments; using Silverfly.Parselets; using Silverfly.Parselets.Literals; +using Silverfly.Sample.Rockstar.Matchers; +using Silverfly.Sample.Rockstar.Parselets; using static Silverfly.PredefinedSymbols; -namespace Sample.Rockstar; +namespace Silverfly.Sample.Rockstar; public class RockstarGrammar : Parser { @@ -18,11 +17,13 @@ protected override void InitLexer(LexerConfig lexer) var emptyStringMatcher = new MappingMatcher("#empty_string", ["empty", "silent", "silence"]); var nullStringMatcher = new MappingMatcher("#null", ["null", "nothing", "nowhere", "nobody", "gone"]); var pronounMatcher = new MappingMatcher("#pronoun", ["it", "he", "she", "him", "her", "they", "them", "ze", "hir", "zie", "zir", "xe", "xem", "ve", "ver"]); + var poeticLiteralMatcher = new MappingMatcher("#poetic", ["is", "are", "was", "were"]); lexer.AddKeywords(AliasedBooleanMatcher.TrueAliases); lexer.AddKeywords(AliasedBooleanMatcher.FalseAliases); lexer.AddKeywords(emptyStringMatcher.Aliases); lexer.AddKeywords(pronounMatcher.Aliases); + lexer.AddKeywords(poeticLiteralMatcher.Aliases); lexer.AddKeywords(PrintParselet.Aliases); lexer.AddKeywords("let", "be", "put", "into"); @@ -34,6 +35,7 @@ protected override void InitLexer(LexerConfig lexer) lexer.AddMatcher(emptyStringMatcher); lexer.AddMatcher(nullStringMatcher); lexer.AddMatcher(pronounMatcher); + lexer.AddMatcher(poeticLiteralMatcher); lexer.IgnoreWhitespace(); lexer.Ignore(new MultiLineCommentIgnoreMatcher("(", ")")); @@ -49,6 +51,7 @@ protected override void InitParser(ParserDefinition def) def.Register("#pronoun", new MappingParselet(null)); def.Register(new AssignmentParselet(), "let", "put"); + def.InfixLeft("#poetic", "Sum"); def.Register(Name, new NameParselet()); def.Register(Number, new NumberParselet()); diff --git a/Source/Samples/Sample.Rockstar/RockstarNameAdvancer.cs b/Source/Samples/Sample.Rockstar/RockstarNameAdvancer.cs index 40fc4df..a7b744e 100644 --- a/Source/Samples/Sample.Rockstar/RockstarNameAdvancer.cs +++ b/Source/Samples/Sample.Rockstar/RockstarNameAdvancer.cs @@ -1,7 +1,6 @@ -using Silverfly; using Silverfly.Lexing; -namespace Sample.Rockstar; +namespace Silverfly.Sample.Rockstar; public class RockstarNameAdvancer : INameAdvancer { From b2fb56dfe06a379a53c693c3b9f013a2e0dfcd51 Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Tue, 27 Aug 2024 18:41:45 +0200 Subject: [PATCH 21/42] feat(sample-rockstar): Implement Poetic Numbers --- .../Parselets/PoeticLiteralParselet.cs | 75 +++++++++++++++++++ .../Sample.Rockstar/RockstarGrammar.cs | 6 +- Source/Silverfly/Parser.cs | 2 +- 3 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 Source/Samples/Sample.Rockstar/Parselets/PoeticLiteralParselet.cs diff --git a/Source/Samples/Sample.Rockstar/Parselets/PoeticLiteralParselet.cs b/Source/Samples/Sample.Rockstar/Parselets/PoeticLiteralParselet.cs new file mode 100644 index 0000000..4743bc4 --- /dev/null +++ b/Source/Samples/Sample.Rockstar/Parselets/PoeticLiteralParselet.cs @@ -0,0 +1,75 @@ +using Silverfly.Nodes; +using Silverfly.Nodes.Operators; +using Silverfly.Parselets; + +namespace Silverfly.Sample.Rockstar.Parselets; + +public class PoeticLiteralParselet : IInfixParselet +{ + public AstNode Parse(Parser parser, AstNode left, Token token) + { + AstNode value; + if (parser.IsMatch(PredefinedSymbols.Name)) + { + List tmp = []; + while (!parser.Match(PredefinedSymbols.EOL, PredefinedSymbols.EOF)) + { + tmp.AddRange(parser.Consume().Text.ToString().Split(' ')); + } + + var numValue = ConvertPoeticNumber(tmp); + + value = new LiteralNode(numValue, token); + } + else + { + value = parser.ParseExpression(); + } + + return new BinaryOperatorNode(left, token.Rewrite("="), value); + } + + private static double ConvertPoeticNumber(List tmp) + { + var numValue = 0.0; + var decimalPointEncountered = false; + var decimalMultiplier = 0.1; + + // Iterate over words after the variable name and 'is/was/are/were' + for (int i = 0; i < tmp.Count; i++) + { + var word = tmp[i]; + + // Remove non-alphabetical characters + var cleanedWord = new string(word.Where(char.IsLetter).ToArray()); + + if (cleanedWord.Length > 0) + { + // Handle the period (decimal point) + if (word.Contains('.')) + { + decimalPointEncountered = true; + continue; + } + + // Calculate the digit + var digit = cleanedWord.Length % 10; + + // Append the digit to the number + if (decimalPointEncountered) + { + numValue += digit * decimalMultiplier; + decimalMultiplier *= 0.1; + } + else + { + numValue = numValue * 10 + digit; + } + } + } + + return numValue; + } + + public int GetBindingPower() => 100; +} diff --git a/Source/Samples/Sample.Rockstar/RockstarGrammar.cs b/Source/Samples/Sample.Rockstar/RockstarGrammar.cs index 12dc9c8..ce664d9 100644 --- a/Source/Samples/Sample.Rockstar/RockstarGrammar.cs +++ b/Source/Samples/Sample.Rockstar/RockstarGrammar.cs @@ -27,6 +27,8 @@ protected override void InitLexer(LexerConfig lexer) lexer.AddKeywords(PrintParselet.Aliases); lexer.AddKeywords("let", "be", "put", "into"); + lexer.AddSymbol(Environment.NewLine); + lexer.MatchNumber(false,false); lexer.UseNameAdvancer(new RockstarNameAdvancer()); @@ -37,7 +39,7 @@ protected override void InitLexer(LexerConfig lexer) lexer.AddMatcher(pronounMatcher); lexer.AddMatcher(poeticLiteralMatcher); - lexer.IgnoreWhitespace(); + lexer.Ignore(" "); lexer.Ignore(new MultiLineCommentIgnoreMatcher("(", ")")); } @@ -51,7 +53,7 @@ protected override void InitParser(ParserDefinition def) def.Register("#pronoun", new MappingParselet(null)); def.Register(new AssignmentParselet(), "let", "put"); - def.InfixLeft("#poetic", "Sum"); + def.Register("#poetic", new PoeticLiteralParselet()); def.Register(Name, new NameParselet()); def.Register(Number, new NumberParselet()); diff --git a/Source/Silverfly/Parser.cs b/Source/Silverfly/Parser.cs index b9e5020..279499e 100644 --- a/Source/Silverfly/Parser.cs +++ b/Source/Silverfly/Parser.cs @@ -211,7 +211,7 @@ public bool Match(Symbol expected) /// /// An array of expected symbols to match. /// True if any of the expected symbols match; otherwise, false. - public bool Match(Symbol[] expected) + public bool Match(params Symbol[] expected) { foreach (var symbol in expected) { From 9a7c9c41688ac917bb47f49d71359dce95fb8c4d Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Tue, 27 Aug 2024 22:07:50 +0200 Subject: [PATCH 22/42] fix(sample-rockstar): Print --- Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs | 2 +- Source/Samples/Sample.Rockstar/Parselets/PrintParselet.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs b/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs index 328911d..9e2c37a 100644 --- a/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs +++ b/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs @@ -20,7 +20,7 @@ private object VisitAssignment(BinaryOperatorNode node, Scope scope) private object VisitCall(CallNode call, Scope scope) { - if (call.FunctionExpr is NameNode name && name.Token == "say") + if (call.FunctionExpr is NameNode name && name.Token == "print") { Console.WriteLine(Visit(call.Arguments[0], scope)); } diff --git a/Source/Samples/Sample.Rockstar/Parselets/PrintParselet.cs b/Source/Samples/Sample.Rockstar/Parselets/PrintParselet.cs index 148576a..17d6c81 100644 --- a/Source/Samples/Sample.Rockstar/Parselets/PrintParselet.cs +++ b/Source/Samples/Sample.Rockstar/Parselets/PrintParselet.cs @@ -9,7 +9,7 @@ public class PrintParselet : IPrefixParselet public static readonly string[] Aliases = ["say", "shout", "whisper", "scream"]; public AstNode Parse(Parser parser, Token token) { - var func = new NameNode(token); + var func = new NameNode(token.Rewrite("print")); ImmutableList args = [parser.ParseExpression()]; return new CallNode(func, args).WithRange(token, parser.LookAhead(0)); From 5da3413d8962a06fd8d4e22b4f482069a000d316 Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Wed, 28 Aug 2024 12:43:56 +0200 Subject: [PATCH 23/42] refactor(sample-rockstar): Some Stuff --- .../Evaluation/EvaluationVisitor.cs | 14 +++++++++++--- Source/Samples/Sample.Rockstar/RockstarGrammar.cs | 5 ++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs b/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs index 9e2c37a..7a8e708 100644 --- a/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs +++ b/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs @@ -1,7 +1,7 @@ using Silverfly.Generator; using Silverfly.Nodes; using Silverfly.Nodes.Operators; -using Silverfly.Sample.Rockstar.Evaluation; +using Silverfly.Text; namespace Silverfly.Sample.Rockstar.Evaluation; @@ -13,7 +13,7 @@ private object VisitAssignment(BinaryOperatorNode node, Scope scope) { if (node.LeftExpr is not NameNode name) return null; - scope.Define(name.Token.Text.ToString(), Visit(node.RightExpr, scope)); + scope.Define(name.Token.Text.Trim().ToString(), Visit(node.RightExpr, scope)); return null; } @@ -22,7 +22,15 @@ private object VisitCall(CallNode call, Scope scope) { if (call.FunctionExpr is NameNode name && name.Token == "print") { - Console.WriteLine(Visit(call.Arguments[0], scope)); + var arg = Visit(call.Arguments[0], scope); + + if (arg is null && call.Arguments[0] is NameNode n) + { + call.Arguments[0].AddMessage(MessageSeverity.Error, $"Variable '{n.Token.Text}' is not defined"); + return null; + } + + Console.WriteLine(arg); } return null; diff --git a/Source/Samples/Sample.Rockstar/RockstarGrammar.cs b/Source/Samples/Sample.Rockstar/RockstarGrammar.cs index ce664d9..4461b67 100644 --- a/Source/Samples/Sample.Rockstar/RockstarGrammar.cs +++ b/Source/Samples/Sample.Rockstar/RockstarGrammar.cs @@ -26,7 +26,8 @@ protected override void InitLexer(LexerConfig lexer) lexer.AddKeywords(poeticLiteralMatcher.Aliases); lexer.AddKeywords(PrintParselet.Aliases); lexer.AddKeywords("let", "be", "put", "into"); - + + lexer.AddSymbol(Environment.NewLine + Environment.NewLine); //blank lines lexer.AddSymbol(Environment.NewLine); lexer.MatchNumber(false,false); @@ -59,5 +60,7 @@ protected override void InitParser(ParserDefinition def) def.Register(Number, new NumberParselet()); def.Register(new PrintParselet(), PrintParselet.Aliases); + + def.Block("if", "#line"); } } From 9855375bfc63c3ee14ade8ccf006cc81c510b679 Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Sat, 31 Aug 2024 14:35:31 +0200 Subject: [PATCH 24/42] feat(sample-brainf*ck): Add Initial Work --- .../Samples/Sample.Brainfuck/EvalVisitor.cs | 68 +++++++++++++++++++ .../Sample.Brainfuck/Nodes/OperationNode.cs | 9 +++ .../Parselets/OperationParselet.cs | 14 ++++ Source/Samples/Sample.Brainfuck/Program.cs | 9 +++ Source/Samples/Sample.Brainfuck/Repl.cs | 12 ++++ Source/Silverfly.sln | 7 ++ 6 files changed, 119 insertions(+) create mode 100644 Source/Samples/Sample.Brainfuck/EvalVisitor.cs create mode 100644 Source/Samples/Sample.Brainfuck/Nodes/OperationNode.cs create mode 100644 Source/Samples/Sample.Brainfuck/Parselets/OperationParselet.cs create mode 100644 Source/Samples/Sample.Brainfuck/Program.cs create mode 100644 Source/Samples/Sample.Brainfuck/Repl.cs diff --git a/Source/Samples/Sample.Brainfuck/EvalVisitor.cs b/Source/Samples/Sample.Brainfuck/EvalVisitor.cs new file mode 100644 index 0000000..1b79ada --- /dev/null +++ b/Source/Samples/Sample.Brainfuck/EvalVisitor.cs @@ -0,0 +1,68 @@ +using Sample.Brainfuck.Nodes; +using Silverfly; +using Silverfly.Generator; +using Silverfly.Nodes; + +namespace Sample.Brainfuck; + +[Visitor] +public partial class EvalVisitor : NodeVisitor +{ + private int pointer = 0; + char[] cells = new char[100]; + + [VisitorCondition("_.Token == '.'")] + void Print(OperationNode node) + { + Console.WriteLine(cells[pointer]); + } + + [VisitorCondition("_.Token == ','")] + void Read(OperationNode node) + { + cells[pointer] = Console.ReadKey().KeyChar; + } + + [VisitorCondition("_.Token == '<'")] + void Decrement(OperationNode node) + { + pointer--; + } + + [VisitorCondition("_.Token == '>'")] + void Increment(OperationNode node) + { + pointer++; + } + + [VisitorCondition("_.Token == '+'")] + void IncrementCell(OperationNode node) + { + cells[pointer]++; + } + + [VisitorCondition("_.Token == '-'")] + void DecrementCell(OperationNode node) + { + cells[pointer]--; + } + + [VisitorCondition("_.LeftSymbol == '['")] + void Loop(GroupNode node) + { + while (cells[pointer] != 0) { + Visit(); + } + } +} + +/* +> ptr++; inkrementiert den Zeiger +< ptr--; dekrementiert den Zeiger ++ cell[ptr]++; inkrementiert den aktuellen Zellenwert +− cell[ptr]--; dekrementiert den aktuellen Zellenwert +. putchar (cell[ptr]); Gibt den aktuellen Zellenwert als ASCII-Zeichen auf der Standardausgabe aus +, cell[ptr] = getchar(); Liest ein Zeichen von der Standardeingabe und speichert dessen ASCII-Wert in der aktuellen Zelle +[ while (cell[ptr]) { Springt nach vorne, hinter den passenden ]-Befehl, wenn der aktuelle Zellenwert 0 ist +] } Springt zurück, hinter den passenden [-Befehl, wenn der aktuelle Zellenwert nicht 0 ist +*/ diff --git a/Source/Samples/Sample.Brainfuck/Nodes/OperationNode.cs b/Source/Samples/Sample.Brainfuck/Nodes/OperationNode.cs new file mode 100644 index 0000000..6532f24 --- /dev/null +++ b/Source/Samples/Sample.Brainfuck/Nodes/OperationNode.cs @@ -0,0 +1,9 @@ +using Silverfly; +using Silverfly.Nodes; + +namespace Sample.Brainfuck.Nodes; + +public record OperationNode(Token Token) : AstNode +{ + +} diff --git a/Source/Samples/Sample.Brainfuck/Parselets/OperationParselet.cs b/Source/Samples/Sample.Brainfuck/Parselets/OperationParselet.cs new file mode 100644 index 0000000..871b67f --- /dev/null +++ b/Source/Samples/Sample.Brainfuck/Parselets/OperationParselet.cs @@ -0,0 +1,14 @@ +using Sample.Brainfuck.Nodes; +using Silverfly; +using Silverfly.Nodes; +using Silverfly.Parselets; + +namespace Sample.Brainfuck.Parselets; + +public class OperationParselet : IPrefixParselet +{ + public AstNode Parse(Parser parser, Token token) + { + return new OperationNode(token).WithRange(token); + } +} diff --git a/Source/Samples/Sample.Brainfuck/Program.cs b/Source/Samples/Sample.Brainfuck/Program.cs new file mode 100644 index 0000000..0bb4abf --- /dev/null +++ b/Source/Samples/Sample.Brainfuck/Program.cs @@ -0,0 +1,9 @@ +namespace Sample.Brainfuck; + +public static class Program +{ + public static async Task Main(string[] args) + { + new Repl().Run(); + } +} diff --git a/Source/Samples/Sample.Brainfuck/Repl.cs b/Source/Samples/Sample.Brainfuck/Repl.cs new file mode 100644 index 0000000..ef8fd71 --- /dev/null +++ b/Source/Samples/Sample.Brainfuck/Repl.cs @@ -0,0 +1,12 @@ +using Silverfly.Repl; + +namespace Sample.Brainfuck; + +public class Repl : ReplInstance +{ + protected override void Evaluate(string input) + { + var parsed = Parser.Parse(input); + parsed.Tree.Accept(new EvalVisitor()); + } +} diff --git a/Source/Silverfly.sln b/Source/Silverfly.sln index 905aca0..addedbf 100644 --- a/Source/Silverfly.sln +++ b/Source/Silverfly.sln @@ -23,6 +23,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silverfly.Repl", "Silverfly EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.Rockstar", "Samples\Sample.Rockstar\Sample.Rockstar.csproj", "{554CFF94-2F55-4E55-A176-A05283BF1063}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.Brainfuck", "Samples\Sample.Brainfuck\Sample.Brainfuck.csproj", "{C43A7724-4D0D-4600-9778-E479F8BD946B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -65,6 +67,10 @@ Global {554CFF94-2F55-4E55-A176-A05283BF1063}.Debug|Any CPU.Build.0 = Debug|Any CPU {554CFF94-2F55-4E55-A176-A05283BF1063}.Release|Any CPU.ActiveCfg = Release|Any CPU {554CFF94-2F55-4E55-A176-A05283BF1063}.Release|Any CPU.Build.0 = Release|Any CPU + {C43A7724-4D0D-4600-9778-E479F8BD946B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C43A7724-4D0D-4600-9778-E479F8BD946B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C43A7724-4D0D-4600-9778-E479F8BD946B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C43A7724-4D0D-4600-9778-E479F8BD946B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -76,5 +82,6 @@ Global {48BC421F-8E9A-467D-922F-601EA7B09B06} = {DBC8A4C9-DBD3-4397-9EDC-CE9363947718} {AF32C673-CC70-4012-89D9-0249EC74ACB6} = {DBC8A4C9-DBD3-4397-9EDC-CE9363947718} {554CFF94-2F55-4E55-A176-A05283BF1063} = {DBC8A4C9-DBD3-4397-9EDC-CE9363947718} + {C43A7724-4D0D-4600-9778-E479F8BD946B} = {DBC8A4C9-DBD3-4397-9EDC-CE9363947718} EndGlobalSection EndGlobal From e61a438a0d27267ae47852ed6381bd67c6a8b539 Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Sat, 31 Aug 2024 14:36:24 +0200 Subject: [PATCH 25/42] feat(lexer): Add Ability To Set TokenType For A Symbol --- Source/Silverfly/Lexer.cs | 9 +++++---- Source/Silverfly/LexerConfig.cs | 10 ++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Source/Silverfly/Lexer.cs b/Source/Silverfly/Lexer.cs index 08ab6fb..343693a 100644 --- a/Source/Silverfly/Lexer.cs +++ b/Source/Silverfly/Lexer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Text.RegularExpressions; using Silverfly.Text; @@ -178,7 +179,7 @@ private bool InvokeSymbols(out Token token) continue; } - token = LexSymbol(symbol.Key, Document); + token = LexSymbol(symbol, Document); return true; } @@ -216,13 +217,13 @@ private bool AdvanceIgnoreMatcher(char c) return false; } - private Token LexSymbol(string punctuatorKey, SourceDocument document) + private Token LexSymbol(KeyValuePair punctuatorKey, SourceDocument document) { var oldColumn = _column; - Advance(punctuatorKey.Length); + Advance(punctuatorKey.Key.Length); - return new(punctuatorKey, punctuatorKey.AsMemory(), _line, oldColumn, document); + return new(punctuatorKey.Value, punctuatorKey.Key.AsMemory(), _line, oldColumn, document); } private Token LexName(SourceDocument document) diff --git a/Source/Silverfly/LexerConfig.cs b/Source/Silverfly/LexerConfig.cs index 633219b..9ddd4fa 100644 --- a/Source/Silverfly/LexerConfig.cs +++ b/Source/Silverfly/LexerConfig.cs @@ -50,6 +50,16 @@ public void AddSymbol(string symbol) Symbols.Add(symbol, PredefinedSymbols.Pool.Get(symbol)); } + public void AddSymbol(string symbol, string type) + { + if (Symbols.ContainsKey(symbol)) + { + return; + } + + Symbols.Add(symbol, type); + } + /// /// Adds a keyword to the Keywords collection and Symbols dictionary. /// From 36a29a309ce180e90c8918b48fe89bb811e413a9 Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Sat, 31 Aug 2024 14:36:57 +0200 Subject: [PATCH 26/42] feat(parser): Better Error Messages --- Source/Silverfly/Parser.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Silverfly/Parser.cs b/Source/Silverfly/Parser.cs index 279499e..a2abfad 100644 --- a/Source/Silverfly/Parser.cs +++ b/Source/Silverfly/Parser.cs @@ -132,7 +132,7 @@ public AstNode Parse(int precedence) if (!ParserDefinition._prefixParselets.TryGetValue(token.Type, out var prefix)) { - token.Document.AddMessage(MessageSeverity.Error, "Could not parse prefix \"" + token.Type + "\".", token.GetRange()); + token.Document.AddMessage(MessageSeverity.Error, $"Failed to parse token '{token.Text}'", token.GetRange()); return new InvalidNode(token).WithRange(token); } @@ -146,7 +146,9 @@ public AstNode Parse(int precedence) if (!ParserDefinition._infixParselets.TryGetValue(token.Type, out var infix)) { token.Document.Messages.Add( - Message.Error("Could not parse \"" + token.Text + "\".", token.GetRange())); + Message.Error($"Failed to parse token '{token.Text}'", token.GetRange())); + + return new InvalidNode(token).WithRange(token); } left = infix!.Parse(this, left, token); From 7745b6f5d60f745b8ad23642c30f4a9ab36a5b49 Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Sat, 31 Aug 2024 14:37:24 +0200 Subject: [PATCH 27/42] feat(generator): Change Visibility --- Source/Silverfly.Generator/Definition/DefinitionGrammar.cs | 2 +- Source/Silverfly.Generator/Definition/GeneratorVisitor.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Silverfly.Generator/Definition/DefinitionGrammar.cs b/Source/Silverfly.Generator/Definition/DefinitionGrammar.cs index e54e8c6..6f4715f 100644 --- a/Source/Silverfly.Generator/Definition/DefinitionGrammar.cs +++ b/Source/Silverfly.Generator/Definition/DefinitionGrammar.cs @@ -3,7 +3,7 @@ namespace Silverfly.Generator.Definition; -public class DefinitionGrammar : Parser +internal class DefinitionGrammar : Parser { protected override void InitLexer(LexerConfig lexer) { diff --git a/Source/Silverfly.Generator/Definition/GeneratorVisitor.cs b/Source/Silverfly.Generator/Definition/GeneratorVisitor.cs index a875dc8..97896c3 100644 --- a/Source/Silverfly.Generator/Definition/GeneratorVisitor.cs +++ b/Source/Silverfly.Generator/Definition/GeneratorVisitor.cs @@ -4,7 +4,7 @@ namespace Silverfly.Generator.Definition; -public class GeneratorVisitor : NodeVisitor +internal class GeneratorVisitor : NodeVisitor { private const string IndentationString = " "; // 4 spaces for each indentation level private readonly StringBuilder _builder; From 717182b3dea78865965ccc520cd54c68b398e89a Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Sat, 31 Aug 2024 14:37:58 +0200 Subject: [PATCH 28/42] feat(sample-brainf*ck): Add Brainfuck Parser --- .../Sample.Brainfuck/BrainfuckParser.cs | 19 +++++++++++++++++++ .../Sample.Brainfuck/Sample.Brainfuck.csproj | 17 +++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 Source/Samples/Sample.Brainfuck/BrainfuckParser.cs create mode 100644 Source/Samples/Sample.Brainfuck/Sample.Brainfuck.csproj diff --git a/Source/Samples/Sample.Brainfuck/BrainfuckParser.cs b/Source/Samples/Sample.Brainfuck/BrainfuckParser.cs new file mode 100644 index 0000000..209359d --- /dev/null +++ b/Source/Samples/Sample.Brainfuck/BrainfuckParser.cs @@ -0,0 +1,19 @@ +using Sample.Brainfuck.Parselets; +using Silverfly; + +namespace Sample.Brainfuck; + +public class BrainfuckParser : Parser +{ + protected override void InitLexer(LexerConfig lexer) + { + + } + + protected override void InitParser(ParserDefinition def) + { + def.Group("[", "]"); + + def.Register(new OperationParselet(), "+", "-", "<", ">", ".", ","); + } +} diff --git a/Source/Samples/Sample.Brainfuck/Sample.Brainfuck.csproj b/Source/Samples/Sample.Brainfuck/Sample.Brainfuck.csproj new file mode 100644 index 0000000..6e433ac --- /dev/null +++ b/Source/Samples/Sample.Brainfuck/Sample.Brainfuck.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + latest + enable + enable + Exe + + + + + + + + + From 43ca1ad74b71bd2af4bbcb4fab5196216f611b20 Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Sun, 1 Sep 2024 09:59:24 +0200 Subject: [PATCH 29/42] feat(sample-brainfuck): Finalization --- .../Sample.Brainfuck/BrainfuckParser.cs | 9 ++-- .../Samples/Sample.Brainfuck/EvalVisitor.cs | 48 ++++++++++--------- .../Parselets/LoopParselet.cs | 18 +++++++ Source/Samples/Sample.Brainfuck/Repl.cs | 22 ++++++++- 4 files changed, 70 insertions(+), 27 deletions(-) create mode 100644 Source/Samples/Sample.Brainfuck/Parselets/LoopParselet.cs diff --git a/Source/Samples/Sample.Brainfuck/BrainfuckParser.cs b/Source/Samples/Sample.Brainfuck/BrainfuckParser.cs index 209359d..4ef773d 100644 --- a/Source/Samples/Sample.Brainfuck/BrainfuckParser.cs +++ b/Source/Samples/Sample.Brainfuck/BrainfuckParser.cs @@ -1,5 +1,6 @@ using Sample.Brainfuck.Parselets; using Silverfly; +using Silverfly.Lexing.IgnoreMatcher.Comments; namespace Sample.Brainfuck; @@ -7,13 +8,15 @@ public class BrainfuckParser : Parser { protected override void InitLexer(LexerConfig lexer) { - + lexer.IgnoreWhitespace(); + lexer.Ignore(new SingleLineCommentIgnoreMatcher("#")); } protected override void InitParser(ParserDefinition def) { - def.Group("[", "]"); - def.Register(new OperationParselet(), "+", "-", "<", ">", ".", ","); + def.Register("[", new LoopParselet()); + + def.Block(PredefinedSymbols.SOF, PredefinedSymbols.EOF); } } diff --git a/Source/Samples/Sample.Brainfuck/EvalVisitor.cs b/Source/Samples/Sample.Brainfuck/EvalVisitor.cs index 1b79ada..fadcb31 100644 --- a/Source/Samples/Sample.Brainfuck/EvalVisitor.cs +++ b/Source/Samples/Sample.Brainfuck/EvalVisitor.cs @@ -8,61 +8,63 @@ namespace Sample.Brainfuck; [Visitor] public partial class EvalVisitor : NodeVisitor { - private int pointer = 0; - char[] cells = new char[100]; + private int _pointer = 0; + readonly char[] _cells = new char[100]; [VisitorCondition("_.Token == '.'")] void Print(OperationNode node) { - Console.WriteLine(cells[pointer]); + Console.WriteLine(_cells[_pointer]); } [VisitorCondition("_.Token == ','")] void Read(OperationNode node) { - cells[pointer] = Console.ReadKey().KeyChar; + _cells[_pointer] = Console.ReadKey().KeyChar; } [VisitorCondition("_.Token == '<'")] void Decrement(OperationNode node) { - pointer--; + _pointer--; } [VisitorCondition("_.Token == '>'")] void Increment(OperationNode node) { - pointer++; + _pointer++; } [VisitorCondition("_.Token == '+'")] void IncrementCell(OperationNode node) { - cells[pointer]++; + _cells[_pointer]++; } [VisitorCondition("_.Token == '-'")] void DecrementCell(OperationNode node) { - cells[pointer]--; + _cells[_pointer]--; } - [VisitorCondition("_.LeftSymbol == '['")] - void Loop(GroupNode node) + [VisitorCondition("_.Tag == 'loop'")] + void Loop(BlockNode node) { - while (cells[pointer] != 0) { - Visit(); + while (_cells[_pointer] != '\0') + { + foreach (var child in node.Children) + { + Visit(child); + } } } -} -/* -> ptr++; inkrementiert den Zeiger -< ptr--; dekrementiert den Zeiger -+ cell[ptr]++; inkrementiert den aktuellen Zellenwert -− cell[ptr]--; dekrementiert den aktuellen Zellenwert -. putchar (cell[ptr]); Gibt den aktuellen Zellenwert als ASCII-Zeichen auf der Standardausgabe aus -, cell[ptr] = getchar(); Liest ein Zeichen von der Standardeingabe und speichert dessen ASCII-Wert in der aktuellen Zelle -[ while (cell[ptr]) { Springt nach vorne, hinter den passenden ]-Befehl, wenn der aktuelle Zellenwert 0 ist -] } Springt zurück, hinter den passenden [-Befehl, wenn der aktuelle Zellenwert nicht 0 ist -*/ + [VisitorCondition("_.Tag == null")] + void Block(BlockNode node) + { + foreach (var child in node.Children) + { + Visit(child); + } + } +} diff --git a/Source/Samples/Sample.Brainfuck/Parselets/LoopParselet.cs b/Source/Samples/Sample.Brainfuck/Parselets/LoopParselet.cs new file mode 100644 index 0000000..85c387f --- /dev/null +++ b/Source/Samples/Sample.Brainfuck/Parselets/LoopParselet.cs @@ -0,0 +1,18 @@ +using Silverfly; +using Silverfly.Nodes; +using Silverfly.Parselets; + +namespace Sample.Brainfuck.Parselets; + +public class LoopParselet : IPrefixParselet +{ + public AstNode Parse(Parser parser, Token token) + { + var instructions = parser.ParseList(terminators: "]"); + + return new BlockNode(null, "]") + .WithChildren(instructions) + .WithTag("loop") + .WithRange(token, parser.LookAhead(0)); + } +} diff --git a/Source/Samples/Sample.Brainfuck/Repl.cs b/Source/Samples/Sample.Brainfuck/Repl.cs index ef8fd71..4d79bbe 100644 --- a/Source/Samples/Sample.Brainfuck/Repl.cs +++ b/Source/Samples/Sample.Brainfuck/Repl.cs @@ -6,7 +6,27 @@ public class Repl : ReplInstance { protected override void Evaluate(string input) { - var parsed = Parser.Parse(input); + var helloWorld = """ + ++++++++++ + [ + >+++++++>++++++++++>+++>+<<<<- + ] + >++. #'H' + >+. #'e' + +++++++. #'l' + . #'l' + +++. #'o' + >++. #Space + <<+++++++++++++++. #'W' + >. #'o' + +++. #'r' + ------. #'l' + --------. #'d' + >+. #'!' + >. + +++. +"""; + var parsed = Parser.Parse(helloWorld); parsed.Tree.Accept(new EvalVisitor()); } } From ef7148297abfb041d2377cf8deae3f6ece2725e5 Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Sun, 1 Sep 2024 09:59:56 +0200 Subject: [PATCH 30/42] feat: Add tag to all nodes --- Source/Silverfly/Nodes/AstNode.cs | 12 ++++++++++++ .../Silverfly/Nodes/Operators/PostfixOperatorNode.cs | 2 +- .../Silverfly/Nodes/Operators/PrefixOperatorNode.cs | 2 +- .../Parselets/Operators/PostfixOperatorParselet.cs | 5 +++-- .../Parselets/Operators/PrefixOperatorParselet.cs | 5 +++-- 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Source/Silverfly/Nodes/AstNode.cs b/Source/Silverfly/Nodes/AstNode.cs index 612b4e7..fe07f58 100644 --- a/Source/Silverfly/Nodes/AstNode.cs +++ b/Source/Silverfly/Nodes/AstNode.cs @@ -14,6 +14,11 @@ public abstract record AstNode /// public SourceRange Range { get; set; } + /// + /// A property to store extra information + /// + public object Tag { get; set; } + /// /// Gets or sets the parent node of this AST node. /// @@ -107,4 +112,11 @@ public void AddMessage(MessageSeverity severity, string message, Token token) { Range.Document.Messages.Add(new Message(severity, message, token.GetRange())); } + + public AstNode WithTag(object tag) + { + Tag = tag; + + return this; + } } diff --git a/Source/Silverfly/Nodes/Operators/PostfixOperatorNode.cs b/Source/Silverfly/Nodes/Operators/PostfixOperatorNode.cs index 842d0c4..7f3aab3 100644 --- a/Source/Silverfly/Nodes/Operators/PostfixOperatorNode.cs +++ b/Source/Silverfly/Nodes/Operators/PostfixOperatorNode.cs @@ -3,6 +3,6 @@ namespace Silverfly.Nodes.Operators; /// /// A postfix unary arithmetic expression like "a!" /// -public record PostfixOperatorNode(AstNode Expr, Token Operator, string Tag) : AstNode +public record PostfixOperatorNode(AstNode Expr, Token Operator) : AstNode { } diff --git a/Source/Silverfly/Nodes/Operators/PrefixOperatorNode.cs b/Source/Silverfly/Nodes/Operators/PrefixOperatorNode.cs index 2c4f9eb..63ff8b0 100644 --- a/Source/Silverfly/Nodes/Operators/PrefixOperatorNode.cs +++ b/Source/Silverfly/Nodes/Operators/PrefixOperatorNode.cs @@ -3,6 +3,6 @@ namespace Silverfly.Nodes.Operators; /// /// A prefix unary arithmetic expression like "!a" or "-b". /// -public record PrefixOperatorNode(Token Operator, AstNode Expr, string Tag) : AstNode +public record PrefixOperatorNode(Token Operator, AstNode Expr) : AstNode { } diff --git a/Source/Silverfly/Parselets/Operators/PostfixOperatorParselet.cs b/Source/Silverfly/Parselets/Operators/PostfixOperatorParselet.cs index 0daf6e9..b30feeb 100644 --- a/Source/Silverfly/Parselets/Operators/PostfixOperatorParselet.cs +++ b/Source/Silverfly/Parselets/Operators/PostfixOperatorParselet.cs @@ -11,8 +11,9 @@ public class PostfixOperatorParselet(int bindingPower, string tag) : IInfixParse public AstNode Parse(Parser parser, AstNode left, Token token) { - var node = new PostfixOperatorNode(left, token, tag) - .WithRange(left.Range.Document, left.Range.Start, token.GetSourceSpanEnd()); + var node = new PostfixOperatorNode(left, token) + .WithTag(tag) + .WithRange(left.Range.Document, left.Range.Start, token.GetSourceSpanEnd()); left.WithParent(node); diff --git a/Source/Silverfly/Parselets/Operators/PrefixOperatorParselet.cs b/Source/Silverfly/Parselets/Operators/PrefixOperatorParselet.cs index 9c35d08..840720a 100644 --- a/Source/Silverfly/Parselets/Operators/PrefixOperatorParselet.cs +++ b/Source/Silverfly/Parselets/Operators/PrefixOperatorParselet.cs @@ -16,8 +16,9 @@ public AstNode Parse(Parser parser, Token token) // take *this* parselet's result as its left-hand argument. var right = parser.Parse(bindingPower); - var node = new PrefixOperatorNode(token, right, tag) - .WithRange(token.Document, token.GetSourceSpanStart(), right.Range.End); + var node = new PrefixOperatorNode(token, right) + .WithTag(tag) + .WithRange(token.Document, token.GetSourceSpanStart(), right.Range.End); right.WithParent(node); From bd4dc1490dabe6c5c3c07f21cf19aff45997711e Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Sun, 1 Sep 2024 11:44:30 +0200 Subject: [PATCH 31/42] fix(sample-brainfuck): Wrong Console Write --- Source/Samples/Sample.Brainfuck/EvalVisitor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Samples/Sample.Brainfuck/EvalVisitor.cs b/Source/Samples/Sample.Brainfuck/EvalVisitor.cs index fadcb31..5de7c56 100644 --- a/Source/Samples/Sample.Brainfuck/EvalVisitor.cs +++ b/Source/Samples/Sample.Brainfuck/EvalVisitor.cs @@ -14,7 +14,7 @@ public partial class EvalVisitor : NodeVisitor [VisitorCondition("_.Token == '.'")] void Print(OperationNode node) { - Console.WriteLine(_cells[_pointer]); + Console.Write(_cells[_pointer]); } [VisitorCondition("_.Token == ','")] From 8410ce878d869685bb21498d416506f9d4a0540f Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 4 Sep 2024 08:45:57 +0200 Subject: [PATCH 32/42] feat(lexer): Add AdvanceIfMatch --- Source/Silverfly/Lexer.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Source/Silverfly/Lexer.cs b/Source/Silverfly/Lexer.cs index 343693a..9172f59 100644 --- a/Source/Silverfly/Lexer.cs +++ b/Source/Silverfly/Lexer.cs @@ -261,6 +261,17 @@ public void Advance(int distance = 1) _column += distance; } + /// + /// Advances the current position in the document if a symbol matches. + /// + public void AdvanceIfMatch(string symbol) + { + if (lexer.IsMatch(symbol, Config.IgnoreCasing)) + { + lexer.Advance(symbol.Length); + } + } + /// /// Determines whether the specified token name is a punctuator. /// From 1080dc292ac6b49a694fb27423154d8f719cc68c Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Wed, 4 Sep 2024 20:05:12 +0200 Subject: [PATCH 33/42] refactor: Cleanup --- Source/Silverfly/Lexer.cs | 4 ++-- Source/Silverfly/LexerConfig.cs | 4 ++-- Source/Silverfly/Lexing/Matcher/BooleanMatcher.cs | 15 ++++----------- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/Source/Silverfly/Lexer.cs b/Source/Silverfly/Lexer.cs index 9172f59..7be120c 100644 --- a/Source/Silverfly/Lexer.cs +++ b/Source/Silverfly/Lexer.cs @@ -266,9 +266,9 @@ public void Advance(int distance = 1) /// public void AdvanceIfMatch(string symbol) { - if (lexer.IsMatch(symbol, Config.IgnoreCasing)) + if (IsMatch(symbol, Config.IgnoreCasing)) { - lexer.Advance(symbol.Length); + Advance(symbol.Length); } } diff --git a/Source/Silverfly/LexerConfig.cs b/Source/Silverfly/LexerConfig.cs index 9ddd4fa..82180ba 100644 --- a/Source/Silverfly/LexerConfig.cs +++ b/Source/Silverfly/LexerConfig.cs @@ -181,9 +181,9 @@ public void AddSymbols(params string[] symbols) /// Adds a matcher to identify boolean values ('true' and 'false'). /// /// Flag indicating whether casing should be ignored when matching. - public void MatchBoolean(bool ignoreCasing = false) + public void MatchBoolean() { - AddMatcher(new BooleanMatcher(ignoreCasing)); + AddMatcher(new BooleanMatcher()); } /// diff --git a/Source/Silverfly/Lexing/Matcher/BooleanMatcher.cs b/Source/Silverfly/Lexing/Matcher/BooleanMatcher.cs index 1dec995..70e39b3 100644 --- a/Source/Silverfly/Lexing/Matcher/BooleanMatcher.cs +++ b/Source/Silverfly/Lexing/Matcher/BooleanMatcher.cs @@ -3,8 +3,7 @@ /// /// Represents a matcher that identifies boolean literals ("true" or "false") in the lexer input. /// -/// Determines whether the casing of the boolean literals should be ignored. -public class BooleanMatcher(bool ignoreCasing = false) : IMatcher +public class BooleanMatcher : IMatcher { /// /// Determines whether the current lexer position matches a boolean literal ("true" or "false"). @@ -16,7 +15,7 @@ public class BooleanMatcher(bool ignoreCasing = false) : IMatcher /// public bool Match(Lexer lexer, char c) { - return lexer.IsMatch("true", ignoreCasing) || lexer.IsMatch("false", ignoreCasing); + return lexer.IsMatch("true", lexer.Config.IgnoreCasing) || lexer.IsMatch("false", lexer.Config.IgnoreCasing); } /// @@ -34,14 +33,8 @@ public Token Build(Lexer lexer, ref int index, ref int column, ref int line) var oldColumn = column; var oldIndex = index; - if (lexer.IsMatch("true", ignoreCasing)) - { - lexer.Advance("true".Length); - } - else if (lexer.IsMatch("false", ignoreCasing)) - { - lexer.Advance("false".Length); - } + lexer.AdvanceIfMatch("true"); + lexer.AdvanceIfMatch("false"); return new(PredefinedSymbols.Boolean, lexer.Document.Source[oldIndex..index], line, oldColumn, lexer.Document); } From 7d417a5f9a0ffc3b1a1321c3c00cca7e2f3ed03d Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Sun, 8 Sep 2024 18:35:38 +0200 Subject: [PATCH 34/42] refactor: Cleanup --- Source/TestProject/TestParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/TestProject/TestParser.cs b/Source/TestProject/TestParser.cs index 78b1007..a00fc25 100644 --- a/Source/TestProject/TestParser.cs +++ b/Source/TestProject/TestParser.cs @@ -37,7 +37,7 @@ protected override void InitLexer(LexerConfig lexer) lexer.IgnoreWhitespace(); lexer.Ignore("\r", "\r\n"); - lexer.MatchBoolean(ignoreCasing: true); + lexer.MatchBoolean(); lexer.MatchString("'", "'"); lexer.MatchNumber(allowHex: true, allowBin: true); From 135cabb0a26b2639954ac9cb798baa2222100bf2 Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Sun, 8 Sep 2024 18:37:06 +0200 Subject: [PATCH 35/42] feat(sample-rock): Add if and while --- .../Samples/Sample.Rockstar/Nodes/IfNode.cs | 7 ++++++ .../Samples/Sample.Rockstar/Nodes/LoopNode.cs | 6 +++++ .../Sample.Rockstar/Parselets/IfParselet.cs | 16 ++++++++++++++ .../Sample.Rockstar/Parselets/LoopParselet.cs | 22 +++++++++++++++++++ 4 files changed, 51 insertions(+) create mode 100644 Source/Samples/Sample.Rockstar/Nodes/IfNode.cs create mode 100644 Source/Samples/Sample.Rockstar/Nodes/LoopNode.cs create mode 100644 Source/Samples/Sample.Rockstar/Parselets/IfParselet.cs create mode 100644 Source/Samples/Sample.Rockstar/Parselets/LoopParselet.cs diff --git a/Source/Samples/Sample.Rockstar/Nodes/IfNode.cs b/Source/Samples/Sample.Rockstar/Nodes/IfNode.cs new file mode 100644 index 0000000..4df387f --- /dev/null +++ b/Source/Samples/Sample.Rockstar/Nodes/IfNode.cs @@ -0,0 +1,7 @@ +using System.Collections.Immutable; +using Silverfly.Nodes; + +namespace Silverfly.Sample.Rockstar.Nodes; + +public record IfNode(AstNode Condition, ImmutableList TruePart, ImmutableList FalsePart) + : StatementNode; diff --git a/Source/Samples/Sample.Rockstar/Nodes/LoopNode.cs b/Source/Samples/Sample.Rockstar/Nodes/LoopNode.cs new file mode 100644 index 0000000..d388442 --- /dev/null +++ b/Source/Samples/Sample.Rockstar/Nodes/LoopNode.cs @@ -0,0 +1,6 @@ +using System.Collections.Immutable; +using Silverfly.Nodes; + +namespace Silverfly.Sample.Rockstar.Nodes; + +public record LoopNode(AstNode Condition, ImmutableList Body) : StatementNode; diff --git a/Source/Samples/Sample.Rockstar/Parselets/IfParselet.cs b/Source/Samples/Sample.Rockstar/Parselets/IfParselet.cs new file mode 100644 index 0000000..212dd6e --- /dev/null +++ b/Source/Samples/Sample.Rockstar/Parselets/IfParselet.cs @@ -0,0 +1,16 @@ +using Silverfly.Nodes; +using Silverfly.Parselets; +using Silverfly.Sample.Rockstar.Nodes; + +namespace Silverfly.Sample.Rockstar.Parselets; + +public class IfParselet : IStatementParselet +{ + public AstNode Parse(Parser parser, Token token) + { + var condition = parser.ParseExpression(); + var body = parser.ParseList(PredefinedSymbols.EOF, Environment.NewLine + Environment.NewLine); + + return new IfNode(condition, body, []); + } +} diff --git a/Source/Samples/Sample.Rockstar/Parselets/LoopParselet.cs b/Source/Samples/Sample.Rockstar/Parselets/LoopParselet.cs new file mode 100644 index 0000000..3901748 --- /dev/null +++ b/Source/Samples/Sample.Rockstar/Parselets/LoopParselet.cs @@ -0,0 +1,22 @@ +using Silverfly.Nodes; +using Silverfly.Parselets; +using Silverfly.Sample.Rockstar.Nodes; + +namespace Silverfly.Sample.Rockstar.Parselets; + +public class LoopParselet : IStatementParselet +{ + public AstNode Parse(Parser parser, Token token) + { + var condition = parser.ParseExpression(); + + if (parser.IsMatch(",") || parser.IsMatch("\n")) + { + parser.Consume(); + } + + var body = parser.ParseList(PredefinedSymbols.EOF, "\n\n", "."); + + return new LoopNode(condition, body); + } +} From f34daa7abd7a064da48470c6502ab44382fa7291 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 10 Sep 2024 10:15:38 +0200 Subject: [PATCH 36/42] feat(lexer): AdvanceIfMatch returns boolean --- Source/Silverfly/Lexer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Silverfly/Lexer.cs b/Source/Silverfly/Lexer.cs index 7be120c..e60565c 100644 --- a/Source/Silverfly/Lexer.cs +++ b/Source/Silverfly/Lexer.cs @@ -264,12 +264,16 @@ public void Advance(int distance = 1) /// /// Advances the current position in the document if a symbol matches. /// - public void AdvanceIfMatch(string symbol) + public bool AdvanceIfMatch(string symbol) { if (IsMatch(symbol, Config.IgnoreCasing)) { Advance(symbol.Length); + + return true; } + + return false; } /// From 2fbcd79c5d3c56aada18b7c98b6aaa966ea74645 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 10 Sep 2024 11:42:28 +0000 Subject: [PATCH 37/42] feat(repl): Add Number Highlighting --- Source/Silverfly.Repl/ReplPromptCallbacks.cs | 21 ++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Source/Silverfly.Repl/ReplPromptCallbacks.cs b/Source/Silverfly.Repl/ReplPromptCallbacks.cs index 9afc6e0..ed6f788 100644 --- a/Source/Silverfly.Repl/ReplPromptCallbacks.cs +++ b/Source/Silverfly.Repl/ReplPromptCallbacks.cs @@ -21,6 +21,7 @@ protected override Task> HighlightCallbackAsync( var spans = GetKeywordSpans(text, keywords) .Concat(brackets) + .Concat(GetNumberSpans(text)) .Concat(GetStringsSpans(text)) .ToList(); @@ -109,4 +110,24 @@ private static IEnumerable GetStringsSpans(string text) offset = endIndex + 1; } } + + private static IEnumerable GetNumberSpans(string text) + { + int offset = 0; + + while (offset < text.Length) + { + if (char.IsDigit(text[offset])) + { + int startIndex = offset; + + while (char.IsDigit(text[offset])) + { + offset++; + } + + yield return new FormatSpan(startIndex, offset, ToAnsi(MessageFormatter.Theme.Number)); + } + } + } } From 9bed3b0fb19644d19342c889b8457c13b20b1f0f Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 11 Sep 2024 06:15:25 +0000 Subject: [PATCH 38/42] feat(lexer): Add StringSyntaxAttribute to autocomplete patterns --- Source/Silverfly/LexerConfig.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Source/Silverfly/LexerConfig.cs b/Source/Silverfly/LexerConfig.cs index 82180ba..6594e8c 100644 --- a/Source/Silverfly/LexerConfig.cs +++ b/Source/Silverfly/LexerConfig.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text.RegularExpressions; using Silverfly.Lexing; @@ -70,14 +71,14 @@ public void AddKeyword(string keyword) Symbols.TryAdd(keyword, keyword); } - + /// /// Adds multiple keywords to the Keywords collection and Symbols dictionary. /// /// An array of keywords to be added. public void AddKeywords(params string[] keywords) { - this.Keywords.AddRange(keywords); + Keywords.AddRange(keywords); foreach (var keyword in keywords) { @@ -136,7 +137,7 @@ public void MatchNumber(bool allowHex, bool allowBin, Symbol floatingPointSymbol AddMatcher(new NumberMatcher(allowHex, allowBin, floatingPointSymbol ?? PredefinedSymbols.Dot, separatorSymbol ?? PredefinedSymbols.Underscore)); } - + /// /// Adds a new regular expression matcher for a specified symbol type. /// @@ -150,7 +151,7 @@ public void MatchPattern(Symbol type, Regex regex) { AddMatcher(new RegexMatcher(type, regex)); } - + /// /// Adds a new regular expression matcher for a specified symbol type. /// @@ -160,7 +161,7 @@ public void MatchPattern(Symbol type, Regex regex) /// This method creates a new instance of with the given symbol type /// and regular expression pattern, and adds it to the matchers collection. /// - public void MatchPattern(Symbol type, string pattern) + public void MatchPattern(Symbol type, [StringSyntax(StringSyntaxAttribute.Regex)] string pattern) { MatchPattern(type, new Regex(pattern)); } @@ -203,7 +204,7 @@ public void Ignore(char c) /// This method converts the provided pattern into a object and /// calls the method to handle the exclusion. /// - public void IgnorePattern(string pattern) + public void IgnorePattern([StringSyntax(StringSyntaxAttribute.Regex)] string pattern) { IgnorePattern(new Regex(pattern)); } From 17478ba8986f318649fd887e22a007870ef241fc Mon Sep 17 00:00:00 2001 From: Chris Anders Date: Wed, 11 Sep 2024 09:01:08 +0200 Subject: [PATCH 39/42] refactor: Cleanup --- Source/Samples/Sample.Rockstar/Repl.cs | 5 ++--- Source/Silverfly/LexerConfig.cs | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Source/Samples/Sample.Rockstar/Repl.cs b/Source/Samples/Sample.Rockstar/Repl.cs index d32af07..eab91d2 100644 --- a/Source/Samples/Sample.Rockstar/Repl.cs +++ b/Source/Samples/Sample.Rockstar/Repl.cs @@ -1,8 +1,7 @@ -using Sample.Rockstar.Evaluation; -using Silverfly.Repl; +using Silverfly.Repl; using Silverfly.Sample.Rockstar.Evaluation; -namespace Sample.Rockstar; +namespace Silverfly.Sample.Rockstar; public class Repl : ReplInstance { diff --git a/Source/Silverfly/LexerConfig.cs b/Source/Silverfly/LexerConfig.cs index 6594e8c..0236751 100644 --- a/Source/Silverfly/LexerConfig.cs +++ b/Source/Silverfly/LexerConfig.cs @@ -15,8 +15,8 @@ public class LexerConfig internal INameAdvancer NameAdvancer = new DefaultNameAdvancer(); internal Dictionary Symbols = []; public readonly List Keywords = []; - internal readonly List Matchers = []; - internal readonly List IgnoreMatchers = []; + public readonly List Matchers = []; + public readonly List IgnoreMatchers = []; public bool IgnoreCasing { get; set; } public StringComparison Casing => IgnoreCasing ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; From c2545c64e680774474ea17e9e7229b6c5a91a935 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 27 Sep 2024 07:12:11 +0000 Subject: [PATCH 40/42] feat: Add Helper Methods To Interact With The AST --- Source/Silverfly/Nodes/AstNode.cs | 59 ++++++++++++++++++++++++++++- Source/Silverfly/Nodes/BlockNode.cs | 24 +++++++++++- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/Source/Silverfly/Nodes/AstNode.cs b/Source/Silverfly/Nodes/AstNode.cs index fe07f58..d6177ad 100644 --- a/Source/Silverfly/Nodes/AstNode.cs +++ b/Source/Silverfly/Nodes/AstNode.cs @@ -17,7 +17,7 @@ public abstract record AstNode /// /// A property to store extra information /// - public object Tag { get; set; } + public object? Tag { get; set; } /// /// Gets or sets the parent node of this AST node. @@ -103,20 +103,77 @@ public AstNode WithRange(AstNode node, Token token) /// The tag to provide to the visitor. public void Accept(TaggedNodeVisitor visitor, TTag tag) => visitor.Visit(this, tag); + /// + /// Adds a message to the document with the current node range + /// + /// + /// public void AddMessage(MessageSeverity severity, string message) { Range.Document.Messages.Add(new Message(severity, message, Range)); } + /// + /// Adds a message to the document and uses a token for a location + /// + /// + /// public void AddMessage(MessageSeverity severity, string message, Token token) { Range.Document.Messages.Add(new Message(severity, message, token.GetRange())); } + /// + /// Set a tag + /// + /// + /// public AstNode WithTag(object tag) { Tag = tag; return this; } + + /// + /// Check if the node has a parent of a specific type + /// + /// + /// + public bool HasParent() + where T : AstNode + { + return Parent is T; + } + + /// + /// Get the parent as specific type + /// + /// + /// + public T? GetParentAs() + where T : AstNode + { + return Parent as T; + } + + /// + /// Checks if the node has a specific tag set + /// + /// + /// + public bool HasTag(object tag) + { + return Tag == tag; + } + + /// + /// Get the tag as type + /// + /// + /// + public T? GetTag() + { + return (T?)Tag; + } } diff --git a/Source/Silverfly/Nodes/BlockNode.cs b/Source/Silverfly/Nodes/BlockNode.cs index 800de76..07385e0 100644 --- a/Source/Silverfly/Nodes/BlockNode.cs +++ b/Source/Silverfly/Nodes/BlockNode.cs @@ -1,4 +1,6 @@ -using System.Collections.Immutable; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; namespace Silverfly.Nodes; @@ -22,4 +24,24 @@ public BlockNode WithChildren(ImmutableList nodes) Children = nodes; return this; } + + /// + /// Get children of a specific type + /// + /// + /// + public IEnumerable GetChildren() + { + return Children.OfType(); + } + + /// + /// Checks if it has at least one child of a specific type + /// + /// + /// + public bool HasChild() + { + return Children.Any(n => n is T); + } } From 7349e30c13f6d0b4f962545e447a9a2b20ca2132 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 27 Sep 2024 07:45:00 +0000 Subject: [PATCH 41/42] chore: Update Version --- Source/Samples/Sample.FuncLanguage/Sample.FuncLanguage.csproj | 2 +- Source/Samples/Sample.JSON/Sample.JSON.csproj | 2 +- Source/Samples/Sample.Rockstar/Sample.Rockstar.csproj | 2 +- Source/Silverfly.Generator/Silverfly.Generator.csproj | 2 +- Source/Silverfly.Repl/Silverfly.Repl.csproj | 2 +- Source/Silverfly/Silverfly.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/Samples/Sample.FuncLanguage/Sample.FuncLanguage.csproj b/Source/Samples/Sample.FuncLanguage/Sample.FuncLanguage.csproj index f4d403e..63f2bd7 100644 --- a/Source/Samples/Sample.FuncLanguage/Sample.FuncLanguage.csproj +++ b/Source/Samples/Sample.FuncLanguage/Sample.FuncLanguage.csproj @@ -1,7 +1,7 @@  - 1.0.68 + 1.0.69 Exe preview true diff --git a/Source/Samples/Sample.JSON/Sample.JSON.csproj b/Source/Samples/Sample.JSON/Sample.JSON.csproj index 6a95c8b..066ed36 100644 --- a/Source/Samples/Sample.JSON/Sample.JSON.csproj +++ b/Source/Samples/Sample.JSON/Sample.JSON.csproj @@ -1,6 +1,7 @@  + 1.0.69 Exe net8.0 true @@ -14,7 +15,6 @@ net8.0 enable enable - 1.0.68 diff --git a/Source/Samples/Sample.Rockstar/Sample.Rockstar.csproj b/Source/Samples/Sample.Rockstar/Sample.Rockstar.csproj index c72590d..9a1eaeb 100644 --- a/Source/Samples/Sample.Rockstar/Sample.Rockstar.csproj +++ b/Source/Samples/Sample.Rockstar/Sample.Rockstar.csproj @@ -1,7 +1,7 @@  - 1.0.68 + 1.0.69 Exe preview true diff --git a/Source/Silverfly.Generator/Silverfly.Generator.csproj b/Source/Silverfly.Generator/Silverfly.Generator.csproj index 8e6d0b3..4ae0e8d 100644 --- a/Source/Silverfly.Generator/Silverfly.Generator.csproj +++ b/Source/Silverfly.Generator/Silverfly.Generator.csproj @@ -1,7 +1,7 @@  - 1.0.68 + 1.0.69 net8.0 enable enable diff --git a/Source/Silverfly.Repl/Silverfly.Repl.csproj b/Source/Silverfly.Repl/Silverfly.Repl.csproj index 016d596..e6f80e7 100644 --- a/Source/Silverfly.Repl/Silverfly.Repl.csproj +++ b/Source/Silverfly.Repl/Silverfly.Repl.csproj @@ -1,7 +1,7 @@  - 1.0.68 + 1.0.69 preview true Silverfly.Repl diff --git a/Source/Silverfly/Silverfly.csproj b/Source/Silverfly/Silverfly.csproj index 4aab185..ccc4396 100644 --- a/Source/Silverfly/Silverfly.csproj +++ b/Source/Silverfly/Silverfly.csproj @@ -1,7 +1,7 @@ - 1.0.68 + 1.0.69 preview true Silverfly From 887d10e0bda68f397cb897d2e20af9e1b8a24ec5 Mon Sep 17 00:00:00 2001 From: codefactor-io Date: Fri, 27 Sep 2024 07:45:32 +0000 Subject: [PATCH 42/42] [CodeFactor] Apply fixes --- Source/Samples/Sample.Brainfuck/Nodes/OperationNode.cs | 3 +-- .../Sample.Rockstar/Parselets/PoeticLiteralParselet.cs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Source/Samples/Sample.Brainfuck/Nodes/OperationNode.cs b/Source/Samples/Sample.Brainfuck/Nodes/OperationNode.cs index 6532f24..32adc3b 100644 --- a/Source/Samples/Sample.Brainfuck/Nodes/OperationNode.cs +++ b/Source/Samples/Sample.Brainfuck/Nodes/OperationNode.cs @@ -1,9 +1,8 @@ -using Silverfly; +using Silverfly; using Silverfly.Nodes; namespace Sample.Brainfuck.Nodes; public record OperationNode(Token Token) : AstNode { - } diff --git a/Source/Samples/Sample.Rockstar/Parselets/PoeticLiteralParselet.cs b/Source/Samples/Sample.Rockstar/Parselets/PoeticLiteralParselet.cs index 4743bc4..ed538b6 100644 --- a/Source/Samples/Sample.Rockstar/Parselets/PoeticLiteralParselet.cs +++ b/Source/Samples/Sample.Rockstar/Parselets/PoeticLiteralParselet.cs @@ -1,4 +1,4 @@ -using Silverfly.Nodes; +using Silverfly.Nodes; using Silverfly.Nodes.Operators; using Silverfly.Parselets; @@ -63,7 +63,7 @@ private static double ConvertPoeticNumber(List tmp) } else { - numValue = numValue * 10 + digit; + numValue = (numValue * 10) + digit; } } }