nk 1.0.0
C++ Numerical Computing Runtime
Loading...
Searching...
No Matches
tree_walker.hpp
1// include/tree_walker.hpp
2#pragma once
3
4#include <nk/core/debugger.hpp>
5#include <nk/core/types.hpp>
6
7#include <string>
8#include <vector>
9
10namespace nk {
11
12class Engine;
13
14class TreeWalker
15{
16public:
17 explicit TreeWalker(Engine &engine);
18
19 Value execute(const ASTNode *ast, Environment *env);
20 void setMaxRecursionDepth(int d) { maxRecursionDepth_ = d; }
21
22 // True when executing inside a user function (not top-level script)
23 int callDepth() const { return currentRecursionDepth_; }
24
25 // Active call stack (parallel to C++ recursion of callUserFunction).
26 // Maintained via FrameGuard RAII so it stays in sync across exception
27 // unwind. Front = outermost; back = innermost. Used by Engine's
28 // callerEnv() / inputName() so builtins can introspect the stack
29 // regardless of which backend is active.
31 {
32 Environment *env = nullptr;
33 // Arg names from caller's CALL site (bare identifier or empty).
34 // Read by inputname(k).
35 std::vector<std::string> callerArgNames;
36 };
37 const std::vector<ActiveFrame> &activeFrames() const { return activeFrames_; }
38
39 // ── Public callback API ───────────────────────────────────
40 // Used by Engine::callFunctionHandle so that builtins (e.g.
41 // cellfun, fzero, integral) can invoke a function handle from C++
42 // regardless of the active backend (TW or VM). Both forms also
43 // accept anonymous handles — they're stored as synthetic user
44 // functions and looked up via lookupUserFunction.
45 Value callHandlePublic(const Value &handle,
47 Environment *env);
48 std::vector<Value> callHandleMultiPublic(const Value &handle,
50 Environment *env,
51 size_t nout);
52
53 // classdef support (runs method/constructor bodies on the TreeWalker
54 // regardless of the active backend; the object dispatch that calls
55 // these may originate in the VM). `runClassMethod` passes args verbatim
56 // (the caller already prepends `self`). `runClassCtor` pre-seeds the
57 // constructor's output variable with the default-constructed instance
58 // (MATLAB's implicit `obj` initialisation), then returns it.
59 std::vector<Value> runClassMethod(const UserFunction &uf, Span<const Value> args,
60 size_t nout);
61 Value runClassCtor(const UserFunction &ctor, const Value &seed,
63 // Evaluate a standalone expression AST (used for classdef property
64 // default values at registration time).
65 Value evalExpressionPublic(const ASTNode *expr, Environment *env);
66
67private:
68 Engine &engine_;
69
70 FlowSignal flowSignal_ = FlowSignal::NONE;
71 int currentRecursionDepth_ = 0;
72 int maxRecursionDepth_ = 500;
73
74 struct IndexContext
75 {
76 const Value *array = nullptr;
77 int dimension = 0;
78 int ndims = 1;
79 };
80 std::vector<IndexContext> indexContextStack_;
81 std::vector<Value> callArgsBuf_;
82 std::atomic<int> anonCounter_{0};
83
84 // Parallel call stack of (localEnv, argNames) — pushed on entry to
85 // callUserFunction(), popped on exit (RAII). Lifetime safe because
86 // each localEnv lives on the C++ stack frame of the same call.
87 std::vector<ActiveFrame> activeFrames_;
88
89 class FrameGuard
90 {
91 public:
92 FrameGuard(std::vector<ActiveFrame> &stack, Environment *env,
93 std::vector<std::string> argNames = {})
94 : stack_(stack)
95 {
96 ActiveFrame f;
97 f.env = env;
98 f.callerArgNames = std::move(argNames);
99 stack_.push_back(std::move(f));
100 }
101 ~FrameGuard() { stack_.pop_back(); }
102 FrameGuard(const FrameGuard &) = delete;
103 FrameGuard &operator=(const FrameGuard &) = delete;
104
105 private:
106 std::vector<ActiveFrame> &stack_;
107 };
108
109 class IndexContextGuard
110 {
111 public:
112 IndexContextGuard(std::vector<IndexContext> &stack, IndexContext ctx)
113 : stack_(stack)
114 {
115 stack_.push_back(ctx);
116 }
117 ~IndexContextGuard() { stack_.pop_back(); }
118 IndexContextGuard(const IndexContextGuard &) = delete;
119 IndexContextGuard &operator=(const IndexContextGuard &) = delete;
120
121 private:
122 std::vector<IndexContext> &stack_;
123 };
124
125 class RecursionGuard
126 {
127 public:
128 RecursionGuard(int &depth, int maxDepth)
129 : depth_(depth)
130 {
131 if (++depth_ > maxDepth)
132 throw std::runtime_error("Maximum recursion depth (" + std::to_string(maxDepth)
133 + ") exceeded");
134 }
135 ~RecursionGuard() { --depth_; }
136 RecursionGuard(const RecursionGuard &) = delete;
137 RecursionGuard &operator=(const RecursionGuard &) = delete;
138
139 private:
140 int &depth_;
141 };
142
143 // Display helpers
144 void output(const std::string &s);
145 void displayValue(const std::string &name, const Value &val);
146
147 // Core dispatch
148 // execNode is the single-value entry point: it collapses any comma-separated list
149 // the expression produced (CSL of 1 -> the element; 0/>1 -> error), so every
150 // operand / condition / index / assignment-RHS gets a single value and a CSL can
151 // never leak. Splice contexts (call args, [..], {..}, multi-assign, varargout) call
152 // execNodeExpand instead, which preserves a CSL so they can flatten it.
153 Value execNode(const ASTNode *node, Environment *env);
154 Value execNodeExpand(const ASTNode *node, Environment *env);
155 Value execNodeInner(const ASTNode *node, Environment *env);
156 Value execBlock(const ASTNode *node, Environment *env);
157 bool tryEvalFast(const ASTNode *expr, Environment *env, Value &out);
158
159 // Element-wise fusion: if `node` matches a registered idiom, evaluate its
160 // (side-effect-free) operands and run the fused kernel into `out`; returns
161 // true on success. false = no match / kernel declined → caller uses the
162 // normal path (re-evaluating the pure operands is safe).
163 bool tryFusion(const ASTNode *node, Environment *env, Value &out);
164
165 // Statements
166 Value execIdentifier(const ASTNode *node, Environment *env, size_t nargout = 1);
167 Value execAssign(const ASTNode *node, Environment *env);
168 Value execMultiAssign(const ASTNode *node, Environment *env);
169 Value execBinaryOp(const ASTNode *node, Environment *env);
170 Value execUnaryOp(const ASTNode *node, Environment *env);
171 Value execCall(const ASTNode *node, Environment *env, size_t nargout = 1);
172 // Superclass-qualified call `lhs@Base(args)` (classdef super-call).
173 // `superRef` is the SUPERCLASS_REF node, `callNode` the enclosing CALL
174 // (its children[1..] are the arguments). Disambiguates super-constructor
175 // (`obj@Base(args)`, lhs is a variable) from super-method
176 // (`method@Base(obj,...)`, lhs is a method-name identifier).
177 std::vector<Value> execSuperCall(const ASTNode *superRef, const ASTNode *callNode,
178 Environment *env, size_t nargout);
179 Value execCellIndex(const ASTNode *node, Environment *env);
180 // The SELECTED contents of a single-subscript cell brace-index node `c{sub}`
181 // as a comma-separated list: c{:} -> all elements (column-major); c{vec} ->
182 // the indexed elements; c{i} -> exactly one. Throws if the base is not a cell.
183 // Shared by every CSL-consuming site (array literal, call args, cell literal).
184 std::vector<Value> cellBraceContents(const ASTNode *node, Environment *env);
185 Value execFieldAccess(const ASTNode *node, Environment *env);
186 Value execMatrixLiteral(const ASTNode *node, Environment *env);
187 Value execCellLiteral(const ASTNode *node, Environment *env);
188 Value execColonExpr(const ASTNode *node, Environment *env);
189 Value execIf(const ASTNode *node, Environment *env);
190 Value execFor(const ASTNode *node, Environment *env);
191 Value execWhile(const ASTNode *node, Environment *env);
192 Value execSwitch(const ASTNode *node, Environment *env);
193 Value execFunctionDef(const ASTNode *node, Environment *env);
194 Value execExprStmt(const ASTNode *node, Environment *env);
195 Value execCommandCall(const ASTNode *node, Environment *env);
196 Value execAnonFunc(const ASTNode *node, Environment *env);
197 Value execTryCatch(const ASTNode *node, Environment *env);
198 Value execDeleteAssign(const ASTNode *node, Environment *env);
199 Value execGlobalPersistent(const ASTNode *node, Environment *env);
200
201 // Assignment helpers
202 // Dispatch an already-evaluated value to any lvalue target node
203 // (identifier / a(i) / s.f / s.(e) / c{i}). Shared by single-target
204 // ASSIGN and multi-output MULTI_ASSIGN.
205 void assignLValue(const ASTNode *lhs, const Value &rhs, Environment *env);
206 void execIndexedAssign(const ASTNode *lhs, const Value &rhs, Environment *env);
207 void execFieldAssign(const ASTNode *lhs, const Value &rhs, Environment *env);
208 void execCellAssign(const ASTNode *lhs, const Value &rhs, Environment *env);
209 // Resolve any addressable lvalue *object* to a mutable storage slot,
210 // creating/coercing containers along the way. Handles identifiers,
211 // field chains (incl. struct-array element fields), dynamic fields
212 // and cell-content slots, composing to arbitrary depth
213 // (`a.b{2}.c(3) = ...`). Paren-indexing of a plain array is NOT a
214 // stable slot — it is applied as the final write by execIndexedAssign
215 // on the slot this returns.
216 Value &resolveObjectSlot(const ASTNode *node, Environment *env);
217 Value &resolveCellSlot(const ASTNode *node, Environment *env);
218 Value &resolveFieldLValue(const ASTNode *node, Environment *env);
219
220 // Object model (object_model.md): property access via class hooks.
221 Value objectPropGet(const Value &obj, const std::string &name, Environment *env);
222 void objectPropSet(Value &objSlot, const std::string &name, const Value &rhs,
223 Environment *env);
224
225 // Indexing helpers
226 bool tryResolveScalarIndex(const ASTNode *indexExpr,
227 const Value &array,
228 int dim,
229 int ndims,
230 Environment *env,
231 size_t &outIdx);
232 std::vector<size_t> resolveIndex(
233 const ASTNode *indexExpr, const Value &array, int dim, int ndims, Environment *env);
234 Value execIndexAccess(const Value &var, const ASTNode *callNode, Environment *env);
235 std::vector<Value> execCallMulti(const ASTNode *node, Environment *env, size_t nout);
236
237 // Function call
238 // Optional `callNode` is the AST CALL node used to extract caller's
239 // arg names for inputname(k); nullptr falls back to no names.
240 Value callUserFunction(const UserFunction &func, Span<const Value> args,
241 Environment *env,
242 const ASTNode *callNode = nullptr);
243 std::vector<Value> callUserFunctionMulti(const UserFunction &func,
244 Span<const Value> args,
245 Environment *env,
246 size_t nout,
247 const ASTNode *callNode = nullptr);
248 Value callFuncHandle(const Value &handle, Span<const Value> args, Environment *env,
249 const ASTNode *callNode = nullptr);
250 std::vector<Value> callFuncHandleMulti(const Value &handle,
251 Span<const Value> args,
252 Environment *env,
253 size_t nout,
254 const ASTNode *callNode = nullptr);
255
256 // Utilities
257 bool isKnownFunction(const std::string &name) const;
258
259 // ── Debugger ──────────────────────────────────────────
260 std::string topLevelName_{"<script>"};
261 DebugController *debugCtl();
262};
263
264} // namespace nk
Core MATLAB-compatible multidimensional array and scalar value type.
Definition value.hpp:61