[Library API]

Embed pynescript: parse, dump, unparse, walk, transform, and lint Pine Script™ from Python.

Library API

Abstract

The public Python surface for language work lives primarily under pynescript.ast: parse source into an ASDL-backed tree, serialize it (dump), regenerate source (unparse), traverse (walk, visitors), rewrite (NodeTransformer), and lint (lint_script). Expression evaluation (literal_eval) and full script execution (evaluator / Runtime) sit adjacent; this page focuses on structural APIs. Evaluation workflows are covered in Evaluate scripts.

Conceptual model

Design intent mirrors Python’s ast module: stable tree operations, location attributes, and visitor protocols — specialized for Pine’s script model (annotations, series-aware semantics live at evaluation time).

Interface surface

Imports

# Re-exported convenience (node classes, helpers, visitors, transformers)
from pynescript.ast import parse, unparse, dump, walk
from pynescript.ast import NodeVisitor, NodeTransformer
from pynescript.ast.helper import literal_eval, iter_child_nodes, get_source_segment
from pynescript.ast.linter import lint_script, lint_file, PineLinter, LintWarning

pynescript.ast.__init__ star-imports helper, node, visitor, transformer, and error.

parse(source, filename="<unknown>", mode="exec") -> AST

ArgMeaning
sourceFull script or expression text
filenameError reporting; absolutized if file exists
mode"exec" → script statements; "eval" → single expression

Raises ValueError on bad mode; SyntaxError (via error listener) on grammar failure.

tree = parse('//@version=5\nindicator("x")\nplot(close)\n')
expr = parse("close > open", mode="eval")

unparse(node) -> str

Round-trips an AST through NodeUnparser.

normalized = unparse(parse(messy_source))

dump(node, *, annotate_fields=True, include_attributes=False, indent=None) -> str

Human-readable tree serialization (debug, tests, CLI).

print(dump(tree, indent=2, include_attributes=True))

walk(node) -> Iterator[AST]

Breadth-first (deque) traversal yielding every node.

from pynescript.ast import walk
names = [n for n in walk(tree) if n.__class__.__name__ == "Name"]

Location helpers

FunctionRole
copy_location(new, old)Copy lineno/col offsets
fix_missing_locations(node)Fill missing positions
increment_lineno(node, n=1)Shift lines
get_source_segment(source, node, *, padded=False)Slice original text
iter_fields / iter_child_nodesSchema-aware iteration

Visitors and transformers

from pynescript.ast import NodeVisitor, NodeTransformer, parse

class CallCounter(NodeVisitor):
    def __init__(self):
        self.calls = 0
    def visit_Call(self, node):
        self.calls += 1
        self.generic_visit(node)

class RenameClose(NodeTransformer):
    def visit_Name(self, node):
        # {/* TODO: verify exact Name field names on your generated nodes */}
        if getattr(node, "id", None) == "close":
            node.id = "price"
        return node

tree = parse(source)
CallCounter().visit(tree)
tree2 = RenameClose().visit(tree)

Linter

from pynescript.ast.linter import lint_script, LintWarning

warnings: list[LintWarning] = lint_script(source, "strat.pine")
for w in warnings:
    print(w.code, w.severity, w.line, w.message)
Code familyExamples
E001Syntax error (error severity)
W001W002Missing / old @version
W101W103Deprecated patterns
C001C004Naming / style (line length 120, trailing newline, …)

PineLinter.lint runs: syntax → version → deprecated → naming → style.

literal_eval (bridge to evaluation)

from pynescript.ast.helper import literal_eval

literal_eval("ta.sma([1,2,3,4,5], 3)")
literal_eval("close[1]", {"close": [10.0, 11.0, 12.0]})

Optional data_feed / data_provider for request.* in literal contexts.

Internals (repo paths)

PathRole
src/pynescript/ast/helper.pyPublic parse/dump/walk/unparse/literal_eval
src/pynescript/ast/builder.pyParse tree → ASDL nodes
src/pynescript/ast/unparser.pyNodeUnparser
src/pynescript/ast/visitor.pyNodeVisitor
src/pynescript/ast/transformer.pyNodeTransformer
src/pynescript/ast/node.pyNode re-exports / helpers
src/pynescript/ast/grammar/asdl/resource/Pinescript.asdlAlgebraic schema
src/pynescript/ast/linter.pyStatic rules
src/pynescript/ast/error.pyError types
examples/parse_dump_unparse.pyRound-trip demo

Pipeline inside parse:

  1. InputStream / FileStream
  2. PinescriptLexer + PinescriptParser (error listener)
  3. PinescriptASTBuilder.visit
  4. In exec mode: StatementCollector, comment collection, @ annotation attach

Invariants & edge cases

  • Annotation comments (//@version, etc.) attach to script / function / type / assign nodes by kind suffix — see _add_annotations in helper.py.
  • Deep nesting: temporary sys.setrecursionlimit(max(old, 5000)) during parse.
  • dump requires a true AST nodeTypeError otherwise.
  • Transformers mutate or replace nodes depending on your visit methods; prefer returning new nodes if you need purity.
  • Round-trip fidelity is tested against a large corpus but is not a proof of bit-identical source; whitespace and some sugar may normalize.
  • Do not edit generated grammar modules; extend via resource grammars if contributing.

Worked examples

End-to-end inspect

from pynescript.ast import parse, dump, unparse, walk

source = open("examples/rsi_strategy.pine", encoding="utf-8").read()
tree = parse(source, "examples/rsi_strategy.pine")
print(dump(tree, indent=2)[:500], "...")
print("---")
print(unparse(tree))
print("node count", sum(1 for _ in walk(tree)))

Collect all call names

from pynescript.ast import parse, NodeVisitor

class Calls(NodeVisitor):
    def __init__(self):
        self.names = []
    def visit_Call(self, node):
        func = node.func
        # Attribute vs Name depending on ta.rsi vs f()
        self.names.append(func)
        self.generic_visit(node)

c = Calls()
c.visit(parse(source))

Lint programmatically with fail semantics

from pynescript.ast.linter import lint_script

def assert_clean(path: str) -> None:
    text = open(path, encoding="utf-8").read()
    issues = lint_script(text, path)
    errors = [i for i in issues if i.severity == "error"]
    if errors:
        raise SystemExit("\n".join(map(str, errors)))

Expression mode for tooling

from pynescript.ast import parse, dump

print(dump(parse("ta.rsi(close, 14)", mode="eval"), indent=2))

Failure modes

Exception / resultMeaning
SyntaxErrorLexer/parser rejection
ValueError: invalid argument modemode not in {exec,eval}
Empty / odd treeEmpty body scripts; annotations-only edge cases
Unparse mismatchNode kinds without unparser support — file issue against unparser
Linter false positivesStyle rules are heuristic (regex naming) — treat C00x as advisory

See also