Error Model

SyntaxError, IndentationError, SyntaxErrorDetails, and PinescriptErrorListener bridge from ANTLR to diagnostics.

This page

Error Model

Parse failures in PYNE are not bare ANTLR console messages. They are structured exceptions with file, line, column, source excerpt, and a caret — suitable for CLI, tests, and LSP adapters.

Abstract

Two layers cooperate:

  1. src/pynescript/ast/error.pySyntaxErrorDetails, SyntaxError, IndentationError.
  2. src/pynescript/ast/grammar/antlr4/error_listener.pyPinescriptErrorListener implements ANTLR’s ErrorListener.syntaxError, builds details from the recognizer + offending token, and raises the project SyntaxError.

helper._parse installs this singleton listener on both lexer and parser after removing default listeners. Indentation problems raised from PinescriptLexerBase use the same exception hierarchy (IndentationError subclass).

Note: these names intentionally shadow Python builtins (SyntaxError, IndentationError) inside the package — import carefully:

from pynescript.ast.error import SyntaxError as PineSyntaxError

Conceptual model

Diagram

Rendering…

Interface surface

SyntaxErrorDetails (NamedTuple)

FieldTypeMeaning
filenamestrPath or <unknown>
linenoint1-based line
offsetint0-based column
textstrSource line (or excerpt)
end_linenoint | NoneMulti-line span end
end_offsetint | NoneEnd column

SyntaxError(Exception)

class SyntaxError(Exception):
    def __init__(self, message: str, *details): ...

details may be:

  • a single SyntaxErrorDetails instance, or
  • positional components matching the NamedTuple fields.

Attributes: message, details.

__str__ renders:

{message}
  File "{filename}", line {lineno}
    {stripped_line}
    {spaces}^

Offset is adjusted when the displayed line is lstrip()’d so the caret still points at the logical column.

IndentationError(SyntaxError)

Empty subclass for indent-stack failures from the lexer base (wrong nest, inconsistent levels). Catch either specifically or via the base SyntaxError.

PinescriptErrorListener

from pynescript.ast.grammar.antlr4.error_listener import PinescriptErrorListener

listener = PinescriptErrorListener.INSTANCE  # singleton
lexer.removeErrorListeners()
lexer.addErrorListener(listener)
parser.removeErrorListeners()
parser.addErrorListener(listener)
MethodRole
syntaxError(...)Build details; raise SyntaxError
_getFilenameFrom(recognizer)Walk Parser → TokenStream → Lexer → InputStream/FileStream
_getInputTextFrom(recognizer, lineno)Full buffer or single line
_splitLinesPortable newline split (\r\n/\n/\r)

If the ANTLR exception e is already a project SyntaxError, its details are updated; otherwise the new error’s __cause__ is set to e.

Internals

Paths

PathRole
src/pynescript/ast/error.pyException types
src/pynescript/ast/grammar/antlr4/error_listener.pyANTLR bridge
src/pynescript/ast/grammar/antlr4/resource/PinescriptLexerBase.pyMay raise IndentationError / SyntaxError during tokenization
src/pynescript/ast/helper.pyWires listener; maps filename onto streams

Filename resolution order (InputStream)

  1. getSourceName() if present
  2. sourceName attribute
  3. input_stream.name (set by helper to the user filename)
  4. "<unknown>"

FileStream uses fileName.

End span from offending token

symbol_len = stop - start + 1
end_lineno = token.line + newlines_in_text
end_offset = adjusted if multiline else column + symbol_len

Mirrors the builder’s location math so diagnostics and AST spans speak the same coordinate system.

Relationship to linter E001

PineLinter._check_syntax catches any Exception from parse and wraps it as LintWarning(code="E001", severity="error"). It does copy details.lineno / details.offset onto the warning when present, and prefers the short .message over the caret __str__. For the full caret rendering, catch pynescript.ast.error.SyntaxError at the call site instead of only reading linter output.

LSP / tooling

Adapters typically map:

Exception fieldLSP Diagnostic
lineno / offsetRange.start
end_lineno / end_offsetRange.end
messagemessage
IndentationErrorsame, possibly different code

Invariants

  1. Default ANTLR listeners must stay removed on the parse path — otherwise errors print twice and may not raise.
  2. Singleton listener is stateless enough to share (INSTANCE); thread-local needs would require new instances.
  3. Raising aborts parse immediately — no error recovery producing a partial tree through the public helper.
  4. Package SyntaxError ≠ builtinexcept SyntaxError without import may catch the wrong class.

Worked examples

Catching a parse error

from pynescript.ast.helper import parse
from pynescript.ast.error import SyntaxError as PineSyntaxError

try:
    parse("f( =>\n", filename="bad.pine")
except PineSyntaxError as e:
    print(e)  # caret form
    print(e.details.lineno, e.details.offset)
    print(e.details.filename)

Distinguishing indentation

from pynescript.ast.error import IndentationError as PineIndentError
from pynescript.ast.error import SyntaxError as PineSyntaxError

try:
    parse("if true\nplot(1)\n")  # missing indent under if — may indent-error depending on tokens
except PineIndentError as e:
    print("indent", e.message)
except PineSyntaxError as e:
    print("syntax", e.message)

Manual construction (tests)

from pynescript.ast.error import SyntaxError, SyntaxErrorDetails

details = SyntaxErrorDetails("t.pine", 3, 4, "    plot(\n", 3, 9)
err = SyntaxError("missing RPAR", details)
assert "t.pine" in str(err)
assert "^" in str(err)

Failure modes

FailureCause
TypeError: unexpected type of inputListener received a recognizer/stream combo it does not support
Caret misalignedTabs vs spaces; display strips leading WS while offset counts raw
Filename always <unknown>Stream name not set (bypassed helper)
Exception swallowed as generic E001Only used linter path (line/column may still be filled from details)
except SyntaxError misses Pine errorsCaught builtin only
Partial treesNot available via parse() after error — redesign would need recovery listeners

See also