[Language Core]
ANTLR4 grammar, ASDL AST, builder, visitors, unparser, type system, linter, and error model for PYNE.
Language Core
The language core is the front half of the evaluate contract: source text becomes a typed tree; the tree can be walked, rewritten, dumped, and unparsed without ever entering the bar loop.
Abstract
PYNE treats Pine Script as a formal pipeline, not a host plugin. Lexing and parsing are ANTLR4-driven. The concrete syntax tree is lowered by a hand-written visitor (PinescriptASTBuilder) into ASDL-generated dataclasses. Public helpers in helper.py mirror Python’s ast module: parse, unparse, dump, walk, literal_eval. Static tools (linter, type registry, LSP) consume the same tree. Evaluation is out of scope here — see Runtime.
Hard rule: edit grammar only under resource/. Paths under generated/ are regenerated artifacts and must not be hand-edited.
Conceptual model
| Stage | Role | Primary path |
|---|---|---|
| Grammar | Concrete syntax | src/pynescript/ast/grammar/antlr4/resource/*.g4 |
| ASDL | Algebraic node shape | src/pynescript/ast/grammar/asdl/resource/Pinescript.asdl |
| Builder | CST → AST | src/pynescript/ast/builder.py |
| Helper | Public parse/unparse API | src/pynescript/ast/helper.py |
| Nodes | Re-export generated types | src/pynescript/ast/node.py |
Interface surface
Consumers almost always enter through pynescript.ast.helper:
from pynescript.ast.helper import parse, unparse, dump, walk, literal_eval
tree = parse('//@version=5\nindicator("x")\nplot(close)')
print(dump(tree, indent=2))
print(unparse(tree))
Modes:
mode | Entry rule | Root node |
|---|---|---|
"exec" (default) | start_script | Script |
"eval" | start_expression | Expression |
Full page inventory:
| Page | Topic |
|---|---|
| Grammar (ANTLR4) | Resource vs generated; regen; v6 triple strings |
| ASDL schema | Algebraic AST; codegen |
| Builder | CST visitor → ASDL nodes |
| Helper API | parse, dump, walk, locations |
| Unparser | Precedence-aware source regen |
| Visitor / transformer | Read vs rewrite |
| Type system | Qualifiers, UDT registry |
| Linter | Static rules, codes |
| Error model | SyntaxError, indent, listener |
Internals
src/pynescript/ast/
helper.py # public pipeline orchestration
builder.py # PinescriptASTBuilder
unparser.py # NodeUnparser + Precedence
visitor.py # NodeVisitor
transformer.py # NodeTransformer
collector.py # StatementCollector (annotation pairing)
linter.py # PineLinter
type_system.py # Type*, TypeRegistry, MethodResolver
error.py # SyntaxError / IndentationError
node.py # from grammar.asdl.generated import *
grammar/
antlr4/
resource/ # EDIT HERE: .g4 + *Base.py
generated/ # NEVER HAND-EDIT
error_listener.py
tool/generate.py
asdl/
resource/ # EDIT HERE: Pinescript.asdl
generated/ # NEVER HAND-EDIT
tool/generate.py
Thin wrappers grammar/antlr4/lexer.py, parser.py, visitor.py re-export generated classes so application code imports stable paths.
Invariants
- Resource is source of truth. Lexer/parser
.g4andPinescript.asdldefine syntax and node shape; generated Python is a build product (committed so install does not require Java). - Round-trip is a test oracle. For the supported surface,
unparse(parse(src))should re-parse and preserve semantics (not byte-identical formatting). - Locations are first-class. Statements and expressions carry
lineno,col_offset, optionalend_*for diagnostics and LSP. - Annotations are not free-floating. Comment tokens on
COMMENT_CHANNELare reified asCommentnodes and attached toScript/FunctionDef/TypeDef/Assignvia kind suffixes (S,F,T,V). - Recursion budget. Deep ternary chains raise the Python recursion limit temporarily during parse (cap ≥ 5000).
Worked examples
Minimal script:
from pynescript.ast.helper import parse, dump
src = """
//@version=5
indicator("demo")
x = ta.sma(close, 14)
plot(x)
"""
tree = parse(src)
assert tree.__class__.__name__ == "Script"
assert any("version" in a for a in (tree.annotations or []))
print(dump(tree, include_attributes=True, indent=2))
Expression-only eval mode:
from pynescript.ast.helper import parse, literal_eval
expr = parse("1 + 2 * 3", mode="eval")
assert literal_eval(expr) == 7
Failure modes
| Symptom | Likely cause | Where to look |
|---|---|---|
SyntaxError with caret | Lexer/parser reject | Error model |
IndentationError | Bad INDENT/DEDENT from LexerBase | PinescriptLexerBase.py |
Missing visit_* after grammar change | Builder not updated for new rule | Builder |
Hand-edit of generated/ lost on regen | Violated resource-only rule | Grammar |
Full regen breaks builder.py | Parser context accessors changed | Prefer targeted lexer refresh (v6 case study) |