New paste Repaste Download
# 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");
Filename: vixen.raku. Size: 8kb. View raw, , hex, or download this file.

This paste expires on 2026-01-03 03:40:08.940576+00:00. Pasted through web.