Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/Agrammon/Environment.rakumod
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
60 changes: 47 additions & 13 deletions lib/Agrammon/Formula.rakumod
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down
13 changes: 13 additions & 0 deletions lib/Agrammon/Formula/Builder.rakumod
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ class Agrammon::Formula::Builder {
)
}

method statement_control:sym<for>($/) {
make Agrammon::Formula::For.new(
iterable => $<EXPR>.ast,
loop-vars => $<variable>.map(-> $var { Agrammon::Formula::VarDecl.new(name => ~$var) }),
block => $<block>.ast
);
}

method block($/) {
make Agrammon::Formula::Block.new(
Expand Down Expand Up @@ -262,6 +269,12 @@ class Agrammon::Formula::Builder {
make $<EXPR>.ast;
}

method term:sym<[ ]>($/) {
make Agrammon::Formula::Array.new(
values => $<EXPR>.map(*.ast)
);
}

method term:sym<{ }>($/) {
make Agrammon::Formula::Hash.new(
pairs => $<pair>.map(*.ast)
Expand Down
10 changes: 10 additions & 0 deletions lib/Agrammon/Formula/Compiler.rakumod
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
}
Expand Down Expand Up @@ -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(',')} )"
}
Expand Down
13 changes: 13 additions & 0 deletions lib/Agrammon/Formula/Parser.rakumod
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ grammar Agrammon::Formula::Parser {
'default' <block>
}

rule statement_control:sym<for> {
'for'
[ '(' <EXPR> ')' || <.panic('Missing or malformed loop expression')> ]
['->' <variable>+ % [ ',' ] || <.panic('Missing or malformed loop variables')> ]
<block>
}

rule block {
[ '{' || <.panic('Expected block')> ]
<statementlist>
Expand Down Expand Up @@ -152,6 +159,12 @@ grammar Agrammon::Formula::Parser {
'(' <EXPR> [ ')' || <.panic('Missing closing )')> ]
}

rule term:sym<[ ]> {
'['
<EXPR>* %% [ ',' ]
[ ']' || <.panic('Missing ] on array literal or malformed array')> ]
}

rule term:sym<{ }> {
'{'
<pair>* %% [ ',' ]
Expand Down
120 changes: 120 additions & 0 deletions t/formula.rakutest
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Loading