diff --git a/lib/Agrammon/Environment.rakumod b/lib/Agrammon/Environment.rakumod index e8d735a6..80c9e4ae 100644 --- a/lib/Agrammon/Environment.rakumod +++ b/lib/Agrammon/Environment.rakumod @@ -20,4 +20,8 @@ class Agrammon::Environment { method find-builtin($name) { %!builtins{$name} // get-builtins(){$name} // die "No such builtin function '$name'"; } + + method iterate($value) { + $value ~~ Map ?? $value.kv !! $value.list + } } diff --git a/lib/Agrammon/Formula.rakumod b/lib/Agrammon/Formula.rakumod index 2a0aa376..6e9aebf0 100644 --- a/lib/Agrammon/Formula.rakumod +++ b/lib/Agrammon/Formula.rakumod @@ -41,6 +41,14 @@ class Agrammon::Formula::StatementList does Agrammon::Formula { } } +class Agrammon::Formula::Block does Agrammon::Formula { + has Agrammon::Formula::StatementList $.statements; + + method input-used() { $!statements.input-used } + method technical-used() { $!statements.technical-used } + method output-used() { $!statements.output-used } +} + class Agrammon::Formula::Routine does Agrammon::Formula { has Agrammon::Formula::StatementList $.statements; @@ -49,6 +57,14 @@ class Agrammon::Formula::Routine does Agrammon::Formula { method output-used() { $!statements.output-used } } +class Agrammon::Formula::VarDecl does Agrammon::Formula::LValue { + has Str $.name; +} + +class Agrammon::Formula::Var does Agrammon::Formula::LValue { + has Str $.name; +} + class Agrammon::Formula::If does Agrammon::Formula { has Agrammon::Formula $.condition; has Agrammon::Formula $.then; @@ -70,12 +86,22 @@ class Agrammon::Formula::If does Agrammon::Formula { } } -class Agrammon::Formula::Block does Agrammon::Formula { - has Agrammon::Formula::StatementList $.statements; +class Agrammon::Formula::For does Agrammon::Formula { + has Agrammon::Formula $.iterable; + has Agrammon::Formula::VarDecl @.loop-vars; + has Agrammon::Formula::Block $.block; - method input-used() { $!statements.input-used } - method technical-used() { $!statements.technical-used } - method output-used() { $!statements.output-used } + method input-used() { + self!merge-inputs: $!iterable.input-used, $!block.input-used + } + + method technical-used() { + self!merge-technicals: $!iterable.technical-used, $!block.technical-used + } + + method output-used() { + self!merge-outputs: $!iterable.output-used, $!block.output-used + } } class Agrammon::Formula::Given does Agrammon::Formula { @@ -145,14 +171,6 @@ class Agrammon::Formula::WhenMod does Agrammon::Formula { } } -class Agrammon::Formula::VarDecl does Agrammon::Formula::LValue { - has Str $.name; -} - -class Agrammon::Formula::Var does Agrammon::Formula::LValue { - has Str $.name; -} - class Agrammon::Formula::CallBuiltin does Agrammon::Formula::LValue { has Str $.name; has Agrammon::Formula @.args; @@ -217,6 +235,22 @@ class Agrammon::Formula::Sum does Agrammon::Formula { method output-used() { ($!reference,) } } +class Agrammon::Formula::Array does Agrammon::Formula { + has Agrammon::Formula @.values; + + method input-used() { + self!merge-inputs: @!values.map(*.input-used) + } + + method technical-used() { + self!merge-technicals: @!values.map(*.technical-used) + } + + method output-used() { + self!merge-outputs: @!values.map(*.output-used) + } +} + class Agrammon::Formula::Hash does Agrammon::Formula { has Agrammon::Formula @.pairs; diff --git a/lib/Agrammon/Formula/Builder.rakumod b/lib/Agrammon/Formula/Builder.rakumod index 71239449..eefea474 100644 --- a/lib/Agrammon/Formula/Builder.rakumod +++ b/lib/Agrammon/Formula/Builder.rakumod @@ -97,6 +97,13 @@ class Agrammon::Formula::Builder { ) } + method statement_control:sym($/) { + make Agrammon::Formula::For.new( + iterable => $.ast, + loop-vars => $.map(-> $var { Agrammon::Formula::VarDecl.new(name => ~$var) }), + block => $.ast + ); + } method block($/) { make Agrammon::Formula::Block.new( @@ -262,6 +269,12 @@ class Agrammon::Formula::Builder { make $.ast; } + method term:sym<[ ]>($/) { + make Agrammon::Formula::Array.new( + values => $.map(*.ast) + ); + } + method term:sym<{ }>($/) { make Agrammon::Formula::Hash.new( pairs => $.map(*.ast) diff --git a/lib/Agrammon/Formula/Compiler.rakumod b/lib/Agrammon/Formula/Compiler.rakumod index d3589575..b58cb01f 100644 --- a/lib/Agrammon/Formula/Compiler.rakumod +++ b/lib/Agrammon/Formula/Compiler.rakumod @@ -41,6 +41,12 @@ multi compile(Agrammon::Formula::Default $default) { q:f"default { &compile($default.block) }" } +multi compile(Agrammon::Formula::For $for) { + q:f"for $env.iterate(&compile($for.iterable)) -> " ~ + $for.loop-vars.map(*.name).join(", ") ~ + q:f" { &compile($for.block) }" +} + multi compile(Agrammon::Formula::WhenMod $when) { q:f"&compile($when.then) when &compile($when.test)" } @@ -69,6 +75,10 @@ multi compile(Agrammon::Formula::Sum $sum) { } } +multi compile(Agrammon::Formula::Array $array) { + q:c"@( {$array.values.map(&compile).join(',')} )" +} + multi compile(Agrammon::Formula::Hash $hash) { q:c"%( {$hash.pairs.map(&compile).join(',')} )" } diff --git a/lib/Agrammon/Formula/Parser.rakumod b/lib/Agrammon/Formula/Parser.rakumod index 22bac8b5..3d045596 100644 --- a/lib/Agrammon/Formula/Parser.rakumod +++ b/lib/Agrammon/Formula/Parser.rakumod @@ -64,6 +64,13 @@ grammar Agrammon::Formula::Parser { 'default' } + rule statement_control:sym { + 'for' + [ '(' ')' || <.panic('Missing or malformed loop expression')> ] + ['->' + % [ ',' ] || <.panic('Missing or malformed loop variables')> ] + + } + rule block { [ '{' || <.panic('Expected block')> ] @@ -152,6 +159,12 @@ grammar Agrammon::Formula::Parser { '(' [ ')' || <.panic('Missing closing )')> ] } + rule term:sym<[ ]> { + '[' + * %% [ ',' ] + [ ']' || <.panic('Missing ] on array literal or malformed array')> ] + } + rule term:sym<{ }> { '{' * %% [ ',' ] diff --git a/t/formula.rakutest b/t/formula.rakutest index c4b21c6d..ffc77ed6 100644 --- a/t/formula.rakutest +++ b/t/formula.rakutest @@ -1098,4 +1098,124 @@ subtest { is $result.Numeric, 42, 'Correct result'; }, 'P+ upgrades scalar value on left side'; +subtest { + my $f = parse-formula(q:to/FORMULA/, 'TestModule'); + my $total = 0; + for ([1, 2, 3]) -> $num { + $total = $total + $num; + } + return $total; + FORMULA + ok $f ~~ Agrammon::Formula, 'Get something doing Agrammon::Formula from parse'; + is-deeply $f.input-used, (), 'Correct inputs-used'; + is-deeply $f.technical-used, (), 'Correct technical-used'; + is-deeply $f.output-used, (), 'Correct output-used'; + my $result = compile-and-evaluate($f, Agrammon::Environment.new); + is $result, 6, 'Correct result from for loop over array literal'; +}, 'Basic for loop over array literal'; + +subtest { + my $f = parse-formula(q:to/FORMULA/, 'TestModule'); + my $nums = [1, 2, 3]; + my $total = 0; + for ($nums) -> $num { + $total = $total + $num; + } + return $total; + FORMULA + ok $f ~~ Agrammon::Formula, 'Get something doing Agrammon::Formula from parse'; + is-deeply $f.input-used, (), 'Correct inputs-used'; + is-deeply $f.technical-used, (), 'Correct technical-used'; + is-deeply $f.output-used, (), 'Correct output-used'; + my $result = compile-and-evaluate($f, Agrammon::Environment.new); + is $result, 6, 'Correct result from for loop over variable'; +}, 'For loop over variable containing array'; + +subtest { + my $f = parse-formula(q:to/FORMULA/, 'TestModule'); + my $total = 0; + for ([3, 5, 7, 9]) -> $a, $b { + $total = $total + $a * $b; + } + return $total; + FORMULA + ok $f ~~ Agrammon::Formula, 'Get something doing Agrammon::Formula from parse'; + is-deeply $f.input-used, (), 'Correct inputs-used'; + is-deeply $f.technical-used, (), 'Correct technical-used'; + is-deeply $f.output-used, (), 'Correct output-used'; + my $result = compile-and-evaluate($f, Agrammon::Environment.new); + is $result, 3*5 + 7*9, 'Correct result from for loop taking two values at a time'; +}, 'For loop taking two values at a time'; + +subtest { + my $f = parse-formula(q:to/FORMULA/, 'TestModule'); + my $total = 0; + for ({ a => 1, b => 3 }) -> $key, $value { + $total = $total + $value; + } + return $total; + FORMULA + ok $f ~~ Agrammon::Formula, 'Get something doing Agrammon::Formula from parse'; + is-deeply $f.input-used, (), 'Correct inputs-used'; + is-deeply $f.technical-used, (), 'Correct technical-used'; + is-deeply $f.output-used, (), 'Correct output-used'; + my $result = compile-and-evaluate($f, Agrammon::Environment.new); + is $result, 4, 'Correct result from for loop over hash literal'; +}, 'For loop over hash literal'; + +subtest { + my $f = parse-formula(q:to/FORMULA/, 'TestModule'); + my $mapping = { x => 10, y => 20 }; + my $total = 0; + for ($mapping) -> $key, $value { + $total = $total + $value; + } + return $total; + FORMULA + ok $f ~~ Agrammon::Formula, 'Get something doing Agrammon::Formula from parse'; + is-deeply $f.input-used, (), 'Correct inputs-used'; + is-deeply $f.technical-used, (), 'Correct technical-used'; + is-deeply $f.output-used, (), 'Correct output-used'; + my $result = compile-and-evaluate($f, Agrammon::Environment.new); + is $result, 30, 'Correct result from for loop over variable containing hash'; +}, 'For loop over variable containing hash'; + +subtest { + my $f = parse-formula(q:to/FORMULA/, 'TestModule'); + my $h = { apple => 2, banana => 3 }; + my $keys = ''; + my $total = 0; + for ($h) -> $key, $value { + $keys = $keys . $key . ','; + $total = $total + $value; + } + return $keys . $total; + FORMULA + ok $f ~~ Agrammon::Formula, 'Get something doing Agrammon::Formula from parse'; + is-deeply $f.input-used, (), 'Correct inputs-used'; + is-deeply $f.technical-used, (), 'Correct technical-used'; + is-deeply $f.output-used, (), 'Correct output-used'; + my $result = compile-and-evaluate($f, Agrammon::Environment.new); + ok $result eq 'apple,banana,5' || $result eq 'banana,apple,5', + 'Correct result with keys concatenated (either order)'; +}, 'For loop over hash verifying keys'; + +subtest { + my $f = parse-formula(q:to/FORMULA/, 'TestModule'); + my $res = 0; + for (['bc', 'th', 'ts']) -> $tech { + $res = $res + $TE->{'app_tech_' . $tech}; + } + return $res; + FORMULA + ok $f ~~ Agrammon::Formula, 'Get something doing Agrammon::Formula from parse'; + is-deeply $f.input-used, (), 'Correct inputs-used'; + is-deeply $f.technical-used, (), 'Correct technical-used (indirect not included)'; + is-deeply $f.output-used, (), 'Correct output-used'; + my $result = compile-and-evaluate($f, Agrammon::Environment.new( + technical => { app_tech_bc => 10, app_tech_th => 20, app_tech_ts => 30 } + )); + is $result, 60, 'Correct result from for loop with dynamic technical access'; +}, 'For loop with indirect technical access'; + done-testing;