@vituum/vite-plugin-latte 1.0.0-beta.1 → 1.1.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 (115) hide show
  1. package/index.js +2 -2
  2. package/package.json +13 -11
  3. package/types/index.d.ts +2 -0
  4. package/vendor/autoload.php +19 -1
  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 +12 -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 +7 -26
  12. package/vendor/composer/autoload_static.php +13 -9
  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 +103 -88
  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/ListItemNode.php +48 -0
  47. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ListNode.php +56 -0
  48. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ModifierNode.php +3 -3
  49. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Scalar/InterpolatedStringNode.php +1 -8
  50. package/vendor/latte/latte/src/Latte/Compiler/PhpHelpers.php +30 -0
  51. package/vendor/latte/latte/src/Latte/Compiler/Position.php +4 -8
  52. package/vendor/latte/latte/src/Latte/Compiler/PrintContext.php +14 -7
  53. package/vendor/latte/latte/src/Latte/Compiler/Tag.php +13 -14
  54. package/vendor/latte/latte/src/Latte/Compiler/TagLexer.php +3 -7
  55. package/vendor/latte/latte/src/Latte/Compiler/TagParser.php +52 -3
  56. package/vendor/latte/latte/src/Latte/Compiler/TagParserData.php +354 -322
  57. package/vendor/latte/latte/src/Latte/Compiler/TemplateGenerator.php +6 -5
  58. package/vendor/latte/latte/src/Latte/Compiler/TemplateLexer.php +95 -176
  59. package/vendor/latte/latte/src/Latte/Compiler/TemplateParser.php +40 -33
  60. package/vendor/latte/latte/src/Latte/Compiler/TemplateParserHtml.php +182 -126
  61. package/vendor/latte/latte/src/Latte/Compiler/Token.php +5 -9
  62. package/vendor/latte/latte/src/Latte/Compiler/TokenStream.php +6 -22
  63. package/vendor/latte/latte/src/Latte/Engine.php +37 -8
  64. package/vendor/latte/latte/src/Latte/Essential/Blueprint.php +0 -2
  65. package/vendor/latte/latte/src/Latte/Essential/CachingIterator.php +0 -4
  66. package/vendor/latte/latte/src/Latte/Essential/CoreExtension.php +14 -3
  67. package/vendor/latte/latte/src/Latte/Essential/Filters.php +8 -6
  68. package/vendor/latte/latte/src/Latte/Essential/Nodes/BlockNode.php +1 -2
  69. package/vendor/latte/latte/src/Latte/Essential/Nodes/CaptureNode.php +2 -13
  70. package/vendor/latte/latte/src/Latte/Essential/Nodes/ContentTypeNode.php +1 -1
  71. package/vendor/latte/latte/src/Latte/Essential/Nodes/DefineNode.php +1 -2
  72. package/vendor/latte/latte/src/Latte/Essential/Nodes/EmbedNode.php +1 -1
  73. package/vendor/latte/latte/src/Latte/Essential/Nodes/ExtendsNode.php +0 -3
  74. package/vendor/latte/latte/src/Latte/Essential/Nodes/FirstLastSepNode.php +1 -1
  75. package/vendor/latte/latte/src/Latte/Essential/Nodes/ForNode.php +1 -1
  76. package/vendor/latte/latte/src/Latte/Essential/Nodes/ForeachNode.php +4 -13
  77. package/vendor/latte/latte/src/Latte/Essential/Nodes/IfChangedNode.php +1 -1
  78. package/vendor/latte/latte/src/Latte/Essential/Nodes/IfContentNode.php +4 -1
  79. package/vendor/latte/latte/src/Latte/Essential/Nodes/IfNode.php +5 -3
  80. package/vendor/latte/latte/src/Latte/Essential/Nodes/IncludeBlockNode.php +5 -2
  81. package/vendor/latte/latte/src/Latte/Essential/Nodes/IterateWhileNode.php +6 -4
  82. package/vendor/latte/latte/src/Latte/Essential/Nodes/JumpNode.php +26 -23
  83. package/vendor/latte/latte/src/Latte/Essential/Nodes/NElseNode.php +88 -0
  84. package/vendor/latte/latte/src/Latte/Essential/Nodes/NTagNode.php +20 -28
  85. package/vendor/latte/latte/src/Latte/Essential/Nodes/PrintNode.php +7 -12
  86. package/vendor/latte/latte/src/Latte/Essential/Nodes/RollbackNode.php +1 -1
  87. package/vendor/latte/latte/src/Latte/Essential/Nodes/SpacelessNode.php +1 -1
  88. package/vendor/latte/latte/src/Latte/Essential/Nodes/SwitchNode.php +1 -1
  89. package/vendor/latte/latte/src/Latte/Essential/Nodes/TranslateNode.php +1 -1
  90. package/vendor/latte/latte/src/Latte/Essential/Nodes/TryNode.php +3 -4
  91. package/vendor/latte/latte/src/Latte/Essential/Nodes/WhileNode.php +1 -1
  92. package/vendor/latte/latte/src/Latte/Essential/Passes.php +9 -11
  93. package/vendor/latte/latte/src/Latte/Essential/RawPhpExtension.php +0 -2
  94. package/vendor/latte/latte/src/Latte/Essential/TranslatorExtension.php +6 -1
  95. package/vendor/latte/latte/src/Latte/Loaders/FileLoader.php +0 -2
  96. package/vendor/latte/latte/src/Latte/Loaders/StringLoader.php +0 -2
  97. package/vendor/latte/latte/src/Latte/PositionAwareException.php +1 -1
  98. package/vendor/latte/latte/src/Latte/Runtime/Block.php +0 -4
  99. package/vendor/latte/latte/src/Latte/Runtime/FilterExecutor.php +43 -51
  100. package/vendor/latte/latte/src/Latte/Runtime/FilterInfo.php +0 -2
  101. package/vendor/latte/latte/src/Latte/Runtime/Filters.php +64 -30
  102. package/vendor/latte/latte/src/Latte/Runtime/Html.php +0 -4
  103. package/vendor/latte/latte/src/Latte/Runtime/Template.php +2 -2
  104. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/FunctionCallNode.php +2 -1
  105. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/MethodCallNode.php +1 -1
  106. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/SandboxNode.php +3 -3
  107. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/{StaticCallNode.php → StaticMethodCallNode.php} +3 -3
  108. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/{StaticCallableNode.php → StaticMethodCallableNode.php} +2 -2
  109. package/vendor/latte/latte/src/Latte/Sandbox/RuntimeChecker.php +0 -2
  110. package/vendor/latte/latte/src/Latte/Sandbox/SandboxExtension.php +11 -9
  111. package/vendor/latte/latte/src/Latte/Sandbox/SecurityPolicy.php +0 -2
  112. package/vendor/latte/latte/src/Latte/exceptions.php +2 -11
  113. package/vendor/latte/latte/src/Tools/Linter.php +13 -37
  114. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Html/QuotedValue.php +0 -53
  115. package/vendor/latte/latte/src/Latte/Strict.php +0 -101
@@ -9,7 +9,6 @@ declare(strict_types=1);
9
9
 
10
10
  namespace Latte\Compiler;
11
11
 
12
- use Latte;
13
12
  use Latte\ContentType;
14
13
 
15
14
 
@@ -18,8 +17,6 @@ use Latte\ContentType;
18
17
  */
19
18
  final class TemplateGenerator
20
19
  {
21
- use Latte\Strict;
22
-
23
20
  /** @var array<string, ?array{body: string, arguments: string, returns: string, comment: ?string}> */
24
21
  private array $methods = [];
25
22
 
@@ -36,7 +33,7 @@ final class TemplateGenerator
36
33
  public function generate(
37
34
  Nodes\TemplateNode $node,
38
35
  string $className,
39
- ?string $comment = null,
36
+ ?string $sourceName = null,
40
37
  bool $strictMode = false,
41
38
  ): string
42
39
  {
@@ -57,6 +54,10 @@ final class TemplateGenerator
57
54
  $this->addConstant('ContentType', $node->contentType);
58
55
  }
59
56
 
57
+ if ($sourceName !== null) {
58
+ $this->addConstant('Source', $sourceName);
59
+ }
60
+
60
61
  $this->generateBlocks($context->blocks, $context);
61
62
 
62
63
  $members = [];
@@ -79,7 +80,7 @@ final class TemplateGenerator
79
80
  $code = "<?php\n\n"
80
81
  . ($strictMode ? "declare(strict_types=1);\n\n" : '')
81
82
  . "use Latte\\Runtime as LR;\n\n"
82
- . ($comment === null ? '' : '/** ' . str_replace('*/', '* /', $comment) . " */\n")
83
+ . ($sourceName === null ? '' : '/** source: ' . str_replace('*/', '* /', $sourceName) . " */\n")
83
84
  . "final class $className extends Latte\\Runtime\\Template\n{\n"
84
85
  . implode("\n\n", $members)
85
86
  . "\n}\n";
@@ -9,15 +9,22 @@ declare(strict_types=1);
9
9
 
10
10
  namespace Latte\Compiler;
11
11
 
12
- use Latte;
13
12
  use Latte\CompileException;
14
- use Latte\ContentType;
15
- use Latte\RegexpException;
16
13
 
17
14
 
18
15
  final class TemplateLexer
19
16
  {
20
- use Latte\Strict;
17
+ public const
18
+ StatePlain = 'Plain',
19
+ StateLatteTag = 'LatteTag',
20
+ StateLatteComment = 'LatteComment',
21
+ StateHtmlText = 'HtmlText',
22
+ StateHtmlTag = 'HtmlTag',
23
+ StateHtmlQuotedValue = 'HtmlQuotedValue',
24
+ StateHtmlQuotedNAttrValue = 'HtmlQuotedNAttrValue',
25
+ StateHtmlRawText = 'HtmlRawText',
26
+ StateHtmlComment = 'HtmlComment',
27
+ StateHtmlBogus = 'HtmlBogus';
21
28
 
22
29
  /** HTML tag name for Latte needs (actually is [a-zA-Z][^\s/>]*) */
23
30
  public const ReTagName = '[a-zA-Z][a-zA-Z0-9:_.-]*';
@@ -27,45 +34,50 @@ final class TemplateLexer
27
34
 
28
35
  /** HTML attribute name/value (\p{C} means \x00-\x1F except space) */
29
36
  private const ReAttrName = '[^\p{C} "\'<>=`/]';
30
- private const StateEnd = 'end';
31
37
 
32
38
  public string $openDelimiter;
33
39
  public string $closeDelimiter;
34
40
  private TagLexer $tagLexer;
35
41
 
36
42
  /** @var array<array{name: string, args: mixed[]}> */
37
- private array $states;
43
+ private array $states = [];
38
44
  private string $input;
39
45
  private Position $position;
40
- private bool $xmlMode;
41
46
 
42
47
 
43
- /** @return \Generator<Token> */
44
- public function tokenize(string $template, string $contentType = ContentType::Html): \Generator
48
+ public function __construct()
45
49
  {
46
- $this->position = new Position(1, 1, 0);
47
- $this->input = $this->normalize($template);
48
- $this->states = [];
49
- $this->setContentType($contentType);
50
+ $this->position = new Position;
51
+ $this->setState(self::StatePlain);
50
52
  $this->setSyntax(null);
51
53
  $this->tagLexer = new TagLexer;
54
+ }
55
+
56
+
57
+ /** @return \Generator<Token> */
58
+ public function tokenize(string $template): \Generator
59
+ {
60
+ $this->input = $this->normalize($template);
52
61
 
53
62
  do {
63
+ $offset = $this->position->offset;
54
64
  $state = $this->states[0];
55
- yield from $this->{$state['name']}(...$state['args']);
56
- } while ($this->states[0]['name'] !== self::StateEnd);
65
+ $tokens = $this->{"state$state[name]"}(...$state['args']);
66
+ yield from $tokens;
67
+
68
+ } while ($offset !== $this->position->offset);
57
69
 
58
- if ($this->position->offset < strlen($this->input)) {
59
- throw new CompileException("Unexpected '" . substr($this->input, $this->position->offset, 10) . "'", $this->position);
70
+ if ($offset < strlen($this->input)) {
71
+ throw new CompileException("Unexpected '" . substr($this->input, $offset, 10) . "'", $this->position);
60
72
  }
61
73
 
62
74
  yield new Token(Token::End, '', $this->position);
63
75
  }
64
76
 
65
77
 
66
- private function statePlain(): \Generator
78
+ private function statePlain(): array
67
79
  {
68
- $m = yield from $this->match('~
80
+ return $this->match('~
69
81
  (?<Text>.+?)??
70
82
  (?<Indentation>(?<=\n|^)[ \t]+)?
71
83
  (
@@ -74,163 +86,102 @@ final class TemplateLexer
74
86
  $
75
87
  )
76
88
  ~xsiAuD');
77
-
78
- if (isset($m['Latte_TagOpen'])) {
79
- $this->pushState('stateLatteTag');
80
- } elseif (isset($m['Latte_CommentOpen'])) {
81
- $this->pushState('stateLatteComment');
82
- } else {
83
- $this->setState(self::StateEnd);
84
- }
85
89
  }
86
90
 
87
91
 
88
- private function stateLatteTag(): \Generator
92
+ private function stateLatteTag(): array
89
93
  {
90
- $pos = $this->states[0]['pos'];
91
- $this->popState();
92
-
93
- yield from $this->match('~
94
+ $tokens[] = $this->match('~
94
95
  (?<Slash>/)?
95
96
  (?<Latte_Name> = | _(?!_) | [a-z]\w*+(?:[.:-]\w+)*+(?!::|\(|\\\\))? # name, /name, but not function( or class:: or namespace\
96
97
  ~xsiAu');
97
98
 
98
- yield from $this->tagLexer->tokenizePartially($this->input, $this->position);
99
+ $tokens[] = $this->tagLexer->tokenizePartially($this->input, $this->position);
99
100
 
100
- yield from $this->match('~
101
+ $tokens[] = $this->match('~
101
102
  (?<Slash>/)?
102
103
  (?<Latte_TagClose>' . $this->closeDelimiter . ')
103
104
  (?<Newline>[ \t]*\R)?
104
- ~xsiAu')
105
- or throw new CompileException('Unterminated Latte tag', $pos);
105
+ ~xsiAu');
106
+
107
+ return array_merge(...$tokens);
106
108
  }
107
109
 
108
110
 
109
- private function stateLatteComment(): \Generator
111
+ private function stateLatteComment(): array
110
112
  {
111
- yield from $this->match('~
113
+ return $this->match('~
112
114
  (?<Text>.+?)??
113
- (?<Latte_CommentClose>\*' . $this->closeDelimiter . ')
114
- (?<Newline>[ \t]*\R{1,2})?
115
- ~xsiAu')
116
- or throw new CompileException('Unterminated Latte comment', $this->states[0]['pos']);
117
- $this->popState();
115
+ (
116
+ (?<Latte_CommentClose>\*' . $this->closeDelimiter . ')(?<Newline>[ \t]*\R{1,2})?|
117
+ $
118
+ )
119
+ ~xsiAu');
118
120
  }
119
121
 
120
122
 
121
- private function stateHtmlText(): \Generator
123
+ private function stateHtmlText(): array
122
124
  {
123
- $m = yield from $this->match('~(?J)
125
+ return $this->match('~(?J)
124
126
  (?<Text>.+?)??
125
127
  (
126
- (?<Indentation>(?<=\n|^)[ \t]+)?(?<Html_TagOpen><)(?<Slash>/)?(?<Html_Name>' . self::ReTagName . ')| # <tag </tag
128
+ (?<Indentation>(?<=\n|^)[ \t]+)?(?<Html_TagOpen><)(?<Slash>/)?(?=[a-z]|' . $this->openDelimiter . ')| # < </ tag
127
129
  (?<Html_CommentOpen><!--(?!>|->))| # <!-- comment
128
- (?<Html_BogusOpen><[/?!])| # <!doctype <?xml or error
130
+ (?<Html_BogusOpen><[?!])| # <!doctype <?xml or error
129
131
  (?<Indentation>(?<=\n|^)[ \t]+)?(?<Latte_TagOpen>' . $this->openDelimiter . '(?!\*))| # {tag
130
132
  (?<Indentation>(?<=\n|^)[ \t]+)?(?<Latte_CommentOpen>' . $this->openDelimiter . '\*)| # {* comment
131
133
  $
132
134
  )
133
135
  ~xsiAuD');
134
-
135
- if (isset($m['Html_TagOpen'])) {
136
- $tagName = isset($m['Slash']) ? null : strtolower($m['Html_Name']);
137
- $this->setState('stateHtmlTag', $tagName);
138
- } elseif (isset($m['Html_CommentOpen'])) {
139
- $this->setState('stateHtmlComment');
140
- } elseif (isset($m['Html_BogusOpen'])) {
141
- $this->setState('stateHtmlBogus');
142
- } elseif (isset($m['Latte_TagOpen'])) {
143
- $this->pushState('stateLatteTag');
144
- } elseif (isset($m['Latte_CommentOpen'])) {
145
- $this->pushState('stateLatteComment');
146
- } else {
147
- $this->setState(self::StateEnd);
148
- }
149
136
  }
150
137
 
151
138
 
152
- private function stateHtmlTag(?string $tagName = null, ?string $attrName = null): \Generator
139
+ private function stateHtmlTag(): array
153
140
  {
154
- $m = yield from $this->match('~
141
+ return $this->match('~(?J)
142
+ (?<Equals>=)
143
+ (?<Whitespace>\s+)?
144
+ (?<Html_Name>(?:(?!' . $this->openDelimiter . ')' . self::ReAttrName . '|/)+)? # HTML attribute value can contain /
145
+ |
155
146
  (?<Whitespace>\s+)| # whitespace
156
- (?<Equals>=)|
157
147
  (?<Quote>["\'])|
158
148
  (?<Slash>/)?(?<Html_TagClose>>)(?<Newline>[ \t]*\R)?| # > />
159
149
  (?<Html_Name>(?:(?!' . $this->openDelimiter . ')' . self::ReAttrName . ')+)| # HTML attribute name/value
160
150
  (?<Latte_TagOpen>' . $this->openDelimiter . '(?!\*))| # {tag
161
151
  (?<Latte_CommentOpen>' . $this->openDelimiter . '\*) # {* comment
162
152
  ~xsiAu');
163
-
164
- if (isset($m['Html_Name'])) {
165
- $this->states[0]['args'][1] = $m['Html_Name']; // sets $attrName
166
- } elseif (isset($m['Equals'])) {
167
- yield from $this->match('~
168
- (?<Whitespace>\s+)? # whitespace
169
- (?<Html_Name>(?:(?!' . $this->openDelimiter . ')' . self::ReAttrName . '|/)+) # HTML attribute value can contain /
170
- ~xsiAu');
171
- } elseif (isset($m['Whitespace'])) {
172
- } elseif (isset($m['Quote'])) {
173
- $this->pushState(str_starts_with($attrName ?? '', self::NPrefix)
174
- ? 'stateHtmlQuotedNAttrValue'
175
- : 'stateHtmlQuotedValue', $m['Quote']);
176
- } elseif (
177
- isset($m['Html_TagClose'])
178
- && !$this->xmlMode
179
- && !isset($m['Slash'])
180
- && in_array($tagName, ['script', 'style'], true)
181
- ) {
182
- $this->setState('stateHtmlRawText', $tagName);
183
- } elseif (isset($m['Html_TagClose'])) {
184
- $this->setState('stateHtmlText');
185
- } elseif (isset($m['Latte_TagOpen'])) {
186
- $this->pushState('stateLatteTag');
187
- } elseif (isset($m['Latte_CommentOpen'])) {
188
- $this->pushState('stateLatteComment');
189
- } else {
190
- $this->setState(self::StateEnd);
191
- }
192
153
  }
193
154
 
194
155
 
195
- private function stateHtmlQuotedValue(string $quote): \Generator
156
+ private function stateHtmlQuotedValue(string $quote): array
196
157
  {
197
- $m = yield from $this->match('~
198
- (?<Text>.+?)??(
158
+ return $this->match('~
159
+ (?<Text>.+?)??
160
+ (
199
161
  (?<Quote>' . $quote . ')|
200
162
  (?<Latte_TagOpen>' . $this->openDelimiter . '(?!\*))| # {tag
201
- (?<Latte_CommentOpen>' . $this->openDelimiter . '\*) # {* comment
163
+ (?<Latte_CommentOpen>' . $this->openDelimiter . '\*)| # {* comment
164
+ $
202
165
  )
203
166
  ~xsiAu');
204
-
205
- if (isset($m['Quote'])) {
206
- $this->popState();
207
- } elseif (isset($m['Latte_TagOpen'])) {
208
- $this->pushState('stateLatteTag');
209
- } elseif (isset($m['Latte_CommentOpen'])) {
210
- $this->pushState('stateLatteComment');
211
- } else {
212
- throw new CompileException('Unterminated HTML attribute value', $this->states[0]['pos']);
213
- }
214
167
  }
215
168
 
216
169
 
217
- private function stateHtmlQuotedNAttrValue(string $quote): \Generator
170
+ private function stateHtmlQuotedNAttrValue(string $quote): array
218
171
  {
219
- $m = yield from $this->match('~
220
- (?<Text>.+?)??(?<Quote>' . $quote . ')|
172
+ return $this->match('~
173
+ (?<Text>.+?)??
174
+ (
175
+ (?<Quote>' . $quote . ')|
176
+ $
177
+ )
221
178
  ~xsiAu');
222
-
223
- if (isset($m['Quote'])) {
224
- $this->popState();
225
- } else {
226
- throw new CompileException('Unterminated n:attribute value', $this->states[0]['pos']);
227
- }
228
179
  }
229
180
 
230
181
 
231
- private function stateHtmlRawText(string $tagName): \Generator
182
+ private function stateHtmlRawText(string $tagName): array
232
183
  {
233
- $m = yield from $this->match('~
184
+ return $this->match('~
234
185
  (?<Text>.+?)??
235
186
  (?<Indentation>(?<=\n|^)[ \t]+)?
236
187
  (
@@ -240,114 +191,82 @@ final class TemplateLexer
240
191
  $
241
192
  )
242
193
  ~xsiAu');
243
-
244
- if (isset($m['Html_TagOpen'])) {
245
- $this->setState('stateHtmlTag');
246
- } elseif (isset($m['Latte_TagOpen'])) {
247
- $this->pushState('stateLatteTag');
248
- } elseif (isset($m['Latte_CommentOpen'])) {
249
- $this->pushState('stateLatteComment');
250
- } else {
251
- $this->setState(self::StateEnd);
252
- }
253
194
  }
254
195
 
255
196
 
256
- private function stateHtmlComment(): \Generator
197
+ private function stateHtmlComment(): array
257
198
  {
258
- $m = yield from $this->match('~(?J)
199
+ return $this->match('~(?J)
259
200
  (?<Text>.+?)??
260
201
  (
261
202
  (?<Html_CommentClose>-->)| # -->
262
203
  (?<Indentation>(?<=\n|^)[ \t]+)?(?<Latte_TagOpen>' . $this->openDelimiter . '(?!\*))| # {tag
263
- (?<Indentation>(?<=\n|^)[ \t]+)?(?<Latte_CommentOpen>' . $this->openDelimiter . '\*) # {* comment
204
+ (?<Indentation>(?<=\n|^)[ \t]+)?(?<Latte_CommentOpen>' . $this->openDelimiter . '\*)| # {* comment
205
+ $
264
206
  )
265
207
  ~xsiAu');
266
-
267
- if (isset($m['Html_CommentClose'])) {
268
- $this->setState('stateHtmlText');
269
- } elseif (isset($m['Latte_TagOpen'])) {
270
- $this->pushState('stateLatteTag');
271
- } elseif (isset($m['Latte_CommentOpen'])) {
272
- $this->pushState('stateLatteComment');
273
- } else {
274
- throw new CompileException('Unterminated HTML comment', $this->states[0]['pos']);
275
- }
276
208
  }
277
209
 
278
210
 
279
- private function stateHtmlBogus(): \Generator
211
+ private function stateHtmlBogus(): array
280
212
  {
281
- $m = yield from $this->match('~
282
- (?<Text>.+?)??(
213
+ return $this->match('~
214
+ (?<Text>.+?)??
215
+ (
283
216
  (?<Html_TagClose>>)| # >
284
217
  (?<Latte_TagOpen>' . $this->openDelimiter . '(?!\*))| # {tag
285
- (?<Latte_CommentOpen>' . $this->openDelimiter . '\*) # {* comment
218
+ (?<Latte_CommentOpen>' . $this->openDelimiter . '\*)| # {* comment
219
+ $
286
220
  )
287
221
  ~xsiAu');
288
-
289
- if (isset($m['Html_TagClose'])) {
290
- $this->setState('stateHtmlText');
291
- } elseif (isset($m['Latte_TagOpen'])) {
292
- $this->pushState('stateLatteTag');
293
- } elseif (isset($m['Latte_CommentOpen'])) {
294
- $this->pushState('stateLatteComment');
295
- } else {
296
- throw new CompileException('Unterminated HTML tag', $this->states[0]['pos']);
297
- }
298
222
  }
299
223
 
300
224
 
301
225
  /**
302
226
  * Matches next token.
227
+ * @return Token[]
303
228
  */
304
- private function match(string $re): \Generator
229
+ private function match(string $re): array
305
230
  {
306
231
  preg_match($re, $this->input, $matches, PREG_UNMATCHED_AS_NULL, $this->position->offset);
307
232
  if (preg_last_error()) {
308
- throw new RegexpException;
233
+ throw new CompileException(preg_last_error_msg());
309
234
  }
310
235
 
236
+ $tokens = [];
311
237
  foreach ($matches as $k => $v) {
312
238
  if ($v !== null && !\is_int($k)) {
313
- yield new Token(\constant(Token::class . '::' . $k), $v, $this->position);
239
+ $tokens[] = new Token(\constant(Token::class . '::' . $k), $v, $this->position);
314
240
  $this->position = $this->position->advance($v);
315
241
  }
316
242
  }
317
243
 
318
- return $matches;
244
+ return $tokens;
319
245
  }
320
246
 
321
247
 
322
- public function setContentType(string $type): static
248
+ public function setState(string $state, ...$args): void
323
249
  {
324
- if ($type === ContentType::Html || $type === ContentType::Xml) {
325
- $this->setState('stateHtmlText');
326
- $this->xmlMode = $type === ContentType::Xml;
327
- } else {
328
- $this->setState('statePlain');
329
- }
330
-
331
- return $this;
250
+ $this->states[0] = ['name' => $state, 'args' => $args];
332
251
  }
333
252
 
334
253
 
335
- private function setState(string $state, ...$args): void
254
+ public function pushState(string $state, ...$args): void
336
255
  {
337
- $this->states[0] = ['name' => $state, 'args' => $args, 'pos' => $this->position];
256
+ array_unshift($this->states, null);
257
+ $this->setState($state, ...$args);
338
258
  }
339
259
 
340
260
 
341
- private function pushState(string $state, ...$args): void
261
+ public function popState(): void
342
262
  {
343
- array_unshift($this->states, null);
344
- $this->setState($state, ...$args);
263
+ array_shift($this->states);
345
264
  }
346
265
 
347
266
 
348
- private function popState(): void
267
+ public function getState(): string
349
268
  {
350
- array_shift($this->states);
269
+ return $this->states[0]['name'];
351
270
  }
352
271
 
353
272
 
@@ -21,17 +21,11 @@ use Latte\SecurityViolationException;
21
21
 
22
22
  final class TemplateParser
23
23
  {
24
- use Latte\Strict;
25
-
26
- public const
27
- LocationHead = 1,
28
- LocationText = 2,
29
- LocationTag = 3;
30
-
31
24
  /** @var Block[][] */
32
25
  public array $blocks = [[]];
33
26
  public int $blockLayer = Template::LayerTop;
34
- public int $location = self::LocationHead;
27
+ public bool $inHead = true;
28
+ public bool $strict = false;
35
29
  public ?Nodes\TextNode $lastIndentation = null;
36
30
 
37
31
  /** @var array<string, callable(Tag, self): (Node|\Generator|void)> */
@@ -42,27 +36,35 @@ final class TemplateParser
42
36
 
43
37
  private TemplateParserHtml $html;
44
38
  private ?TokenStream $stream = null;
45
- private ?TemplateLexer $lexer = null;
39
+ private TemplateLexer $lexer;
46
40
  private ?Policy $policy = null;
47
- private string $contentType = ContentType::Html;
41
+ private string $contentType;
48
42
  private int $counter = 0;
49
43
  private ?Tag $tag = null;
50
44
  private $lastResolver;
45
+ private \WeakMap $lookFor;
46
+
47
+
48
+ public function __construct()
49
+ {
50
+ $this->lexer = new TemplateLexer;
51
+ $this->setContentType(ContentType::Html);
52
+ }
51
53
 
52
54
 
53
55
  /**
54
56
  * Parses tokens to nodes.
55
57
  * @throws CompileException
56
58
  */
57
- public function parse(string $template, TemplateLexer $lexer): Nodes\TemplateNode
59
+ public function parse(string $template): Nodes\TemplateNode
58
60
  {
59
- $this->lexer = $lexer;
60
61
  $this->html = new TemplateParserHtml($this, $this->completeAttrParsers());
61
- $this->stream = new TokenStream($lexer->tokenize($template, $this->contentType));
62
+ $this->stream = new TokenStream($this->lexer->tokenize($template));
63
+ $this->lookFor = new \WeakMap;
62
64
 
63
65
  $headLength = 0;
64
66
  $findLength = function (FragmentNode $fragment) use (&$headLength) {
65
- if ($this->location === self::LocationHead && !end($fragment->children) instanceof Nodes\TextNode) {
67
+ if ($this->inHead && !end($fragment->children) instanceof Nodes\TextNode) {
66
68
  $headLength = count($fragment->children);
67
69
  }
68
70
  };
@@ -119,9 +121,7 @@ final class TemplateParser
119
121
  public function parseText(): Nodes\TextNode
120
122
  {
121
123
  $token = $this->stream->consume(Token::Text, Token::Html_Name);
122
- if ($this->location === self::LocationHead && trim($token->text) !== '') {
123
- $this->location = self::LocationText;
124
- }
124
+ $this->inHead = $this->inHead && trim($token->text) === '';
125
125
  $this->lastIndentation = null;
126
126
  return new Nodes\TextNode($token->text, $token->position);
127
127
  }
@@ -152,20 +152,25 @@ final class TemplateParser
152
152
  if (str_ends_with($this->stream->peek(-1)?->text ?? "\n", "\n")) {
153
153
  $this->lastIndentation ??= new Nodes\TextNode('');
154
154
  }
155
- $this->stream->consume(Token::Latte_CommentOpen);
155
+ $openToken = $this->stream->consume(Token::Latte_CommentOpen);
156
+ $this->lexer->pushState(TemplateLexer::StateLatteComment);
156
157
  $this->stream->consume(Token::Text);
157
- $this->stream->consume(Token::Latte_CommentClose);
158
+ $this->stream->tryConsume(Token::Latte_CommentClose) || $this->stream->throwUnexpectedException([Token::Latte_CommentClose], addendum: " started $openToken->position");
159
+ $this->lexer->popState();
158
160
  return new Nodes\NopNode;
159
161
  }
160
162
 
161
163
 
162
- public function parseLatteStatement(): ?Node
164
+ public function parseLatteStatement(?callable $resolver = null): ?Node
163
165
  {
166
+ $this->lexer->pushState(TemplateLexer::StateLatteTag);
164
167
  if ($this->stream->peek(1)->is(Token::Slash)
165
- || isset($this->tag->data->filters) && in_array($this->stream->peek(1)->text, $this->tag->data->filters, true)
168
+ || (isset($this->tag, $this->lookFor[$this->tag]) && in_array($this->stream->peek(1)->text, $this->lookFor[$this->tag], true))
166
169
  ) {
170
+ $this->lexer->popState();
167
171
  return null; // go back to previous parseLatteStatement()
168
172
  }
173
+ $this->lexer->popState();
169
174
 
170
175
  $token = $this->stream->peek();
171
176
  $startTag = $this->pushTag($this->parseLatteTag());
@@ -186,8 +191,8 @@ final class TemplateParser
186
191
  $res->send([new FragmentNode, $startTag]);
187
192
  } else {
188
193
  while ($res->valid()) {
189
- $startTag->data->filters = $res->current() ?: null;
190
- $content = $this->parseFragment($this->lastResolver);
194
+ $this->lookFor[$startTag] = $res->current() ?: null;
195
+ $content = $this->parseFragment($resolver ?? $this->lastResolver);
191
196
 
192
197
  if (!$this->stream->is(Token::Latte_TagOpen)) {
193
198
  $this->checkEndTag($startTag, null);
@@ -206,7 +211,7 @@ final class TemplateParser
206
211
  $res->send([$content, $tag]);
207
212
  $this->ensureIsConsumed($tag);
208
213
  break;
209
- } elseif (in_array($tag->name, $startTag->data->filters ?? [], true)) {
214
+ } elseif (in_array($tag->name, $this->lookFor[$startTag] ?? [], true)) {
210
215
  $this->pushTag($tag);
211
216
  $res->send([$content, $tag]);
212
217
  $this->ensureIsConsumed($tag);
@@ -238,9 +243,7 @@ final class TemplateParser
238
243
  throw new \LogicException("Incorrect behavior of {{$startTag->name}} parser, unexpected returned value (on line {$startTag->position->line})");
239
244
  }
240
245
 
241
- if ($this->location === self::LocationHead && $startTag->outputMode !== $startTag::OutputNone) {
242
- $this->location = self::LocationText;
243
- }
246
+ $this->inHead = $this->inHead && $startTag->outputMode === $startTag::OutputNone;
244
247
 
245
248
  $this->popTag();
246
249
 
@@ -256,17 +259,21 @@ final class TemplateParser
256
259
  $this->lastIndentation ??= new Nodes\TextNode('');
257
260
  }
258
261
 
262
+ $inTag = in_array($this->lexer->getState(), [TemplateLexer::StateHtmlTag, TemplateLexer::StateHtmlQuotedValue, TemplateLexer::StateHtmlComment, TemplateLexer::StateHtmlBogus], true);
259
263
  $openToken = $stream->consume(Token::Latte_TagOpen);
264
+ $this->lexer->pushState(TemplateLexer::StateLatteTag);
260
265
  $tag = new Tag(
261
266
  position: $openToken->position,
262
267
  closing: $closing = (bool) $stream->tryConsume(Token::Slash),
263
268
  name: $stream->tryConsume(Token::Latte_Name)?->text ?? ($closing ? '' : '='),
264
269
  tokens: $this->consumeTag(),
265
270
  void: (bool) $stream->tryConsume(Token::Slash),
266
- location: $this->location,
271
+ inHead: $this->inHead,
272
+ inTag: $inTag,
267
273
  htmlElement: $this->html->getElement(),
268
274
  );
269
- $stream->consume(Token::Latte_TagClose);
275
+ $stream->tryConsume(Token::Latte_TagClose) || $stream->throwUnexpectedException([Token::Latte_TagClose], addendum: " started $openToken->position");
276
+ $this->lexer->popState();
270
277
  return $tag;
271
278
  }
272
279
 
@@ -309,9 +316,7 @@ final class TemplateParser
309
316
  $hint = ($t = Helpers::getSuggestion(array_keys($this->tagParsers), $name))
310
317
  ? ", did you mean {{$t}}?"
311
318
  : '';
312
- if ($this->contentType === ContentType::Html
313
- && in_array($this->html->getElement()?->name, ['script', 'style'], true)
314
- ) {
319
+ if ($this->html->getElement()?->isRawText()) {
315
320
  $hint .= ' (in JavaScript or CSS, try to put a space after bracket or use n:syntax=off)';
316
321
  }
317
322
  throw new CompileException("Unexpected tag {{$name}}$hint", $pos);
@@ -391,7 +396,9 @@ final class TemplateParser
391
396
  public function setContentType(string $type): static
392
397
  {
393
398
  $this->contentType = $type;
394
- $this->lexer?->setContentType($type);
399
+ $this->lexer->setState($type === ContentType::Html || $type === ContentType::Xml
400
+ ? TemplateLexer::StateHtmlText
401
+ : TemplateLexer::StatePlain);
395
402
  return $this;
396
403
  }
397
404