From d3306943ed3977e179da6ad29964af252ab7ec9f Mon Sep 17 00:00:00 2001 From: Dylan Beattie Date: Tue, 9 Jul 2024 15:36:05 +0100 Subject: [PATCH] Compound assignments (let x be with 5) are working. --- .../Rockstar.Engine/Expressions/Binary.cs | 3 +- Starship/Rockstar.Engine/Values/Value.cs | 11 ++++-- Starship/Rockstar.Engine/rockstar.peg | 32 +++++++++------- .../Rockstar.Test/CompoundAssignmentTests.cs | 11 ++++++ Starship/Rockstar.Test/ConditionalTests.cs | 15 -------- Starship/Rockstar.Test/FixtureBase.cs | 13 ------- .../Rockstar.Test/IncrementDecrementTests.cs | 16 ++++++++ Starship/Rockstar.Test/LiteralTests.cs | 30 +++++++++++++++ Starship/Rockstar.Test/OperatorTests.cs | 14 +++++++ Starship/Rockstar.Test/ParserTestBase.cs | 10 +++++ Starship/Rockstar.Test/ParserTests.cs | 2 - Starship/Rockstar.Test/PronounTests.cs | 1 - Starship/Rockstar.Test/TestEnvironment.cs | 14 +++++++ Starship/Rockstar.Test/VariableTests.cs | 38 ------------------- 14 files changed, 123 insertions(+), 87 deletions(-) create mode 100644 Starship/Rockstar.Test/CompoundAssignmentTests.cs create mode 100644 Starship/Rockstar.Test/IncrementDecrementTests.cs create mode 100644 Starship/Rockstar.Test/LiteralTests.cs create mode 100644 Starship/Rockstar.Test/OperatorTests.cs create mode 100644 Starship/Rockstar.Test/ParserTestBase.cs create mode 100644 Starship/Rockstar.Test/TestEnvironment.cs diff --git a/Starship/Rockstar.Engine/Expressions/Binary.cs b/Starship/Rockstar.Engine/Expressions/Binary.cs index 44cca59..2cd3bdf 100644 --- a/Starship/Rockstar.Engine/Expressions/Binary.cs +++ b/Starship/Rockstar.Engine/Expressions/Binary.cs @@ -17,13 +17,14 @@ public Value Resolve(Func eval) { Operator.Minus => v.Minus(eval(rhs)), Operator.Times => v.Times(eval(rhs)), Operator.Divide => v.Divide(eval(rhs)), + Operator.Equals => v.Equäls(eval(rhs)), Operator.NotEquals => v.NotEquäls(eval(rhs)), - Operator.Nor => v.Divide(eval(rhs)), Operator.LessThanEqual => v.LessThanEqual(eval(rhs)), Operator.MoreThanEqual => v.MoreThanEqual(eval(rhs)), Operator.LessThan => v.LessThan(eval(rhs)), Operator.MoreThan => v.MoreThan(eval(rhs)), + Operator.And => v.Truthy ? eval(rhs) : v, Operator.Or => v.Truthy ? v : eval(rhs), _ => throw new ArgumentOutOfRangeException(nameof(op), op, null) diff --git a/Starship/Rockstar.Engine/Values/Value.cs b/Starship/Rockstar.Engine/Values/Value.cs index 71a2f44..bac850a 100644 --- a/Starship/Rockstar.Engine/Values/Value.cs +++ b/Starship/Rockstar.Engine/Values/Value.cs @@ -76,7 +76,7 @@ public override void Print(StringBuilder sb, int depth) _ => that switch { Booleän booleän => this.Truthy == booleän.Truthy, Strïng s => s.Value.Equals(this.ToStrïng().Value), - _ => throw new NotImplementedException($"Equality not implemented for {this.GetType()} {that.GetType()}") + _ => throw Boom(nameof(Equäls), this, that) }, }); @@ -85,16 +85,19 @@ public Value NotEquäls(Value that) public Value LessThanEqual(Value that) => (Booleän) ((this, that) switch { (Number a, Number b) => a.Value <= b.Value, - _ => throw new NotImplementedException() + _ => throw Boom(nameof(LessThanEqual), this, that) }); public Value MoreThanEqual(Value that) => (Booleän) ((this, that) switch { (Number a, Number b) => a.Value >= b.Value, - _ => throw new NotImplementedException() + _ => throw Boom(nameof(MoreThanEqual), this, that) }); public Value LessThan(Value that) => (Booleän) ((this, that) switch { (Number a, Number b) => a.Value < b.Value, - _ => throw new NotImplementedException() + _ => throw Boom(nameof(LessThan), this, that) }); + + private Exception Boom(string op, Value lhs, Value rhs) + => new NotImplementedException($"{op} not implemented for {lhs.GetType()} {rhs.GetType()}"); } diff --git a/Starship/Rockstar.Engine/rockstar.peg b/Starship/Rockstar.Engine/rockstar.peg index 36599c9..de31543 100644 --- a/Starship/Rockstar.Engine/rockstar.peg +++ b/Starship/Rockstar.Engine/rockstar.peg @@ -76,6 +76,8 @@ bonglit assign_stmt = 'put' _ e:expression _ 'into' _ v:variable { new Assign(v, e, state.Source()) } + / 'let' _ v:variable _ 'be' o:_op_ e:expression + { new Assign(v, new Binary(o, new Looküp(v, state.Source()), e, state.Source()), state.Source()) } / 'let' _ v:variable _ 'be' _ e:expression { new Assign(v, e, state.Source()) } / v:variable _ says _ s:("" [^\r\n]*) @@ -126,7 +128,7 @@ proper_variable // e.g. Big Bad Benny proper_noun = !keyword uppercase_letter letter* -keyword = (is / common_prefix / pronoun / 'let') !letter +keyword = (is / common_prefix / pronoun / 'let' / 'with') !letter common_variable = common_prefix _ simple_variable @@ -135,7 +137,7 @@ common_prefix = ('an' / 'a' / 'the' / 'my' / 'your' / 'our') !letter simple_variable - = ("" letter+) + = !keyword ("" letter+) letter = uppercase_letter / lowercase_letter @@ -213,17 +215,20 @@ math = sum sum -memoize - = lhs:sum op:(plus/minus) rhs:product { new Binary(op, lhs, rhs, state.Source()) } + = lhs:sum op:(_plus_/_minus_) rhs:product { new Binary(op, lhs, rhs, state.Source()) } / product product -memoize - = lhs:product op:(times/divide) rhs:primary { new Binary(op, lhs, rhs, state.Source()) } + = lhs:product op:(_times_/_divide_) rhs:primary { new Binary(op, lhs, rhs, state.Source()) } / primary -plus = (_? '+' _? / _ 'plus' _ / _ 'with' _ ) { Operator.Plus } -minus = (_? '-' _? / _ 'minus' _ / _ 'without' _ ) { Operator.Minus } -times = (_? '*' _? / _ 'times' _ / _ 'of' _ ) { Operator.Times } -divide = (_? '/' _? / _ 'over' _ / _ 'between' _ ) { Operator.Divide } +_plus_ = (_? '+' _? / _ 'plus' _ / _ 'with' _ ) { Operator.Plus } +_minus_ = (_? '-' _? / _ 'minus' _ / _ 'without' _ ) { Operator.Minus } +_times_ = (_? '*' _? / _ 'times' _ / _ 'of' _ ) { Operator.Times } +_divide_ = (_? '/' _? / _ 'over' _ / _ 'between' _ ) { Operator.Divide } + +_op_ + = _plus_ / _minus_ / _times_ / _divide_ primary = literal @@ -255,15 +260,16 @@ string = '"' contents:("" [^"]*) '"' { new Strïng(contents, state.Source(contents)) } number - = digits:([0-9]+ ("." [0-9]+)?) { new Number(Decimal.Parse(digits), state.Source(digits) ) } - / digits:("." [0-9]+) { new Number(Decimal.Parse(digits), state.Source(digits) ) } + = digits:('-'? [0-9]+ ("." [0-9]+)?) { new Number(Decimal.Parse(digits), state.Source(digits) ) } + / digits:('-'? "." [0-9]+) { new Number(Decimal.Parse(digits), state.Source(digits) ) } unary_op = not { Operator.Not } - / minus { Operator.Minus } + / _minus_ { Operator.Minus } -output = "shout" / "say" / "scream" / "whisper" +output + = "shout" / "say" / "scream" / "whisper" _ = "" (whitespace / comment)+ whitespace = [ \t] -comment = '(' [^)]* ')' / '{' [^\}]* '}' / '[' [^\]]* ']' +comment = '(' [^)]* ')' / '{' [^\}]* '}' / '[' [^\]]* ']' \ No newline at end of file diff --git a/Starship/Rockstar.Test/CompoundAssignmentTests.cs b/Starship/Rockstar.Test/CompoundAssignmentTests.cs new file mode 100644 index 0000000..d391e5b --- /dev/null +++ b/Starship/Rockstar.Test/CompoundAssignmentTests.cs @@ -0,0 +1,11 @@ +namespace Rockstar.Test; + +public class CompoundAssignmentTests(ITestOutputHelper output) : ParserTestBase(output) { + [Theory] + [InlineData("let x be with 5")] + [InlineData("let x be without 5")] + [InlineData("let x be over 2")] + [InlineData("let the night be without regret")] + public void ParserParsesSimpleConditionals(string source) + => Parse(source); +} \ No newline at end of file diff --git a/Starship/Rockstar.Test/ConditionalTests.cs b/Starship/Rockstar.Test/ConditionalTests.cs index 3c25c33..b6202d9 100644 --- a/Starship/Rockstar.Test/ConditionalTests.cs +++ b/Starship/Rockstar.Test/ConditionalTests.cs @@ -1,20 +1,5 @@ namespace Rockstar.Test; -public class IncrementDecrementTests(ITestOutputHelper output) { - [Theory] - [InlineData("build X up")] - [InlineData("build X up up")] - [InlineData("build X up, up")] - [InlineData("knock X down")] - [InlineData("knock X down, down, down")] - [InlineData("knock X down down down")] - public void ParserParsesIncrementsAndDecrements(string source) { - var parser = new Parser(); // { Tracer = DiagnosticsTracer.Instance }; - var result = parser.Parse(source); - output.WriteLine(result.ToString()); - } -} - public class ConditionalTests(ITestOutputHelper output) { [Theory] [InlineData("if true say 1")] diff --git a/Starship/Rockstar.Test/FixtureBase.cs b/Starship/Rockstar.Test/FixtureBase.cs index b67d6c2..ae38459 100644 --- a/Starship/Rockstar.Test/FixtureBase.cs +++ b/Starship/Rockstar.Test/FixtureBase.cs @@ -1,18 +1,5 @@ namespace Rockstar.Test; -public class TestEnvironment : RockstarEnvironment { - - private readonly StringBuilder outputStringBuilder = new(); - public string Output => outputStringBuilder.ToString(); - public override string? ReadInput() => null; - - public override void WriteLine(string output) - => this.outputStringBuilder.Append(output + Environment.NewLine); - - public override void Write(string s) - => this.outputStringBuilder.Append(s); -} - public abstract class FixtureBase(ITestOutputHelper testOutput) { private static string[] excludes = []; // ["arrays", "conditionals", "control-flow", "examples"]; diff --git a/Starship/Rockstar.Test/IncrementDecrementTests.cs b/Starship/Rockstar.Test/IncrementDecrementTests.cs new file mode 100644 index 0000000..5dabf7b --- /dev/null +++ b/Starship/Rockstar.Test/IncrementDecrementTests.cs @@ -0,0 +1,16 @@ +namespace Rockstar.Test; + +public class IncrementDecrementTests(ITestOutputHelper output) { + [Theory] + [InlineData("build X up")] + [InlineData("build X up up")] + [InlineData("build X up, up")] + [InlineData("knock X down")] + [InlineData("knock X down, down, down")] + [InlineData("knock X down down down")] + public void ParserParsesIncrementsAndDecrements(string source) { + var parser = new Parser(); // { Tracer = DiagnosticsTracer.Instance }; + var result = parser.Parse(source); + output.WriteLine(result.ToString()); + } +} \ No newline at end of file diff --git a/Starship/Rockstar.Test/LiteralTests.cs b/Starship/Rockstar.Test/LiteralTests.cs new file mode 100644 index 0000000..f3e33ad --- /dev/null +++ b/Starship/Rockstar.Test/LiteralTests.cs @@ -0,0 +1,30 @@ +using Pegasus.Common.Tracing; + +namespace Rockstar.Test; + +public class LiteralTests { + [Theory] + [InlineData("say 1")] + [InlineData("say 1.1")] + [InlineData("say .1")] + public void ParserParsesNumberLiterals(string source) { + var parser = new Parser() { Tracer = DiagnosticsTracer.Instance }; + var result = parser.Parse(source); + result.Statements.Count.ShouldBe(1); + } + + [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)] + [InlineData("Tommy was a. aa aaa aaaa", 1.234)] + [InlineData("Tommy was a aa. aaa aaaa", 12.34)] + public void PoeticLiteralAssignsCorrectValue(string source, decimal value) { + var parser = new Parser() { Tracer = DiagnosticsTracer.Instance }; + var assign = parser.Parse(source).Statements[0] as Assign; + ((Number) assign.Expr).Value.ShouldBe(value); + } +} \ No newline at end of file diff --git a/Starship/Rockstar.Test/OperatorTests.cs b/Starship/Rockstar.Test/OperatorTests.cs new file mode 100644 index 0000000..5fcb2c4 --- /dev/null +++ b/Starship/Rockstar.Test/OperatorTests.cs @@ -0,0 +1,14 @@ +using Pegasus.Common.Tracing; + +namespace Rockstar.Test; + +public class OperatorTests { + [Theory] + [InlineData("say 1 + 2 + 3")] + [InlineData("say 1 plus 2 plus 3")] + [InlineData("say \"hello\" plus \" \" plus .11")] + public void AdditionOperatorWorks(string source) { + var parser = new Parser() { Tracer = DiagnosticsTracer.Instance }; + var result = parser.Parse(source); + } +} \ No newline at end of file diff --git a/Starship/Rockstar.Test/ParserTestBase.cs b/Starship/Rockstar.Test/ParserTestBase.cs new file mode 100644 index 0000000..f7e28e8 --- /dev/null +++ b/Starship/Rockstar.Test/ParserTestBase.cs @@ -0,0 +1,10 @@ +namespace Rockstar.Test; + +public class ParserTestBase(ITestOutputHelper output) { + protected void Parse(string source) { + var parser = new Parser(); // { Tracer = DiagnosticsTracer.Instance }; + var result = parser.Parse(source); + output.WriteLine(result.ToString()); + } + +} \ No newline at end of file diff --git a/Starship/Rockstar.Test/ParserTests.cs b/Starship/Rockstar.Test/ParserTests.cs index 73fba0a..dfb8c24 100644 --- a/Starship/Rockstar.Test/ParserTests.cs +++ b/Starship/Rockstar.Test/ParserTests.cs @@ -1,5 +1,3 @@ -using System.ComponentModel.Design; -using System.Diagnostics; using Pegasus.Common.Tracing; namespace Rockstar.Test; diff --git a/Starship/Rockstar.Test/PronounTests.cs b/Starship/Rockstar.Test/PronounTests.cs index 3cdddcb..6d85522 100644 --- a/Starship/Rockstar.Test/PronounTests.cs +++ b/Starship/Rockstar.Test/PronounTests.cs @@ -1,4 +1,3 @@ -using System.Diagnostics; using Pegasus.Common.Tracing; using Rockstar.Engine.Expressions; diff --git a/Starship/Rockstar.Test/TestEnvironment.cs b/Starship/Rockstar.Test/TestEnvironment.cs new file mode 100644 index 0000000..55e3945 --- /dev/null +++ b/Starship/Rockstar.Test/TestEnvironment.cs @@ -0,0 +1,14 @@ +namespace Rockstar.Test; + +public class TestEnvironment : RockstarEnvironment { + + private readonly StringBuilder outputStringBuilder = new(); + public string Output => outputStringBuilder.ToString(); + public override string? ReadInput() => null; + + public override void WriteLine(string output) + => this.outputStringBuilder.Append(output + Environment.NewLine); + + public override void Write(string s) + => this.outputStringBuilder.Append(s); +} \ No newline at end of file diff --git a/Starship/Rockstar.Test/VariableTests.cs b/Starship/Rockstar.Test/VariableTests.cs index 8029660..7b7e651 100644 --- a/Starship/Rockstar.Test/VariableTests.cs +++ b/Starship/Rockstar.Test/VariableTests.cs @@ -3,44 +3,6 @@ namespace Rockstar.Test; -public class OperatorTests { - [Theory] - [InlineData("say 1 + 2 + 3")] - [InlineData("say 1 plus 2 plus 3")] - [InlineData("say \"hello\" plus \" \" plus .11")] - public void AdditionOperatorWorks(string source) { - var parser = new Parser() { Tracer = DiagnosticsTracer.Instance }; - var result = parser.Parse(source); - } -} - -public class LiteralTests { - [Theory] - [InlineData("say 1")] - [InlineData("say 1.1")] - [InlineData("say .1")] - public void ParserParsesNumberLiterals(string source) { - var parser = new Parser() { Tracer = DiagnosticsTracer.Instance }; - var result = parser.Parse(source); - result.Statements.Count.ShouldBe(1); - } - - [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)] - [InlineData("Tommy was a. aa aaa aaaa", 1.234)] - [InlineData("Tommy was a aa. aaa aaaa", 12.34)] - public void PoeticLiteralAssignsCorrectValue(string source, decimal value) { - var parser = new Parser() { Tracer = DiagnosticsTracer.Instance }; - var assign = parser.Parse(source).Statements[0] as Assign; - ((Number) assign.Expr).Value.ShouldBe(value); - } -} - public class VariableTests { [Theory] [InlineData("a variable", "a variable")]