@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
@@ -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 $templateName = 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 ($templateName !== null && !preg_match('#\n|\?#', $templateName)) {
58
+ $this->addConstant('Source', $templateName);
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
+ . ($templateName === null ? '' : '/** source: ' . str_replace('*/', '* /', $templateName) . " */\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,51 @@ 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
- public string $openDelimiter;
33
- public string $closeDelimiter;
38
+ private string $openDelimiter = '';
39
+ private string $closeDelimiter = '';
40
+ private array $delimiters = [];
34
41
  private TagLexer $tagLexer;
35
42
 
36
43
  /** @var array<array{name: string, args: mixed[]}> */
37
- private array $states;
44
+ private array $states = [];
38
45
  private string $input;
39
46
  private Position $position;
40
- private bool $xmlMode;
41
47
 
42
48
 
43
- /** @return \Generator<Token> */
44
- public function tokenize(string $template, string $contentType = ContentType::Html): \Generator
49
+ public function __construct()
45
50
  {
46
- $this->position = new Position(1, 1, 0);
47
- $this->input = $this->normalize($template);
48
- $this->states = [];
49
- $this->setContentType($contentType);
51
+ $this->position = new Position;
52
+ $this->setState(self::StatePlain);
50
53
  $this->setSyntax(null);
51
54
  $this->tagLexer = new TagLexer;
55
+ }
56
+
57
+
58
+ /** @return \Generator<Token> */
59
+ public function tokenize(string $template): \Generator
60
+ {
61
+ $this->input = $this->normalize($template);
52
62
 
53
63
  do {
64
+ $offset = $this->position->offset;
54
65
  $state = $this->states[0];
55
- yield from $this->{$state['name']}(...$state['args']);
56
- } while ($this->states[0]['name'] !== self::StateEnd);
66
+ $tokens = $this->{"state$state[name]"}(...$state['args']);
67
+ yield from $tokens;
57
68
 
58
- if ($this->position->offset < strlen($this->input)) {
59
- throw new CompileException("Unexpected '" . substr($this->input, $this->position->offset, 10) . "'", $this->position);
69
+ } while ($offset !== $this->position->offset);
70
+
71
+ if ($offset < strlen($this->input)) {
72
+ throw new CompileException("Unexpected '" . substr($this->input, $offset, 10) . "'", $this->position);
60
73
  }
61
74
 
62
75
  yield new Token(Token::End, '', $this->position);
63
76
  }
64
77
 
65
78
 
66
- private function statePlain(): \Generator
79
+ private function statePlain(): array
67
80
  {
68
- $m = yield from $this->match('~
81
+ return $this->match('~
69
82
  (?<Text>.+?)??
70
83
  (?<Indentation>(?<=\n|^)[ \t]+)?
71
84
  (
@@ -74,163 +87,102 @@ final class TemplateLexer
74
87
  $
75
88
  )
76
89
  ~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
90
  }
86
91
 
87
92
 
88
- private function stateLatteTag(): \Generator
93
+ private function stateLatteTag(): array
89
94
  {
90
- $pos = $this->states[0]['pos'];
91
- $this->popState();
92
-
93
- yield from $this->match('~
95
+ $tokens[] = $this->match('~
94
96
  (?<Slash>/)?
95
97
  (?<Latte_Name> = | _(?!_) | [a-z]\w*+(?:[.:-]\w+)*+(?!::|\(|\\\\))? # name, /name, but not function( or class:: or namespace\
96
98
  ~xsiAu');
97
99
 
98
- yield from $this->tagLexer->tokenizePartially($this->input, $this->position);
100
+ $tokens[] = $this->tagLexer->tokenizePartially($this->input, $this->position);
99
101
 
100
- yield from $this->match('~
102
+ $tokens[] = $this->match('~
101
103
  (?<Slash>/)?
102
104
  (?<Latte_TagClose>' . $this->closeDelimiter . ')
103
105
  (?<Newline>[ \t]*\R)?
104
- ~xsiAu')
105
- or throw new CompileException('Unterminated Latte tag', $pos);
106
+ ~xsiAu');
107
+
108
+ return array_merge(...$tokens);
106
109
  }
107
110
 
108
111
 
109
- private function stateLatteComment(): \Generator
112
+ private function stateLatteComment(): array
110
113
  {
111
- yield from $this->match('~
114
+ return $this->match('~
112
115
  (?<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();
116
+ (
117
+ (?<Latte_CommentClose>\*' . $this->closeDelimiter . ')(?<Newline>[ \t]*\R{1,2})?|
118
+ $
119
+ )
120
+ ~xsiAu');
118
121
  }
119
122
 
120
123
 
121
- private function stateHtmlText(): \Generator
124
+ private function stateHtmlText(): array
122
125
  {
123
- $m = yield from $this->match('~(?J)
126
+ return $this->match('~(?J)
124
127
  (?<Text>.+?)??
125
128
  (
126
- (?<Indentation>(?<=\n|^)[ \t]+)?(?<Html_TagOpen><)(?<Slash>/)?(?<Html_Name>' . self::ReTagName . ')| # <tag </tag
129
+ (?<Indentation>(?<=\n|^)[ \t]+)?(?<Html_TagOpen><)(?<Slash>/)?(?=[a-z]|' . $this->openDelimiter . ')| # < </ tag
127
130
  (?<Html_CommentOpen><!--(?!>|->))| # <!-- comment
128
- (?<Html_BogusOpen><[/?!])| # <!doctype <?xml or error
131
+ (?<Html_BogusOpen><[?!])| # <!doctype <?xml or error
129
132
  (?<Indentation>(?<=\n|^)[ \t]+)?(?<Latte_TagOpen>' . $this->openDelimiter . '(?!\*))| # {tag
130
133
  (?<Indentation>(?<=\n|^)[ \t]+)?(?<Latte_CommentOpen>' . $this->openDelimiter . '\*)| # {* comment
131
134
  $
132
135
  )
133
136
  ~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
137
  }
150
138
 
151
139
 
152
- private function stateHtmlTag(?string $tagName = null, ?string $attrName = null): \Generator
140
+ private function stateHtmlTag(): array
153
141
  {
154
- $m = yield from $this->match('~
142
+ return $this->match('~(?J)
143
+ (?<Equals>=)
144
+ (?<Whitespace>\s+)?
145
+ (?<Html_Name>(?:(?!' . $this->openDelimiter . ')' . self::ReAttrName . '|/)+)? # HTML attribute value can contain /
146
+ |
155
147
  (?<Whitespace>\s+)| # whitespace
156
- (?<Equals>=)|
157
148
  (?<Quote>["\'])|
158
149
  (?<Slash>/)?(?<Html_TagClose>>)(?<Newline>[ \t]*\R)?| # > />
159
150
  (?<Html_Name>(?:(?!' . $this->openDelimiter . ')' . self::ReAttrName . ')+)| # HTML attribute name/value
160
151
  (?<Latte_TagOpen>' . $this->openDelimiter . '(?!\*))| # {tag
161
152
  (?<Latte_CommentOpen>' . $this->openDelimiter . '\*) # {* comment
162
153
  ~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
154
  }
193
155
 
194
156
 
195
- private function stateHtmlQuotedValue(string $quote): \Generator
157
+ private function stateHtmlQuotedValue(string $quote): array
196
158
  {
197
- $m = yield from $this->match('~
198
- (?<Text>.+?)??(
159
+ return $this->match('~
160
+ (?<Text>.+?)??
161
+ (
199
162
  (?<Quote>' . $quote . ')|
200
163
  (?<Latte_TagOpen>' . $this->openDelimiter . '(?!\*))| # {tag
201
- (?<Latte_CommentOpen>' . $this->openDelimiter . '\*) # {* comment
164
+ (?<Latte_CommentOpen>' . $this->openDelimiter . '\*)| # {* comment
165
+ $
202
166
  )
203
167
  ~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
168
  }
215
169
 
216
170
 
217
- private function stateHtmlQuotedNAttrValue(string $quote): \Generator
171
+ private function stateHtmlQuotedNAttrValue(string $quote): array
218
172
  {
219
- $m = yield from $this->match('~
220
- (?<Text>.+?)??(?<Quote>' . $quote . ')|
173
+ return $this->match('~
174
+ (?<Text>.+?)??
175
+ (
176
+ (?<Quote>' . $quote . ')|
177
+ $
178
+ )
221
179
  ~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
180
  }
229
181
 
230
182
 
231
- private function stateHtmlRawText(string $tagName): \Generator
183
+ private function stateHtmlRawText(string $tagName): array
232
184
  {
233
- $m = yield from $this->match('~
185
+ return $this->match('~
234
186
  (?<Text>.+?)??
235
187
  (?<Indentation>(?<=\n|^)[ \t]+)?
236
188
  (
@@ -240,114 +192,82 @@ final class TemplateLexer
240
192
  $
241
193
  )
242
194
  ~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
195
  }
254
196
 
255
197
 
256
- private function stateHtmlComment(): \Generator
198
+ private function stateHtmlComment(): array
257
199
  {
258
- $m = yield from $this->match('~(?J)
200
+ return $this->match('~(?J)
259
201
  (?<Text>.+?)??
260
202
  (
261
203
  (?<Html_CommentClose>-->)| # -->
262
204
  (?<Indentation>(?<=\n|^)[ \t]+)?(?<Latte_TagOpen>' . $this->openDelimiter . '(?!\*))| # {tag
263
- (?<Indentation>(?<=\n|^)[ \t]+)?(?<Latte_CommentOpen>' . $this->openDelimiter . '\*) # {* comment
205
+ (?<Indentation>(?<=\n|^)[ \t]+)?(?<Latte_CommentOpen>' . $this->openDelimiter . '\*)| # {* comment
206
+ $
264
207
  )
265
208
  ~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
209
  }
277
210
 
278
211
 
279
- private function stateHtmlBogus(): \Generator
212
+ private function stateHtmlBogus(): array
280
213
  {
281
- $m = yield from $this->match('~
282
- (?<Text>.+?)??(
214
+ return $this->match('~
215
+ (?<Text>.+?)??
216
+ (
283
217
  (?<Html_TagClose>>)| # >
284
218
  (?<Latte_TagOpen>' . $this->openDelimiter . '(?!\*))| # {tag
285
- (?<Latte_CommentOpen>' . $this->openDelimiter . '\*) # {* comment
219
+ (?<Latte_CommentOpen>' . $this->openDelimiter . '\*)| # {* comment
220
+ $
286
221
  )
287
222
  ~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
223
  }
299
224
 
300
225
 
301
226
  /**
302
227
  * Matches next token.
228
+ * @return Token[]
303
229
  */
304
- private function match(string $re): \Generator
230
+ private function match(string $re): array
305
231
  {
306
232
  preg_match($re, $this->input, $matches, PREG_UNMATCHED_AS_NULL, $this->position->offset);
307
233
  if (preg_last_error()) {
308
- throw new RegexpException;
234
+ throw new CompileException(preg_last_error_msg());
309
235
  }
310
236
 
237
+ $tokens = [];
311
238
  foreach ($matches as $k => $v) {
312
239
  if ($v !== null && !\is_int($k)) {
313
- yield new Token(\constant(Token::class . '::' . $k), $v, $this->position);
240
+ $tokens[] = new Token(\constant(Token::class . '::' . $k), $v, $this->position);
314
241
  $this->position = $this->position->advance($v);
315
242
  }
316
243
  }
317
244
 
318
- return $matches;
245
+ return $tokens;
319
246
  }
320
247
 
321
248
 
322
- public function setContentType(string $type): static
249
+ public function setState(string $state, ...$args): void
323
250
  {
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;
251
+ $this->states[0] = ['name' => $state, 'args' => $args];
332
252
  }
333
253
 
334
254
 
335
- private function setState(string $state, ...$args): void
255
+ public function pushState(string $state, ...$args): void
336
256
  {
337
- $this->states[0] = ['name' => $state, 'args' => $args, 'pos' => $this->position];
257
+ array_unshift($this->states, null);
258
+ $this->setState($state, ...$args);
338
259
  }
339
260
 
340
261
 
341
- private function pushState(string $state, ...$args): void
262
+ public function popState(): void
342
263
  {
343
- array_unshift($this->states, null);
344
- $this->setState($state, ...$args);
264
+ array_shift($this->states);
345
265
  }
346
266
 
347
267
 
348
- private function popState(): void
268
+ public function getState(): string
349
269
  {
350
- array_shift($this->states);
270
+ return $this->states[0]['name'];
351
271
  }
352
272
 
353
273
 
@@ -359,6 +279,7 @@ final class TemplateLexer
359
279
  $left = '\{(?![\s\'"{}])';
360
280
  $end = $endTag ? '\{/' . preg_quote($endTag, '~') . '\}' : null;
361
281
 
282
+ $this->delimiters[] = [$this->openDelimiter, $this->closeDelimiter];
362
283
  [$this->openDelimiter, $this->closeDelimiter] = match ($type) {
363
284
  null => [$left, '\}'], // {...}
364
285
  'off' => [$endTag ? '(?=' . $end . ')\{' : '(?!x)x', '\}'],
@@ -371,6 +292,12 @@ final class TemplateLexer
371
292
  }
372
293
 
373
294
 
295
+ public function popSyntax(): void
296
+ {
297
+ [$this->openDelimiter, $this->closeDelimiter] = array_pop($this->delimiters);
298
+ }
299
+
300
+
374
301
  private function normalize(string $str): string
375
302
  {
376
303
  if (str_starts_with($str, "\u{FEFF}")) { // BOM
@@ -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