|
| 1 | +%token BOOLEAN |
| 2 | +%token CLOSE |
| 3 | +%token CLOSE_ARRAY |
| 4 | +%token CLOSE_BLOCK_PARAMS |
| 5 | +%token CLOSE_RAW_BLOCK |
| 6 | +%token CLOSE_SEXPR |
| 7 | +%token CLOSE_UNESCAPED |
| 8 | +%token COMMENT |
| 9 | +%token CONTENT |
| 10 | +%token DATA |
| 11 | +%token END_RAW_BLOCK |
| 12 | +%token EQUALS |
| 13 | +%token ID |
| 14 | +%token INVERSE |
| 15 | +%token NULL |
| 16 | +%token NUMBER |
| 17 | +%token OPEN |
| 18 | +%token OPEN_ARRAY |
| 19 | +%token OPEN_BLOCK |
| 20 | +%token OPEN_BLOCK_PARAMS |
| 21 | +%token OPEN_ENDBLOCK |
| 22 | +%token OPEN_INVERSE |
| 23 | +%token OPEN_INVERSE_CHAIN |
| 24 | +%token OPEN_PARTIAL |
| 25 | +%token OPEN_PARTIAL_BLOCK |
| 26 | +%token OPEN_RAW_BLOCK |
| 27 | +%token OPEN_SEXPR |
| 28 | +%token OPEN_UNESCAPED |
| 29 | +%token PRIVATE_SEP |
| 30 | +%token SEP |
| 31 | +%token STRING |
| 32 | +%token UNDEFINED |
| 33 | + |
| 34 | +%% |
| 35 | + |
| 36 | +/* |
| 37 | + * Should match the grammar (as of 2025-01-15) of |
| 38 | + * https://github.com/handlebars-lang/handlebars-parser/blob/master/src/handlebars.yy |
| 39 | + * EBNF grammar has been converted to BNF. |
| 40 | + */ |
| 41 | + |
| 42 | +program: |
| 43 | + statement_list { $$ = $self->prepareProgram($self->semStack[$1]); } |
| 44 | +; |
| 45 | + |
| 46 | +statement_list: |
| 47 | + statement_list statement { if ($self->semStack[$2] !== null) { $self->semStack[$1][] = $self->semStack[$2]; } $$ = $self->semStack[$1]; } |
| 48 | + | /* empty */ { $$ = []; } |
| 49 | + |
| 50 | +statement: |
| 51 | + mustache { $$ = $self->semStack[$1]; } |
| 52 | + | block { $$ = $self->semStack[$1]; } |
| 53 | + | rawBlock { $$ = $self->semStack[$1]; } |
| 54 | + | partial { $$ = $self->semStack[$1]; } |
| 55 | + | partialBlock { $$ = $self->semStack[$1]; } |
| 56 | + | content { $$ = $self->semStack[$1]; } |
| 57 | + | COMMENT { |
| 58 | + $$ = [ |
| 59 | + 'type' => 'CommentStatement', |
| 60 | + 'value' => $self->stripComment($1), |
| 61 | + 'strip' => $self->stripFlags($1, $1), |
| 62 | + ]; |
| 63 | + }; |
| 64 | + |
| 65 | +content: |
| 66 | + CONTENT { |
| 67 | + $$ = [ |
| 68 | + 'type' => 'ContentStatement', |
| 69 | + 'value' => $self->semStack[$1], |
| 70 | + ]; |
| 71 | + } |
| 72 | +; |
| 73 | + |
| 74 | +content_list: |
| 75 | + content_list content { if ($self->semStack[$2] !== null) { $self->semStack[$1][] = $self->semStack[$2]; } $$ = $self->semStack[$1]; } |
| 76 | + | /* empty */ { $$ = []; } |
| 77 | +; |
| 78 | + |
| 79 | +rawBlock: |
| 80 | + openRawBlock content_list END_RAW_BLOCK { $self->prepareRawBlock($self->semStack[$1], $self->semStack[$2], $self->semStack[$3]); } |
| 81 | +; |
| 82 | + |
| 83 | +openRawBlock: |
| 84 | + OPEN_RAW_BLOCK helperName expr_list optional_hash CLOSE_RAW_BLOCK { $$ = ['path' => $self->semStack[$2], 'params' => $self->semStack[$3], 'hash' => $self->semStack[$4]]; } |
| 85 | +; |
| 86 | + |
| 87 | +block: |
| 88 | + openBlock program optional_inverseChain closeBlock { $$ = $self->prepareBlock($1, $2, $3, $4, false); } |
| 89 | + | openInverse program optional_inverseAndProgram closeBlock { $$ = $self->prepareBlock($1, $2, $3, $4, true); } |
| 90 | +; |
| 91 | + |
| 92 | +openBlock: |
| 93 | + OPEN_BLOCK helperName expr_list optional_hash optional_blockParams CLOSE { |
| 94 | + $$ = [ |
| 95 | + 'open' => $self->semStack[$1], |
| 96 | + 'path' => $self->semStack[$2], |
| 97 | + 'params' => $self->semStack[$3], |
| 98 | + 'hash' => $self->semStack[$4], |
| 99 | + 'blockParams' => $self->semStack[$5], |
| 100 | + 'strip' => $self->stripFlags($self->semStack[$1], $self->semStack[$6]) |
| 101 | + ]; |
| 102 | + } |
| 103 | +; |
| 104 | + |
| 105 | +openInverse: |
| 106 | + OPEN_INVERSE helperName expr_list optional_hash optional_blockParams CLOSE { |
| 107 | + $$ = [ |
| 108 | + 'path' => $self->semStack[$2], |
| 109 | + 'params' => $self->semStack[$3], |
| 110 | + 'hash' => $self->semStack[$4], |
| 111 | + 'blockParams' => $self->semStack[$5], |
| 112 | + 'strip' => $self->stripFlags($self->semStack[$1], $self->semStack[$6]) |
| 113 | + ]; |
| 114 | + } |
| 115 | +; |
| 116 | + |
| 117 | +openInverseChain: |
| 118 | + OPEN_INVERSE_CHAIN helperName expr_list optional_hash optional_blockParams CLOSE { |
| 119 | + $$ = [ |
| 120 | + 'path' => $self->semStack[$2], |
| 121 | + 'params' => $self->semStack[$3], |
| 122 | + 'hash' => $self->semStack[$4], |
| 123 | + 'blockParams' => $self->semStack[$5], |
| 124 | + 'strip' => $self->stripFlags($self->semStack[$1], $self->semStack[$6]) |
| 125 | + ]; |
| 126 | + } |
| 127 | +; |
| 128 | + |
| 129 | +optional_inverseAndProgram: |
| 130 | + inverseAndProgram |
| 131 | + | /* empty */ { $$ = null; } |
| 132 | +; |
| 133 | + |
| 134 | +inverseAndProgram: |
| 135 | + INVERSE program { $$ = ['strip' => $self->stripFlags($self->semStack[$1], $self->semStack[$1]), 'program' => $self->semStack[$2]]; } |
| 136 | +; |
| 137 | + |
| 138 | +optional_inverseChain: |
| 139 | + inverseChain |
| 140 | + | /* empty */ { $$ = null; } |
| 141 | +; |
| 142 | + |
| 143 | +inverseChain: |
| 144 | + openInverseChain program optional_inverseChain { |
| 145 | + $inverse = $self->prepareBlock($self->semStack[$1], $self->semStack[$2], $self->semStack[$3], $self->semStack[$3], false); |
| 146 | + $program = $self->prepareProgram([$inverse], $self->semStack[$2]['loc']); |
| 147 | + $program->chained = true; |
| 148 | + |
| 149 | + $$ = ['strip' => $self->semStack[$1]['strip'], 'program' => $program, 'chain' => true]; |
| 150 | + } |
| 151 | + | inverseAndProgram { $$ = $self->semStack[$1]; } |
| 152 | +; |
| 153 | + |
| 154 | +closeBlock: |
| 155 | + OPEN_ENDBLOCK helperName CLOSE { $$ = ['path' => $self->semStack[$2], 'strip' => $self->stripFlags($self->semStack[$1], $self->semStack[$3])]; } |
| 156 | +; |
| 157 | + |
| 158 | +mustache: |
| 159 | + // Parsing out the '&' escape token at AST level saves ~500 bytes after min due to the removal of one parser node. |
| 160 | + // This also allows for handler unification as all mustache node instances can utilize the same handler |
| 161 | + OPEN hash CLOSE { $$ = $self->prepareMustache($self->syntax->hash($self->semStack[$2], ['syntax' => 'expr']), [], null, $self->semStack[$1], $self->stripFlags($self->semStack[$1], $self->semStack[$3])); } |
| 162 | + | OPEN expr expr_list optional_hash CLOSE { $$ = $self->prepareMustache($self->semStack[$2], $self->semStack[$3], $self->semStack[$4], $self->semStack[$1], $self->stripFlags($self->semStack[$1], $self->semStack[$5])); } |
| 163 | + | OPEN_UNESCAPED expr expr_list optional_hash CLOSE_UNESCAPED { $$ = $self->prepareMustache($self->semStack[$2], $self->semStack[$3], $self->semStack[$4], $self->semStack[$1], $self->stripFlags($self->semStack[$1], $self->semStack[$5])); } |
| 164 | +; |
| 165 | + |
| 166 | +partial: |
| 167 | + OPEN_PARTIAL expr expr_list optional_hash CLOSE { |
| 168 | + $$ = [ |
| 169 | + 'type' => 'PartialStatement', |
| 170 | + 'name' => $self->semStack[$2], |
| 171 | + 'params' => $self->semStack[$3], |
| 172 | + 'hash' => $self->semStack[$4], |
| 173 | + 'indent' => '', |
| 174 | + 'strip' => $self->stripFlags($self->semStack[$1], $self->semStack[$5]), |
| 175 | + ]; |
| 176 | + } |
| 177 | +; |
| 178 | + |
| 179 | +partialBlock: |
| 180 | + openPartialBlock program closeBlock { $$ = $self->preparePartialBlock($self->semStack[$1], $self->semStack[$2], $self->semStack[$3]); } |
| 181 | +; |
| 182 | + |
| 183 | +openPartialBlock: |
| 184 | + OPEN_PARTIAL_BLOCK expr expr_list optional_hash CLOSE { |
| 185 | + $$ = [ |
| 186 | + 'path' => $self->semStack[$2], |
| 187 | + 'params' => $self->semStack[$3], |
| 188 | + 'hash' => $self->semStack[$4], |
| 189 | + 'strip' => $self->stripFlags($self->semStack[$1], $self->semStack[$5]), |
| 190 | + ]; |
| 191 | + } |
| 192 | +; |
| 193 | + |
| 194 | +expr_list: |
| 195 | + expr_list expr { if ($self->semStack[$2] !== null) { $self->semStack[$1][] = $self->semStack[$2]; } $$ = $self->semStack[$1]; } |
| 196 | + | /* empty */ { $$ = []; } |
| 197 | +; |
| 198 | + |
| 199 | +expr: |
| 200 | + helperName { $$ = $self->semStack[$1]; } |
| 201 | + | exprHead { $$ = $self->semStack[$1]; } |
| 202 | +; |
| 203 | + |
| 204 | +exprHead: |
| 205 | + arrayLiteral { $$ = $self->semStack[$1]; } |
| 206 | + | sexpr { $$ = $self->semStack[$1]; } |
| 207 | +; |
| 208 | + |
| 209 | +sexpr: |
| 210 | + OPEN_SEXPR hash CLOSE_SEXPR { $$ = $self->syntax->hash($self->semStack[$2], ['syntax' => 'expr']); } |
| 211 | + | OPEN_SEXPR expr expr_list optional_hash CLOSE_SEXPR { |
| 212 | + $$ = [ |
| 213 | + 'type' => 'SubExpression', |
| 214 | + 'path' => $self->semStack[$2], |
| 215 | + 'params' => $self->semStack[$3], |
| 216 | + 'hash' => $self->semStack[$4], |
| 217 | + ]; |
| 218 | + } |
| 219 | +; |
| 220 | + |
| 221 | +hash: |
| 222 | + non_empty_hashSegment_list { $$ = ['type' => 'Hash', 'pairs' => $self->semStack[$1]]; } |
| 223 | +; |
| 224 | + |
| 225 | +optional_hash: |
| 226 | + hash |
| 227 | + | /* empty */ { $$ = []; } |
| 228 | +; |
| 229 | + |
| 230 | +non_empty_hashSegment_list: |
| 231 | + hashSegment { $$ = [$self->semStack[$1]]; } |
| 232 | + | non_empty_hashSegment_list hashSegment {if ($self->semStack[$2] !== null) { $self->semStack[$1][] = $self->semStack[$2]; } $$ = $self->semStack[$1];} |
| 233 | +; |
| 234 | + |
| 235 | +hashSegment: |
| 236 | + ID EQUALS expr { $$ = ['type' => 'HashPair', 'key' => $self->id($self->semStack[$1]), 'value' => $self->semStack[$3]]; } |
| 237 | +; |
| 238 | + |
| 239 | +arrayLiteral: |
| 240 | + OPEN_ARRAY expr_list CLOSE_ARRAY { $$ = $self->syntax->square($self->semStack[$2], ['syntax' => 'expr']); } |
| 241 | +; |
| 242 | + |
| 243 | +optional_blockParams: |
| 244 | + blockParams |
| 245 | + | /* empty */ { $$ = []; } |
| 246 | +; |
| 247 | + |
| 248 | +non_empty_ID_list: |
| 249 | + ID { $$ = [$self->semStack[$1]]; } |
| 250 | + | non_empty_ID_list ID {if ($self->semStack[$2] !== null) { $self->semStack[$1][] = $self->semStack[$2]; } $$ = $self->semStack[$1];} |
| 251 | +; |
| 252 | + |
| 253 | +blockParams: |
| 254 | + OPEN_BLOCK_PARAMS non_empty_ID_list CLOSE_BLOCK_PARAMS { $$ = $self->id($self->semStack[$2]); } |
| 255 | +; |
| 256 | + |
| 257 | +helperName: |
| 258 | + path { $$ = $self->semStack[$1]; } |
| 259 | + | dataName { $$ = $self->semStack[$1]; } |
| 260 | + | STRING { $$ = ['type' => 'StringLiteral', 'value' => $self->semStack[$1]]; } |
| 261 | + | NUMBER { $$ = ['type' => 'NumberLiteral', 'value' => $self->semStack[$1] + 0]; } |
| 262 | + | BOOLEAN { $$ = ['type' => 'BooleanLiteral', 'value' => $self->semStack[$1] === 'true']; } |
| 263 | + | UNDEFINED { $$ = ['type' => 'UndefinedLiteral', 'value' => null]; } |
| 264 | + | NULL { $$ = ['type' => 'NullLiteral', 'value' => null]; } |
| 265 | +; |
| 266 | + |
| 267 | +dataName: |
| 268 | + DATA pathSegments {$$ = $self->preparePath(true, false, $self->semStack[$2]);} |
| 269 | +; |
| 270 | + |
| 271 | +sep: |
| 272 | + SEP { $$ = $self->semStack[$1]; } |
| 273 | + | PRIVATE_SEP { $$ = $self->semStack[$1]; } |
| 274 | +; |
| 275 | + |
| 276 | +path: |
| 277 | + exprHead sep pathSegments {$$ = $self->preparePath(false, $self->semStack[$1], $self->semStack[$3]);} |
| 278 | + | pathSegments {$$ = $self->preparePath(false, false, $self->semStack[$1]);} |
| 279 | +; |
| 280 | + |
| 281 | +pathSegments: |
| 282 | + pathSegments sep ID { |
| 283 | + $self->semStack[$1][] = ['part' => $self->id($self->semStack[$3]), 'original' => $self->semStack[$3], 'separator' => $self->semStack[$2]]; |
| 284 | + $$ = $self->semStack[$1]; |
| 285 | + } |
| 286 | + | ID {$$ = [['part' => $self->id($self->semStack[$1]), 'original' => $self->semStack[$1]]]; } |
| 287 | +; |
| 288 | + |
| 289 | +%% |
0 commit comments