diff --git a/Starship/Rockstar.Engine/Expressions/Variable.cs b/Starship/Rockstar.Engine/Expressions/Variable.cs index 438406d..9965a0d 100644 --- a/Starship/Rockstar.Engine/Expressions/Variable.cs +++ b/Starship/Rockstar.Engine/Expressions/Variable.cs @@ -1,10 +1,34 @@ using System.Text; +using System.Text.RegularExpressions; namespace Rockstar.Engine.Expressions; -public class Variable(string name, Source source) : Expression(source) { + +public abstract class Variable(string name, Source source) : Expression(source) { public string Name => name; public override void Print(StringBuilder sb, int depth) { sb.Indent(depth).AppendLine($"variable: {name}"); } -} \ No newline at end of file + + protected static Regex whitespace = new("\\s+", RegexOptions.Compiled); + + protected string NormalizedName + => String.Join("_", whitespace.Split(Name)); + + public abstract string Key { get; } +} + +public class SimpleVariable(string name, Source source) : Variable(name, source) { + public SimpleVariable(string name) : this(name, Source.None) { } + public override string Key => Name.ToLowerInvariant(); +} + +public class ProperVariable(string name, Source source) : Variable(name, source) { + public ProperVariable(string name) : this(name, Source.None) { } + public override string Key => NormalizedName.ToUpperInvariant(); +} + +public class CommonVariable(string name, Source source) : Variable(name, source) { + public CommonVariable(string name) : this(name, Source.None) { } + public override string Key => NormalizedName.ToLowerInvariant(); +} diff --git a/Starship/Rockstar.Engine/Interpreter.cs b/Starship/Rockstar.Engine/Interpreter.cs index 9baf5ce..e35c03f 100644 --- a/Starship/Rockstar.Engine/Interpreter.cs +++ b/Starship/Rockstar.Engine/Interpreter.cs @@ -37,8 +37,8 @@ private Result Output(Output output) { Binary binary => binary.Resolve(Eval), // not sure what the difference is here.... ? - Looküp lookup => env.GetVariable(lookup.Variable.Name), - Variable v => env.GetVariable(v.Name), + Looküp lookup => env.GetVariable(lookup.Variable), + Variable v => env.GetVariable(v), Unary u => u switch { { Op: Operator.Minus, Expr: Number n } => n.Negate(), { Op: Operator.Not } => Booleän.Not(Eval(u.Expr)), diff --git a/Starship/Rockstar.Engine/RockstarEnvironment.cs b/Starship/Rockstar.Engine/RockstarEnvironment.cs index c9d2efd..00a03fe 100644 --- a/Starship/Rockstar.Engine/RockstarEnvironment.cs +++ b/Starship/Rockstar.Engine/RockstarEnvironment.cs @@ -1,3 +1,4 @@ +using Rockstar.Engine.Expressions; using Rockstar.Engine.Values; namespace Rockstar.Engine; @@ -8,6 +9,9 @@ public abstract class RockstarEnvironment { public abstract void Write(string s); private readonly Dictionary variables = new(); - public void SetVariable(string name, Value value) => variables[name.ToLowerInvariant()] = value; - public Value GetVariable(string name) => variables[name.ToLowerInvariant()]; + public void SetVariable(Variable variable, Value value) => variables[variable.Key] = value; + + public Value GetVariable(Variable variable) => + variables.TryGetValue(variable.Key, out var value) ? value : throw new Exception($"Unknown variable '{variable.Name}'"); + } \ No newline at end of file diff --git a/Starship/Rockstar.Engine/Statements/Assign.cs b/Starship/Rockstar.Engine/Statements/Assign.cs index bb4d773..cbf43ce 100644 --- a/Starship/Rockstar.Engine/Statements/Assign.cs +++ b/Starship/Rockstar.Engine/Statements/Assign.cs @@ -5,7 +5,7 @@ namespace Rockstar.Engine.Statements; public class Assign(Variable name, Expression expr, Source source) : Statement(source) { - public string Name => name.Name; + public Variable Name => name; public Expression Expr => expr; public override void Print(StringBuilder sb, int depth) { sb.Indent(depth).AppendLine($"assign:"); diff --git a/Starship/Rockstar.Engine/rockstar.peg b/Starship/Rockstar.Engine/rockstar.peg index 597a3f0..33bde4d 100644 --- a/Starship/Rockstar.Engine/rockstar.peg +++ b/Starship/Rockstar.Engine/rockstar.peg @@ -4,6 +4,7 @@ @using Rockstar.Engine.Statements @using Rockstar.Engine.Expressions @using Rockstar.Engine.Values; +@trace true @ignorecase true @@ -20,6 +21,8 @@ __ = _? EOS* EOS = _? EOL _? EOL = '\r'? '\n' EOF = !. +// / unexpected:. #error{ "Unexpected character '" + unexpected + "' at line " + state.Line + ", col " + state.Column } + statement = output_stmt @@ -43,15 +46,14 @@ output_stmt = output _ e:expression { new Output(e, state.Source()) } variable - = name:variable_name { new Variable(name, state.Source(name)) } + = name:variable_name -variable_name - = common_variable - / proper_variable - / simple_variable +variable_name + = name:common_variable { new CommonVariable(name, state.Source(name)) } + / name:proper_variable { new ProperVariable(name, state.Source(name)) } + / name:simple_variable { new SimpleVariable(name, state.Source(name)) } -proper_variable - // e.g. Big Bad Benny +proper_variable // e.g. Big Bad Benny = proper_noun (_ proper_noun)* proper_noun @@ -61,7 +63,7 @@ common_variable = common_prefix _ simple_variable common_prefix - = 'a' / 'an' / 'the' / 'my' / 'your' / 'our' + = 'an' / 'a' / 'the' / 'my' / 'your' / 'our' simple_variable = ("" letter+) diff --git a/Starship/Rockstar.Test/FixtureBase.cs b/Starship/Rockstar.Test/FixtureBase.cs index ba800dc..111628e 100644 --- a/Starship/Rockstar.Test/FixtureBase.cs +++ b/Starship/Rockstar.Test/FixtureBase.cs @@ -15,8 +15,10 @@ public override void Write(string s) public abstract class FixtureBase(ITestOutputHelper testOutput) { - private static string[] ListRockFiles() => - Directory.GetFiles("fixtures", "*.rock", SearchOption.AllDirectories); + private static string[] excludes = ["arrays", "conditionals", "control-flow", "examples"]; + private static IEnumerable ListRockFiles() => + Directory.GetFiles("fixtures", "*.rock", SearchOption.AllDirectories) + .Where(f => !excludes.Any(f.Contains)); public static IEnumerable GetFiles() => ListRockFiles().Select(filePath => new[] { filePath }); diff --git a/Starship/Rockstar.Test/ParserTests.cs b/Starship/Rockstar.Test/ParserTests.cs index 3823c4f..f5271bc 100644 --- a/Starship/Rockstar.Test/ParserTests.cs +++ b/Starship/Rockstar.Test/ParserTests.cs @@ -1,5 +1,46 @@ +using System.ComponentModel.Design; +using System.Diagnostics; +using Pegasus.Common.Tracing; +using Rockstar.Engine.Expressions; + namespace Rockstar.Test { - public class ParserTests { + + public class VariableTests { + [Theory] + [InlineData("a variable", "a variable")] + [InlineData("My variable", "MY VARIABLE")] + [InlineData("My variable", "MY VARIable")] + [InlineData("My variable", "MY VARIable")] + [InlineData("My variable", "MY\t\tVARIable")] + [InlineData("My variable", "MY VARIable")] + public void CommonVariablesAreCaseInsensitiveAndIgnoreWhitespace(string name1, string name2) { + var a = new CommonVariable(name1); + var b = new CommonVariable(name2); + a.Key.ShouldBe(b.Key); + } + + [Theory] + [InlineData("Alpha", "ALPHA")] + [InlineData("beta", "Beta")] + [InlineData("gamma", "GaMmA")] + [InlineData("deLTA", "DElta")] + public void SimpleVariablesAreCaseInsensitiveAndIgnoreWhitespace(string name1, string name2) { + var a = new SimpleVariable(name1); + var b = new SimpleVariable(name2); + a.Key.ShouldBe(b.Key); + } + + [Theory] + [InlineData("Mister Crowley", "MISTER CROWLEY")] + [InlineData("Dr Feelgood", "DR Feelgood")] + [InlineData("Black Betty", "BLaCK BeTTY")] + public void ProperVariablesAreCaseInsensitiveAndIgnoreWhitespace(string name1, string name2) { + var a = new ProperVariable(name1); + var b = new ProperVariable(name2); + a.Key.ShouldBe(b.Key); + } + } + public class ParserTests(ITestOutputHelper helper) { [Theory] [InlineData("")] [InlineData(" ")] @@ -76,5 +117,24 @@ public void ParserParsesSaysLiterals(string source, int count) { var result = parser.Parse(source); result.Statements.Count.ShouldBe(count); } + + [Theory] + [InlineData("an variable is 1", 1)] + [InlineData(""" + an variable is 1 + """, 1)] + [InlineData(""" + a variable is 1 + a variable is 2 + """, 2)] + public void ParserParsesCommonVariables(string source, int count) { + var parser = new Parser { + Tracer = DiagnosticsTracer.Instance + }; + var result = parser.Parse(source); + result.Statements.Count.ShouldBe(count); + } } } + +