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() and hasErrors()
  • Basic Nodes: Node types including error node safe-cast methods