Code Transformers
The transformation logic is split into specialized transformers found in src/transpiler/transformers/.
WrapperTransformer
Ensures the user code is wrapped in a standard context function:
(context) => {
// user code
}
NormalizationTransformer
Prevents users from aliasing standard Pine Script symbols in import destructuring. This ensures the transpiler can reliably identify standard variables like close or ta.
InjectionTransformer
Scans for usage of standard variables (like close, open) and injects the necessary destructuring statements if they are missing:
const { close, open } = context.data;
MainTransformer & StatementTransformer
These handle the bulk of the logic:
- Variable Declaration: Transforms
let x = ...into$.let.scope_x = $.init(...). - Assignment: Transforms
x = ...into$.set($.let.scope_x, ...). - Array Access: Transforms
x[1]into$.get($.let.scope_x, 1). - Loops/Conditionals: Ensures scoping rules are respected within blocks.
ExpressionTransformer
Handles expressions, primarily function calls and binary operations.
- Function Calls: Injects
param()wrappers around arguments. - Hoisting: “Unwraps” nested calls.
ta.ema(ta.sma(close, 10), 10)is transformed into:- Calculate SMA -> temp var.
- Calculate EMA using temp var. This simplifies the generated code and ensures proper order of operations for stateful functions.
- Lazy operands (no hoisting): Pine evaluates
?:branches lazily in every version, and the right operand ofand/orlazily from v6 (v5 is strict). Hoisting a call out of such a position would run it unconditionally —size(a) > 0 ? array.get(a, 0) : nawould throw on an empty array, and ata.*call in an untaken branch would execute on every bar.analysis/LazyOperandPass.tstags nodes in lazy positions with_lazyOperand;transformCallExpressionthen keeps those calls inline (hoisting suppressed for the call and its arguments) and the expression walkers stop descending into them (isInlinedLazyCall). Built-in TA variables (ta.tr,ta.obv, …) are the exception and are still hoisted to the script body viaaddOuterHoistedStatement, because they must run on every bar regardless of position.request.*calls are the other exception: TradingView computes the requested expression on every bar of the other context whatever chart-side condition wraps the call, so they stay hoisted (a ternary inside the requested expression is still lazy). For Pine v5 sources anand/orthat sits inside a lazy branch is tagged_strictLogicaland lowered bytransformStrictLogicalOperatorsto$.pine.math.__and/__or, so both operands are still evaluated (v5 strictness) but only when the branch is taken.