diff --git a/Source/Samples/Sample.Brainfuck/BrainfuckParser.cs b/Source/Samples/Sample.Brainfuck/BrainfuckParser.cs new file mode 100644 index 0000000..4ef773d --- /dev/null +++ b/Source/Samples/Sample.Brainfuck/BrainfuckParser.cs @@ -0,0 +1,22 @@ +using Sample.Brainfuck.Parselets; +using Silverfly; +using Silverfly.Lexing.IgnoreMatcher.Comments; + +namespace Sample.Brainfuck; + +public class BrainfuckParser : Parser +{ + protected override void InitLexer(LexerConfig lexer) + { + lexer.IgnoreWhitespace(); + lexer.Ignore(new SingleLineCommentIgnoreMatcher("#")); + } + + protected override void InitParser(ParserDefinition def) + { + 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 new file mode 100644 index 0000000..5de7c56 --- /dev/null +++ b/Source/Samples/Sample.Brainfuck/EvalVisitor.cs @@ -0,0 +1,70 @@ +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; + readonly char[] _cells = new char[100]; + + [VisitorCondition("_.Token == '.'")] + void Print(OperationNode node) + { + Console.Write(_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("_.Tag == 'loop'")] + void Loop(BlockNode node) + { + while (_cells[_pointer] != '\0') + { + foreach (var child in node.Children) + { + Visit(child); + } + } + } + + [VisitorCondition("_.Tag == null")] + void Block(BlockNode node) + { + foreach (var child in node.Children) + { + Visit(child); + } + } +} diff --git a/Source/Samples/Sample.Brainfuck/Nodes/OperationNode.cs b/Source/Samples/Sample.Brainfuck/Nodes/OperationNode.cs new file mode 100644 index 0000000..32adc3b --- /dev/null +++ b/Source/Samples/Sample.Brainfuck/Nodes/OperationNode.cs @@ -0,0 +1,8 @@ +using Silverfly; +using Silverfly.Nodes; + +namespace Sample.Brainfuck.Nodes; + +public record OperationNode(Token Token) : AstNode +{ +} 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/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..4d79bbe --- /dev/null +++ b/Source/Samples/Sample.Brainfuck/Repl.cs @@ -0,0 +1,32 @@ +using Silverfly.Repl; + +namespace Sample.Brainfuck; + +public class Repl : ReplInstance +{ + protected override void Evaluate(string input) + { + var helloWorld = """ + ++++++++++ + [ + >+++++++>++++++++++>+++>+<<<<- + ] + >++. #'H' + >+. #'e' + +++++++. #'l' + . #'l' + +++. #'o' + >++. #Space + <<+++++++++++++++. #'W' + >. #'o' + +++. #'r' + ------. #'l' + --------. #'d' + >+. #'!' + >. + +++. +"""; + var parsed = Parser.Parse(helloWorld); + parsed.Tree.Accept(new EvalVisitor()); + } +} 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 + + + + + + + + + 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/Evaluation/EvaluationVisitor.cs b/Source/Samples/Sample.Rockstar/Evaluation/EvaluationVisitor.cs index ece22a9..7a8e708 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; +using Silverfly.Text; -namespace Sample.Rockstar.Evaluation; +namespace Silverfly.Sample.Rockstar.Evaluation; [Visitor] public partial class EvaluationVisitor : TaggedNodeVisitor @@ -14,16 +13,24 @@ 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; } 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)); + 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/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/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/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/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); + } +} 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/PoeticLiteralParselet.cs b/Source/Samples/Sample.Rockstar/Parselets/PoeticLiteralParselet.cs new file mode 100644 index 0000000..ed538b6 --- /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/Parselets/PrintParselet.cs b/Source/Samples/Sample.Rockstar/Parselets/PrintParselet.cs index bfc120e..17d6c81 100644 --- a/Source/Samples/Sample.Rockstar/Parselets/PrintParselet.cs +++ b/Source/Samples/Sample.Rockstar/Parselets/PrintParselet.cs @@ -1,16 +1,15 @@ 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 { 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)); 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 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/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..4461b67 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,13 +17,18 @@ 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"); + + lexer.AddSymbol(Environment.NewLine + Environment.NewLine); //blank lines + lexer.AddSymbol(Environment.NewLine); lexer.MatchNumber(false,false); @@ -34,8 +38,9 @@ protected override void InitLexer(LexerConfig lexer) lexer.AddMatcher(emptyStringMatcher); lexer.AddMatcher(nullStringMatcher); lexer.AddMatcher(pronounMatcher); + lexer.AddMatcher(poeticLiteralMatcher); - lexer.IgnoreWhitespace(); + lexer.Ignore(" "); lexer.Ignore(new MultiLineCommentIgnoreMatcher("(", ")")); } @@ -49,10 +54,13 @@ protected override void InitParser(ParserDefinition def) def.Register("#pronoun", new MappingParselet(null)); def.Register(new AssignmentParselet(), "let", "put"); + def.Register("#poetic", new PoeticLiteralParselet()); def.Register(Name, new NameParselet()); def.Register(Number, new NumberParselet()); def.Register(new PrintParselet(), PrintParselet.Aliases); + + def.Block("if", "#line"); } } 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 { 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/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; 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/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)); + } + } + } } 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.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 diff --git a/Source/Silverfly/Lexer.cs b/Source/Silverfly/Lexer.cs index 08ab6fb..e60565c 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) @@ -260,6 +261,21 @@ public void Advance(int distance = 1) _column += distance; } + /// + /// Advances the current position in the document if a symbol matches. + /// + public bool AdvanceIfMatch(string symbol) + { + if (IsMatch(symbol, Config.IgnoreCasing)) + { + Advance(symbol.Length); + + return true; + } + + return false; + } + /// /// Determines whether the specified token name is a punctuator. /// diff --git a/Source/Silverfly/LexerConfig.cs b/Source/Silverfly/LexerConfig.cs index 633219b..0236751 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; @@ -14,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; @@ -50,6 +51,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. /// @@ -60,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) { @@ -126,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. /// @@ -140,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. /// @@ -150,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)); } @@ -171,9 +182,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()); } /// @@ -193,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)); } 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); } diff --git a/Source/Silverfly/Nodes/AstNode.cs b/Source/Silverfly/Nodes/AstNode.cs index 612b4e7..d6177ad 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. /// @@ -98,13 +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); + } } 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); diff --git a/Source/Silverfly/Parser.cs b/Source/Silverfly/Parser.cs index b9e5020..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); @@ -211,7 +213,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) { 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 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);