| # use Grammar::Tracer;
|
|
|
| grammar Vixen {
|
| token id { <[A..Za..z*]>+ <![:]> }
|
| token selector { <[A..Za..z*]>+ \: }
|
| token string { \" <[A..Za..z*]>* \" }
|
| token param { \: <id> }
|
| token ass { \:\= }
|
|
|
| proto rule lit {*}
|
| rule lit:sym<block> { '[' <param>* '|' <exprs> ']' }
|
| rule lit:sym<paren> { '(' <call> ')' }
|
| rule lit:sym<string> { <string> }
|
| rule lit:sym<name> { <id> }
|
|
|
| proto rule unary {*}
|
| rule unary:sym<chain> { <lit> <id>* }
|
|
|
| rule keyword { <selector> <unary> }
|
|
|
| proto rule message {*}
|
| rule message:sym<key> { <keyword>+ }
|
|
|
| rule messages { <message>* % ';' }
|
|
|
| proto rule call {*}
|
| rule call:sym<cascade> { <unary> <messages> }
|
|
|
| proto rule assign {*}
|
| rule assign:sym<name> { <id> <ass> <call> }
|
| rule assign:sym<call> { <call> }
|
|
|
| rule statement { <assign> '.' }
|
|
|
| proto rule exprs {*}
|
| rule exprs:sym<rv> { <statement>* '^' <call> }
|
| rule exprs:sym<nil> { <statement>* <call> }
|
|
|
| rule TOP { '[' <param>* '|' <exprs> ']' }
|
| }
|
|
|
| role EmitLiteral {
|
| method prepareLiteral($compiler) { ... }
|
| }
|
|
|
| class VUnary does EmitLiteral {
|
| has EmitLiteral $.receiver;
|
| has Str @.verb;
|
| # NB: @verb is inhabited!
|
| method prepareLiteral($compiler) {
|
| my $receiver = $.receiver.prepareLiteral: $compiler;
|
| for @.verb {
|
| my $name = $compiler.gensym;
|
| $compiler.callBind: $name, $receiver, $_, [];
|
| $receiver = "\$" ~ $name;
|
| };
|
| $receiver;
|
| }
|
| }
|
|
|
| class VKeyword {
|
| has Str $.selector;
|
| has EmitLiteral $.arg;
|
| }
|
|
|
| class VMessage {
|
| has VKeyword @.keywords;
|
| method prepareMessage($compiler) {
|
| my $verb = @.keywords.map(*.selector).join;
|
| my @row = @.keywords.map({ $_.arg.prepareLiteral: $compiler });
|
| $verb, @row;
|
| }
|
| }
|
|
|
| role Call {
|
| method prepareBind($name, $compiler) { ... }
|
| method prepareOnly($compiler) { ... }
|
| }
|
|
|
| class VCall does Call does EmitLiteral {
|
| has EmitLiteral $.unary;
|
| has VMessage @.cascades;
|
| # NB: @cascades is inhabited!
|
| method prepareBind($name, $compiler) {
|
| my $receiver = $.unary.prepareLiteral: $compiler;
|
| my $last = @.cascades[*-1];
|
| for @.cascades[0 ...^ @.cascades.elems - 1] {
|
| my ($verb, @row) = $_.prepareMessage: $compiler;
|
| $compiler.callOnly: $receiver, $verb, @row;
|
| };
|
| my ($verb, @row) = $last.prepareMessage: $compiler;
|
| $compiler.callBind: $name, $receiver, $verb, @row;
|
| }
|
| method prepareOnly($compiler) {
|
| my $receiver = $.unary.prepareLiteral: $compiler;
|
| for @.cascades {
|
| my ($verb, @row) = $_.prepareMessage: $compiler;
|
| $compiler.callOnly: $receiver, $verb, @row;
|
| };
|
| }
|
| method prepareLiteral($compiler) {
|
| my $name = $compiler.gensym;
|
| self.prepareBind: $name, $compiler;
|
| "\$" ~ $name;
|
| }
|
| }
|
|
|
| role EmitStatement {
|
| method assignTo($compiler) { ... }
|
| }
|
|
|
| class VAssignName does EmitStatement {
|
| has Call $.call;
|
| has Str $.target;
|
| method assignTo($compiler) { $.call.prepareBind: $.target, $compiler; }
|
| }
|
|
|
| class VIgnore does EmitStatement {
|
| has Call $.call;
|
| method assignTo($compiler) { $.call.prepareOnly: $compiler; }
|
| }
|
|
|
| class VExprs {
|
| has EmitStatement @.statements;
|
| has EmitLiteral $.rv;
|
| method compileExprs($compiler) {
|
| for @.statements { $_.assignTo: $compiler };
|
| $.rv.prepareLiteral: $compiler;
|
| }
|
| }
|
|
|
| class VBlock does EmitLiteral {
|
| has Str @.params;
|
| has VExprs $.exprs;
|
| method prepareLiteral($compiler) {
|
| # XXX size? locals? closure?
|
| $compiler.pushBlock: $compiler.genblock;
|
| # XXX params
|
| $.exprs.compile: $compiler;
|
| $compiler.popBlock;
|
| }
|
| }
|
|
|
| class VParen does EmitLiteral {
|
| has Call $.call;
|
| method prepareLiteral($compiler) { $.call.prepareLiteral: $compiler; }
|
| }
|
|
|
| class VStr does EmitLiteral {
|
| has Str $.s;
|
| method prepareLiteral($compiler) { $.s; }
|
| }
|
|
|
| class VName does Call does EmitLiteral {
|
| has Str $.n;
|
| method prepareBind($name, $compiler) { $compiler.assignName: $.n, $name; }
|
| method prepareOnly($compiler) {;}
|
| method prepareLiteral($compiler) { "\$" ~ $.n; }
|
| }
|
|
|
| class VTop {
|
| has Str @.params;
|
| has VExprs $.assigns;
|
|
|
| method compile($compiler) {
|
| $compiler.pushBlock: "main";
|
| $compiler.push: "#!/usr/bin/env -S execlineb -s" ~ @.params.elems + 1;
|
| $compiler.push: "importas -iS V";
|
| $compiler.push: "importas -iS Nil";
|
| $compiler.assignName: "1", "self";
|
| for @.params.kv -> $i, $n {
|
| $compiler.assignName: $i + 2, substr($n, 1);
|
| }
|
| my $rv = $.assigns.compileExprs: $compiler;
|
| $compiler.push: "echo $rv";
|
| $compiler.popBlock;
|
| }
|
| }
|
|
|
| class VixenActions {
|
| method lit:sym<block>($/) {
|
| make VBlock.new(:params($<param>.made), :assigns($<exprs>.made));
|
| }
|
| method lit:sym<paren>($/) { make $<call>.made; }
|
| method lit:sym<string>($/) { make VStr.new(:s($<string>.Str)); }
|
| method lit:sym<name>($/) { make VName.new(:n($<id>.Str)); }
|
|
|
| method unary:sym<chain>($/) {
|
| my $receiver = $<lit>.made;
|
| my @verb = $<id>.map: *.Str;
|
| make @verb ?? VUnary.new(:$receiver, :@verb) !! $receiver;
|
| }
|
|
|
| method keyword($/) {
|
| my $selector = $<selector>.Str;
|
| my $arg = $<unary>.made;
|
| make VKeyword.new(:$selector, :$arg);
|
| }
|
|
|
| method message:sym<key>($/) {
|
| my @keywords = $<keyword>.values.map: *.made;
|
| make VMessage.new(:@keywords);
|
| }
|
|
|
| method messages($/) { make $<message>.values.map: *.made; }
|
|
|
| method call:sym<cascade>($/) {
|
| my $unary = $<unary>.made;
|
| my @cascades = $<messages>.made;
|
| make @cascades ?? VCall.new(:$unary, :@cascades) !! $unary;
|
| }
|
|
|
| method assign:sym<name>($/) {
|
| make VAssignName.new(:target($<id>.Str), :call($<call>.made));
|
| }
|
| method assign:sym<call>($/) { make VIgnore.new(:call($<call>.made)); }
|
|
|
| method statement($/) { make $<assign>.made; }
|
|
|
| method exprs:sym<rv>($/) {
|
| my @statements = $<statement>.values.map: *.made;
|
| make VExprs.new(:@statements, :rv($<call>.made));
|
| }
|
| method exprs:sym<nil>($/) {
|
| my @statements = $<statement>.values.map: *.made;
|
| my $rv = VName.new(:n("Nil"));
|
| make VExprs.new(:@statements, :$rv);
|
| }
|
|
|
| method TOP($/) {
|
| make VTop.new(:params($<param>.map: *.Str), :assigns($<exprs>.made));
|
| }
|
| }
|
|
|
| sub formatCall($receiver, $verb, @args) {
|
| my $row = @args.join: ' ';
|
| "\$\{V\}/call: $receiver $verb $row";
|
| }
|
|
|
| class Compiler {
|
| has Int $!syms;
|
| method gensym { $!syms += 1; "gs" ~ $!syms; }
|
| method genblock { $!syms += 1; "gb" ~ $!syms; }
|
|
|
| has Str %.lines;
|
| has Str @.blocks;
|
|
|
| method push($line) { %.lines{ @.blocks[* - 1] } ~= $line ~ "\n"; }
|
| method pushBlock($name) {
|
| %.lines{ $name } = '';
|
| @.blocks.push: $name;
|
| }
|
| method popBlock() { @.blocks.pop; }
|
|
|
| method callBind($name, $receiver, $verb, @args) {
|
| self.push: "backtick -E $name \{ " ~ formatCall($receiver, $verb, @args) ~ " \}";
|
| }
|
| method callOnly($receiver, $verb, @args) {
|
| self.push: "foreground \{ " ~ formatCall($receiver, $verb, @args) ~ " \}";
|
| }
|
|
|
| method assignName($from, $to) { self.push: "define $to \$$from"; }
|
| }
|
|
|
| my $actions = VixenActions.new;
|
| my $input = slurp $*IN;
|
| my $parsed = Vixen.parse: $input, :$actions;
|
| # say "Parsed: ", $parsed;
|
| my $ast = $parsed.made;
|
| my $compiler = Compiler.new;
|
| my $compiled = $ast.compile: $compiler;
|
| say $compiler.lines{ "main" }.trim;
|
| say $input.trim.split("\n").map({ "# " ~ $_ }).join("\n");
|