@vituum/vite-plugin-latte 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/index.js +2 -2
  2. package/latte/PlaceholderFunction.php +2 -2
  3. package/package.json +10 -11
  4. package/vendor/autoload.php +18 -0
  5. package/vendor/bin/latte-lint +16 -4
  6. package/vendor/composer/ClassLoader.php +72 -65
  7. package/vendor/composer/InstalledVersions.php +21 -12
  8. package/vendor/composer/autoload_classmap.php +14 -8
  9. package/vendor/composer/autoload_namespaces.php +1 -1
  10. package/vendor/composer/autoload_psr4.php +1 -1
  11. package/vendor/composer/autoload_real.php +3 -22
  12. package/vendor/composer/autoload_static.php +13 -7
  13. package/vendor/composer/installed.json +8 -8
  14. package/vendor/composer/installed.php +10 -10
  15. package/vendor/latte/latte/bin/latte-lint +8 -2
  16. package/vendor/latte/latte/composer.json +1 -1
  17. package/vendor/latte/latte/readme.md +6 -6
  18. package/vendor/latte/latte/src/Bridges/Tracy/BlueScreenPanel.php +1 -0
  19. package/vendor/latte/latte/src/Bridges/Tracy/LattePanel.php +3 -2
  20. package/vendor/latte/latte/src/Latte/Compiler/Block.php +0 -3
  21. package/vendor/latte/latte/src/Latte/Compiler/Escaper.php +113 -89
  22. package/vendor/latte/latte/src/Latte/Compiler/ExpressionBuilder.php +2 -1
  23. package/vendor/latte/latte/src/Latte/Compiler/Node.php +0 -4
  24. package/vendor/latte/latte/src/Latte/Compiler/NodeHelpers.php +0 -4
  25. package/vendor/latte/latte/src/Latte/Compiler/NodeTraverser.php +0 -4
  26. package/vendor/latte/latte/src/Latte/Compiler/Nodes/AuxiliaryNode.php +11 -3
  27. package/vendor/latte/latte/src/Latte/Compiler/Nodes/FragmentNode.php +10 -0
  28. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Html/AttributeNode.php +22 -4
  29. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Html/ElementNode.php +35 -12
  30. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ArgumentNode.php +6 -0
  31. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ArrayItemNode.php +12 -0
  32. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ArrayNode.php +4 -31
  33. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/AssignNode.php +15 -1
  34. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/AssignOpNode.php +11 -0
  35. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/AuxiliaryNode.php +42 -0
  36. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ClassConstantFetchNode.php +2 -2
  37. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ClosureNode.php +1 -1
  38. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ConstantFetchNode.php +8 -0
  39. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/IssetNode.php +15 -1
  40. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/PostOpNode.php +11 -0
  41. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/PreOpNode.php +11 -0
  42. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/{StaticCallNode.php → StaticMethodCallNode.php} +11 -1
  43. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/{StaticCallableNode.php → StaticMethodCallableNode.php} +11 -1
  44. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/TemporaryNode.php +38 -0
  45. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ExpressionNode.php +24 -0
  46. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/FilterNode.php +1 -1
  47. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ListItemNode.php +48 -0
  48. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ListNode.php +56 -0
  49. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ModifierNode.php +5 -5
  50. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/NameNode.php +11 -21
  51. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Scalar/InterpolatedStringNode.php +1 -8
  52. package/vendor/latte/latte/src/Latte/Compiler/PhpHelpers.php +30 -0
  53. package/vendor/latte/latte/src/Latte/Compiler/Position.php +4 -8
  54. package/vendor/latte/latte/src/Latte/Compiler/PrintContext.php +15 -8
  55. package/vendor/latte/latte/src/Latte/Compiler/Tag.php +13 -14
  56. package/vendor/latte/latte/src/Latte/Compiler/TagLexer.php +5 -9
  57. package/vendor/latte/latte/src/Latte/Compiler/TagParser.php +52 -3
  58. package/vendor/latte/latte/src/Latte/Compiler/TagParserData.php +353 -326
  59. package/vendor/latte/latte/src/Latte/Compiler/TemplateGenerator.php +6 -5
  60. package/vendor/latte/latte/src/Latte/Compiler/TemplateLexer.php +105 -178
  61. package/vendor/latte/latte/src/Latte/Compiler/TemplateParser.php +40 -33
  62. package/vendor/latte/latte/src/Latte/Compiler/TemplateParserHtml.php +186 -126
  63. package/vendor/latte/latte/src/Latte/Compiler/Token.php +5 -9
  64. package/vendor/latte/latte/src/Latte/Compiler/TokenStream.php +6 -22
  65. package/vendor/latte/latte/src/Latte/Engine.php +136 -95
  66. package/vendor/latte/latte/src/Latte/Essential/AuxiliaryIterator.php +46 -0
  67. package/vendor/latte/latte/src/Latte/Essential/Blueprint.php +42 -27
  68. package/vendor/latte/latte/src/Latte/Essential/CachingIterator.php +0 -4
  69. package/vendor/latte/latte/src/Latte/Essential/CoreExtension.php +81 -66
  70. package/vendor/latte/latte/src/Latte/Essential/Filters.php +103 -43
  71. package/vendor/latte/latte/src/Latte/Essential/Nodes/BlockNode.php +1 -2
  72. package/vendor/latte/latte/src/Latte/Essential/Nodes/CaptureNode.php +2 -13
  73. package/vendor/latte/latte/src/Latte/Essential/Nodes/ContentTypeNode.php +8 -1
  74. package/vendor/latte/latte/src/Latte/Essential/Nodes/DefineNode.php +1 -2
  75. package/vendor/latte/latte/src/Latte/Essential/Nodes/EmbedNode.php +1 -1
  76. package/vendor/latte/latte/src/Latte/Essential/Nodes/ExtendsNode.php +0 -3
  77. package/vendor/latte/latte/src/Latte/Essential/Nodes/FirstLastSepNode.php +1 -1
  78. package/vendor/latte/latte/src/Latte/Essential/Nodes/ForNode.php +1 -1
  79. package/vendor/latte/latte/src/Latte/Essential/Nodes/ForeachNode.php +40 -13
  80. package/vendor/latte/latte/src/Latte/Essential/Nodes/IfChangedNode.php +1 -1
  81. package/vendor/latte/latte/src/Latte/Essential/Nodes/IfContentNode.php +4 -1
  82. package/vendor/latte/latte/src/Latte/Essential/Nodes/IfNode.php +5 -3
  83. package/vendor/latte/latte/src/Latte/Essential/Nodes/IncludeBlockNode.php +5 -2
  84. package/vendor/latte/latte/src/Latte/Essential/Nodes/IterateWhileNode.php +6 -4
  85. package/vendor/latte/latte/src/Latte/Essential/Nodes/JumpNode.php +26 -23
  86. package/vendor/latte/latte/src/Latte/Essential/Nodes/NElseNode.php +88 -0
  87. package/vendor/latte/latte/src/Latte/Essential/Nodes/NTagNode.php +20 -28
  88. package/vendor/latte/latte/src/Latte/Essential/Nodes/PrintNode.php +7 -12
  89. package/vendor/latte/latte/src/Latte/Essential/Nodes/RollbackNode.php +1 -1
  90. package/vendor/latte/latte/src/Latte/Essential/Nodes/SpacelessNode.php +1 -1
  91. package/vendor/latte/latte/src/Latte/Essential/Nodes/SwitchNode.php +1 -1
  92. package/vendor/latte/latte/src/Latte/Essential/Nodes/TemplatePrintNode.php +25 -3
  93. package/vendor/latte/latte/src/Latte/Essential/Nodes/TranslateNode.php +1 -1
  94. package/vendor/latte/latte/src/Latte/Essential/Nodes/TryNode.php +3 -4
  95. package/vendor/latte/latte/src/Latte/Essential/Nodes/VarPrintNode.php +9 -2
  96. package/vendor/latte/latte/src/Latte/Essential/Nodes/WhileNode.php +1 -1
  97. package/vendor/latte/latte/src/Latte/Essential/Passes.php +16 -58
  98. package/vendor/latte/latte/src/Latte/Essential/RawPhpExtension.php +0 -2
  99. package/vendor/latte/latte/src/Latte/Essential/TranslatorExtension.php +6 -1
  100. package/vendor/latte/latte/src/Latte/Helpers.php +3 -1
  101. package/vendor/latte/latte/src/Latte/Loader.php +1 -0
  102. package/vendor/latte/latte/src/Latte/Loaders/FileLoader.php +1 -4
  103. package/vendor/latte/latte/src/Latte/Loaders/StringLoader.php +0 -2
  104. package/vendor/latte/latte/src/Latte/PositionAwareException.php +1 -1
  105. package/vendor/latte/latte/src/Latte/Runtime/Block.php +0 -4
  106. package/vendor/latte/latte/src/Latte/Runtime/FilterExecutor.php +43 -51
  107. package/vendor/latte/latte/src/Latte/Runtime/FilterInfo.php +0 -2
  108. package/vendor/latte/latte/src/Latte/Runtime/Filters.php +64 -33
  109. package/vendor/latte/latte/src/Latte/Runtime/FunctionExecutor.php +68 -0
  110. package/vendor/latte/latte/src/Latte/Runtime/Html.php +0 -4
  111. package/vendor/latte/latte/src/Latte/Runtime/Template.php +3 -5
  112. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/FunctionCallNode.php +2 -1
  113. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/MethodCallNode.php +1 -1
  114. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/SandboxNode.php +3 -3
  115. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/{StaticCallNode.php → StaticMethodCallNode.php} +3 -3
  116. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/{StaticCallableNode.php → StaticMethodCallableNode.php} +2 -2
  117. package/vendor/latte/latte/src/Latte/Sandbox/RuntimeChecker.php +0 -2
  118. package/vendor/latte/latte/src/Latte/Sandbox/SandboxExtension.php +11 -9
  119. package/vendor/latte/latte/src/Latte/Sandbox/SecurityPolicy.php +0 -2
  120. package/vendor/latte/latte/src/Latte/exceptions.php +2 -11
  121. package/vendor/latte/latte/src/Tools/Linter.php +13 -37
  122. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Html/QuotedValue.php +0 -53
  123. package/vendor/latte/latte/src/Latte/Strict.php +0 -101
@@ -24,13 +24,14 @@ use Latte\SecurityViolationException;
24
24
  */
25
25
  final class TemplateParserHtml
26
26
  {
27
- use Latte\Strict;
28
-
29
27
  /** @var array<string, callable(Tag, TemplateParser): (Node|\Generator|void)> */
30
28
  private array /*readonly*/ $attrParsers;
31
29
  private ?Html\ElementNode $element = null;
32
30
  private TemplateParser /*readonly*/ $parser;
33
31
 
32
+ /** @var array{string, ?Nodes\Php\ExpressionNode} */
33
+ private ?array $endName = null;
34
+
34
35
 
35
36
  public function __construct(TemplateParser $parser, array $attrParsers)
36
37
  {
@@ -50,9 +51,7 @@ final class TemplateParserHtml
50
51
  $stream = $this->parser->getStream();
51
52
  $token = $stream->peek();
52
53
  return match ($token->type) {
53
- Token::Html_TagOpen => $stream->peek(1)?->is(Token::Slash)
54
- ? $this->parseEndTag()
55
- : $this->parseElement(),
54
+ Token::Html_TagOpen => $this->parseTag(),
56
55
  Token::Html_CommentOpen => $this->parseComment(),
57
56
  Token::Html_BogusOpen => $this->parseBogusTag(),
58
57
  default => $this->parser->inTextResolve(),
@@ -76,11 +75,40 @@ final class TemplateParserHtml
76
75
  }
77
76
 
78
77
 
78
+ private function parseTag(): ?Node
79
+ {
80
+ $stream = $this->parser->getStream();
81
+ $this->parser->getLexer()->setState(TemplateLexer::StateHtmlTag);
82
+ if (!$stream->peek(1)?->is(Token::Slash)) {
83
+ return $this->parseElement();
84
+ }
85
+
86
+ if ($this->element
87
+ && $this->parser->peekTag() === $this->element->data->tag // is directly in the element
88
+ ) {
89
+ $save = $stream->getIndex();
90
+ $this->endName = [$endText] = $this->parseEndTag();
91
+ if ($this->element->is($endText) || $this->element->data->textualName === $endText) {
92
+ return null; // go to parseElement() one level up to close the element
93
+ }
94
+ $stream->seek($save);
95
+ if (!in_array($endText, $this->element->data->unclosedTags ?? [], true)) {
96
+ return null; // go to parseElement() one level up to collapse
97
+ }
98
+ }
99
+
100
+ if ($this->parser->strict) {
101
+ $stream->throwUnexpectedException(excerpt: '/');
102
+ }
103
+ return $this->parseBogusEndTag();
104
+ }
105
+
106
+
79
107
  private function parseElement(): Node
80
108
  {
81
109
  $res = new FragmentNode;
82
110
  $res->append($this->extractIndentation());
83
- $res->append($this->parseTag($this->element));
111
+ $res->append($this->parseStartTag($this->element));
84
112
  $elem = $this->element;
85
113
 
86
114
  $stream = $this->parser->getStream();
@@ -102,16 +130,21 @@ final class TemplateParserHtml
102
130
  $frag = $this->parser->parseFragment([$this, 'inTextResolve']);
103
131
  $content->append($this->finishNAttrNodes($frag, $innerNodes));
104
132
 
105
- if ($this->isClosingTag($elem->name)) {
133
+ [$endText, $endVariable] = $this->endName;
134
+ $this->endName = null;
135
+ if ($endText && ($this->element->is($endText) || $this->element->data->textualName === $endText)) {
106
136
  $elem->content = $content;
107
137
  $elem->content->append($this->extractIndentation());
108
- $this->parseTag();
109
138
 
110
139
  } elseif ($outerNodes || $innerNodes || $tagNodes
111
- || ($this->parser->getContentType() === ContentType::Html && in_array(strtolower($elem->name), ['script', 'style'], true))
140
+ || $this->parser->strict
141
+ || $elem->variableName
142
+ || $endVariable
143
+ || $elem->isRawText()
112
144
  ) {
113
145
  $stream->throwUnexpectedException(
114
- addendum: ", expecting </{$elem->name}> for element started " . $elem->position->toWords(),
146
+ addendum: ", expecting </{$elem->data->textualName}> for element started $elem->position",
147
+ excerpt: $endText ? "/{$endText}>" : $stream->peek(1)?->text . $stream->peek(2)?->text,
115
148
  );
116
149
  } else { // element collapsed to tags
117
150
  $res->append($content);
@@ -145,50 +178,116 @@ final class TemplateParserHtml
145
178
  }
146
179
 
147
180
 
148
- private function parseTag(&$elem = null): Html\ElementNode
181
+ private function parseStartTag(&$elem = null): Html\ElementNode
149
182
  {
150
183
  $stream = $this->parser->getStream();
151
184
  $openToken = $stream->consume(Token::Html_TagOpen);
152
- $stream->tryConsume(Token::Slash);
185
+ $this->parser->getLexer()->setState(TemplateLexer::StateHtmlTag);
186
+
187
+ [$textual, $variable] = $this->parseTagName($this->parser->strict);
188
+ if (($this->parser->strict || $variable)
189
+ && !$stream->is(Token::Whitespace, Token::Slash, Token::Html_TagClose)
190
+ ) {
191
+ throw $stream->throwUnexpectedException();
192
+ }
193
+
153
194
  $this->parser->lastIndentation = null;
154
- $this->parser->location = $this->parser::LocationTag;
195
+ $this->parser->inHead = false;
155
196
  $elem = new Html\ElementNode(
156
- name: $stream->consume(Token::Html_Name)->text,
197
+ name: $variable ? '' : $textual,
157
198
  position: $openToken->position,
158
199
  parent: $this->element,
159
200
  data: (object) ['tag' => $this->parser->peekTag()],
201
+ contentType: $this->parser->getContentType(),
160
202
  );
161
203
  $elem->attributes = $this->parser->parseFragment([$this, 'inTagResolve']);
162
204
  $elem->selfClosing = (bool) $stream->tryConsume(Token::Slash);
205
+ $elem->variableName = $variable;
206
+ $elem->data->textualName = $textual;
163
207
  $stream->consume(Token::Html_TagClose);
164
- $this->parser->location = $this->parser::LocationText;
208
+ $state = !$elem->selfClosing && $elem->isRawText()
209
+ ? TemplateLexer::StateHtmlRawText
210
+ : TemplateLexer::StateHtmlText;
211
+ $this->parser->getLexer()->setState($state, $elem->name);
165
212
  return $elem;
166
213
  }
167
214
 
168
215
 
169
- private function parseEndTag(): ?Html\BogusTagNode
216
+ /** @return array{string, ?Nodes\Php\ExpressionNode} */
217
+ private function parseEndTag(): array
170
218
  {
171
219
  $stream = $this->parser->getStream();
172
- $name = $stream->peek(2)->text ?? '';
173
-
174
- if ($this->element
175
- && $this->parser->peekTag() === $this->element->data->tag
176
- && (strcasecmp($name, $this->element->name) === 0
177
- || !in_array($name, $this->element->data->unclosedTags ?? [], true))
178
- ) {
179
- return null; // go back to parseElement()
220
+ $lexer = $this->parser->getLexer();
221
+ $stream->consume(Token::Html_TagOpen);
222
+ $lexer->setState(TemplateLexer::StateHtmlTag);
223
+ $stream->consume(Token::Slash);
224
+ if (isset($this->element->nAttributes['syntax'])) { // hardcoded
225
+ $lexer->popSyntax();
180
226
  }
227
+ $name = $this->parseTagName();
228
+ $stream->tryConsume(Token::Whitespace);
229
+ $stream->consume(Token::Html_TagClose);
230
+ $lexer->setState(TemplateLexer::StateHtmlText);
231
+ return $name;
232
+ }
233
+
234
+
235
+ /** @return array{string, ?Nodes\Php\ExpressionNode} */
236
+ private function parseTagName(bool $strict = true): array
237
+ {
238
+ $variable = $text = null;
239
+ $parts = [];
240
+ $stream = $this->parser->getStream();
241
+ do {
242
+ if ($stream->is(Token::Latte_TagOpen)) {
243
+ $save = $stream->getIndex();
244
+ $statement = $this->parser->parseLatteStatement([$this, 'inTagResolve']);
245
+ if (!$statement instanceof Latte\Essential\Nodes\PrintNode) {
246
+ if (!$parts || $strict) {
247
+ throw new CompileException('Only expression can be used as a HTML tag name.', $statement->position);
248
+ }
249
+ $stream->seek($save);
250
+ break;
251
+ }
252
+ $parts[] = $statement->expression;
253
+ $save -= $stream->getIndex();
254
+ while ($save < 0) {
255
+ $text .= $stream->peek($save++)->text;
256
+ }
257
+ $variable = true;
258
+
259
+ } elseif ($token = $stream->tryConsume(Token::Html_Name)) {
260
+ $parts[] = new Latte\Compiler\Nodes\Php\Scalar\StringNode($token->text, $token->position);
261
+ $text .= $token->text;
262
+
263
+ } elseif (!$parts) {
264
+ throw $stream->throwUnexpectedException([Token::Html_Name, Token::Latte_TagOpen]);
265
+ } else {
266
+ break;
267
+ }
268
+ } while (true);
269
+
270
+ $variable = $variable
271
+ ? Latte\Compiler\Nodes\Php\Expression\BinaryOpNode::nest('.', ...$parts)
272
+ : null;
273
+ return [$text, $variable];
274
+ }
181
275
 
276
+
277
+ private function parseBogusEndTag(): ?Html\BogusTagNode
278
+ {
279
+ $stream = $this->parser->getStream();
182
280
  $openToken = $stream->consume(Token::Html_TagOpen);
281
+ $this->parser->getLexer()->setState(TemplateLexer::StateHtmlTag);
183
282
  $this->parser->lastIndentation = null;
184
- $this->parser->location = $this->parser::LocationTag;
283
+ $this->parser->inHead = false;
185
284
  $node = new Html\BogusTagNode(
186
- openDelimiter: $openToken->text . $stream->consume(Token::Slash)->text . $stream->tryConsume(Token::Text)?->text,
187
- content: $this->parser->parseFragment([$this, 'inTagResolve']),
285
+ openDelimiter: $openToken->text . $stream->consume(Token::Slash)->text . $stream->consume(Token::Html_Name)->text,
286
+ content: new Nodes\TextNode($stream->tryConsume(Token::Whitespace)->text ?? ''),
188
287
  endDelimiter: $stream->consume(Token::Html_TagClose)->text,
189
288
  position: $openToken->position,
190
289
  );
191
- $this->parser->location = $this->parser::LocationText;
290
+ $this->parser->getLexer()->setState(TemplateLexer::StateHtmlText);
192
291
  return $node;
193
292
  }
194
293
 
@@ -197,13 +296,11 @@ final class TemplateParserHtml
197
296
  {
198
297
  $stream = $this->parser->getStream();
199
298
  $openToken = $stream->consume(Token::Html_BogusOpen);
299
+ $this->parser->getLexer()->setState(TemplateLexer::StateHtmlBogus);
200
300
  $this->parser->lastIndentation = null;
201
- $this->parser->location = $this->parser::LocationTag;
202
- $content = $this->parser->parseFragment(fn() => match ($stream->peek()->type) {
203
- Token::Html_TagClose => null,
204
- default => $this->parser->inTextResolve(),
205
- });
206
- $this->parser->location = $this->parser::LocationText;
301
+ $this->parser->inHead = false;
302
+ $content = $this->parser->parseFragment([$this->parser, 'inTextResolve']);
303
+ $this->parser->getLexer()->setState(TemplateLexer::StateHtmlText);
207
304
  return new Html\BogusTagNode(
208
305
  openDelimiter: $openToken->text,
209
306
  content: $content,
@@ -215,7 +312,7 @@ final class TemplateParserHtml
215
312
 
216
313
  private function resolveVoidness(Html\ElementNode $elem): bool
217
314
  {
218
- if ($this->parser->getContentType() !== ContentType::Html) {
315
+ if ($elem->contentType !== ContentType::Html) {
219
316
  return $elem->selfClosing;
220
317
  } elseif (isset(Helpers::$emptyElements[strtolower($elem->name)])) {
221
318
  return true;
@@ -246,69 +343,56 @@ final class TemplateParserHtml
246
343
  private function parseAttribute(): ?Node
247
344
  {
248
345
  $stream = $this->parser->getStream();
249
- $followsLatte = $stream->is(Token::Latte_TagOpen);
250
- $save = $stream->getIndex();
251
- try {
252
- $name = $this->parser->parseFragment(fn() => match ($stream->peek()->type) {
253
- Token::Html_Name => $this->parser->parseText(),
254
- Token::Latte_TagOpen => $this->parser->parseLatteStatement(),
255
- Token::Latte_CommentOpen => $this->parser->parseLatteComment(),
256
- default => null,
257
- });
258
- } catch (CompileException $e) {
259
- if ($followsLatte // attribute name together with the value inside the tag
260
- && $stream->peek() // it is not lexer exception
261
- ) {
262
- $stream->seek($save);
263
- return $this->parser->parseLatteStatement();
346
+ if ($stream->is(Token::Latte_TagOpen)) {
347
+ $name = $this->parser->parseLatteStatement();
348
+ if (!$name instanceof Latte\Essential\Nodes\PrintNode) {
349
+ return $name; // value like '<span {if true}attr1=val{/if}>'
264
350
  }
265
- throw $e;
266
- }
267
-
268
- if (!$name->children) {
269
- return null;
270
- } elseif (count($name->children) === 1 && $name->children[0] instanceof Nodes\TextNode) {
271
- $name = $name->children[0];
272
- }
273
-
274
- $save = $stream->getIndex();
275
- $this->consumeIgnored();
276
- if ($stream->tryConsume(Token::Equals)) {
277
- $this->consumeIgnored();
278
- $value = match ($stream->peek()->type) {
279
- Token::Quote => $this->parseAttributeQuote(),
280
- Token::Html_Name => $this->parser->parseText(),
281
- Token::Latte_TagOpen => $this->parser->parseFragment(
282
- function (FragmentNode $fragment) use ($stream) {
283
- if ($fragment->children) {
284
- return null;
285
- }
286
- return match ($stream->peek()->type) {
287
- Token::Quote => $this->parseAttributeQuote(),
288
- Token::Html_Name => $this->parser->parseText(),
289
- Token::Latte_TagOpen => $this->parser->parseLatteStatement(),
290
- Token::Latte_CommentOpen => $this->parser->parseLatteComment(),
291
- default => null,
292
- };
293
- },
294
- ),
295
- default => null,
296
- }
297
- ?? $stream->throwUnexpectedException();
298
-
299
351
  } else {
300
- $stream->seek($save);
301
- $value = null;
352
+ $name = $this->parser->parseText();
302
353
  }
303
354
 
355
+ [$value, $quote] = $this->parseAttributeValue();
304
356
  return new Html\AttributeNode(
305
357
  name: $name,
306
358
  value: $value,
359
+ quote: $quote,
307
360
  position: $name->position,
308
361
  );
309
362
  }
310
363
 
311
364
 
365
+ private function parseAttributeValue(): ?array
366
+ {
367
+ $stream = $this->parser->getStream();
368
+ $save = $stream->getIndex();
369
+ $this->consumeIgnored();
370
+ if (!$stream->tryConsume(Token::Equals)) {
371
+ $stream->seek($save);
372
+ return null;
373
+ }
374
+
375
+ $this->consumeIgnored();
376
+ if ($quoteToken = $stream->tryConsume(Token::Quote)) {
377
+ $this->parser->getLexer()->setState(TemplateLexer::StateHtmlQuotedValue, $quoteToken->text);
378
+ $value = $this->parser->parseFragment([$this->parser, 'inTextResolve']);
379
+ $stream->tryConsume(Token::Quote) || $stream->throwUnexpectedException([$quoteToken->text], addendum: ", end of HTML attribute started $quoteToken->position");
380
+ $this->parser->getLexer()->setState(TemplateLexer::StateHtmlTag);
381
+ return [$value, $quoteToken->text];
382
+ }
383
+
384
+ $value = $this->parser->parseFragment(
385
+ fn() => match ($stream->peek()->type) {
386
+ Token::Html_Name => $this->parser->parseText(),
387
+ Token::Latte_TagOpen => $this->parser->parseLatteStatement(),
388
+ Token::Latte_CommentOpen => $this->parser->parseLatteComment(),
389
+ default => null,
390
+ },
391
+ )->simplify() ?? $stream->throwUnexpectedException();
392
+ return [$value, null];
393
+ }
394
+
395
+
312
396
  private function parseNAttribute(): Nodes\TextNode
313
397
  {
314
398
  $stream = $this->parser->getStream();
@@ -326,10 +410,12 @@ final class TemplateParserHtml
326
410
  $this->consumeIgnored();
327
411
  if ($stream->tryConsume(Token::Equals)) {
328
412
  $this->consumeIgnored();
329
- if ($stream->tryConsume(Token::Quote)) {
413
+ if ($quoteToken = $stream->tryConsume(Token::Quote)) {
414
+ $this->parser->getLexer()->setState(TemplateLexer::StateHtmlQuotedNAttrValue, $quoteToken->text);
330
415
  $valueToken = $stream->tryConsume(Token::Text);
331
416
  $pos = $stream->peek()->position;
332
- $stream->consume(Token::Quote);
417
+ $stream->tryConsume(Token::Quote) || $stream->throwUnexpectedException([$quoteToken->text], addendum: ", end of n:attribute started $quoteToken->position");
418
+ $this->parser->getLexer()->setState(TemplateLexer::StateHtmlTag);
333
419
  } else {
334
420
  $valueToken = $stream->consume(Token::Html_Name);
335
421
  }
@@ -346,28 +432,10 @@ final class TemplateParserHtml
346
432
  tokens: $tokens,
347
433
  position: $nameToken->position,
348
434
  prefix: $this->getPrefix($name),
349
- location: $this->parser->location,
435
+ inTag: true,
350
436
  htmlElement: $this->element,
351
- data: (object) ['node' => $node = new Nodes\TextNode('')], // TODO: better
352
- );
353
- return $node;
354
- }
355
-
356
-
357
- private function parseAttributeQuote(): Html\QuotedValue
358
- {
359
- $stream = $this->parser->getStream();
360
- $quoteToken = $stream->consume(Token::Quote);
361
- $value = $this->parser->parseFragment(fn() => match ($stream->peek()->type) {
362
- Token::Quote => null,
363
- default => $this->parser->inTextResolve(),
364
- });
365
- $node = new Html\QuotedValue(
366
- value: $value,
367
- quote: $quoteToken->text,
368
- position: $quoteToken->position,
437
+ nAttributeNode: $node = new Nodes\TextNode(''),
369
438
  );
370
- $stream->consume(Token::Quote);
371
439
  return $node;
372
440
  }
373
441
 
@@ -375,17 +443,16 @@ final class TemplateParserHtml
375
443
  private function parseComment(): Html\CommentNode
376
444
  {
377
445
  $this->parser->lastIndentation = null;
378
- $this->parser->location = $this->parser::LocationTag;
446
+ $this->parser->inHead = false;
447
+ $this->parser->getLexer()->setState(TemplateLexer::StateHtmlComment);
379
448
  $stream = $this->parser->getStream();
449
+ $openToken = $stream->consume(Token::Html_CommentOpen);
380
450
  $node = new Html\CommentNode(
381
- position: $stream->consume(Token::Html_CommentOpen)->position,
382
- content: $this->parser->parseFragment(fn() => match ($stream->peek()->type) {
383
- Token::Html_CommentClose => null,
384
- default => $this->parser->inTextResolve(),
385
- }),
451
+ position: $openToken->position,
452
+ content: $this->parser->parseFragment([$this->parser, 'inTextResolve']),
386
453
  );
387
- $stream->consume(Token::Html_CommentClose);
388
- $this->parser->location = $this->parser::LocationText;
454
+ $stream->tryConsume(Token::Html_CommentClose) || $stream->throwUnexpectedException([Token::Html_CommentClose], addendum: " started $openToken->position");
455
+ $this->parser->getLexer()->setState(TemplateLexer::StateHtmlText);
389
456
  return $node;
390
457
  }
391
458
 
@@ -398,8 +465,10 @@ final class TemplateParserHtml
398
465
  continue;
399
466
  }
400
467
  if ($stream->tryConsume(Token::Latte_CommentOpen)) {
468
+ $this->parser->getLexer()->pushState(TemplateLexer::StateLatteComment);
401
469
  $stream->consume(Token::Text);
402
470
  $stream->consume(Token::Latte_CommentClose);
471
+ $this->parser->getLexer()->popState();
403
472
  $stream->tryConsume(Token::Newline);
404
473
  continue;
405
474
  }
@@ -408,15 +477,6 @@ final class TemplateParserHtml
408
477
  }
409
478
 
410
479
 
411
- private function isClosingTag(string $name): bool
412
- {
413
- $stream = $this->parser->getStream();
414
- return $stream->is(Token::Html_TagOpen)
415
- && $stream->peek(1)->is(Token::Slash)
416
- && strcasecmp($name, $stream->peek(2)->text ?? '') === 0;
417
- }
418
-
419
-
420
480
  private function prepareNAttrs(array $attrs, bool $void): array
421
481
  {
422
482
  $res = [];
@@ -454,7 +514,7 @@ final class TemplateParserHtml
454
514
  if ($res instanceof \Generator && $res->valid()) {
455
515
  $toClose[] = [$res, $tag];
456
516
 
457
- } elseif ($res instanceof Node) {
517
+ } elseif ($res instanceof AreaNode) {
458
518
  $this->parser->ensureIsConsumed($tag);
459
519
  $res->position = $tag->position;
460
520
  $tag->replaceNAttribute($res);
@@ -9,13 +9,9 @@ declare(strict_types=1);
9
9
 
10
10
  namespace Latte\Compiler;
11
11
 
12
- use Latte;
13
-
14
12
 
15
13
  final class Token
16
14
  {
17
- use Latte\Strict;
18
-
19
15
  public const
20
16
  End = 0,
21
17
  Text = 10000,
@@ -124,7 +120,7 @@ final class Token
124
120
  Php_True = 336,
125
121
  Php_False = 337;
126
122
 
127
- public const NAMES = [
123
+ public const Names = [
128
124
  self::End => '[EOF]',
129
125
  self::Text => 'text',
130
126
  self::Whitespace => 'whitespace',
@@ -134,11 +130,11 @@ final class Token
134
130
  self::Equals => "'='",
135
131
  self::Quote => 'quote',
136
132
 
137
- self::Latte_TagOpen => "'{'",
138
- self::Latte_TagClose => "'}'",
133
+ self::Latte_TagOpen => 'Latte tag',
134
+ self::Latte_TagClose => 'end of Latte tag',
139
135
  self::Latte_Name => 'tag name',
140
- self::Latte_CommentOpen => "'{*'",
141
- self::Latte_CommentClose => "'*}'",
136
+ self::Latte_CommentOpen => 'Latte comment',
137
+ self::Latte_CommentClose => 'end of Latte comment',
142
138
 
143
139
  self::Html_TagOpen => 'HTML tag',
144
140
  self::Html_TagClose => 'end of HTML tag',
@@ -9,7 +9,6 @@ declare(strict_types=1);
9
9
 
10
10
  namespace Latte\Compiler;
11
11
 
12
- use Latte;
13
12
  use Latte\CompileException;
14
13
 
15
14
 
@@ -19,8 +18,6 @@ use Latte\CompileException;
19
18
  */
20
19
  final class TokenStream
21
20
  {
22
- use Latte\Strict;
23
-
24
21
  /** @var Token[] */
25
22
  private array $tokens = [];
26
23
  private \Iterator /*readonly*/ $source;
@@ -70,7 +67,7 @@ final class TokenStream
70
67
  {
71
68
  $token = $this->peek();
72
69
  if ($kind && !$token->is(...$kind)) {
73
- $kind = array_map(fn($item) => is_string($item) ? "'$item'" : Token::NAMES[$item], $kind);
70
+ $kind = array_map(fn($item) => is_string($item) ? "'$item'" : Token::Names[$item], $kind);
74
71
  $this->throwUnexpectedException($kind);
75
72
  } elseif (!$token->isEnd()) {
76
73
  $this->index++;
@@ -119,28 +116,15 @@ final class TokenStream
119
116
  * @throws CompileException
120
117
  * @return never
121
118
  */
122
- public function throwUnexpectedException(array $expected = [], string $addendum = ''): void
119
+ public function throwUnexpectedException(array $expected = [], string $addendum = '', string $excerpt = ''): void
123
120
  {
124
- $s = null;
125
- $i = 0;
126
- do {
127
- $token = $this->peek($i++);
128
- if ($token->isEnd()) {
129
- break;
130
- }
131
- $s .= $token->text;
132
- if (strlen($s) > 5) {
133
- break;
134
- }
135
- } while (true);
136
-
137
- $expected = array_map(fn($item) => is_int($item) ? Token::NAMES[$item] : $item, $expected);
138
-
121
+ $token = $this->peek()->text . $excerpt;
122
+ $expected = array_map(fn($item) => is_int($item) ? Token::Names[$item] : $item, $expected);
139
123
  throw new CompileException(
140
124
  'Unexpected '
141
- . ($s === null
125
+ . ($token === ''
142
126
  ? 'end'
143
- : "'" . trim($s, "\n") . "'")
127
+ : "'" . trim($token, "\n") . "'")
144
128
  . ($expected && count($expected) < 5
145
129
  ? ', expecting ' . implode(', ', $expected)
146
130
  : '')