Parsing
Error Recovery
Forte is a fault-tolerant parser. It always produces a complete AST, even from broken or malformed input. Errors are collected in a DiagnosticBag rather than throwing exceptions, so you can inspect problems while still working with the valid parts of the tree.
#Error Node Types
When Forte encounters malformed markup, it produces specialized node types that preserve the original source while signaling the problem.
#StrayClosingTagNode
An unmatched closing tag like </div> with no corresponding opening tag is captured as a StrayClosingTagNode:
1<?php
2
3use Forte\Ast\Elements\StrayClosingTagNode;
4use Forte\Facades\Forte;
5
6$doc = Forte::parse('<p>text</p></div>');
7
8$stray = $doc->find(
9 fn($n) => $n instanceof StrayClosingTagNode
10);
11
12$stray->tagNameText(); // "div"
The node provides position information through tagStartOffset(), tagEndOffset(), and tagLength() so you can pinpoint the stray tag in the source.
#BogusCommentNode
Malformed comment-like constructs (e.g., <!invalid>) produce a BogusCommentNode. Use content() to retrieve the inner text, hasClose() to check whether the construct was properly terminated, and isEmpty() to check for empty content.
#ConditionalCommentNode
IE conditional comments (<!--[if IE]>...<![endif]-->) are parsed as ConditionalCommentNode. The node exposes condition() for the condition expression, isDownlevelHidden() and isDownlevelRevealed() for the comment variant, content() for the body, and hasClose() for termination status.
#ProcessingInstructionNode
XML processing instructions (<?xml-stylesheet type="text/xsl"?>) are captured as ProcessingInstructionNode. Use target() for the instruction target, data() for the remaining content, content() for the full inner text, and hasClose() for termination.
#Lexer Errors
The ErrorReason enum covers tokenization failures that the lexer records as diagnostics:
| Error | Description |
|---|---|
UnexpectedNestedEcho |
A {{ }} echo opened inside another echo |
UnexpectedNestedRawEcho |
A {!! !!} echo opened inside another echo |
UnexpectedNestedTripleEcho |
A {{{ }}} echo opened inside another echo |
UnexpectedEof |
End of input reached while scanning a construct |
UnclosedString |
A quoted string was never terminated |
UnclosedComment |
A comment (<!-- --> or {{-- --}}) was never closed |
InvalidEscape |
An invalid escape sequence was encountered |
UnimplementedState |
The lexer reached a state it does not handle |
ConstructCollision |
Two constructs overlap in an invalid way |
PhpCloseTagInComment |
A ?> tag appeared inside a comment |
#Parser Errors
The ParseErrorKind enum covers tree-building failures:
| Error | Description |
|---|---|
UnclosedElement |
An element was never closed (e.g., <div> without </div>) |
UnclosedDirective |
A block directive was never closed (e.g., @if without @endif) |
MismatchedClosingTag |
A closing tag does not match its opening tag |
UnexpectedClosingTag |
A closing tag with no matching opener |
InvalidNesting |
Elements nested in a way the parser cannot resolve |
MissingContent |
A required content section is empty |
InvalidAttribute |
An attribute could not be parsed |
SyntaxError |
A general syntax error |
#Working with Errors
The diagnostics() method on a Document returns a DiagnosticBag containing all errors and warnings from both the lexer and parser:
1<?php
2
3use Forte\Facades\Forte;
4
5$doc = Forte::parse('<div>{{ $unclosed</div>');
6
7$bag = $doc->diagnostics();
8
9$bag->hasErrors(); // true
10$bag->count(); // int (at least 1)
You can filter diagnostics by severity or source stage:
1<?php
2
3$bag->errors(); // Collection<Diagnostic> (errors only)
4$bag->warnings(); // Collection<Diagnostic> (warnings only)
5$bag->fromSource('lexer'); // Collection<Diagnostic> (lexer errors only)
6$bag->fromSource('parser'); // Collection<Diagnostic> (parser errors only)
The format method produces a human-readable summary with line and column numbers:
1<?php
2
3echo $bag->format($doc->source());
#Partial AST Traversal
Even when the document contains errors, the valid parts of the tree are fully queryable. Error nodes coexist with regular nodes, so you can traverse, search, and even rewrite the well-formed portions of a broken template:
1<?php
2
3use Forte\Facades\Forte;
4
5$doc = Forte::parse('<div class="valid">Hello</div></span><p>World</p>');
6
7$div = $doc->findElementByName('div');
8$div->tagNameText(); // "div"
9
10$p = $doc->findElementByName('p');
11$p->tagNameText(); // "p"
12
13$doc->getElements(); // array containing both elements
The stray </span> produces a StrayClosingTagNode, but the <div> and <p> elements are parsed and queryable as normal.
#See also
- Diagnostics: Full DiagnosticBag and Diagnostic API reference
- Documents: The Document class that provides
diagnostics()andhasErrors() - Basic Nodes: Node types including error node safe-cast methods