1DSLs, Fowler, 2010
We have three broad classifications, but as we'll see in Perl 6, the distinction can be murky. Internal may look like variants. Variants are handled in a similar manner to external DSLs.
For the rest of this talk, we'll look at few general techniques from each category and apply them to a few specific examples.
&[+]
say &[+](1,2)
&[+]
say &[+](1,2)
sub plus-twice($a,$b) { $a + 2 * $b }say 1 [&plus-twice] 2
Custom operators are declared like this:
sub infix:<plus>($x,$y) { $x + $y}say 1 plus 2;
Custom operators are declared like this:
sub infix:<plus>($x,$y) { $x + $y}say 1 plus 2;
3
Custom operators are declared like this:
sub infix:<plus>($x,$y) { $x + $y}say 1 plus 2;
3
sub prefix:<@@>($x) { $x * 2 }sub postfix:<+++>($y is rw) { $y+=3 }my $z = @@10;$z+++;say $z;
Custom operators are declared like this:
sub infix:<plus>($x,$y) { $x + $y}say 1 plus 2;
3
sub prefix:<@@>($x) { $x * 2 }sub postfix:<+++>($y is rw) { $y+=3 }my $z = @@10;$z+++;say $z;
23
# dot productsub infix:<∙>(@a,@b) { return [+] @a Z* @b}say (1,2) ∙ (3,4)
# dot productsub infix:<∙>(@a,@b) { return [+] @a Z* @b}say (1,2) ∙ (3,4)
11
sub circumfix:<⌊ ⌋>($x) { $x.floor}say ⌊2.4⌋
sub circumfix:<⌊ ⌋>($x) { $x.floor}say ⌊2.4⌋
2
sub infix:<plus>($x,$y) { $x + $y}sub infix:<times>($x,$y) { $x * $y}say 1 plus 2 times 3;
sub infix:<plus>($x,$y) { $x + $y}sub infix:<times>($x,$y) { $x * $y}say 1 plus 2 times 3;
9
But multiplication has higher precedence than addition. We want this to be 7.
is tighter
controls precedence
sub infix:<plus>($x,$y) { $x + $y}sub infix:<times>($x,$y) is tighter(&infix:<plus>) { $x * $y}say 1 plus 2 times 3;
is tighter
controls precedence
sub infix:<plus>($x,$y) { $x + $y}sub infix:<times>($x,$y) is tighter(&infix:<plus>) { $x * $y}say 1 plus 2 times 3;
7
Also is looser
, is equiv
sub infix:<to-the-power>($x,$y) { $x ** $y}say 2 to-the-power 3 to-the-power 2
sub infix:<to-the-power>($x,$y) { $x ** $y}say 2 to-the-power 3 to-the-power 2
64
sub infix:<to-the-power>($x,$y) { $x ** $y}say 2 to-the-power 3 to-the-power 2
64
232
treated as
(23)2
but should be
2(32)
We can fix this. is assoc
!
sub infix:<to-the-power>($x,$y) is assoc<right> { $x ** $y}say 2 to-the-power 3 to-the-power 2
We can fix this. is assoc
!
sub infix:<to-the-power>($x,$y) is assoc<right> { $x ** $y}say 2 to-the-power 3 to-the-power 2
512
We can fix this. is assoc
!
sub infix:<to-the-power>($x,$y) is assoc<right> { $x ** $y}say 2 to-the-power 3 to-the-power 2
512
Associativity types:
right
left
non
chain
(1 < 2 < 3
)
list
(1,2 X 3,4 X 5,6
)
define subtraction between strings
sub infix:<->($x,$y) { $x.subst($y, "", :g);}say "house" - "u";
define subtraction between strings
sub infix:<->($x,$y) { $x.subst($y, "", :g);}say "house" - "u";
"hose"
define subtraction between strings
sub infix:<->($x,$y) { $x.subst($y, "", :g);}say "house" - "u";
"hose"
But
say 32 - 2
define subtraction between strings
sub infix:<->($x,$y) { $x.subst($y, "", :g);}say "house" - "u";
"hose"
But
say 32 - 2
3
define subtraction between strings
sub infix:<->($x,$y) { $x.subst($y, "", :g);}say "house" - "u";
"hose"
But
say 32 - 2
3
Argh!
We can fix this, too.
multi infix:<->(Str $x, Str $y) { $x.subst($y, "", :g);}say "house" - "u";say 32 - 3;
We can fix this, too.
multi infix:<->(Str $x, Str $y) { $x.subst($y, "", :g);}say "house" - "u";say 32 - 3;
hose29
Also works for strings, ints, constants, or any class.
multi sub infix:<->(Str $x, Str $y) { $x.subst($y, "", :g);}multi sub infix:<->(Str $x, Int $y) { $x.substr(0,$x.chars-$y)}multi sub infix:<->("escalator","electricity") { "stairs"}say "catamaran" - "a";say "catamaran" - 6;say "escalator" - "electricity";say 10 - 5;
Also works for strings, ints, constants, or any class.
multi sub infix:<->(Str $x, Str $y) { $x.subst($y, "", :g);}multi sub infix:<->(Str $x, Int $y) { $x.substr(0,$x.chars-$y)}multi sub infix:<->("escalator","electricity") { "stairs"}say "catamaran" - "a";say "catamaran" - 6;say "escalator" - "electricity";say 10 - 5;
ctmrncatstairs5
emulate python % operator
multi sub infix:<%>(Str $f, Numeric $n) { return sprintf($f,$n)}multi sub infix:<%>(Str $f,List $l) { return sprintf($f,$l.flat)}say 'This is %d.' % 40;say 'Pi is about %0.2f and e is about %0.2f' % ( π, e );
Custom operators are an "internal" language, but may look like a variant
emulate python % operator
multi sub infix:<%>(Str $f, Numeric $n) { return sprintf($f,$n)}multi sub infix:<%>(Str $f,List $l) { return sprintf($f,$l.flat)}say 'This is %d.' % 40;say 'Pi is about %0.2f and e is about %0.2f' % ( π, e );
This is 40.Pi is about 3.14 and e is about 2.72
Custom operators are an "internal" language, but may look like a variant
Perl 6 operators add new techniques:
SELECT idFROM user INNER JOIN address ON address.user=user.idWHERE name = 'ed' and fullname='Ed Jones'
(User + Address)[ name == 'ed' and fullname == 'Ed Jones' ]
and
, ==
can be defined for columns(User + Address)[ name == 'ed' and fullname == 'Ed Jones' ]
class Table { ... }multi sub infix:<+>(Table $a, Table $b) { ...}
(User + Address)[ name == 'ed' and fullname == 'Ed Jones' ]
class Filter { ... }class Column { ... }multi sub infix:<==>(Column $a, $b) { ... return Filter.new( ... )}multi sub infix:<and>(Filter $a, Filter $b) { ... return Filter.new( ... )}
Though as far as operators and SQL go, there is already a precedent. The mathematical foundation of SQL is relational algebra.
Relational algebra
sub infix:<⋈>($a, $b) { "$a NATURAL JOIN $b"}say "users" ⋈ "addresses"
users NATURAL JOIN addresses
sub prefix:<∏>(@x) { "SELECT " ~ @x.join(', ')}say ∏<name age>;
SELECT name, age
Conclusion: Custom operators are useful building blocks for internal informal DSLs.
half way: 20 minutes
Let's look at how to use grammars to parse one of them.
Looks like this:
html head title Slim Examples body h1 Markup examples
generates
<html> <head> <title>Slim Examples</title> </head> <body> <h1>Markup examples</h1> </body> </html>
grammar slim { rule TOP { <line>+ %% <eol>} token line { <indentation> <tag> [ ' ' <text> ]? } token indentation { <indent>* } token indent { ' ' } token tag { \w+ } token text { \V+ } token eol { \n+ }}say slim.parse(q:to/DONE/);html head title Slim Examples body h1 Markup ExamplesDONE
X %% Y
means [ X ][ Y X ]*
html head title Slim Examples body h1 Markup examples
grammar slim { rule TOP { <line>+ %% <eol> } token line { <indentation> <tag> [ ' ' <text> ]? } token indentation { <indent>* } token indent { ' ' } token tag { \w+ } token text { \V+ }}
line => 「html tag => 「html」 indentation => 「」line => 「 head」 tag => 「head」 indentation => 「 」 indent => 「 」line => 「 title Slim Examples」 tag => 「title」 text => 「Slim Examples」 indentation => 「 」 indent => 「 」 indent => 「 」...
A grammar generates a match object.
grammar slim { token tag { ... } token text { ... } token indentation { ... } ...}
class DOM { method tag { ... } method text { ... } method indentation { ... }}my $dom = DOM.new;slim.parse($text, actions => $dom);
Tag: push.
Indentation: maybe pop.
0 html1 head2 title Slim Examples1 body2 h1 Markup examples0
indent 0, stack 0: push
0 html1 head2 title Slim Examples1 body2 h1 Markup examples0
indent 0, stack 0: push
0 html1 head2 title Slim Examples1 body2 h1 Markup examples0
indent 1, stack 1: push
0 html1 head2 title Slim Examples1 body2 h1 Markup examples0
indent 1, stack 1: push
0 html1 head2 title Slim Examples1 body2 h1 Markup examples0
indent 2, stack 2: push
0 html1 head2 title Slim Examples1 body2 h1 Markup examples0
indent 2, stack 2: push
0 html1 head2 title Slim Examples1 body2 h1 Markup examples0
indent 1, stack 3: pop 3-1=2 then push
0 html1 head2 title Slim Examples1 body2 h1 Markup examples0
indent 1, stack 3: pop 3-1=2 then push
0 html1 head2 title Slim Examples1 body2 h1 Markup examples0
indent 1, stack 3: pop 3-1=2 then push
0 html1 head2 title Slim Examples1 body2 h1 Markup examples0
indent 1, stack 3: pop 3-1=2 then push
0 html1 head2 title Slim Examples1 body2 h1 Markup examples0
indent 1, stack 3: pop 3-1=2 then push
0 html1 head2 title Slim Examples1 body2 h1 Markup examples0
indent 2, stack 2: push
0 html1 head2 title Slim Examples1 body2 h1 Markup examples0
indent 2, stack 2: push
0 html1 head2 title Slim Examples1 body2 h1 Markup examples0
indent: 0, stack 3: pop 3-0-3
0 html1 head2 title Slim Examples1 body2 h1 Markup examples0
indent: 0, stack 3: pop 3-0-3
0 html1 head2 title Slim Examples1 body2 h1 Markup examples0
indent: 0, stack 3: pop 3-0-3
0 html1 head2 title Slim Examples1 body2 h1 Markup examples0
indent: 0, stack 3: pop 3-0-3
class Node { has Str $.tag; has Str $.text is rw; has Node $.parent is rw; has Node @.children;}class DOM { my Node @stack; has Node $.tree; method tag { ... } method text { ... } method indentation { ... }}grammar slim { ... token tag { ... } token text { ... } token indentation { ... }}
1. When you see a tag, push a new node onto the stack.
method tag($/) { @stack.push: Node.new: tag => ~$/; }
2. When you see text, set the text of the top node.
method text($/) { @stack[*-1].text = ~$/; }
$/
is the match object for the token
~
stringifies
3. When you see indentation, pop until the stack size is the
level of indentation.
4. Connect parent + child nodes when moving from the stack to the tree.
method indentation($/) { while @stack > @$<indent> { my $node = @stack.pop; with @stack[*-1] -> $top { $node.parent = $top; $top.children.push: $node; } } }
$/
is the match object for the indentation
token
with
checks for definedness
To print, dump a node, and recursively dump children at one more level of indentation.
class Node { ... method dump(:$level=0) { say "<$.tag>".indent($level); say .indent($level) with $.text; .dump(level => $level+1) for self.children; say "</$.tag>".indent($level); }}
.indent
on a string prepends spaces
A leading .
calls a method on $_
grammar slim { rule TOP { <line>+ %% <eol>} token line { <indentation> <tag> [ ' ' <text> ]? } token indentation { <indent>* } token indent { ' ' } token tag { \w+ } token text { \V+ } token eol { \n+ }}class Node { has Str $.tag; has Str $.text is rw; has Node $.parent is rw; has Node @.children; method dump(:$level=0) { say "<$.tag>".indent($level); say $.text.indent($level) with $.text; .dump(level => $level+1) for self.children; say "</$.tag>".indent($level); }}
class DOM { my Node @stack; has Node $.top; method tag($/) { @stack.push: Node.new: tag => ~$/; $!top //= @stack[0]; } method text($/) { @stack[*-1].text = ~$/; } method indentation($/) { while @stack > @$<indent> { my $node = @stack.pop; with @stack[*-1] -> $top { $node.parent = $top; $top.children.push: $node; } } }}my $dom = DOM.new;my $match = slim.parse(q:to/DONE/,actions => $dom) or die 'no parse';html head title Slim Examples body h1 Markup ExamplesDONE$dom.top.dump;
<html> <head> <title> Slim Examples </title> </head> <body> <h1> Markup Examples </h1> </body></html>
Conclusion: Grammars are useful for parsing external informal DSLs.
Perl6::Grammar
and Perl6::Actions
Perl6::Grammar
1 design.perl6.org
%*LANG
variable, has grammars and actions.BEGIN { for sort keys %*LANG -> $k { say "$k is { %*LANG{$k}.^name }" }}
MAIN is Perl6::GrammarMAIN-actions is Perl6::ActionsP5Regex is Perl6::P5RegexGrammarP5Regex-actions is Perl6::P5RegexActionsQuote is Perl6::QGrammarQuote-actions is Perl6::QActionsRegex is Perl6::RegexGrammarRegex-actions is Perl6::RegexActions
for $~MAIN, $~Quote, $~P5Regex, $~Regex { say .grammar.^name; say .actions.^name;}
Perl6::GrammarPerl6::ActionsPerl6::P5RegexGrammarPerl6::P5RegexActionsPerl6::QGrammarPerl6::QActionsPerl6::RegexGrammarPerl6::RegexActions
To see examples, use --target=parse
:
perl6 --target=parse -e "say 'hello, world'"
To see examples, use --target=parse
:
perl6 --target=parse -e "say 'hello, world'"
- statementlist: say 'hello, world' - statement: 1 matches - EXPR: say 'hello, world' - longname: say - name: say - identifier: say - args: 'hello, world' - arglist: 'hello, world' - EXPR: 'hello, world' - value: 'hello, world' - quote: 'hello, world' - nibble: hello, world
grammar Perl6::Grammar { rule statementlist { ... <statement> ... } token statement { ... <EXPR> ... } token longname { ... <name> .. } token name { ... <identifier> ... } token identifier { ... } ...}
Let's try to make a slang in which subroutines are declared like:
lambda foo() { ... }
instead of
sub foo() { ... }
Original (Perl6/Grammar.nqp)
token routine_declarator:sym<sub> { 'sub' <.end_keyword> <routine_def('sub')>}
Let's change 'sub' to 'lambda'.
Deriving from a class and applying a role gives us a way to have a new class with a single method replaced.
Deriving from a grammar and applying a role gives us a way to have a new grammar with a single token replaced.
but creates a copy of an object, with a role mixed in.
but creates a copy of an object, with a role mixed in.
BEGIN { %*LANG<MAIN> := $~MAIN.grammar but role { rule routine_declarator:sym<sub> { 'lambda' <.end_keyword> <routine_def('sub')> } }}
but creates a copy of an object, with a role mixed in.
BEGIN { %*LANG<MAIN> := $~MAIN.grammar but role { rule routine_declarator:sym<sub> { 'lambda' <.end_keyword> <routine_def('sub')> } }}
lambda foo { say 'hello, world' }foo();
If you put your slang in a module:
# lambda.pmsub EXPORT { %*LANG<MAIN> := $~MAIN but role { ... }}
Using it is lexically scoped:
{ use lambda; lambda hello { say 'hi' }}## lambda bye { say 'bye' } # illegal!sub bye { say 'bye' } # ok
a few slangs on modules.perl6.org
sub ($x)
)augment slang
, supercede slang
augment slang MAIN { ... }supercede slang Regex { ... }
Keyboard shortcuts
↑, ←, Pg Up, k | Go to previous slide |
↓, →, Pg Dn, Space, j | Go to next slide |
Home | Go to first slide |
End | Go to last slide |
Number + Return | Go to specific slide |
b / m / f | Toggle blackout / mirrored / fullscreen mode |
c | Clone slideshow |
p | Toggle presenter mode |
t | Restart the presentation timer |
?, h | Toggle this help |
Esc | Back to slideshow |