Skip to content

Instantly share code, notes, and snippets.

@codesections
Created February 16, 2022 07:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save codesections/a0f18fcb611d982d7c21b1e55541056c to your computer and use it in GitHub Desktop.
Save codesections/a0f18fcb611d982d7c21b1e55541056c to your computer and use it in GitHub Desktop.
A role for using multiple grammars/action objects at once in Raku
role Subgrammar[::G :$grammar, :$actions] {
multi method slag(G) {
my $old-actions = self.actions;
self.set_actions: $actions;
my @wrapped = G.^methods(:local).map: -> &m {
with self.^methods.first({.name eq &m.name}) {
next if $_ === &m or .name eq 'BUILDALL';
.wrap: method (|c) { m self, |c } }
}
LEAVE { .restore for @wrapped;
self.set_actions: $old-actions }
self.TOP
}
}
class CssActions {
method TOP($/) { make %($<style-rule>».made) }
method style-rule($/) { make ~$<selector>.join.trim
=> %($<property>».made) }
method property($/) { make ~$<key> => ~$<value> }
}
grammar Css {
rule TOP { <style-rule>+ }
rule style-rule { <selector>+ '{' ~ '}' <property>+ }
rule selector { 'body' | 'p' | 'div' | 'span' | 'h1' }
rule property { $<key>=<-[:]>+ ':' $<value>=<-[;]>+ ';' }
}
class JsActions {
method TOP($/) { make 'Javascript parser NYI' }}
grammar Js {
rule TOP { <-[<]>+ #`[ let's just imagine this one] } #`[ > hlfix ]}
grammar Html
is Css does Subgrammar[:grammar(Css),
:actions(CssActions.new)]
is Js does Subgrammar[:grammar(Js),
:actions(JsActions)] {
proto rule tag {*}
rule head { '<head>' ~ '</head>' [<style>|<script>]* }
rule script { '<script>' ~ '</script>' <slag(Js)>? }
rule TOP { '<html>' ~ '</html>' [<head> <body>?] }
rule style { '<style>' ~ '</style>' <slag(Css)>? }
rule body { '<body>' ~ '</body>' <inner>* }
rule tag:sym<p> { '<p>' ~ '</p>' <inner>* }
rule tag:sym<em> { '<em>' ~ '</em>' <inner>* }
rule text { <-[<]>+ } # > hlfix
rule inner { <tag>|<text> }
}
class HtmlActions {
method TOP($/) { make %(html => $<body>.made,
|$<head>.made) }
method head($/) { make %(css => %($<style>».made),
js => ~$<script>».made) }
method script($/) { make $<slag>.made }
method style($/) { make $<slag>.made // Empty }
method body($/) { make $<inner>».made.join }
method tag:sym<em>($/) { make "**{$<inner>».made.join}**" }
method tag:sym<p>($/) { make "\n{$<inner>».made.join}\n" }
method inner($/) { make $/.caps».value
.map: {.made || $_} }
}
my $combined-text
= q:to/§html/;
<html>
<head>
<style>
body {
margin: 40px auto;
max-width: 650px;
line-height: 1.6;
font-size: 18px;
color: #444;
padding: 0 10px;
}
</style>
<script>
console.log('Hello, world!');
</script>
</head>
<body>
<p> Hello, <em>world</em>! </p>
<p> Welcome to a test website 🦋 </p>
</body>
</html>
§html
my $match
= Html.parse: $combined-text,
:actions(HtmlActions.new);
sub pretty($_, :$nl=False) {
when Pair { .key, pretty(.value, :nl) }
when Str { .raku }
when Map -> :@_ = .map(&pretty) {
my $n = @_»[0]».chars.max+1;
with [ @_.map({.fmt: "\%-{$n}s", '=> '})\
».trim.sort(&[lt]).join(",\n")
.indent: $nl ?? 4 !! 2 ] {
'{'~($nl ?? "\n$_" !! " {.trim}")~ '}' }}}
say $match.made.&pretty;
# OUTPUT:
# { js => "Javascript parser NYI",
# html => "\nHello, **world**! \n\nWelcome to a test website 🦋 \n",
# css => {
# body => {
# padding => "0 10px",
# max-width => "650px",
# margin => "40px auto",
# line-height => "1.6",
# font-size => "18px",
# color => "#444"}}}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment