From bd8b85d3957cddc9e936f59ec0fd7b2e14220ed0 Mon Sep 17 00:00:00 2001 From: Dylan Beattie Date: Fri, 12 Jul 2024 15:22:30 +0100 Subject: [PATCH] Completely rebuilt keyword grammar. I may regret this later. --- Starship/Rockstar.Engine/Result.cs | 20 +- .../Rockstar.Engine/RockstarEnvironment.cs | 33 ++- Starship/Rockstar.Engine/Statements/Return.cs | 10 + Starship/Rockstar.Engine/rockstar.peg | 233 +++++++++--------- Starship/Rockstar.Test/FixtureBase.cs | 11 +- .../ParserTests/FunctionTests.cs | 81 +++++- .../Rockstar.Test/ParserTests/LiteralTests.cs | 13 + .../Rockstar.Test/ParserTests/LoopTests.cs | 11 +- .../ParserTests/ParserTestBase.cs | 9 +- .../Rockstar.Test.v3.ncrunchproject | 23 -- Starship/Rockstar.Test/TestEnvironment.cs | 9 +- .../fixtures/control-flow/indented_else.rock | 1 - 12 files changed, 286 insertions(+), 168 deletions(-) diff --git a/Starship/Rockstar.Engine/Result.cs b/Starship/Rockstar.Engine/Result.cs index c4d1cb1..acc9470 100644 --- a/Starship/Rockstar.Engine/Result.cs +++ b/Starship/Rockstar.Engine/Result.cs @@ -1,6 +1,18 @@ +using Rockstar.Engine.Values; + namespace Rockstar.Engine; -public class Result { - public static readonly Result Ok = new(); - public static readonly Result Unknown = new(); -} \ No newline at end of file +public enum WhatToDo { + Unknown, + Next, + Skip, + Break, + Return +} + +public class Result(Value value, WhatToDo whatToDo = WhatToDo.Next) { + public Value Value => value; + public WhatToDo WhatToDo => whatToDo; + public static Result Unknown = new(new Null(), WhatToDo.Unknown); + public static Result Return(Value value) => new Result(value, WhatToDo.Return); +} diff --git a/Starship/Rockstar.Engine/RockstarEnvironment.cs b/Starship/Rockstar.Engine/RockstarEnvironment.cs index deb89b1..8c705bf 100644 --- a/Starship/Rockstar.Engine/RockstarEnvironment.cs +++ b/Starship/Rockstar.Engine/RockstarEnvironment.cs @@ -23,9 +23,9 @@ private void CopyVariablesFrom(RockstarEnvironment that) { } protected RockstarIO IO = io; - + public string? ReadInput() => IO.Read(); - public void WriteLine(string? output) => IO.Write((output ?? String.Empty)+ Environment.NewLine); + public void WriteLine(string? output) => IO.Write((output ?? String.Empty) + Environment.NewLine); public void Write(string output) => IO.Write(output); private Variable? pronounTarget; @@ -42,7 +42,7 @@ public Result SetVariable(Variable variable, Value value) { variables[variable.Key] = value; } - return Result.Ok; + return new(value); } public Value GetVariable(Variable variable) { @@ -52,7 +52,10 @@ public Value GetVariable(Variable variable) { public Result Exec(Block block) { var result = Result.Unknown; - foreach (var statement in block.Statements) result = Exec(statement); + foreach (var statement in block.Statements) { + result = Exec(statement); + if (result.WhatToDo == WhatToDo.Return) return result; + } return result; } @@ -64,9 +67,23 @@ public Result Exec(Block block) { Decrement dec => Decrement(dec), Loop loop => Loop(loop), FunctionCall call => Call(call), + Return r => Return(r), + Listen l => Listen(l), _ => throw new($"I don't know how to execute {statement.GetType().Name} statements") }; + private Result Listen(Listen l) { + var input = ReadInput(); + Value value = input == default ? new Null() : new Strïng(input); + if (l.Variable != default) SetVariable(l.Variable, value); + return new(value); + } + + private Result Return(Return r) { + var value = Eval(r.Expression); + return Result.Return(value); + } + private Result Call(FunctionCall call) { var value = GetVariable(call.Function); if (value is not Function function) throw new($"'{call.Function.Name}' is not a function"); @@ -91,7 +108,7 @@ private Result Loop(Loop loop) { 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 ? Result.Ok : Assign(inc.Variable, b.Negate), + 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}") }; } @@ -99,7 +116,7 @@ private Result Increment(Increment inc) { private Result Decrement(Decrement dec) { return Eval(dec.Variable) switch { Number n => Assign(dec.Variable, new Number(n.Value - dec.Multiple)), - Booleän b => dec.Multiple % 2 == 0 ? Result.Ok : Assign(dec.Variable, b.Negate), + 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}") }; } @@ -117,13 +134,12 @@ private Result Assign(Assign assign) private Result Output(Output output) { var value = Eval(output.Expr); WriteLine(value.ToStrïng().Value); - return Result.Ok; + return new(value); } private Value Eval(Expression expr) => expr switch { Value value => value, Binary binary => binary.Resolve(Eval), - // not sure what the difference is here.... ? Looküp lookup => GetVariable(lookup.Variable), Variable v => GetVariable(v), @@ -132,6 +148,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}") }, + FunctionCall call => Call(call).Value, _ => throw new NotImplementedException($"Eval not implemented for {expr.GetType()}") }; } \ No newline at end of file diff --git a/Starship/Rockstar.Engine/Statements/Return.cs b/Starship/Rockstar.Engine/Statements/Return.cs index 89ba694..48b6cb2 100644 --- a/Starship/Rockstar.Engine/Statements/Return.cs +++ b/Starship/Rockstar.Engine/Statements/Return.cs @@ -1,7 +1,17 @@ +using System.Runtime.CompilerServices; using Rockstar.Engine.Expressions; namespace Rockstar.Engine.Statements; public class Return(Expression expr, Source source) : Statement(source) { + public Expression Expression => expr; +} + +public class Listen(Source source) + : Statement(source) { + public Variable? Variable { get; init; } = default; + public Listen(Variable variable, Source source) : this(source) { + this.Variable = variable; + } } diff --git a/Starship/Rockstar.Engine/rockstar.peg b/Starship/Rockstar.Engine/rockstar.peg index 1e508de..bb859fe 100644 --- a/Starship/Rockstar.Engine/rockstar.peg +++ b/Starship/Rockstar.Engine/rockstar.peg @@ -4,24 +4,20 @@ @using System.Text.RegularExpressions @using Rockstar.Engine.Statements @using Rockstar.Engine.Expressions -@using Rockstar.Engine.Values; +@using Rockstar.Engine.Values @trace true @ignorecase true program - = __ head:statements tail:program - { head.Concat(tail) } - / __ EOF - { new Block() } + = __ head:statements tail:program { head.Concat(tail) } + / __ EOF { new Block() } __ = ("" _ / EOL)* statements - = head:statement EOS tail:statements - { tail.Insert(head) } - / s:statement // THIS IS THE EOS THAT MAKES IT ALL GO WONKULAR EOS - { new Block(s) } + = head:statement EOS tail:statements { tail.Insert(head) } + / s:statement { new Block(s) } EOS = _? EOL _? / _? EOF @@ -32,23 +28,25 @@ EOF = !. #error{ $"Unexpected '{Regex.Escape(unexpected)}' at line {state.Line}, col {state.Column - 1}" } statement - = loop + = listen_stmt + / assign_stmt + / loop / alternate / output_stmt / function_call / return - / assign_stmt / increment / decrement + return - = 'give back' _ e:expression + = give _ e:expression _ back + { new Return(e, state.Source()) } + / give _ back _ e:expression { new Return(e, state.Source()) } - -takes = ('takes' / 'wants') function_call - = name:variable _ 'taking' _ args:expression_list + = name:variable _ taking _ args:expression_list { new FunctionCall(name, args, state.Source()) } expression_list > @@ -57,10 +55,10 @@ expression_list > / expr:expression { new List { expr } } _XLS_ - = (_? ', and' _ / _? ('&' / ',' / "'n'") _?) + = (_? ', ' and _ / _? ('&' / ',' / "'n'") _?) _VLS_ - = _ 'and' _ / _XLS_ + = _ and _ / _XLS_ variable_list > = head:variable _VLS_ tail:variable_list @@ -70,11 +68,11 @@ variable_list > noise = (_ / [;,]) increment - = 'build'i _ v:variable _ t:('up' noise*)+ + = build _ v:variable _ t:(up noise*)+ { new Increment(v, t.Count, state.Source()) } decrement - = 'knock'i _ v:variable _ t:('down' noise*)+ + = knock _ v:variable _ t:(down noise*)+ { new Decrement(v, t.Count, state.Source()) } block @@ -84,44 +82,37 @@ block { b } alternate - = 'if' _ e:expression c:block 'else' a:block + = if _ e:expression c:block else a:block { new Conditional(e, c, state.Source()).Else(a) } - / 'if' _ e:expression c:block _? 'else' a:block + / if _ e:expression c:block _? else a:block { new Conditional(e, c, state.Source()).Else(a) } - / 'if' _ e:expression c:block EOS 'else' a:block + / if _ e:expression c:block EOS else a:block { new Conditional(e, c, state.Source()).Else(a) } - / 'if' _ e:expression c:block + / if _ e:expression c:block EOS { new Conditional(e, c, state.Source()) } -//alternate -// = c:conditional 'else' a:block -// { c.Else(a) } -// / c:conditional _? 'else' a:block -// { c.Else(a) } -// / c:conditional EOS 'else' a:block -// { c.Else(a) } -// / c:conditional { c } -// -//conditional -// = 'if' _ e:expression c:block -// { new Conditional(e, c, state.Source()) } - loop - = 'while' _ e:expression _ s:statement + = while _ e:expression _ s:statement { new WhileLoop(e, new Block(s), state.Source()) } - / 'while' _ e:expression EOS b:statements EOS + / while _ e:expression EOS b:statements EOS { new WhileLoop(e, b, state.Source()) } - / 'until' _ e:expression _ s:statement + / until _ e:expression _ s:statement { new UntilLoop(e, new Block(s), state.Source()) } - / 'until' _ e:expression EOS b:statements EOS + / until _ e:expression EOS b:statements EOS { new UntilLoop(e, b, state.Source()) } +listen_stmt + = listen _ to _ v:variable + { new Listen(v, state.Source()) } + / listen + { new Listen(state.Source()) } + assign_stmt - = 'put' _ e:expression _ 'into' _ v:variable + = put _ e:expression _ into _ v:variable { new Assign(v, e, state.Source()) } - / 'let' _ v:variable _ 'be' o:_op_ e:expression + / 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 + / let _ v:variable _ be _ e:expression { new Assign(v, e, state.Source()) } / name:variable _ takes _ args:variable_list EOS body:statements { new Assign(name, new Function(args, body, state.Source()), state.Source()) } @@ -133,8 +124,6 @@ assign_stmt //TODO: reinstate for 2.0 // v:variable _is _ e:expression { new Assign(v, e, state.Source()) } -says = ('says' / 'said') - poetic_literal = whole_part:poetic_digits _? '.' _? fractional_part:poetic_digits { new Number (whole_part + "." + fractional_part) } @@ -162,103 +151,74 @@ variable / n:proper_variable { new ProperVariable(n, state.Source(n)) } / n:simple_variable { new SimpleVariable(n, state.Source(n)) } -pronoun - = ('they' / 'them' - / 'she' / 'him' / 'her' / 'hir' / 'zie' / 'zir' / 'xem' / 'ver' - / 'ze' / 've' / 'xe' / 'it' / 'he') !letter - proper_variable // e.g. Big Bad Benny = proper_noun (_ proper_noun)+ proper_noun - = !keyword uppercase_letter letter* - -keyword = (is / common_prefix / pronoun / 'let' / 'with') !letter + = !keyword (uppercase_letter letter*) common_variable - = common_prefix _ simple_variable - -common_prefix - = ('an' / 'a' / 'the' / 'my' / 'your' / 'our') !letter + = the _ identifier -simple_variable - = !keyword ("" letter (letter / [0-9])*) - -letter - = uppercase_letter / lowercase_letter - -uppercase_letter - = [A-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮİIJĴĶĸĹĻĽĿŁŃŅŇŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŸŹŻŽ]s - -lowercase_letter - = [a-zàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıijĵķĸĺļľŀłńņňŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷÿźżžʼnß]s +identifier = ("" letter (letter / [0-9])*) +simple_variable = !keyword identifier +letter = uppercase_letter / lowercase_letter +uppercase_letter = [A-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮİIJĴĶĸĹĻĽĿŁŃŅŇŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŸŹŻŽ]s +lowercase_letter = [a-zàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıijĵķĸĺļľŀłńņňŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷÿźżžʼnß]s expression = unary - / function_call / variable unary = op:unary_op ex:unary { new Unary(op, ex, state.Source()) } - / primary + / primaroo -primary +primaroo = function_call - / or - -// nor -// = lhs:or _ 'nor' rhs:or -// { new Binary(Operator.Nor, lhs, rhs, state.Source()) } -// / or + / binary_or -or - = lhs:and _ 'or' _ rhs:or +binary_or + = lhs:binary_and _ or _ rhs:binary_or { new Binary(Operator.Or, lhs, rhs, state.Source()) } - / and + / binary_and -and - = lhs:equals _ 'and' _ rhs:and +binary_and + = lhs:equals _ and _ rhs:binary_and { new Binary(Operator.And, lhs, rhs, state.Source()) } / equals equals - = lhs:not op:_eq _ rhs:equals + = lhs:compare op:_eq _ rhs:equals { new Binary(op,lhs,rhs, state.Source()) } - / not - -not - = ('non-' / 'non' _ / 'not' _ ) ex:not { new Unary(Operator.Not, ex, state.Source()) } / compare +//not +// = ('non-' / 'non' _ / 'not' _ ) ex:not { new Unary(Operator.Not, ex, state.Source()) } +// / compare + compare = lhs:math op:comparator _ rhs:compare { new Binary(op, lhs, rhs, state.Source()) } / math -gtr = ('greater'/ 'higher' / 'bigger' / 'stronger' / 'more' ) -ltr = ('less' / 'lower' / 'smaller' / 'weaker' ) -gte = ('great' / 'high' / 'big' / 'strong' ) -lte = ('less' / 'low' / 'small' / 'weak' ) - comparator - = _is _ gtr _ 'than' { Operator.MoreThan } - / _is _ ltr _ 'than' { Operator.LessThan } - / _is _ 'as' _ gte _ 'as' { Operator.MoreThanEqual } - / _is _ 'as' _ lte _ 'as' { Operator.LessThanEqual } + = _is _ gtr _ than { Operator.MoreThan } + / _is _ ltr _ than { Operator.LessThan } + / _is _ as _ gte _ as { Operator.MoreThanEqual } + / _is _ as _ lte _ as { Operator.LessThanEqual } _eq = _is_not / _is _is - = ("'s" / "'re" / _ ('=' / is)) !letter + = ("'s" / "'re" / _ ('=' / is)) { Operator.Equals } -is = 'is' / 'was' / 'are' / 'were' - _is_not - = _ ('!=' / _is _ "not" / "isnt" / "isn't" / 'aint' / "ain't" / "wasn't" / "wasnt" / "aren't" / "arent" / "weren't" / "werent") !letter + = (_ '!=' / _is _ not / isnt) { Operator.NotEquals } math @@ -272,10 +232,10 @@ product -memoize = 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 _ ) { Operator.Plus } +_minus_ = (_? '-' _? / _ minus _ ) { Operator.Minus } +_times_ = (_? '*' _? / _ times _ ) { Operator.Times } +_divide_ = (_? '/' _? / _ over _ ) { Operator.Divide } _op_ = _plus_ / _minus_ / _times_ / _divide_ @@ -297,19 +257,8 @@ constant = t:true { new Booleän(true, state.Source(t)) } / f:false { new Booleän(false, state.Source(f)) } / n:null { new Null(state.Source(n)) } - / m:'mysterious' { Mysterious.Instance } + / m:mysterious { Mysterious.Instance } / e:empty { Strïng.Empty } - -empty - = ('empty'i / 'silent'i / 'silence'i) - -true - = ("true" / "yes" / "ok" / "right") !letter -false - = ("false" / "lies" / "no" / "wrong") !letter -null - = ('null' / 'nothing' / 'nowhere' / 'nobody' / 'gone') !letter - string = '"' contents:("" [^"]*) '"' { new Strïng(contents, state.Source(contents)) } @@ -321,9 +270,55 @@ unary_op = not { Operator.Not } / _minus_ { Operator.Minus } -output - = "shout" / "say" / "scream" / "whisper" _ = "" (whitespace / comment)+ whitespace = [ \t] -comment = '(' [^)]* ')' / '{' [^\}]* '}' / '[' [^\]]* ']' \ No newline at end of file +comment = '(' [^)]* ')' / '{' [^\}]* '}' / '[' [^\]]* ']' + +mysterious = 'mysterious' !letter +plus = 'plus' !letter / 'with' !letter +minus = 'minus' !letter / 'without' !letter +times = 'times' !letter / 'of' !letter +over = 'over' !letter / 'between' !letter +as = 'as' !letter +isnt = "isnt" !letter / "isn't" !letter / 'aint' !letter / "ain't" !letter / "wasn't" !letter / "wasnt" !letter + / "aren't" !letter / "arent" !letter / "weren't" !letter / "werent" !letter +not = 'not' !letter +is = 'is' !letter / 'was' !letter / 'are' !letter / 'were' !letter +put = 'put' !letter +into = 'into' !letter / 'in' !letter +let = 'let' !letter +be = 'be' !letter +listen = 'listen' !letter +to = 'to' !letter +while = 'while' !letter +until = 'until' !letter +else = 'else' !letter +if = 'if' !letter +build = 'build' !letter +up = 'up' !letter +down = 'down' !letter +knock = 'knock' !letter +and = 'and' !letter +or = 'or' !letter +back = 'back' !letter +taking = 'taking' !letter +give = '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 + / 'ze' !letter/ 've' !letter/ 'xe' !letter/ 'it' !letter/ 'he'!letter +the = 'an' !letter/ 'a' !letter / 'the' !letter / 'my' !letter / 'your' !letter / 'our' !letter +gtr = 'greater'/ 'higher' / 'bigger' / 'stronger' !letter/ 'more' !letter +ltr = 'less' !letter/ 'lower' / 'smaller' !letter/ 'weaker' !letter +gte = 'great' !letter/ 'high' !letter/ 'big' !letter/ 'strong' !letter +lte = 'less' !letter/ 'low' !letter/ 'small' !letter/ 'weak' !letter +than = 'than' !letter +empty = 'empty' !letter / 'silent' !letter / 'silence' !letter +true = "true" !letter/ "yes" !letter/ "ok" !letter/ "right"!letter +false = "false" !letter/ "lies" !letter/ "no" !letter/ "wrong"!letter +null = 'null' !letter/ 'nothing' !letter/ 'nowhere' !letter/ 'nobody' !letter/ 'gone'!letter +output = "shout" !letter / "say" !letter / "scream" !letter / "whisper" !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 diff --git a/Starship/Rockstar.Test/FixtureBase.cs b/Starship/Rockstar.Test/FixtureBase.cs index 72dbc93..7fa648b 100644 --- a/Starship/Rockstar.Test/FixtureBase.cs +++ b/Starship/Rockstar.Test/FixtureBase.cs @@ -72,8 +72,9 @@ private void PrettyPrint(string source, string filePath, FormatException ex) { testOutput.WriteLine(ncrunchOutputMessage); } - private string RunProgram(Block program) { - var env = new TestEnvironment(); + private string RunProgram(Block program, Queue? inputs = null) { + string? ReadInput() => inputs != null && inputs.TryDequeue(out var result) ? result : null; + var env = new TestEnvironment(ReadInput); env.Exec(program); return env.Output; } @@ -90,7 +91,8 @@ public void RunFile(string filePath, string directory) { throw; } try { - var result = RunProgram(program); + var inputs = ReadInputs(filePath); + var result = RunProgram(program, inputs); var expect = ExtractExpects(filePath); if (String.IsNullOrEmpty(expect)) return; result.ShouldBe(expect); @@ -99,4 +101,7 @@ public void RunFile(string filePath, string directory) { throw; } } + + private Queue? ReadInputs(string filePath) + => File.Exists(filePath + ".in") ? new(File.ReadAllLines(filePath + ".in")) : null; } \ No newline at end of file diff --git a/Starship/Rockstar.Test/ParserTests/FunctionTests.cs b/Starship/Rockstar.Test/ParserTests/FunctionTests.cs index 3f3508f..448a5bf 100644 --- a/Starship/Rockstar.Test/ParserTests/FunctionTests.cs +++ b/Starship/Rockstar.Test/ParserTests/FunctionTests.cs @@ -50,9 +50,86 @@ public void FunctionWithReturnStatementWorks() { Function takes x give back x with x (end function) - say Function taking 1, 2 + say Function taking 2 """; - Run(source).ShouldBe("3"); + Run(source).ShouldBe("4\n".ReplaceLineEndings()); + } + + [Fact] + public void ParseFunctionWorks() { + var source = """ + AddAndPrint takes X, and Y + put X plus Y into Value + say Value + Give back Value + (end function) + say AddAndPrint taking 3, 4 + """; + Run(source).ShouldBe("7\n7\n".ReplaceLineEndings()); + } + + [Fact] + public void ParsePolly() { + var source = """ + Polly wants a cracker + Cheese is delicious + Put a cracker with cheese in your mouth + Give it back + + Shout Polly taking 100 + """; + Run(source).ShouldBe("109\n".ReplaceLineEndings()); + } + + [Fact] + public void RecursionWorks() { + var source = """ + Decrement takes X + If X is nothing + Give back X + Else + Put X minus 1 into NewX + Give back Decrement taking NewX + + Say Decrement taking 5 + + """; + var program = Parse(source); + output.WriteLine(program.ToString()); + var result = Run(program); + Run(source).ShouldBe("0\n".ReplaceLineEndings()); + + } + + [Fact] + public void ParseFunctionWithExpressionListWorks() { + var source = """ + AddOrSub takes X, and B + if B + Give Back X plus 1 + (end if) + say "else" + Give Back X minus 1 + (end function) + say AddOrSub taking 4, true + say AddOrSub taking 4, false + + """; + var program = Parse(source); + output.WriteLine(program.ToString()); + var result = Run(program); + Run(source).ShouldBe("5\nelse\n3\n".ReplaceLineEndings()); + } + + [Fact] + public void FunctionWithMultipleParametersAndReturnStatementWorks() { + var source = """ + Function takes x and y and z + give back x with " " with y with " " with z + (end function) + say Function taking "eddie", "van", and "halen" + """; + Run(source).ShouldBe("eddie van halen\n".ReplaceLineEndings()); } [Theory] diff --git a/Starship/Rockstar.Test/ParserTests/LiteralTests.cs b/Starship/Rockstar.Test/ParserTests/LiteralTests.cs index e6accb1..da29ab7 100644 --- a/Starship/Rockstar.Test/ParserTests/LiteralTests.cs +++ b/Starship/Rockstar.Test/ParserTests/LiteralTests.cs @@ -3,6 +3,7 @@ namespace Rockstar.Test.ParserTests; public class LiteralTests { + [Theory] [InlineData("say 1")] [InlineData("say 1.1")] @@ -27,4 +28,16 @@ public void PoeticLiteralAssignsCorrectValue(string source, decimal value) { var assign = parser.Parse(source).Statements[0] as Assign; ((Number) assign.Expr).Value.ShouldBe(value); } + + [Theory] + [InlineData(""" + variables are 1 + say variables + Variables Are 2 + say variables + """)] + public void PoeticLiteralWorks(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/ParserTests/LoopTests.cs b/Starship/Rockstar.Test/ParserTests/LoopTests.cs index 4137fdd..3ccb048 100644 --- a/Starship/Rockstar.Test/ParserTests/LoopTests.cs +++ b/Starship/Rockstar.Test/ParserTests/LoopTests.cs @@ -15,18 +15,25 @@ say done [Theory] [InlineData(""" + Say "begin" X is 10 While X is greater than nothing + Y is 0 While Y is less than 3 Build Y up Say Y - + Knock X down + Say X + + Say "end" + """)] public void ParserParsesNestedLoop(string source) { var parsed = Parse(source); - parsed.Statements.Count.ShouldBe(2); + output.WriteLine(parsed.ToString()); + parsed.Statements.Count.ShouldBe(4); } [Fact] diff --git a/Starship/Rockstar.Test/ParserTests/ParserTestBase.cs b/Starship/Rockstar.Test/ParserTests/ParserTestBase.cs index c307c19..7325226 100644 --- a/Starship/Rockstar.Test/ParserTests/ParserTestBase.cs +++ b/Starship/Rockstar.Test/ParserTests/ParserTestBase.cs @@ -1,3 +1,5 @@ +using Pegasus.Common.Tracing; + namespace Rockstar.Test.ParserTests; public class ParserTestBase(ITestOutputHelper output) { @@ -8,10 +10,11 @@ protected Block Parse(string source) { return result; } - protected string Run(string source) { - var parsed = Parse(source); + protected string Run(string source) => Run(Parse(source)); + + protected string Run(Block block) { var e = new TestEnvironment(); - e.Exec(parsed); + e.Exec(block); return e.Output; } diff --git a/Starship/Rockstar.Test/Rockstar.Test.v3.ncrunchproject b/Starship/Rockstar.Test/Rockstar.Test.v3.ncrunchproject index 98333f7..e0aae56 100644 --- a/Starship/Rockstar.Test/Rockstar.Test.v3.ncrunchproject +++ b/Starship/Rockstar.Test/Rockstar.Test.v3.ncrunchproject @@ -1,28 +1,5 @@  5000 - - - Rockstar.Test.FixtureTests.Fixture("arrays\\array_functions.rock") - - - Rockstar.Test.FixtureTests.Fixture("arrays\\arrayalike.rock") - - - Rockstar.Test.FixtureTests.Fixture("arrays\\arrays.rock") - - - Rockstar.Test.FixtureTests.Fixture("arrays\\hash.rock") - - - Rockstar.Test.FixtureTests.Fixture("arrays\\join.rock") - - - Rockstar.Test.FixtureTests.Fixture("arrays\\split.rock") - - - Rockstar.Test.FixtureTests.Fixture("arrays\\split_delimiters.rock") - - \ No newline at end of file diff --git a/Starship/Rockstar.Test/TestEnvironment.cs b/Starship/Rockstar.Test/TestEnvironment.cs index 965ac51..45297da 100644 --- a/Starship/Rockstar.Test/TestEnvironment.cs +++ b/Starship/Rockstar.Test/TestEnvironment.cs @@ -1,12 +1,15 @@ namespace Rockstar.Test; -public class StringBuilderIO : RockstarIO { +public class StringBuilderIO(Func readInput) : RockstarIO { + public StringBuilderIO() : this(() => null) { } private readonly StringBuilder sb = new(); - public string? Read() => null; + public string? Read() => readInput(); public void Write(string? s) => sb.Append(s); public string Output => sb.ToString(); } -public class TestEnvironment() : RockstarEnvironment(new StringBuilderIO()) { +public class TestEnvironment(Func readInput) : RockstarEnvironment(new StringBuilderIO(readInput)) { + public TestEnvironment() : this(() => null) { + } public string Output => ((StringBuilderIO) IO).Output; } \ No newline at end of file diff --git a/Starship/Rockstar.Test/fixtures/control-flow/indented_else.rock b/Starship/Rockstar.Test/fixtures/control-flow/indented_else.rock index c42cbac..ce290cc 100644 --- a/Starship/Rockstar.Test/fixtures/control-flow/indented_else.rock +++ b/Starship/Rockstar.Test/fixtures/control-flow/indented_else.rock @@ -5,7 +5,6 @@ While my hope is less than my dream Shout my dream If my dream is my hope Shout "No" - Else Shout "Yes"