Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ parameters:
paths:
- src/Latte/Compiler/TagParser.php
- src/Latte/Compiler/TemplateParser.php
- src/Latte/Compiler/TemplateParserHtml.php

- # TemplateParser: nullable Tag when processing nested tags
message: '#on Latte\\Compiler\\Tag\|null#'
Expand All @@ -65,7 +66,7 @@ parameters:
# --- TagParser: generated trait TagParserData ---

- # Methods called from generated trait TagParserData (excluded from analysis)
message: '#^(Static method|Method) Latte\\Compiler\\TagParser::(checkFunctionName|handleBuiltinTypes|parseOffset|parseDocString)\(\) is unused\.$#'
message: '#^(Static method|Method) Latte\\Compiler\\TagParser::(checkFunctionName|handleBuiltinTypes|parseOffset|parseDocString|createPosition)\(\) is unused\.$#'
path: src/Latte/Compiler/TagParser.php

- # Internal parser state machine — types from regex results and token operations
Expand Down
4 changes: 4 additions & 0 deletions src/Latte/Compiler/Nodes/StatementNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

namespace Latte\Compiler\Nodes;

use Latte\Compiler\Position;


/**
* Base for Latte tags like {if}, {foreach}, {block}.
Expand All @@ -16,4 +18,6 @@
*/
abstract class StatementNode extends AreaNode
{
/** @var list<Position> positions of all tags (opening, intermediate like {else}, closing) */
public array $tagPositions = [];
}
15 changes: 15 additions & 0 deletions src/Latte/Compiler/Position.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,29 @@
*/
final readonly class Position
{
public static function range(?self $start, ?self $end): ?self
{
return $start && $end
? $start->withLength($end->offset + $end->length - $start->offset)
: $start;
}


public function __construct(
public int $line = 1,
public int $column = 1,
public int $offset = 0,
public ?int $length = null,
) {
}


public function withLength(int $length): self
{
return new self($this->line, $this->column, $this->offset, $length);
}


/**
* Returns a new position advanced by the length of the given string.
*/
Expand Down
19 changes: 16 additions & 3 deletions src/Latte/Compiler/TagParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ public function tryConsumeTokenBeforeUnquotedString(string ...$kind): ?Token
{
$token = $this->stream->peek();
return $token->is(...$kind) // is followed by whitespace
&& $this->stream->peek(1)->position->offset > $token->position->offset + strlen($token->text)
&& $this->stream->peek(1)->position->offset > $token->position->offset + $token->position->length
? $this->stream->consume()
: null;
}
Expand All @@ -164,6 +164,7 @@ private function parse(string $schema, bool $recovery = false): mixed
{
$symbol = self::SymbolNone; // We start off with no lookahead-token
$this->startTokenStack = []; // Keep stack of start token
$this->endTokenStack = []; // Keep stack of end token
$token = null;
$state = 0; // Start off in the initial state and keep a stack of previous states
$stateStack = [$state];
Expand All @@ -177,7 +178,7 @@ private function parse(string $schema, bool $recovery = false): mixed
} else {
if ($symbol === self::SymbolNone) {
$recovery = $recovery
? [$this->stream->getIndex(), $state, $stateStack, $stackPos, $this->semValue, $this->semStack, $this->startTokenStack]
? [$this->stream->getIndex(), $state, $stateStack, $stackPos, $this->semValue, $this->semStack, $this->startTokenStack, $this->endTokenStack]
: null;


Expand Down Expand Up @@ -208,6 +209,7 @@ private function parse(string $schema, bool $recovery = false): mixed
$stateStack[$stackPos] = $state = $action;
$this->semStack[$stackPos] = $token->text;
$this->startTokenStack[$stackPos] = $token;
$this->endTokenStack[$stackPos] = $token;
$symbol = self::SymbolNone;
if ($action < self::NumNonLeafStates) {
continue;
Expand All @@ -228,6 +230,7 @@ private function parse(string $schema, bool $recovery = false): mixed
return $this->semValue;

} elseif ($rule !== self::UnexpectedTokenRule) { // reduce
$lastEndToken = $this->endTokenStack[$stackPos] ?? $token;
$this->reduce($rule, $stackPos);

// Goto - shift nonterminal
Expand All @@ -244,12 +247,13 @@ private function parse(string $schema, bool $recovery = false): mixed
++$stackPos;
$stateStack[$stackPos] = $state;
$this->semStack[$stackPos] = $this->semValue;
$this->endTokenStack[$stackPos] = $lastEndToken;
if ($ruleLength === 0) {
$this->startTokenStack[$stackPos] = $token;
}

} elseif ($recovery && $this->isExpectedEof($state)) { // recoverable error
[, $state, $stateStack, $stackPos, $this->semValue, $this->semStack, $this->startTokenStack] = $recovery;
[, $state, $stateStack, $stackPos, $this->semValue, $this->semStack, $this->startTokenStack, $this->endTokenStack] = $recovery;
$this->stream->seek($recovery[0]);
$token = new Token(Token::End, '');
goto recovery;
Expand Down Expand Up @@ -291,6 +295,15 @@ private function isExpectedEof(int $state): bool
}


private function createPosition(int $startPos, int $endPos): ?Position
{
return Position::range(
$this->startTokenStack[$startPos]->position,
$this->endTokenStack[$endPos]->position,
);
}


public function throwReservedKeywordException(Token $token): never
{
throw new Latte\CompileException("Keyword '$token->text' cannot be used in Latte.", $token->position);
Expand Down
Loading
Loading