From af16141f8efac38ff2b80e3e41d2c708bcbd4426 Mon Sep 17 00:00:00 2001 From: Dylan Beattie Date: Sat, 6 Jul 2024 15:49:50 +0100 Subject: [PATCH] Poetic literals (integers) are working. --- .obsidian/workspace.json | 22 +++---- .../Rockstar.Engine/Expressions/Variable.cs | 6 ++ Starship/Rockstar.Engine/Interpreter.cs | 2 +- .../Rockstar.Engine/RockstarEnvironment.cs | 18 +++++- Starship/Rockstar.Engine/Statements/Assign.cs | 6 +- Starship/Rockstar.Engine/Values/Null.cs | 1 + Starship/Rockstar.Engine/Values/Number.cs | 6 ++ Starship/Rockstar.Engine/rockstar.peg | 52 ++++++++++------ Starship/Rockstar.Test/PronounTests.cs | 59 +++++++++++++++++++ Starship/Rockstar.Test/VariableTests.cs | 44 +++++++++++++- 10 files changed, 180 insertions(+), 36 deletions(-) create mode 100644 Starship/Rockstar.Test/PronounTests.cs diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json index b711b8c..980c446 100644 --- a/.obsidian/workspace.json +++ b/.obsidian/workspace.json @@ -148,17 +148,17 @@ }, "active": "af9edaf1541aa592", "lastOpenFiles": [ - "Starship/Rockstar.Test/obj/Debug/net8.0/nCrunchTemp_511cc3bb-5125-4e86-9e1b-af622834b219.csproj.AssemblyReference.cache", - "Starship/Rockstar.Test/obj/Debug/net8.0/nCrunchTemp_511cc3bb-5125-4e86-9e1b-af622834b219.GlobalUsings.g.cs", - "Starship/Rockstar.Test/obj/Debug/net8.0/nCrunchTemp_511cc3bb-5125-4e86-9e1b-af622834b219.assets.cache", - "Starship/Rockstar.Test/nCrunchTemp_511cc3bb-5125-4e86-9e1b-af622834b219.csproj", - "Starship/Rockstar.Test/obj/nCrunchTemp_511cc3bb-5125-4e86-9e1b-af622834b219.csproj.nuget.g.targets", - "Starship/Rockstar.Test/obj/nCrunchTemp_511cc3bb-5125-4e86-9e1b-af622834b219.csproj.nuget.g.props", - "Starship/Rockstar.Engine/rockstar.peg~RF19ed0814.TMP", - "Starship/Rockstar.Engine/rockstar.peg~RF19eacad6.TMP", - "Starship/Rockstar.Engine/rockstar.peg~RF19e8e3c6.TMP", - "Starship/Rockstar.Engine/rockstar.peg~RF19e7b067.TMP", - "Starship/Rockstar.Test/ParserTests.cs~RF19e07a84.TMP", + "Starship/Rockstar.Engine/rockstar.peg~RF1ac18bc3.TMP", + "Starship/Rockstar.Engine/rockstar.peg~RF1ac1496b.TMP", + "Starship/Rockstar.Engine/rockstar.peg~RF1ac093a8.TMP", + "Starship/Rockstar.Wasm/bin/Debug/net8.0/wwwroot/_framework/Rockstar.Engine.wasm.gz", + "Starship/Rockstar.Wasm/bin/Debug/net8.0/wwwroot/_framework/Rockstar.Engine.pdb.gz", + "Starship/Rockstar.Wasm/obj/Debug/net8.0/webcil/System.Configuration.wasm", + "Starship/Rockstar.Wasm/obj/Debug/net8.0/webcil/System.ComponentModel.wasm", + "Starship/Rockstar.Wasm/obj/Debug/net8.0/webcil/System.Data.DataSetExtensions.wasm", + "Starship/Rockstar.Engine/rockstar.peg~RF1abbe124.TMP", + "Starship/Rockstar.Engine/rockstar.peg~RF1abbd29d.TMP", + "Starship/Rockstar.Engine/rockstar.peg~RF1abab3df.TMP", "codewithrockstar.com/getting-started.md", "codewithrockstar.com/index copy.md" ] diff --git a/Starship/Rockstar.Engine/Expressions/Variable.cs b/Starship/Rockstar.Engine/Expressions/Variable.cs index 9965a0d..ab28eb8 100644 --- a/Starship/Rockstar.Engine/Expressions/Variable.cs +++ b/Starship/Rockstar.Engine/Expressions/Variable.cs @@ -18,6 +18,12 @@ protected string NormalizedName public abstract string Key { get; } } +public class Pronoun(string name, Source source) : Variable(name, source) { + public Pronoun() : this(String.Empty) { } + public Pronoun(string name) : this(name, Source.None) { } + public override string Key => Name.ToLowerInvariant(); +} + public class SimpleVariable(string name, Source source) : Variable(name, source) { public SimpleVariable(string name) : this(name, Source.None) { } public override string Key => Name.ToLowerInvariant(); diff --git a/Starship/Rockstar.Engine/Interpreter.cs b/Starship/Rockstar.Engine/Interpreter.cs index e35c03f..726b92a 100644 --- a/Starship/Rockstar.Engine/Interpreter.cs +++ b/Starship/Rockstar.Engine/Interpreter.cs @@ -22,7 +22,7 @@ public int Run(Progräm program) { }; private Result Assign(Assign assign) { - env.SetVariable(assign.Name, Eval(assign.Expr)); + env.SetVariable(assign.Variable, Eval(assign.Expr)); return Result.Ok; } diff --git a/Starship/Rockstar.Engine/RockstarEnvironment.cs b/Starship/Rockstar.Engine/RockstarEnvironment.cs index 00a03fe..2328b3d 100644 --- a/Starship/Rockstar.Engine/RockstarEnvironment.cs +++ b/Starship/Rockstar.Engine/RockstarEnvironment.cs @@ -7,11 +7,25 @@ public abstract class RockstarEnvironment { public abstract string? ReadInput(); public abstract void WriteLine(string? output); public abstract void Write(string s); + private Variable? pronounTarget; private readonly Dictionary variables = new(); - public void SetVariable(Variable variable, Value value) => variables[variable.Key] = value; + + private Variable AssertTarget(Pronoun pronoun) + => pronounTarget ?? throw new($"You must assign a variable before using a pronoun ('{pronoun.Name}')"); + + public void SetVariable(Variable variable, Value value) { + pronounTarget = variable; + variables[variable.Key] = value; + } + + public void SetVariable(Pronoun pronoun, Value value) + => SetVariable(AssertTarget(pronoun), value); + + public Value GetVariable(Pronoun pronoun) + => GetVariable(AssertTarget(pronoun)); public Value GetVariable(Variable variable) => - variables.TryGetValue(variable.Key, out var value) ? value : throw new Exception($"Unknown variable '{variable.Name}'"); + variables.TryGetValue(variable.Key, out var value) ? value : throw new($"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 cbf43ce..52b6350 100644 --- a/Starship/Rockstar.Engine/Statements/Assign.cs +++ b/Starship/Rockstar.Engine/Statements/Assign.cs @@ -3,13 +3,13 @@ namespace Rockstar.Engine.Statements; -public class Assign(Variable name, Expression expr, Source source) +public class Assign(Variable variable, Expression expr, Source source) : Statement(source) { - public Variable Name => name; + public Variable Variable => variable; public Expression Expr => expr; public override void Print(StringBuilder sb, int depth) { sb.Indent(depth).AppendLine($"assign:"); - name.Print(sb, depth + 1); + variable.Print(sb, depth + 1); expr.Print(sb, depth + 1); } } \ No newline at end of file diff --git a/Starship/Rockstar.Engine/Values/Null.cs b/Starship/Rockstar.Engine/Values/Null.cs index 5ae53e6..97f017a 100644 --- a/Starship/Rockstar.Engine/Values/Null.cs +++ b/Starship/Rockstar.Engine/Values/Null.cs @@ -2,5 +2,6 @@ namespace Rockstar.Engine.Values; public class Null(Source source) : Value(source) { + public Null() : this(Source.None) { } public override bool Truthy => false; } \ No newline at end of file diff --git a/Starship/Rockstar.Engine/Values/Number.cs b/Starship/Rockstar.Engine/Values/Number.cs index fb8def8..b3b6f9c 100644 --- a/Starship/Rockstar.Engine/Values/Number.cs +++ b/Starship/Rockstar.Engine/Values/Number.cs @@ -4,7 +4,13 @@ namespace Rockstar.Engine.Values; public class Number(decimal value, Source source) : Value(source) { + + public Number(string s) : this(Decimal.Parse(s)) { } + + public Number(string s, Source source) : this(Decimal.Parse(s), source) { } + public Number(decimal value) : this(value, Source.None) { } + public decimal Value => value; public override string ToString() => value.ToString(CultureInfo.InvariantCulture); diff --git a/Starship/Rockstar.Engine/rockstar.peg b/Starship/Rockstar.Engine/rockstar.peg index b0975e7..8e90ce2 100644 --- a/Starship/Rockstar.Engine/rockstar.peg +++ b/Starship/Rockstar.Engine/rockstar.peg @@ -9,10 +9,6 @@ @ignorecase true -// Ok, so a program is: -// Whitespace followed by EOF. Empty program. -// A single statement followed by an empty program - program = ("" _ / EOL)+ p:program { p } @@ -37,7 +33,6 @@ comment / '{' [^\}]* '}' / '[' [^\]]* ']' - statement = output_stmt / assign_stmt @@ -45,33 +40,51 @@ statement assign_stmt = 'let' _ v:variable _ 'be' _ e:expression { new Assign(v, e, state.Source()) } - / v:variable _ 'says' _ s:("" [^\r\n]*) + / 'put' _ e:expression _ 'into' _ v:variable + { new Assign(v, e, state.Source()) } + / v:variable _ says _ s:("" [^\r\n]*) { new Assign(v, new Strïng(s, state.Source(s)), state.Source()) } -// / v:variable _ "was a lovestruck ladykiller" -// { new Assign(v, new Number(100), state.Source()) } - / v:variable _is _ l:literal + / v:variable _is _ l:(literal / poetic_literal) { new Assign(v, l, state.Source()) } //TODO: reinstate for 2.0 // v:variable _is _ e:expression { new Assign(v, e, state.Source()) } +says = ('says' / 'say' / 'said') + +poetic_literal + = head:poetic_digit _ tail:poetic_digits + { new Number(head + tail) } + / d:poetic_digit { new Number(d) } + +poetic_digits + = head:poetic_digit _ tail:poetic_digits + { head + tail } + / d:poetic_digit { d } + +poetic_digit + = word:("" [A-Za-z\-']+) + { (word.Length % 10).ToString() } output_stmt = output _ e:expression { new Output(e, state.Source()) } variable - = name:variable_name + = p:pronoun { new Pronoun(p, state.Source(p)) } + / n:common_variable { new CommonVariable(n, state.Source(n)) } + / n:proper_variable { new ProperVariable(n, state.Source(n)) } + / n:simple_variable { new SimpleVariable(n, state.Source(n)) } -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)) } +pronoun + = p:( 'they' / 'them' ) proper_variable // e.g. Big Bad Benny = proper_noun (_ proper_noun)* proper_noun - = uppercase_letter letter* + = !keyword uppercase_letter letter* + +keyword = 'is' common_variable = common_prefix _ simple_variable @@ -145,11 +158,13 @@ _eq / _is _is - = ("'s" / "'re" / _ ('=' / 'is' / 'was' / 'are' / 'were')) !letter + = ("'s" / "'re" / _ ('=' / is)) !letter { Operator.Equals } +is = 'is' / 'was' / 'are' / 'were' + _is_not - = _ ('!=' / "'s" _ "not" / 'is' _ 'not' / "isnt" / "isn't" / 'aint' / "ain't" / "wasn't" / "wasnt" / "aren't" / "arent" / "weren't" / "werent") !letter + = _ ('!=' / _is _ "not" / "isnt" / "isn't" / 'aint' / "ain't" / "wasn't" / "wasnt" / "aren't" / "arent" / "weren't" / "werent") !letter { Operator.NotEquals } math @@ -173,7 +188,8 @@ primary / lookup lookup - = v:variable { new Looküp(v, state.Source()) } + = v:variable + { new Looküp(v, state.Source()) } literal = constant diff --git a/Starship/Rockstar.Test/PronounTests.cs b/Starship/Rockstar.Test/PronounTests.cs new file mode 100644 index 0000000..da601ed --- /dev/null +++ b/Starship/Rockstar.Test/PronounTests.cs @@ -0,0 +1,59 @@ +using Rockstar.Engine.Expressions; + +namespace Rockstar.Test; + +public class PronounTests { + + private void TestPronoun(Variable variable, Value value) { + var e = new TestEnvironment(); + var pronoun = new Pronoun(); + e.SetVariable(variable, value); + var result = e.GetVariable(pronoun) as Number; + result.ShouldBe(value); + } + + + [Fact] + public void AssigningProperVariableSetsPronoun() + => TestPronoun(new ProperVariable("Doctor Feelgood"), new Number(123)); + + [Fact] + public void AssigningSimpleVariableSetsPronoun() + => TestPronoun(new SimpleVariable("Doctor Feelgood"), new Number(123)); + + [Fact] + public void AssigningCommonVariableSetsPronoun() + => TestPronoun(new CommonVariable("Doctor Feelgood"), new Number(123)); + + [Fact] + public void LookupPronounWithoutAssigningVariableThrowsException() { + var e = new TestEnvironment(); + Should.Throw(() => e.GetVariable(new("him"))); + } + + [Fact] + public void AssignPronounWithoutAssigningVariableThrowsException() { + var e = new TestEnvironment(); + Should.Throw(() => e.SetVariable(new("him"), new Number(123))); + } + + private void AssignPronounAfterAssigningVariableUpdatesVariable(Variable variable, Value value) { + var e = new TestEnvironment(); + e.SetVariable(variable, new Null()); + e.SetVariable(new(), value); + e.GetVariable(variable).ShouldBe(value); + } + + [Fact] + public void AssignPronounAfterAssigningProperVariableUpdatesVariable() + => AssignPronounAfterAssigningVariableUpdatesVariable(new ProperVariable("Mr Crowley"), new Strïng("hey")); + + [Fact] + public void AssignPronounAfterAssigningSimpleVariableUpdatesVariable() + => AssignPronounAfterAssigningVariableUpdatesVariable(new SimpleVariable("crowley"), new Strïng("hey")); + + [Fact] + public void AssignPronounAfterAssigningCommonVariableUpdatesVariable() + => AssignPronounAfterAssigningVariableUpdatesVariable(new CommonVariable("my humps"), new Strïng("my lady humps")); + +} diff --git a/Starship/Rockstar.Test/VariableTests.cs b/Starship/Rockstar.Test/VariableTests.cs index 95d7e33..9ebac90 100644 --- a/Starship/Rockstar.Test/VariableTests.cs +++ b/Starship/Rockstar.Test/VariableTests.cs @@ -1,4 +1,3 @@ -using System.Runtime.InteropServices; using Pegasus.Common.Tracing; using Rockstar.Engine.Expressions; @@ -9,6 +8,10 @@ public class LiteralTests { [Theory] [InlineData("the sky is crying", 6)] [InlineData("Tommy was a lovestruck ladykiller", 100)] + [InlineData("Tommy was a", 1)] + [InlineData("Tommy was a aa", 12)] + [InlineData("Tommy was a aa aaa", 123)] + [InlineData("Tommy was a aa aaa aaaa aaaaa", 12345)] public void PoeticLiteralAssignsCorrectValue(string source, decimal value) { var parser = new Parser() { Tracer = DiagnosticsTracer.Instance @@ -52,4 +55,43 @@ public void ProperVariablesAreCaseInsensitiveAndIgnoreWhitespace(string name1, s var b = new ProperVariable(name2); a.Key.ShouldBe(b.Key); } + + [Theory] + [InlineData("Variable Is 5")] + [InlineData(""" + variable is 1 + say variable + """)] + [InlineData(""" + variable is 1 + say variable + variable iS 3 + say variable + VARIABLE is 4 + say variable + """)] + [InlineData(""" + variable is 1 + say variable + variable iS 3 + say variable + VARIABLE IS 4 + say variable + """)] + [InlineData(""" + variable is 1 + say variable + Variable Is 2 + say variable + variable iS 3 + say variable + VARIABLE IS 4 + say variable + """)] + public void LiteralAssignmentsAreCaseInsensitive(string source) { + var parser = new Parser() { + Tracer = DiagnosticsTracer.Instance + }; + var result = parser.Parse(source); + } }