Skip to content

Commit 44fcc8c

Browse files
committed
Track column position for each token
1 parent 9172bea commit 44fcc8c

File tree

5 files changed

+59
-21
lines changed

5 files changed

+59
-21
lines changed

composer.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ParserAbstract.php

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use DevTheorem\HandlebarsParser\Ast\InverseChain;
1717
use DevTheorem\HandlebarsParser\Ast\Literal;
1818
use DevTheorem\HandlebarsParser\Ast\MustacheStatement;
19+
use DevTheorem\HandlebarsParser\Ast\Node;
1920
use DevTheorem\HandlebarsParser\Ast\OpenBlock;
2021
use DevTheorem\HandlebarsParser\Ast\OpenHelper;
2122
use DevTheorem\HandlebarsParser\Ast\OpenPartialBlock;
@@ -167,13 +168,14 @@ protected function postprocessTokens(array $tokens): array
167168

168169
if ($numTokens === 0) {
169170
// empty input - just add sentinel token
170-
return [new Token(Lexer::T_EOF, "\0", 0)];
171+
return [new Token(Lexer::T_EOF, "\0", 0, 0)];
171172
}
172173

173174
$lastToken = $tokens[$numTokens - 1];
174175

175176
// Add sentinel token
176-
$tokens[] = new Token(Lexer::T_EOF, "\0", $lastToken->line);
177+
$column = $lastToken->column + strlen($lastToken->text);
178+
$tokens[] = new Token(Lexer::T_EOF, "\0", $lastToken->line, $column);
177179
return $tokens;
178180
}
179181

@@ -384,6 +386,12 @@ protected function getErrorMessage(int $symbol, int $state, int $line): string
384386
return "Parse error on line $line" . $expectedString . ', got ' . $this->symbolToName[$symbol];
385387
}
386388

389+
private function getNodeError(string $message, Node $node): string
390+
{
391+
$start = $node->loc->start;
392+
return $message . ' - ' . $start->line . ':' . $start->column;
393+
}
394+
387395
/**
388396
* Get limited number of expected tokens in given state.
389397
*
@@ -505,7 +513,10 @@ protected function locInfo(int $tokenStartPos, int $tokenEndPos): SourceLocation
505513
$source .= $token->text;
506514
}
507515

508-
return new SourceLocation($source, new Position($startToken->line, -1), new Position($endToken->line, -1));
516+
$start = new Position($startToken->line, $startToken->column);
517+
$end = new Position($endToken->line, $endToken->column + strlen($endToken->text) - 1);
518+
519+
return new SourceLocation($source, $start, $end);
509520
}
510521

511522
protected function id(string $token): string
@@ -539,7 +550,7 @@ protected function prepareProgram(array $statements, ?SourceLocation $loc = null
539550
$lastLoc = $statements[count($statements) - 1]->loc;
540551
$loc = new SourceLocation($firstLoc->source, $firstLoc->start, $lastLoc->end);
541552
} else {
542-
$loc = new SourceLocation('', new Position(0, -1), new Position(0, -1));
553+
$loc = new SourceLocation('', new Position(0, 0), new Position(0, 0));
543554
}
544555
}
545556

@@ -579,7 +590,8 @@ private function validateClose(OpenHelper $open, CloseBlock|string $close): void
579590
}
580591

581592
if ($open->path->original !== $close) {
582-
throw new \Exception("{$open->path->original} doesn't match {$close}");
593+
$msg = $this->getNodeError("{$open->path->original} doesn't match {$close}", $open->path);
594+
throw new \Exception($msg);
583595
}
584596
}
585597

@@ -610,7 +622,8 @@ protected function preparePath(bool $data, SubExpression|null $sexpr, array $par
610622

611623
if (!$isLiteral && ($part === '..' || $part === '.' || $part === 'this')) {
612624
if (count($tail) > 0) {
613-
throw new \Exception('Invalid path: ' . $original);
625+
$msg = $this->getNodeError("Invalid path: $original", new Node('', $loc));
626+
throw new \Exception($msg);
614627
} elseif ($part === '..') {
615628
$depth++;
616629
}

src/Phlexer/Phlexer.php

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ public function getNextToken(): ?Token
5757
return null;
5858
}
5959

60-
$line = substr_count($this->text, "\n", 0, $this->cursor + 1) + 1;
6160
$subject = substr($this->text, $this->cursor);
6261

6362
foreach ($this->rules as $rule) {
@@ -66,6 +65,7 @@ public function getNextToken(): ?Token
6665
}
6766

6867
if (preg_match("/\\A{$rule->pattern}/", $subject, $matches)) {
68+
[$line, $column] = $this->getPosition();
6969
$this->yytext = $matches[0];
7070
$this->cursor += strlen($this->yytext);
7171
$tokenName = ($rule->handler)();
@@ -75,13 +75,37 @@ public function getNextToken(): ?Token
7575
return $this->getNextToken();
7676
}
7777

78-
return new Token($tokenName, $this->yytext, $line);
78+
return new Token($tokenName, $this->yytext, $line, $column);
7979
}
8080
}
8181

8282
throw new \Exception('Unexpected token: "' . $subject[0] . '"');
8383
}
8484

85+
/**
86+
* @return array{int, int}
87+
*/
88+
private function getPosition(): array
89+
{
90+
$line = 1;
91+
$column = -1;
92+
93+
for ($i = 0; $i < $this->cursor + 1; $i++) {
94+
if ($this->text[$i] === "\n") {
95+
$line++;
96+
$column = -1;
97+
} else {
98+
$column++;
99+
}
100+
}
101+
102+
if ($column === -1) {
103+
$column = 0;
104+
}
105+
106+
return [$line, $column];
107+
}
108+
85109
protected function pushState(string $state): void
86110
{
87111
$this->states[] = $state;

src/Phlexer/Token.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ public function __construct(
88
public string $name,
99
public string $text,
1010
public int $line,
11+
public int $column,
1112
) {}
1213
}

tests/LexerTest.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,14 @@ public function testLineNumbers(): void
5959
{{lines}}
6060
_tpl;
6161
$expected = [
62-
new Token(Lexer::T_CONTENT, "This\nis a ", 1),
63-
new Token(Lexer::T_OPEN, '{{', 2),
64-
new Token(Lexer::T_ID, 'template', 2),
65-
new Token(Lexer::T_CLOSE, '}}', 2),
66-
new Token(Lexer::T_CONTENT, "\nwith multiple\n", 3),
67-
new Token(Lexer::T_OPEN, '{{', 4),
68-
new Token(Lexer::T_ID, 'lines', 4),
69-
new Token(Lexer::T_CLOSE, '}}', 4),
62+
new Token(Lexer::T_CONTENT, "This\nis a ", 1, 0),
63+
new Token(Lexer::T_OPEN, '{{', 2, 5),
64+
new Token(Lexer::T_ID, 'template', 2, 7),
65+
new Token(Lexer::T_CLOSE, '}}', 2, 15),
66+
new Token(Lexer::T_CONTENT, "\nwith multiple\n", 3, 0),
67+
new Token(Lexer::T_OPEN, '{{', 4, 0),
68+
new Token(Lexer::T_ID, 'lines', 4, 2),
69+
new Token(Lexer::T_CLOSE, '}}', 4, 7),
7070
];
7171
$this->assertEquals($expected, (new Lexer())->tokenize($template));
7272
}

0 commit comments

Comments
 (0)