Helper API
parse, sha256 LRU cache, unparse, dump, walk, literal_eval, and location utilities — the public AST surface.
This page
Helper API
src/pynescript/ast/helper.py is the stable library face of the language core. It orchestrates ANTLR, the builder, annotations, and the unparser behind functions deliberately similar to CPython’s ast module.
Abstract
Call parse to obtain an ASDL tree; dump / walk / iter_* to inspect it; unparse to regenerate source; literal_eval for constant-folding-safe evaluation of literal expressions. Location helpers (copy_location, fix_missing_locations, get_source_segment, increment_lineno) support tooling that rewrites or reports on trees. clear_parse_cache / parse_cache_info control the process-local sha256 LRU.
Everything higher in the stack (CLI, LSP, Pro API, workers) ultimately routes through this module for front-end work.
Conceptual model
Rendering…
Interface surface
parse(source, filename="<unknown>", mode="exec") -> AST
| Parameter | Meaning |
|---|---|
source | Full script or expression text |
filename | Error reporting name; resolved to absolute path if the file exists |
mode | "exec" → start_script → Script; "eval" → start_expression → Expression |
Raises:
ValueError— invalid modepynescript.ast.error.SyntaxError— lexer/parser failure (viaPinescriptErrorListener)
Side effects during exec mode:
- Temporarily raises
sys.getrecursionlimitto at least 5000. - Collects COMMENT tokens containing
@and attaches annotations to eligible statements. If the source has no@, the annotation pass is skipped.
filename is not part of the cache key (only diagnostics).
Parse cache
Successful trees are stored in a process-local, thread-safe OrderedDict LRU keyed by (sha256(source.encode("utf-8")), mode).
| Control | Default | Meaning |
|---|---|---|
PYNE_PARSE_CACHE | on | 0 / false / off / no disables |
PYNE_PARSE_CACHE_MAX | 128 | Max entries (must be ≥ 1) |
clear_parse_cache() | — | Drop all entries and reset hit/miss counters |
parse_cache_info() | — | {enabled, size, maxsize, hits, misses} |
On a cache hit, the same AST object is returned (after scrubbing stale _pine_call_site attrs). Treat it as read-only: a NodeTransformer or increment_lineno mutates the cached entry for every later caller. After intentional mutation, call clear_parse_cache() or parse with the cache disabled.
unparse(node) -> str
Delegates to unparse_node — a per-thread reused NodeUnparser (warm visitor cache). Public return value is still a new str. See Unparser.
dump(node, *, annotate_fields=True, include_attributes=False, indent=None) -> str
Recursive pretty-print of node trees. With indent (int spaces or string), multi-line layout is used for non-trivial nodes.
literal_eval(node_or_string, context=None, data_feed=None, data_provider=None) -> Any
- Strings are
parse(..., mode="eval")first. - Unwraps
Expression.body. - Uses
NodeLiteralEvaluator— not full script evaluation. Non-literal graphs raise.
Tree navigation
| Function | Behavior |
|---|---|
iter_fields(node) | Yields (name, value) for set _fields |
iter_child_nodes(node) | Direct AST children (incl. list items) |
walk(node) | BFS over the full subtree |
Location utilities
| Function | Behavior |
|---|---|
copy_location(new, old) | Copy lineno/col/end_* when both define the attribute |
fix_missing_locations(node) | Fill gaps from parent defaults starting at (1, 0) |
increment_lineno(node, n=1) | Shift all line numbers |
get_source_segment(source, node, *, padded=False) | Slice original source by node span; None if incomplete ends |
__all__ export list
clear_parse_cache, copy_location, dump, fix_missing_locations, get_source_segment, increment_lineno, iter_child_nodes, iter_fields, literal_eval, parse, parse_cache_info, unparse, walk.
Internals
Path
src/pynescript/ast/helper.py — all public helpers above.
Related:
src/pynescript/ast/collector.py—StatementCollectorfor annotation pairingsrc/pynescript/ast/unparser.py—unparse_node(thread-localNodeUnparser)src/pynescript/ast/evaluator—NodeLiteralEvaluator(literal_eval only)
Parse pipeline (_parse)
- Validate
mode∈{exec, eval}. - Raise recursion limit to ≥ 5000 if needed.
- Bind a thread-local lexer + token stream + parser (
_ThreadParseEngine). Process-wide reuse is unsafe (indent / token-stream bleed). - SLL first (
BailErrorStrategy). OnParseCancellationException, reset and re-parse LL (DefaultErrorStrategy). Trees match pure-LL on success. PinescriptASTBuilder.visitvia the shared stateless builder.- Exec mode: if source contains
@, collect statements +@comments and_add_annotations.
Default ANTLR console listeners are removed; only PinescriptErrorListener.INSTANCE is installed.
Annotation algorithm (_add_annotations)
- Merge comments and statements; sort by
(lineno, col_offset). - Group consecutive comments vs statements.
- Keep only kinds starting with
@. - Script-level: first group members with kind ending in
S→script.annotations. - Pair remaining comment groups with following statements; attach
F/T/Vfilters toFunctionDef/TypeDef/Assign.
Comment nodes are not left in Script.body; only string annotation lists are stored on targets. Collection itself skips tokens whose text has no @ (plain // and //# region never become Comment nodes).
Stream helpers
| Internal | Role |
|---|---|
_parse_inputstream | InputStream(source) + stream.name = filename |
_parse_filestream | FileStream for on-disk scripts |
_get_absolute_path | Resolve existing paths; leave <unknown> alone |
Invariants
modeis a closed set. Onlyexecandeval.- Default listeners are removed. Console ANTLR spam is replaced by raising project
SyntaxError. dumprequires anASTinstance. Non-nodes raiseTypeError.- Round-trip tests should use
parse+unparse, not raw builder access, so annotation behavior matches production. - Cache identity is shared. Do not mutate a cached tree unless you also
clear_parse_cache(). filenameis diagnostic-only and is not part of the sha256 key.
Worked examples
Inspect a tree
from pynescript.ast.helper import parse, dump, walk
tree = parse("""
//@version=6
indicator("demo")
plot(close)
""")
print(dump(tree, indent=2))
print(sum(1 for _ in walk(tree)), "nodes")
Source segment
from pynescript.ast.helper import parse, get_source_segment
src = "a = 1\nb = a + 2\n"
tree = parse(src)
assign_b = tree.body[1]
print(get_source_segment(src, assign_b)) # "b = a + 2\n" or similar span
Literal evaluation
from pynescript.ast.helper import literal_eval
assert literal_eval("2 * 3 + 4") == 10
assert literal_eval("'hi'") == "hi"
Parse cache
from pynescript.ast.helper import parse, parse_cache_info, clear_parse_cache
src = 'indicator("x")\nplot(close)\n'
a = parse(src)
b = parse(src)
assert a is b # same object on hit
print(parse_cache_info()) # enabled, size, maxsize, hits, misses
clear_parse_cache()
Failure modes
| Failure | Notes |
|---|---|
| Deeply nested ternaries hit recursion | Mitigated by temporary limit ≥ 5000; pathological depth can still fail |
get_source_segment returns None | Missing end_lineno / end_col_offset |
literal_eval raises | Non-literal AST (names, calls beyond allowed set) |
Filename <unknown> in errors | Expected when parsing pure strings without a path |
| Annotations missing | Comment not @… form, or not immediately preceding eligible stmt |
Transformer “leaks” across parse() calls | Cache hit returned the same object you mutated — clear or disable cache |
PYNE_PARSE_CACHE_MAX ignored | Non-integer or < 1 falls back to 128 |