|
1 |
| -use v6; |
| 1 | +unit class CSS::Grammar::AST; |
2 | 2 |
|
3 |
| -class CSS::Grammar::AST { |
| 3 | +use CSS::Grammar::Defs :CSSObject, :CSSValue, :CSSSelector, :CSSUnits, :CSSTrait; |
4 | 4 |
|
5 |
| - use CSS::Grammar::Defs :CSSObject, :CSSValue, :CSSSelector, :CSSUnits, :CSSTrait; |
| 5 | +# re-exports (may be deprecated) |
| 6 | +constant css-obj is export(:CSSObject) = CSSObject; |
| 7 | +constant css-val is export(:CSSObject) = CSSValue; |
| 8 | +constant css-sel is export(:CSSSelector) = CSSValue; |
| 9 | +constant css-units is export(:CSSUnits) = CSSUnits; |
| 10 | +constant css-trait is export(:CSSTrait) = CSSTrait; |
6 | 11 |
|
7 |
| - # re-exports (may be deprecated) |
8 |
| - constant css-obj is export(:CSSObject) = CSSObject; |
9 |
| - constant css-val is export(:CSSObject) = CSSValue; |
10 |
| - constant css-sel is export(:CSSSelector) = CSSValue; |
11 |
| - constant css-units is export(:CSSUnits) = CSSUnits; |
12 |
| - constant css-trait is export(:CSSTrait) = CSSTrait; |
| 12 | +BEGIN our %known-type = |
| 13 | + %( CSSObject.enums.invert ), |
| 14 | + %( CSSValue.enums.invert ), |
| 15 | + %( CSSSelector.enums.invert ), |
| 16 | +; |
13 | 17 |
|
14 |
| - BEGIN our %known-type = |
15 |
| - %( CSSObject.enums.invert ), |
16 |
| - %( CSSValue.enums.invert ), |
17 |
| - %( CSSSelector.enums.invert ), |
18 |
| - ; |
| 18 | +#| utility token builder method, e.g.: $.token(42, :type<cm>) --> :cm(42) |
| 19 | +method token(Mu $ast, Str :$type is copy) { |
19 | 20 |
|
20 |
| - #| utility token builder method, e.g.: $.token(42, :type<cm>) --> :cm(42) |
21 |
| - method token(Mu $ast, Str :$type is copy) { |
| 21 | + die 'usage: $.token($ast, :$type)' |
| 22 | + unless $type; |
22 | 23 |
|
23 |
| - die 'usage: $.token($ast, :$type)' |
24 |
| - unless $type; |
| 24 | + return unless $ast.defined; |
25 | 25 |
|
26 |
| - return unless $ast.defined; |
| 26 | + my Str $units = $type; |
| 27 | + $type = $_ with CSSUnits.enums{$type}; |
27 | 28 |
|
28 |
| - my Str $units = $type; |
29 |
| - $type = $_ with CSSUnits.enums{$type}; |
| 29 | + my $raw-type = $type.split(':').head; |
| 30 | + die "unknown type: '$raw-type'" |
| 31 | + unless %known-type{$raw-type}:exists; |
30 | 32 |
|
31 |
| - my $raw-type = $type.split(':').head; |
32 |
| - die "unknown type: '$raw-type'" |
33 |
| - unless %known-type{$raw-type}:exists; |
| 33 | + $ast.isa(Pair) |
| 34 | + ?? ($units => $ast.value) |
| 35 | + !! ($units => $ast); |
| 36 | +} |
34 | 37 |
|
35 |
| - $ast.isa(Pair) |
36 |
| - ?? ($units => $ast.value) |
37 |
| - !! ($units => $ast); |
38 |
| - } |
| 38 | +#| utility AST builder method for leaf nodes (no repeated tokens) |
| 39 | +method node($/ --> Hash) { |
| 40 | + my %terms; |
| 41 | + |
| 42 | + # unwrap Parcels |
| 43 | + my @l = $/.can('caps') |
| 44 | + ?? $/ |
| 45 | + !! $/.grep: *.defined; |
| 46 | + |
| 47 | + for @l { |
| 48 | + for .caps -> $cap { |
| 49 | + my ($key, $value) = $cap.kv; |
| 50 | + next if $key eq '0'; |
| 51 | + $key .= lc; |
| 52 | + my $type = $key.split(':').head; |
| 53 | + |
| 54 | + $value .= ast |
| 55 | + // next; |
39 | 56 |
|
40 |
| - #| utility AST builder method for leaf nodes (no repeated tokens) |
41 |
| - method node($/ --> Hash) { |
42 |
| - my %terms; |
43 |
| - |
44 |
| - # unwrap Parcels |
45 |
| - my @l = $/.can('caps') |
46 |
| - ?? $/ |
47 |
| - !! $/.grep: *.defined; |
48 |
| - |
49 |
| - for @l { |
50 |
| - for .caps -> $cap { |
51 |
| - my ($key, $value) = $cap.kv; |
52 |
| - next if $key eq '0'; |
53 |
| - $key .= lc; |
54 |
| - my $type = $key.split(':').head; |
55 |
| - |
56 |
| - $value .= ast |
57 |
| - // next; |
58 |
| - |
59 |
| - if $key.starts-with('expr-') { |
60 |
| - $key.substr-rw(4,1) = ':'; |
61 |
| - } |
62 |
| - elsif $value.isa(Pair) { |
63 |
| - ($key, $value) = $value.kv; |
64 |
| - } |
65 |
| - elsif %known-type{$type}:!exists { |
66 |
| - warn "{$value.perl} has unknown type: $type"; |
67 |
| - } |
68 |
| - |
69 |
| - if %terms{$key}:exists { |
70 |
| - warn "repeated term " ~ $key ~ ':' ~ $value; |
71 |
| - return Any; |
72 |
| - } |
73 |
| - |
74 |
| - %terms{$key} = $value; |
| 57 | + if $key.starts-with('expr-') { |
| 58 | + $key.substr-rw(4,1) = ':'; |
| 59 | + } |
| 60 | + elsif $value.isa(Pair) { |
| 61 | + ($key, $value) = $value.kv; |
| 62 | + } |
| 63 | + elsif %known-type{$type}:!exists { |
| 64 | + warn "{$value.perl} has unknown type: $type"; |
| 65 | + } |
| 66 | + |
| 67 | + if %terms{$key}:exists { |
| 68 | + warn "repeated term " ~ $key ~ ':' ~ $value; |
| 69 | + return Any; |
75 | 70 | }
|
76 |
| - } |
77 | 71 |
|
78 |
| - %terms; |
| 72 | + %terms{$key} = $value; |
| 73 | + } |
79 | 74 | }
|
80 | 75 |
|
81 |
| - #| utility AST builder method for nodes with repeatable elements |
82 |
| - method list($/ --> Array) { |
83 |
| - my @terms; |
84 |
| - |
85 |
| - # unwrap Parcels |
86 |
| - my @l = $/.can('caps') |
87 |
| - ?? $/ |
88 |
| - !! $/.grep: *.defined; |
89 |
| - |
90 |
| - for @l { |
91 |
| - for .caps -> $cap { |
92 |
| - my ($key, $value) = $cap.kv; |
93 |
| - next if $key eq '0'; |
94 |
| - $key .= lc; |
95 |
| - |
96 |
| - my $type = $key.split(':').head; |
97 |
| - |
98 |
| - $value .= ast |
99 |
| - // next; |
100 |
| - |
101 |
| - if $key.starts-with('expr-') { |
102 |
| - $key.substr-rw(4,1) = ':'; |
103 |
| - } |
104 |
| - elsif $value.isa(Pair) { |
105 |
| - ($key, $value) = $value.kv; |
106 |
| - } |
107 |
| - elsif %known-type{$type}:!exists { |
108 |
| - warn "{$value.raku} has unknown type: $type"; |
109 |
| - } |
110 |
| - |
111 |
| - push( @terms, {$key => $value} ); |
| 76 | + %terms; |
| 77 | +} |
| 78 | + |
| 79 | +#| utility AST builder method for nodes with repeatable elements |
| 80 | +method list($/ --> Array) { |
| 81 | + my @terms; |
| 82 | + |
| 83 | + # unwrap Parcels |
| 84 | + my @l = $/.can('caps') |
| 85 | + ?? $/ |
| 86 | + !! $/.grep: *.defined; |
| 87 | + |
| 88 | + for @l { |
| 89 | + for .caps -> $cap { |
| 90 | + my ($key, $value) = $cap.kv; |
| 91 | + next if $key eq '0'; |
| 92 | + $key .= lc; |
| 93 | + |
| 94 | + my $type = $key.split(':').head; |
| 95 | + |
| 96 | + $value .= ast |
| 97 | + // next; |
| 98 | + |
| 99 | + if $key.starts-with('expr-') { |
| 100 | + $key.substr-rw(4,1) = ':'; |
| 101 | + } |
| 102 | + elsif $value.isa(Pair) { |
| 103 | + ($key, $value) = $value.kv; |
| 104 | + } |
| 105 | + elsif %known-type{$type}:!exists { |
| 106 | + warn "{$value.raku} has unknown type: $type"; |
112 | 107 | }
|
113 |
| - } |
114 | 108 |
|
115 |
| - @terms; |
| 109 | + push( @terms, {$key => $value} ); |
| 110 | + } |
116 | 111 | }
|
117 | 112 |
|
118 |
| - method at-rule($/) { |
119 |
| - my %terms = $.node($/); |
120 |
| - %terms{ CSSValue::AtKeywordComponent } //= $0.lc; |
121 |
| - return $.token( %terms, :type(CSSObject::AtRule)); |
122 |
| - } |
| 113 | + @terms; |
| 114 | +} |
123 | 115 |
|
124 |
| - method func(Str:D $ident, |
125 |
| - $args, |
126 |
| - :$type = CSSValue::FunctionComponent, |
127 |
| - :$arg-type = CSSValue::ArgumentListComponent, |
128 |
| - |c --> Pair) { |
129 |
| - my %ast = $args.isa(List) |
130 |
| - ?? ($arg-type => $args) |
131 |
| - !! $args; |
132 |
| - %ast ,= :$ident; |
133 |
| - $.token( %ast, :$type, |c ); |
134 |
| - } |
| 116 | +method at-rule($/) { |
| 117 | + my %terms = $.node($/); |
| 118 | + %terms{ CSSValue::AtKeywordComponent } //= $0.lc; |
| 119 | + return $.token( %terms, :type(CSSObject::AtRule)); |
| 120 | +} |
| 121 | + |
| 122 | +method func(Str:D $ident, |
| 123 | + $args, |
| 124 | + :$type = CSSValue::FunctionComponent, |
| 125 | + :$arg-type = CSSValue::ArgumentListComponent, |
| 126 | + |c --> Pair) { |
| 127 | + my %ast = $args.isa(List) |
| 128 | + ?? ($arg-type => $args) |
| 129 | + !! $args; |
| 130 | + %ast ,= :$ident; |
| 131 | + $.token( %ast, :$type, |c ); |
| 132 | +} |
| 133 | + |
| 134 | +method pseudo-func( Str $ident, $/ --> Pair) { |
| 135 | + my $expr = $.list($/); |
| 136 | + my %ast = :$ident, :$expr; |
| 137 | + $.token( %ast, :type(CSSSelector::PseudoFunction) ); |
| 138 | +} |
135 | 139 |
|
136 |
| - method pseudo-func( Str $ident, $/ --> Pair) { |
137 |
| - my $expr = $.list($/); |
138 |
| - my %ast = :$ident, :$expr; |
139 |
| - $.token( %ast, :type(CSSSelector::PseudoFunction) ); |
| 140 | +method decl($/, :$obj!) { |
| 141 | + |
| 142 | + my %ast; |
| 143 | + |
| 144 | + %ast<ident> = .trim.lc |
| 145 | + with $0; |
| 146 | + |
| 147 | + with $<val> { |
| 148 | + my Hash $val = .ast; |
| 149 | + |
| 150 | + with $val<usage> -> $synopsis { |
| 151 | + my $usage = 'usage ' ~ $synopsis; |
| 152 | + $usage ~= ' | ' ~ $_ |
| 153 | + for @.proforma; |
| 154 | + $obj.warning($usage); |
| 155 | + return; |
| 156 | + } |
| 157 | + elsif ! $val<expr> { |
| 158 | + $obj.warning('dropping declaration', %ast<ident>); |
| 159 | + return; |
| 160 | + } |
| 161 | + else { |
| 162 | + %ast<expr> = $val<expr>; |
| 163 | + } |
140 | 164 | }
|
141 | 165 |
|
| 166 | + return %ast; |
142 | 167 | }
|
| 168 | + |
| 169 | +method rule($/) { |
| 170 | + $.node($/).pairs[0]; |
| 171 | +} |
| 172 | + |
| 173 | +method proforma { [] } |
0 commit comments