← All showcases

raku — a grammar of Raku, written in Raku, run by rakupp

Source on GitHub ↗

The other showcases parse a language rakupp is not: Lisp, Forth, JavaScript, Python, JSON, Markdown. This one parses the language rakupp is. The precedence ladder, statement forms and term shapes in raku-grammar.raku are the ones in src/Parser.cpp, restated as a Raku grammar instead of as a recursive-descent parser — and rakupp runs it, so the compiler ends up parsing its own language with a grammar it executes itself.

build/rakupp showcase/raku/raku-grammar.raku examples/fibonacci.raku
build/rakupp showcase/raku/raku-grammar.raku --tree examples/hanoi.raku
build/rakupp showcase/raku/raku-grammar.raku --check=examples

--tree prints the parse tree, collapsing the levels an expression passed straight through, so what is left is the structure the operators imposed:

$ build/rakupp showcase/raku/raku-grammar.raku --tree /tmp/x.raku
statementlist  my $x = 1 + 2 * 3; say $x if $x > 5;
  declaration  my $x = 1 + 2 * 3
    scope  my
    assign-expr  $x = 1 + 2 * 3
      variable  $x
      assign-op  =
      additive-expr  1 + 2 * 3
        number  1
        additive-op  +
        multiplicative-expr  2 * 3
          number  2
          multiplicative-op  *
          number  3

The ladder is the compiler's

The fifteen precedence levels come from classifyInfix in src/Parser.cpp, where they are BP_OR through BP_POW. Each level in the grammar is one rule of the form <tighter>+ % <operators at this level>, named after the BP_* constant it mirrors, so the grammar states what the binding-power table states:

LevelOperators
BP_ORor xor orelse ==> <==
BP_ANDand andthen notandthen
BP_ZIP... ...^ ^... ^...^ Z X minmax
BP_COMMA,
BP_ASSIGN= := .= +==>
BP_TERNARY?? !!, the eight flip-flops
BP_OROR|| // ^^
BP_ANDAND&&
BP_COMPARE== eq ~~ <=> ===, the set relations
BP_RANGE.. ..^ ^.. ^..^ o
BP_CONCAT~
BP_REPLICATEx xx
BP_ADD+ - min max, the set combinators
BP_MUL* / % div mod does but
BP_POW** (right-associative)

Two consequences fall out of writing it this way. / starting a regex and / meaning division never conflict, because a regex literal is only reachable where a term is expected and the ladder has already taken the infix. And the --tree output above is a direct reading of the table: 2 * 3 sits under 1 + 2 * 3 because BP_MUL is tighter than BP_ADD.

What it covers, measured

Run against the Raku in this repository, comparing with what the compiler accepts:

CorpusFiles parsed
examples/36 / 36
showcase/25 / 27
tools/69 / 75
live/, bindings/6 / 6
t/280 / 406
total418 / 553

raku-grammar.raku is one of the 25 in showcase/: the grammar parses itself.

t/ is the regression suite, so it is deliberately a catalogue of unusual syntax — atomic operators, %?RESOURCES, binding to hash keys, declarator doc-comments — and it is where the long tail lives.

What it is not

Raku is not a context-free language, and this is not a complete grammar of it. Three things in the compiler sit outside any fixed grammar, and they are worth naming because they are the reason a hand-written parser was chosen in the first place (see PARSING.md):

So this describes one member of a family: Raku with the built-in operator table. Two further boundaries are drawn deliberately rather than forced:

It is a recogniser for well-formed Raku, and it errs toward accepting. On a set of eight malformed inputs it agrees with the compiler's accept/reject on six; the two it wrongly accepts are cases where a statement separator is missing (if $x say 1;). Requiring separators strictly was tried and measured: it recovers one of those two, and costs 73 files of real coverage (418 → 345). For a grammar meant to describe the language, wrongly rejecting valid Raku is the worse failure, so the lenient rule is the one that ships.