From 1d8671fc404bf819c8fead875c1bd4bc31abdfb6 Mon Sep 17 00:00:00 2001 From: Dylan Beattie Date: Sun, 14 Jul 2024 01:25:35 +0100 Subject: [PATCH] This is very close to working but the last few bits are making me mad... --- .../Rockstar.Engine/Expressions/Binary.cs | 54 ++++++------- .../Expressions/Look\303\274p.cs" | 37 +++++++++ Starship/Rockstar.Engine/Result.cs | 4 +- .../Rockstar.Engine/RockstarEnvironment.cs | 80 ++++++++++++------- Starship/Rockstar.Engine/Statements/Break.cs | 5 ++ .../Rockstar.Engine/Statements/Continue.cs | 5 ++ .../Rockstar.Engine/Statements/Mutation.cs | 11 +++ Starship/Rockstar.Engine/Statements/Round.cs | 7 ++ .../Rockstar.Engine/Statements/Rounding.cs | 8 ++ .../Rockstar.Engine/Statements/Statement.cs | 30 +------ Starship/Rockstar.Engine/Values/Array.cs | 50 +++++++++--- Starship/Rockstar.Engine/Values/Number.cs | 3 + .../Rockstar.Engine/Values/Str\303\257ng.cs" | 6 +- Starship/Rockstar.Engine/Values/Value.cs | 20 ++++- Starship/Rockstar.Engine/rockstar.peg | 40 ++++++++-- .../Rockstar.Test/ParserTests/ArrayTests.cs | 27 ++----- .../Rockstar.Test/ParserTests/BinaryTests.cs | 16 ++++ .../ParserTests/FizzBuzzTests.cs | 25 ++++++ .../ParserTests/FunctionTests.cs | 18 +++++ .../Rockstar.Test/ParserTests/LiteralTests.cs | 25 +----- .../ParserTests/MutationTests.cs | 33 ++++++++ .../ParserTests/OperatorTests.cs | 14 ++++ .../Rockstar.Test/ParserTests/ParserTests.cs | 8 +- .../Rockstar.Test/ParserTests/QueueTests.cs | 59 ++++++++++++++ .../Rockstar.Test.v3.ncrunchproject | 2 +- Starship/Rockstar.Test/Values/ArrayTests.cs | 78 ++++++++++++++++++ Starship/Starship.sln.DotSettings | 1 + 27 files changed, 503 insertions(+), 163 deletions(-) create mode 100644 Starship/Rockstar.Engine/Statements/Break.cs create mode 100644 Starship/Rockstar.Engine/Statements/Continue.cs create mode 100644 Starship/Rockstar.Engine/Statements/Mutation.cs create mode 100644 Starship/Rockstar.Engine/Statements/Round.cs create mode 100644 Starship/Rockstar.Engine/Statements/Rounding.cs create mode 100644 Starship/Rockstar.Test/ParserTests/BinaryTests.cs create mode 100644 Starship/Rockstar.Test/ParserTests/FizzBuzzTests.cs create mode 100644 Starship/Rockstar.Test/ParserTests/MutationTests.cs create mode 100644 Starship/Rockstar.Test/ParserTests/QueueTests.cs create mode 100644 Starship/Rockstar.Test/Values/ArrayTests.cs diff --git a/Starship/Rockstar.Engine/Expressions/Binary.cs b/Starship/Rockstar.Engine/Expressions/Binary.cs index 64bdeb2..aa8f027 100644 --- a/Starship/Rockstar.Engine/Expressions/Binary.cs +++ b/Starship/Rockstar.Engine/Expressions/Binary.cs @@ -9,51 +9,43 @@ namespace Rockstar.Engine.Expressions; public class Binary : Expression { private readonly Operator op; private readonly Expression lhs; - private readonly Expression rhs; + private readonly ICollection rhs; - public static Binary Reduce(Expression lhs, IList lists, Source source) { - var binary = lists.First().Reduce(lhs); - return lists.Skip(1).Aggregate(binary, (current, list) => list.Reduce(current)); - } + public static Expression Reduce(Expression lhs, IList lists, Source source) + => lists.Aggregate(lhs, (expr, next) => new Binary(next.Operator, expr, next.List, source)); public Binary(Operator op, Expression lhs, Expression rhs, Source source) : base(source) { this.op = op; this.lhs = lhs; - this.rhs = rhs; + this.rhs = new List { rhs }; } public Binary(Operator op, Expression lhs, ICollection rhs, Source source) : base(source) { this.op = op; this.lhs = lhs; - this.rhs = rhs.Count > 1 - ? new Binary(op, rhs.First(), rhs.Skip(1).ToList(), source) - : rhs.Single(); + this.rhs = rhs; } - // public Operator Op => op; - //public Expression Lhs => lhs; - //public Expression Rhs => rhs; - public Value Resolve(Func eval) { var v = eval(lhs); return op switch { - Operator.Plus => v.Plus(eval(rhs)), - Operator.Minus => v.Minus(eval(rhs)), - Operator.Times => v.Times(eval(rhs)), - Operator.Divide => v.Divide(eval(rhs)), + Operator.Plus => v.Plus(rhs.Select(eval)), + Operator.Minus => v.Minus(rhs.Select(eval)), + Operator.Times => v.Times(rhs.Select(eval)), + Operator.Divide => v.Divide(rhs.Select(eval)), - Operator.Equals => v.Equäls(eval(rhs)), - Operator.NotEquals => v.NotEquäls(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.Equals => v.Equäls(eval(rhs.Single())), + Operator.NotEquals => v.NotEquäls(eval(rhs.Single())), + Operator.LessThanEqual => v.LessThanEqual(eval(rhs.Single())), + Operator.MoreThanEqual => v.MoreThanEqual(eval(rhs.Single())), + Operator.LessThan => v.LessThan(eval(rhs.Single())), + Operator.MoreThan => v.MoreThan(eval(rhs.Single())), - Operator.Nor => new Booleän(v.Falsy && eval(rhs).Falsy), - Operator.And => v.Truthy ? eval(rhs) : v, - Operator.Or => v.Truthy ? v : eval(rhs), + Operator.Nor => new Booleän(v.Falsy && eval(rhs.Single()).Falsy), + Operator.And => v.Truthy ? eval(rhs.Single()) : v, + Operator.Or => v.Truthy ? v : eval(rhs.Single()), _ => throw new ArgumentOutOfRangeException(nameof(op), op, null) }; } @@ -61,7 +53,7 @@ public Value Resolve(Func eval) { public override void Print(StringBuilder sb, string prefix) { sb.Append(prefix).AppendLine($"{op}:".ToLowerInvariant()); lhs.Print(sb, prefix + INDENT); - rhs.Print(sb, prefix + INDENT); + foreach(var expr in rhs) expr.Print(sb, prefix + INDENT); } public override string ToString() { @@ -75,8 +67,8 @@ public class OpList(Operator op, List list) { public Operator Operator => op; public List List => list; - public Binary Reduce(Expression lhs) { - var binary = new Binary(op, lhs, list.First(), Source.None); - return list.Skip(1).Aggregate(binary, (current, tail) => new(op, current, tail, Source.None)); - } + //public Binary Reduce(Expression lhs) { + // var binary = new Binary(op, lhs, list.First(), Source.None); + // return list.Skip(1).Aggregate(binary, (current, tail) => new(op, current, tail, Source.None)); + //} } \ No newline at end of file diff --git "a/Starship/Rockstar.Engine/Expressions/Look\303\274p.cs" "b/Starship/Rockstar.Engine/Expressions/Look\303\274p.cs" index 0e12852..f833cb2 100644 --- "a/Starship/Rockstar.Engine/Expressions/Look\303\274p.cs" +++ "b/Starship/Rockstar.Engine/Expressions/Look\303\274p.cs" @@ -1,4 +1,6 @@ +using System.Runtime.CompilerServices; using System.Text; +using Rockstar.Engine.Statements; namespace Rockstar.Engine.Expressions; @@ -9,3 +11,38 @@ public class Looküp(Variable variable, Source source) public override void Print(StringBuilder sb, string prefix) => sb.Append(prefix).AppendLine($"lookup: {variable.Name} ({variable.GetType().Name})"); } + +public class Delist(Variable variable, Source source) + : Expression(source) { + public Variable Variable => variable; + + public override void Print(StringBuilder sb, string prefix) { + base.Print(sb, prefix); + variable.Print(sb, prefix + INDENT); + } +} + +public class Enlist(Variable variable, Source source) + : Statement(source) { + + public Variable Variable => variable; + public List Expressions = new(); + + public override void Print(StringBuilder sb, string prefix) { + base.Print(sb, prefix); + variable.Print(sb, prefix + INDENT); + foreach (var expr in Expressions) expr.Print(sb, prefix + INDENT); + } + + public Enlist(Variable variable, Expression expr, Source source) + : this(variable, source) { + Expressions.Add(expr); + + } + + public Enlist(Variable variable, IEnumerable list, Source source) + : this(variable, source) { + Expressions.AddRange(list); + } + +} \ No newline at end of file diff --git a/Starship/Rockstar.Engine/Result.cs b/Starship/Rockstar.Engine/Result.cs index acc9470..222574f 100644 --- a/Starship/Rockstar.Engine/Result.cs +++ b/Starship/Rockstar.Engine/Result.cs @@ -13,6 +13,8 @@ public enum WhatToDo { public class Result(Value value, WhatToDo whatToDo = WhatToDo.Next) { public Value Value => value; public WhatToDo WhatToDo => whatToDo; + public static Result Continue => new(new Null(), WhatToDo.Skip); + public static Result Break => new(new Null(), WhatToDo.Break); public static Result Unknown = new(new Null(), WhatToDo.Unknown); - public static Result Return(Value value) => new Result(value, WhatToDo.Return); + public static Result Return(Value value) => new(value, WhatToDo.Return); } diff --git a/Starship/Rockstar.Engine/RockstarEnvironment.cs b/Starship/Rockstar.Engine/RockstarEnvironment.cs index 50ecd2f..fd2e2b2 100644 --- a/Starship/Rockstar.Engine/RockstarEnvironment.cs +++ b/Starship/Rockstar.Engine/RockstarEnvironment.cs @@ -15,19 +15,12 @@ public class RockstarEnvironment(RockstarIO io) { public RockstarEnvironment(RockstarIO io, RockstarEnvironment parent) : this(io) { Parent = parent; - //this.CopyVariablesFrom(parent); } public RockstarEnvironment? Parent { get; init; } public RockstarEnvironment Extend() => new(IO, this); - //private void CopyVariablesFrom(RockstarEnvironment that) { - // foreach (var variable in that.variables) { - // this.variables[variable.Key] = variable.Value; - // } - //} - protected RockstarIO IO = io; public string? ReadInput() => IO.Read(); @@ -38,8 +31,10 @@ public RockstarEnvironment(RockstarIO io, RockstarEnvironment parent) private readonly Dictionary variables = new(); private readonly Dictionary> arrays = new(); - private Variable AssertTarget(Pronoun pronoun) - => pronounTarget ?? throw new($"You must assign a variable before using a pronoun ('{pronoun.Name}')"); + private Variable QualifyPronoun(Variable variable) => + variable is Pronoun pronoun + ? pronounTarget ?? throw new($"You must assign a variable before using a pronoun ('{pronoun.Name}')") + : variable; public RockstarEnvironment? GetScope(Variable variable) { return variables.ContainsKey(variable.Key) ? this : Parent?.GetScope(variable); @@ -59,7 +54,7 @@ private void SetLocal(Variable variable, Value value) public Result SetVariable(Variable variable, Value value) { var scope = GetScope(variable) ?? this; if (variable is Pronoun pronoun) { - scope.SetLocal(AssertTarget(pronoun), value); + scope.SetLocal(QualifyPronoun(pronoun), value); } else if (variable.Index != default) { var index = Eval(variable.Index); scope.SetArray(variable, index, value); @@ -70,11 +65,8 @@ public Result SetVariable(Variable variable, Value value) { return new(value); } - //private Value Scalar(Dictionary array) - // => new Number(1 + array.Keys.Where(k => k is Number).Select(k => Math.Ceiling(((Number) k).Value)).Max()); - public Value Lookup(Variable variable) { - var key = (variable is Pronoun pronoun ? AssertTarget(pronoun).Key : variable.Key); + var key = (variable is Pronoun pronoun ? QualifyPronoun(pronoun).Key : variable.Key); var value = LookupValue(key); if (variable.Index == default) return value ?? throw new($"Unknown variable '{variable.Name}'"); var index = Eval(variable.Index); @@ -85,9 +77,6 @@ public Value Lookup(Variable variable) { }; } - private Dictionary? LookupArray(string key) - => arrays.TryGetValue(key, out var array) ? array : Parent?.LookupArray(key); - private Value? LookupValue(string key) => variables.TryGetValue(key, out var value) ? value : Parent?.LookupValue(key); @@ -95,7 +84,11 @@ public Result Exec(Block block) { var result = Result.Unknown; foreach (var statement in block.Statements) { result = Exec(statement); - if (result.WhatToDo == WhatToDo.Return) return result; + switch (result.WhatToDo) { + case WhatToDo.Skip: return result; + case WhatToDo.Break: return result; + case WhatToDo.Return: return result; + } } return result; } @@ -112,9 +105,31 @@ public Result Exec(Block block) { Listen l => Listen(l), Rounding r => Rounding(r), Mutation m => Mutation(m), + Enlist e => Enlist(e), + Continue => Result.Continue, + Break => Result.Break, + ExpressionStatement e => new(Eval(e.Expression)), _ => throw new($"I don't know how to execute {statement.GetType().Name} statements") }; + private Result Delist(Delist delist) { + var key = delist.Variable is Pronoun pronoun ? QualifyPronoun(pronoun).Key : delist.Variable.Key; + var value = LookupValue(key); + if (value is Array array) return new(array.Pop()); + return new(new Null()); + } + + private Result Enlist(Enlist e) { + var key = (e.Variable is Pronoun pronoun ? QualifyPronoun(pronoun).Key :e.Variable.Key); + var value = LookupValue(key); + if (value is not Array array) { + array = value == null ? new Array() : new(value); + SetLocal(e.Variable, array); + } + foreach (var expr in e.Expressions) array.Push(Eval(expr)); + return new(array); + } + private Result Mutation(Mutation m) => m.Operator switch { Operator.Join => Join(m), @@ -188,27 +203,35 @@ private Result Call(FunctionCall call) { private Result Loop(Loop loop) { var result = Result.Unknown; - var condition = Eval(loop.Condition); - while (condition.Truthy == loop.CompareTo) { + while (Eval(loop.Condition).Truthy == loop.CompareTo) { result = Exec(loop.Body); - condition = Eval(loop.Condition); + switch (result.WhatToDo) { + case WhatToDo.Skip: continue; + case WhatToDo.Break: break; + case WhatToDo.Return: + return result; + } } return result; } private Result Increment(Increment inc) { - return Eval(inc.Variable) switch { - Number n => Assign(inc.Variable, new Number(n.Value + inc.Multiple)), - Booleän b => inc.Multiple % 2 == 0 ? new(b) : Assign(inc.Variable, b.Negate), - { } v => throw new($"Cannot increment '{inc.Variable.Name}' because it has type {v.GetType().Name}") + var variable = QualifyPronoun(inc.Variable); + return Eval(variable) switch { + Null n => Assign(variable, new Number(inc.Multiple)), + Number n => Assign(variable, new Number(n.Value + inc.Multiple)), + Booleän b => inc.Multiple % 2 == 0 ? new(b) : Assign(variable, b.Negate), + { } v => throw new($"Cannot increment '{variable.Name}' because it has type {v.GetType().Name}") }; } private Result Decrement(Decrement dec) { + var variable = QualifyPronoun(dec.Variable); return Eval(dec.Variable) switch { - Number n => Assign(dec.Variable, new Number(n.Value - dec.Multiple)), - Booleän b => dec.Multiple % 2 == 0 ? new(b) : Assign(dec.Variable, b.Negate), - { } v => throw new($"Cannot increment '{dec.Variable.Name}' because it has type {v.GetType().Name}") + Null n => Assign(variable, new Number(-dec.Multiple)), + Number n => Assign(variable, new Number(n.Value - dec.Multiple)), + Booleän b => dec.Multiple % 2 == 0 ? new(b) : Assign(variable, b.Negate), + { } v => throw new($"Cannot increment '{variable.Name}' because it has type {v.GetType().Name}") }; } @@ -239,6 +262,7 @@ private Result Output(Output output) { { Op: Operator.Not } => Booleän.Not(Eval(u.Expr)), _ => throw new NotImplementedException($"Cannot apply {u.Op} to {u.Expr}") }, + Delist delist => Delist(delist).Value, FunctionCall call => Call(call).Value, _ => throw new NotImplementedException($"Eval not implemented for {expr.GetType()}") }; diff --git a/Starship/Rockstar.Engine/Statements/Break.cs b/Starship/Rockstar.Engine/Statements/Break.cs new file mode 100644 index 0000000..80e576d --- /dev/null +++ b/Starship/Rockstar.Engine/Statements/Break.cs @@ -0,0 +1,5 @@ +namespace Rockstar.Engine.Statements; + +public class Break(Source source) : Statement(source) { + +} diff --git a/Starship/Rockstar.Engine/Statements/Continue.cs b/Starship/Rockstar.Engine/Statements/Continue.cs new file mode 100644 index 0000000..22e11ee --- /dev/null +++ b/Starship/Rockstar.Engine/Statements/Continue.cs @@ -0,0 +1,5 @@ +namespace Rockstar.Engine.Statements; + +public class Continue(Source source) : Statement(source) { + +} diff --git a/Starship/Rockstar.Engine/Statements/Mutation.cs b/Starship/Rockstar.Engine/Statements/Mutation.cs new file mode 100644 index 0000000..1aa45ec --- /dev/null +++ b/Starship/Rockstar.Engine/Statements/Mutation.cs @@ -0,0 +1,11 @@ +using Rockstar.Engine.Expressions; + +namespace Rockstar.Engine.Statements; + +public class Mutation(Operator op, Expression expr, Source source, Variable? target = default, Expression? modifier = default) + : Statement(source) { + public Operator Operator => op; + public Expression Expression => expr; + public Variable? Target => target; + public Expression? Modifier => modifier; +} diff --git a/Starship/Rockstar.Engine/Statements/Round.cs b/Starship/Rockstar.Engine/Statements/Round.cs new file mode 100644 index 0000000..681dc96 --- /dev/null +++ b/Starship/Rockstar.Engine/Statements/Round.cs @@ -0,0 +1,7 @@ +namespace Rockstar.Engine.Statements; + +public enum Round { + Up, + Down, + Nearest +} diff --git a/Starship/Rockstar.Engine/Statements/Rounding.cs b/Starship/Rockstar.Engine/Statements/Rounding.cs new file mode 100644 index 0000000..b204b31 --- /dev/null +++ b/Starship/Rockstar.Engine/Statements/Rounding.cs @@ -0,0 +1,8 @@ +using Rockstar.Engine.Expressions; + +namespace Rockstar.Engine.Statements; + +public class Rounding(Variable variable, Round round, Source source) : Statement(source) { + public Variable Variable => variable; + public Round Round => round; +} diff --git a/Starship/Rockstar.Engine/Statements/Statement.cs b/Starship/Rockstar.Engine/Statements/Statement.cs index e0ef54c..51313dc 100644 --- a/Starship/Rockstar.Engine/Statements/Statement.cs +++ b/Starship/Rockstar.Engine/Statements/Statement.cs @@ -10,35 +10,9 @@ public Block Concat(IList list) public Block Concat(Statement tail) => new Block(new List { this }.Concat([ tail ])); - }; -public enum Round { - Up, - Down, - Nearest -} - -public class Rounding(Variable variable, Round round, Source source) : Statement(source) { - public Variable Variable => variable; - public Round Round => round; -} -public class Mutation(Operator op, Expression expr, Source source, Variable? target = default, Expression? modifier = default) - : Statement(source) { - public Operator Operator => op; +public class ExpressionStatement(Expression expr, Source source) : Statement(source) { public Expression Expression => expr; - public Variable? Target => target; - public Expression? Modifier => modifier; -} - -public class Noop() : Statement(Source.None) { - public static Noop Instance => new Noop(); + public override void Print(StringBuilder sb, string prefix) => expr.Print(sb, prefix); } - -public class Break(Source source) : Statement(source) { - -} - -public class Continue(Source source) : Statement(source) { - -} \ No newline at end of file diff --git a/Starship/Rockstar.Engine/Values/Array.cs b/Starship/Rockstar.Engine/Values/Array.cs index 2b3a985..90672a6 100644 --- a/Starship/Rockstar.Engine/Values/Array.cs +++ b/Starship/Rockstar.Engine/Values/Array.cs @@ -5,20 +5,34 @@ namespace Rockstar.Engine.Values; public class Array(Source source) : Value(source) { private readonly Dictionary entries = new(); + private int maxIndex = -1; + public Number Length => new(maxIndex + 1); + + public bool ArrayEquals(Array that) { + if (this.Length != that.Length) return false; + foreach (var key in this.entries.Keys) { + if (!(this.entries.TryGetValue(key, out var thisValue) + && that.entries.TryGetValue(key, out var thatValue) + && thisValue.Equäls(thatValue).Truthy)) return false; + } + return true; + } - public Array(Value[] args) : this(Source.None) { - for (var i = 0; i < args.Length; i++) entries.Add(new Number(i), args[i]); + public Array(params Value[] args) : this(Source.None) { + for (var i = 0; i < args.Length; i++) Set(new Number(i), args[i]); } public override bool Truthy => entries.Count > 0; - public Value Length - => new Number(1 + this.entries.Keys.Where(k => k is Number) - .Select(k => (int) ((Number) k).NumericValue) - .Max()); + private IEnumerable NumericIndices + => this.entries.Keys.Where(k => k is Number) + .Select(k => (int) ((Number) k).NumericValue); - public Value Set(Value index, Value value) - => entries[index] = value; + public Value Set(Value index, Value value) { + entries[index] = value; + if (index is Number { IsPositiveInteger: true } n && n.Value > maxIndex) maxIndex = (int) n.Value; + return value; + } public Value Get(Value index) => entries.TryGetValue(index, out var value) ? value : Mysterious.Instance; @@ -28,4 +42,22 @@ public Strïng Join(string joiner) { .OrderBy(k => ((Number) k.Key).NumericValue) .Select(k => k.Value.ToString()).ToArray())); } -} + + public Value Push(Value value) { + entries[Length] = value; + maxIndex++; + return value; + } + + public Value Pop() { + entries.Remove(new Number(0), out var value); + if (maxIndex >= 0) maxIndex--; + var keys = this.entries.Keys.ToList(); + foreach (var key in keys) { + if (key is not Number { IsPositiveInteger: true } n) continue; + entries.Remove(n, out var temp); + if (temp != null) entries[new Number(n.NumericValue - 1)] = temp; + } + return value ?? Mysterious.Instance; + } +} \ No newline at end of file diff --git a/Starship/Rockstar.Engine/Values/Number.cs b/Starship/Rockstar.Engine/Values/Number.cs index aaab46d..c7d3742 100644 --- a/Starship/Rockstar.Engine/Values/Number.cs +++ b/Starship/Rockstar.Engine/Values/Number.cs @@ -9,6 +9,7 @@ public class Number(decimal value, Source source) public override bool Equals(object? obj) => Equals(obj as Number); public override int GetHashCode() => this.Value.GetHashCode(); public bool Equals(Number? that) => that != null && this.Value == that.Value; + public bool IsPositiveInteger => this.Value >= 0 && this.Value == (int) this.Value; public Number(string s) : this(Decimal.Parse(s)) { } @@ -27,4 +28,6 @@ public override string ToString() public Value Negate() => new Number(-value); public decimal NumericValue => value; + + public void Decrement() => value -= 1; } \ No newline at end of file diff --git "a/Starship/Rockstar.Engine/Values/Str\303\257ng.cs" "b/Starship/Rockstar.Engine/Values/Str\303\257ng.cs" index 6e94702..cf4e9e5 100644 --- "a/Starship/Rockstar.Engine/Values/Str\303\257ng.cs" +++ "b/Starship/Rockstar.Engine/Values/Str\303\257ng.cs" @@ -35,11 +35,11 @@ private Strïng(IEnumerable strings) public Strïng(params char[] chars) : this(new string(chars)) { } - public Value Concat(Value that) + public Strïng Concat(Value that) => new Strïng(this.Value + that.ToStrïng().Value); - public Value Repeat(IHaveANumber n) - => new Strïng(Enumerable + public Strïng Repeat(IHaveANumber n) + => new(Enumerable .Range(0, (int) n.NumericValue) .Select(_ => this.Value)); diff --git a/Starship/Rockstar.Engine/Values/Value.cs b/Starship/Rockstar.Engine/Values/Value.cs index 94db260..cac3f98 100644 --- a/Starship/Rockstar.Engine/Values/Value.cs +++ b/Starship/Rockstar.Engine/Values/Value.cs @@ -9,9 +9,8 @@ public abstract class Value(Source source) public abstract bool Truthy { get; } public bool Falsy => !Truthy; - //public Value And(Value that) => this.Truthy ? that : this; - - //public Value Or(Value that) => this.Truthy ? this : that; + public Value Plus(IEnumerable that) + => that.Aggregate(this, (memo, next) => memo.Plus(next)); public Value Plus(Value that) => (this, that) switch { (Strïng a, _) => a.Concat(that), @@ -41,12 +40,18 @@ public override void Print(StringBuilder sb, string prefix) _ => throw new NotImplementedException() }; + public Value Minus(IEnumerable that) + => that.Aggregate(this, (memo,next) => memo.Minus(next)); + public Value Minus(Value that) => (this, that) switch { (IHaveANumber a, IHaveANumber b) => new Number(a.NumericValue - b.NumericValue), _ => throw Boom(nameof(Minus), this, that) }; + public Value Times(IEnumerable that) + => that.Aggregate(this, (memo, next) => memo.Times(next)); + public Value Times(Value that) => (this, that) switch { (IHaveANumber a, IHaveANumber b) => new Number(a.NumericValue * b.NumericValue), @@ -54,6 +59,9 @@ public override void Print(StringBuilder sb, string prefix) _ => Mysterious.Instance }; + public Value Divide(IEnumerable that) + => that.Aggregate(this, (memo, next) => memo.Divide(next)); + public Value Divide(Value that) => (this, that) switch { (IHaveANumber a, IHaveANumber b) => new Number(a.NumericValue / b.NumericValue), @@ -61,6 +69,12 @@ public override void Print(StringBuilder sb, string prefix) }; public Value Equäls(Value that) => (Booleän) (this switch { + Array lhs => that switch { + (Array rhs) => lhs.ArrayEquals(rhs), + (Number rhs) => lhs.Length.Equals(rhs), + (Null) => lhs.Length.Value == 0, + _ => throw Boom(nameof(Equäls), this, that) + }, Booleän lhs => lhs.Truthy == that.Truthy, Number lhs => that switch { (Number rhs) => lhs.Value == rhs.Value, diff --git a/Starship/Rockstar.Engine/rockstar.peg b/Starship/Rockstar.Engine/rockstar.peg index 48d7851..c21b599 100644 --- a/Starship/Rockstar.Engine/rockstar.peg +++ b/Starship/Rockstar.Engine/rockstar.peg @@ -36,7 +36,7 @@ statement / loop / conditional / operation -// / expression + / e:expression { new ExpressionStatement(e, state.Source()) } break_stmt = break ignore_rest_of_line @@ -71,6 +71,8 @@ return { new Return(e, state.Source()) } / give _ back _ e:expression { new Return(e, state.Source()) } + / give _ e:expression + { new Return(e, state.Source()) } operation = listen_stmt / output_stmt / crement / mutation / assign_stmt / rounding @@ -202,7 +204,9 @@ add_list { new OpList(op, list) } addition - = head:product_simple lists:add_list+ + = lhs:product_simple op:(_plus_ / _minus_) rhs:product + { new Binary(op, lhs, rhs, state.Source()) } + / head:product_simple lists:add_list+ { Binary.Reduce(head, lists, state.Source()) } / product_simple @@ -250,15 +254,27 @@ assign_stmt { new Assign(v, new Strïng(s, state.Source(s)), state.Source()) } / put _ e:expression _ into _ v:assignable { new Assign(v, e, state.Source()) } + / let _ v:assignable _ be o:_op_ e:expression_list + { new Assign(v, new Binary(o, new Looküp(v, state.Source()), e, state.Source()), state.Source()) } / let _ v:assignable _ be o:_op_ e:expression { new Assign(v, new Binary(o, new Looküp(v, state.Source()), e, state.Source()), state.Source()) } / let _ v:assignable _ be _ e:expression { new Assign(v, e, state.Source()) } / name:assignable _ takes _ args:variable_list EOL body:block EOL { new Assign(name, new Function(args, body, state.Source()), state.Source()) } - - //TODO: reinstate for 2.0 - // v:variable _is _ e:expression { new Assign(v, e, state.Source()) } + / enlist + +enlist + = push _ e:expression _ into _ v:variable + { new Enlist(v, e, state.Source()) } + / push _ v:variable _ like _ e:(literal/poetic_literal) + { new Enlist(v, e, state.Source()) } + / push _ v:variable (_ with)? _ e:expression_list + { new Enlist(v, e, state.Source()) } + / push _ v:variable + { new Enlist(v, state.Source()) } + / d:delist _ into _ t:assignable + { new Assign(t, d, state.Source()) } poetic_literal = whole_part:poetic_digits _? '.' _? fractional_part:poetic_digits @@ -306,8 +322,13 @@ primary = literal / lookup +delist + = pop _ v:variable + { new Delist(v, state.Source()) } + lookup - = v:variable i:indexer + = d:delist { d } + / v:variable i:indexer { new Looküp(v.AtIndex(i), state.Source()) } / v:variable { new Looküp(v, state.Source()) } @@ -415,7 +436,7 @@ and = 'and' !letter or = 'or' !letter back = 'back' !letter taking = 'taking' !letter -give = 'give' !letter / 'send' !letter +give = 'return' !letter / 'give' !letter / 'send' !letter takes = 'takes' !letter / 'wants' !letter says = 'says' !letter / 'said' !letter pronoun = 'they' !letter / 'them'!letter / 'she' !letter / 'him' !letter / 'her' !letter / 'hir' !letter/ 'zie' !letter/ 'zir' !letter/ 'xem' !letter/ 'ver'!letter @@ -433,6 +454,9 @@ null = 'null' !letter/ 'nothing' !letter/ 'nowhere' !letter/ 'nobody' !letter/ ' output = "shout" !letter / "say" !letter / "scream" !letter / "whisper" !letter break = 'break' !letter continue = 'continue' !letter / 'take' _ 'it' _ 'to' _ 'the' _ 'top' !letter - +push = 'rock' !letter / 'push' !letter +pop = 'roll' !letter / 'pop' !letter +with = 'with' !letter +like = 'like' !letter keyword = mysterious / plus / minus / times / over / as / isnt / not / is / put / into / let / be / listen / to / while / until / else / if / build / up / down / knock / and / or / back / taking / give / takes / says / pronoun / the / gtr / ltr / gte / lte / than / empty / true / false / null / output / non diff --git a/Starship/Rockstar.Test/ParserTests/ArrayTests.cs b/Starship/Rockstar.Test/ParserTests/ArrayTests.cs index e7e3f8f..2e8dbc1 100644 --- a/Starship/Rockstar.Test/ParserTests/ArrayTests.cs +++ b/Starship/Rockstar.Test/ParserTests/ArrayTests.cs @@ -1,30 +1,12 @@ namespace Rockstar.Test.ParserTests; -public class MutationTests(ITestOutputHelper output) : ParserTestBase(output) { +public class ArrayTests(ITestOutputHelper output) : ParserTestBase(output) { [Fact] - public void JoinWorks() { - var source = """ - Let the array at 0 be "a" - Let the array at 1 be "b" - Let the array at 2 be "c" - Join the array into ResultA - Shout ResultA - - Join the array into ResultB with "-" - Shout ResultB - - Join the array - Shout the array - - Split "abcde" into tokens - Join tokens with ";" - Shout tokens - - """; + public void ParseRock() { + var source = "rock first with 0, 1, 2"; var parsed = Parse(source); + parsed.Statements.Count.ShouldBe(1); } -} -public class ArrayTests(ITestOutputHelper testOutput) : ParserTestBase(testOutput) { [Fact] public void ParseThing() { @@ -50,6 +32,7 @@ shout my array var result = Run(parsed); result.ShouldBe("a\nb\n2\n".ReplaceLineEndings()); } + [Fact] public void ParserParsesArrays() { var source = """ diff --git a/Starship/Rockstar.Test/ParserTests/BinaryTests.cs b/Starship/Rockstar.Test/ParserTests/BinaryTests.cs new file mode 100644 index 0000000..0d10004 --- /dev/null +++ b/Starship/Rockstar.Test/ParserTests/BinaryTests.cs @@ -0,0 +1,16 @@ +using Rockstar.Engine.Expressions; + +namespace Rockstar.Test.ParserTests; + +public class BinaryTests(ITestOutputHelper output) { + + [Fact] + public void ComplicatedExpressionListsReduceProperly() { + var list1 = new OpList(Operator.Plus, [new Number(2), new Number(3), new Number(4), new Number(5)]); + var list2 = new OpList(Operator.Times, [new Number(5), new Number(6), new Number(7), new Number(8)]); + var list3 = new OpList(Operator.Plus, [new Number(9), new Number(10), new Number(11), new Number(12)]); + + var binary = Binary.Reduce(new Number(1), new List { list1, list2, list3 }, Source.None); + output.WriteLine(binary.ToString()); + } +} \ No newline at end of file diff --git a/Starship/Rockstar.Test/ParserTests/FizzBuzzTests.cs b/Starship/Rockstar.Test/ParserTests/FizzBuzzTests.cs new file mode 100644 index 0000000..cc2cf8c --- /dev/null +++ b/Starship/Rockstar.Test/ParserTests/FizzBuzzTests.cs @@ -0,0 +1,25 @@ +namespace Rockstar.Test.ParserTests; + +public class FizzBuzzTests(ITestOutputHelper output) : ParserTestBase(output) { + [Fact] + public void ParserParsesFizzBuzz() { + var source = """ + If Modulus taking Counter, Fizz is 0 and Modulus taking Counter, Buzz is 0 + Say "FizzBuzz!" + Continue + (blank line ending 'If' Block) + If Modulus taking Counter & Fizz is 0 + Say "Fizz!" + Continue + (blank line ending 'If' Block) + If Modulus taking Counter & Buzz is 0 + Say "Buzz!" + Continue + (blank line ending 'If' Block) + Say Counter + (EOL ending Until block) + + """; + Parse(source).Statements.Count.ShouldBe(4); + } +} diff --git a/Starship/Rockstar.Test/ParserTests/FunctionTests.cs b/Starship/Rockstar.Test/ParserTests/FunctionTests.cs index 3876dcb..cf83918 100644 --- a/Starship/Rockstar.Test/ParserTests/FunctionTests.cs +++ b/Starship/Rockstar.Test/ParserTests/FunctionTests.cs @@ -1,6 +1,7 @@ namespace Rockstar.Test.ParserTests; public class FunctionTests(ITestOutputHelper output) : ParserTestBase(output) { + [Theory] [InlineData(""" OuterFunction takes X @@ -34,6 +35,23 @@ public void ParserParsesFunctions(string source) { var result = Parse(source); } + [Fact] + public void ParserParsesArrayCopy() { + var source = """ + ArrayCopy takes source + rock dest + let i be 0 + let len be source + 0 + while i is less than len + let dest at i be source at i + build i up + + return dest + + """; + Parse(source).Statements.Count.ShouldBe(1); + + } [Fact] public void ParserParsesFunctionWithReturnStatement() { var source = """ diff --git a/Starship/Rockstar.Test/ParserTests/LiteralTests.cs b/Starship/Rockstar.Test/ParserTests/LiteralTests.cs index 7e66b80..cff95a3 100644 --- a/Starship/Rockstar.Test/ParserTests/LiteralTests.cs +++ b/Starship/Rockstar.Test/ParserTests/LiteralTests.cs @@ -1,26 +1,7 @@ using Pegasus.Common.Tracing; -using Rockstar.Engine.Expressions; namespace Rockstar.Test.ParserTests; -public class BinaryTests(ITestOutputHelper output) { - [Fact] - public void ComplicatedExpressionListReducesProperly() { - var list = new OpList(Operator.Plus, [new Number(2), new Number(3), new Number(4), new Number(5)]); - var binary = list.Reduce(new Number(1)); - output.WriteLine(binary.ToString()); - } - - [Fact] - public void ComplicatedExpressionListsReduceProperly() { - var list1 = new OpList(Operator.Plus, [new Number(2), new Number(3), new Number(4), new Number(5)]); - var list2 = new OpList(Operator.Times, [new Number(5), new Number(6), new Number(7), new Number(8)]); - var list3 = new OpList(Operator.Plus, [new Number(9), new Number(10), new Number(11), new Number(12)]); - - var binary = Binary.Reduce(new Number(1), new List { list1, list2, list3 }, Source.None); - output.WriteLine(binary.ToString()); - } -} public class LiteralTests(ITestOutputHelper output) : ParserTestBase(output) { [Theory] @@ -42,8 +23,7 @@ public void ParserParsesNumberLiterals(string source) { [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; + var assign = Parse(source).Statements[0] as Assign; ((Number) assign.Expr).Value.ShouldBe(value); } @@ -55,7 +35,6 @@ Variables Are 2 say variables """)] public void PoeticLiteralWorks(string source) { - var parser = new Parser(); // { Tracer = DiagnosticsTracer.Instance }; - var result = parser.Parse(source); + Parse(source); } } \ No newline at end of file diff --git a/Starship/Rockstar.Test/ParserTests/MutationTests.cs b/Starship/Rockstar.Test/ParserTests/MutationTests.cs new file mode 100644 index 0000000..fb9e2ec --- /dev/null +++ b/Starship/Rockstar.Test/ParserTests/MutationTests.cs @@ -0,0 +1,33 @@ +namespace Rockstar.Test.ParserTests; + +public class MutationTests(ITestOutputHelper output) : ParserTestBase(output) { + [Fact] + public void ParserUnderstandsRoll() { + var source = "Roll the list"; + var parsed = Parse(source); + parsed.Statements.Count.ShouldBe(1); + } + + [Fact] + public void JoinWorks() { + var source = """ + Let the array at 0 be "a" + Let the array at 1 be "b" + Let the array at 2 be "c" + Join the array into ResultA + Shout ResultA + + Join the array into ResultB with "-" + Shout ResultB + + Join the array + Shout the array + + Split "abcde" into tokens + Join tokens with ";" + Shout tokens + + """; + var parsed = Parse(source); + } +} diff --git a/Starship/Rockstar.Test/ParserTests/OperatorTests.cs b/Starship/Rockstar.Test/ParserTests/OperatorTests.cs index f9e40c4..950c4b0 100644 --- a/Starship/Rockstar.Test/ParserTests/OperatorTests.cs +++ b/Starship/Rockstar.Test/ParserTests/OperatorTests.cs @@ -22,6 +22,20 @@ public void NotWorks() { Parse("say not true"); } + [Fact] + public void PrecedenceParses() { + var source = """ + say 1 + 1 * 2 (prints: 3) + say 1 * 2 + 1 (prints: 3) + say 1 / 2 * 3 + 4 (prints: 5.5) + say 5-2*3 (prints: -1) + say 2*3 - 5 (prints: 1) + + """; + var parsed = Parse(source); + parsed.Statements.Count.ShouldBe(5); + } + [Fact] public void AintWorks() { var parsed = Parse("say 5 aint 4"); diff --git a/Starship/Rockstar.Test/ParserTests/ParserTests.cs b/Starship/Rockstar.Test/ParserTests/ParserTests.cs index b3040b7..a5989e1 100644 --- a/Starship/Rockstar.Test/ParserTests/ParserTests.cs +++ b/Starship/Rockstar.Test/ParserTests/ParserTests.cs @@ -84,8 +84,7 @@ Beta says b Delta says rn """, 4)] public void ParserParsesSaysLiterals(string source, int count) { - var parser = new Parser() { Tracer = DiagnosticsTracer.Instance }; - var result = parser.Parse(source); + var result = Parse(source); result.Statements.Count.ShouldBe(count); } @@ -99,10 +98,7 @@ 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); + var result = Parse(source); result.Statements.Count.ShouldBe(count); } } \ No newline at end of file diff --git a/Starship/Rockstar.Test/ParserTests/QueueTests.cs b/Starship/Rockstar.Test/ParserTests/QueueTests.cs new file mode 100644 index 0000000..23768ba --- /dev/null +++ b/Starship/Rockstar.Test/ParserTests/QueueTests.cs @@ -0,0 +1,59 @@ +namespace Rockstar.Test.ParserTests; + +public class ListOperatorTests(ITestOutputHelper output) : ParserTestBase(output) { + [Fact] + public void ParseWithoutList() { + var source = "say 10 without 1, 2, and 3"; + var parsed = Parse(source); + var result = Run(parsed); + result.ShouldBe("4\n".ReplaceLineEndings()); + } + + [Fact] + public void ParseWolf() { + var source = """ + The wolf is hungry, out on the street + Fear is the mind killer + Fury is the demon child + Hate is the only truth + Let the wolf be without fear, fury, and hate + Shout the wolf + + """; + var parsed = Parse(source); + var result = Run(parsed); + result.ShouldBe("62190\n".ReplaceLineEndings()); + } + + [Fact] + public void ParseTommy() { + var source = "Let Tommy be with \" to\", \" what\", \" we've\", \" got\""; + var parsed = Parse(source); + parsed.Statements.Count.ShouldBe(1); + output.WriteLine(parsed.ToString()); + } + + [Fact] + public void WolfNoot() { + var source = "say 99999 without 8888, 77, 6"; + var parsed = Parse(source); + output.WriteLine(parsed.ToString()); + Run(parsed).ShouldBe("91028\n".ReplaceLineEndings()); + } +} + +public class QueueTests(ITestOutputHelper output) : ParserTestBase(output) { + [Fact] + public void ParseQueueProgram() { + var source = """ + rock the list with "first" + while the list ain't nothing + shout roll the list + + """; + var parsed = Parse(source); + output.WriteLine(parsed.ToString()); + var result = Run(parsed); + result.ShouldBe("first\n".ReplaceLineEndings()); + } +} \ No newline at end of file diff --git a/Starship/Rockstar.Test/Rockstar.Test.v3.ncrunchproject b/Starship/Rockstar.Test/Rockstar.Test.v3.ncrunchproject index 0507223..e0aae56 100644 --- a/Starship/Rockstar.Test/Rockstar.Test.v3.ncrunchproject +++ b/Starship/Rockstar.Test/Rockstar.Test.v3.ncrunchproject @@ -1,5 +1,5 @@  - 1000 + 5000 \ No newline at end of file diff --git a/Starship/Rockstar.Test/Values/ArrayTests.cs b/Starship/Rockstar.Test/Values/ArrayTests.cs new file mode 100644 index 0000000..4f871a1 --- /dev/null +++ b/Starship/Rockstar.Test/Values/ArrayTests.cs @@ -0,0 +1,78 @@ +using Rockstar.Test.ParserTests; +using Array = Rockstar.Engine.Values.Array; + +namespace Rockstar.Test.Values; + +public class ArrayTests(ITestOutputHelper testOutput) : ParserTestBase(testOutput) { + + [Fact] + public void EmptyArrayHasZeroLength() { + var a = new Array(); + a.Length.ShouldBe(new(0)); + } + + [Fact] + public void AddingNonNumericValuesDoesNotIncreaseLength() { + var a = new Array(); + a.Set(new Strïng("foo"), new Number(0)); + a.Length.ShouldBe(new(0)); + } + + [Fact] + public void PushingValueIncrementsLength() { + var a = new Array(); + a.Length.ShouldBe(new(0)); + a.Push(new Number(1)); + a.Length.ShouldBe(new(1)); + } + + [Fact] + public void PopActuallyShiftsBecauseRockstarIsBroken() { + var a = new Array(); + a.Push(new Number(1)); + a.Push(new Number(2)); + a.Push(new Number(3)); + a.Pop().ShouldBe(new Number(1)); + a.Pop().ShouldBe(new Number(2)); + a.Pop().ShouldBe(new Number(3)); + } + + [Fact] + public void PushingValueDecrementsLength() { + var a = new Array(); + a.Length.ShouldBe(new(0)); + a.Push(new Number(1)); + a.Pop().ShouldBe(new Number(1)); + a.Length.ShouldBe(new(0)); + } + + [Fact] + public void AssigningNumericIndexSetsLength() { + var a = new Array(); + a.Set(new Number(9), new Strïng("foo")); + a.Length.ShouldBe(new(10)); + } + + [Fact] + public void AssigningNumericIndexAndThenPoppingDecrementsLength() { + var a = new Array(); + a.Set(new Number(1), new Strïng("foo")); + a.Length.ShouldBe(new(2)); + a.Pop().ShouldBe(Mysterious.Instance); + a.Length.ShouldBe(new(1)); + a.Pop().ShouldBe(new Strïng("foo")); + a.Length.ShouldBe(new(0)); + } + + [Fact] + public void MissingElementsAreAlwaysMysterious() { + var a = new Array(); + a.Get(new Number(2)).ShouldBe(Mysterious.Instance); + a.Get(new Strïng("foo")).ShouldBe(Mysterious.Instance); + a.Get(Booleän.False).ShouldBe(Mysterious.Instance); + a.Get(Booleän.True).ShouldBe(Mysterious.Instance); + a.Get(a).ShouldBe(Mysterious.Instance); + a.Get(new Null()).ShouldBe(Mysterious.Instance); + a.Get(Mysterious.Instance).ShouldBe(Mysterious.Instance); + } +} \ No newline at end of file diff --git a/Starship/Starship.sln.DotSettings b/Starship/Starship.sln.DotSettings index 01ce8ca..46dc3c1 100644 --- a/Starship/Starship.sln.DotSettings +++ b/Starship/Starship.sln.DotSettings @@ -1,2 +1,3 @@  + True True \ No newline at end of file