@fugood/llama.node 1.4.14 → 1.5.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/binding.ts +13 -6
- package/lib/index.js +2 -2
- package/lib/index.ts +8 -3
- package/package.json +15 -15
- package/scripts/llama.cpp.patch +77 -65
- package/src/LlamaContext.cpp +31 -34
- package/src/llama.cpp/CMakeLists.txt +24 -8
- package/src/llama.cpp/common/CMakeLists.txt +15 -34
- package/src/llama.cpp/common/arg.cpp +59 -10
- package/src/llama.cpp/common/chat-parser.cpp +115 -0
- package/src/llama.cpp/common/chat.cpp +356 -34
- package/src/llama.cpp/common/chat.h +17 -13
- package/src/llama.cpp/common/common.cpp +0 -1
- package/src/llama.cpp/common/common.h +30 -25
- package/src/llama.cpp/common/debug.cpp +165 -0
- package/src/llama.cpp/common/debug.h +43 -0
- package/src/llama.cpp/common/download.cpp +12 -342
- package/src/llama.cpp/common/download.h +6 -0
- package/src/llama.cpp/common/jinja/caps.cpp +237 -0
- package/src/llama.cpp/common/jinja/caps.h +24 -0
- package/src/llama.cpp/common/jinja/lexer.cpp +341 -0
- package/src/llama.cpp/common/jinja/lexer.h +157 -0
- package/src/llama.cpp/common/jinja/parser.cpp +591 -0
- package/src/llama.cpp/common/jinja/parser.h +21 -0
- package/src/llama.cpp/common/jinja/runtime.cpp +865 -0
- package/src/llama.cpp/common/jinja/runtime.h +628 -0
- package/src/llama.cpp/common/jinja/string.cpp +207 -0
- package/src/llama.cpp/common/jinja/string.h +58 -0
- package/src/llama.cpp/common/jinja/utils.h +49 -0
- package/src/llama.cpp/common/jinja/value.cpp +1221 -0
- package/src/llama.cpp/common/jinja/value.h +464 -0
- package/src/llama.cpp/common/preset.cpp +12 -2
- package/src/llama.cpp/common/sampling.cpp +52 -19
- package/src/llama.cpp/ggml/include/ggml.h +39 -7
- package/src/llama.cpp/ggml/src/ggml-cpu/ggml-cpu.c +4 -0
- package/src/llama.cpp/ggml/src/ggml-cpu/ops.cpp +63 -37
- package/src/llama.cpp/ggml/src/ggml-cpu/simd-mappings.h +31 -0
- package/src/llama.cpp/ggml/src/ggml-cpu/vec.cpp +18 -0
- package/src/llama.cpp/include/llama-cpp.h +3 -1
- package/src/llama.cpp/include/llama.h +29 -2
- package/src/llama.cpp/src/CMakeLists.txt +1 -0
- package/src/llama.cpp/src/llama-adapter.cpp +7 -13
- package/src/llama.cpp/src/llama-adapter.h +1 -3
- package/src/llama.cpp/src/llama-arch.cpp +35 -0
- package/src/llama.cpp/src/llama-arch.h +1 -0
- package/src/llama.cpp/src/llama-chat.cpp +20 -0
- package/src/llama.cpp/src/llama-chat.h +1 -0
- package/src/llama.cpp/src/llama-context.cpp +232 -144
- package/src/llama.cpp/src/llama-context.h +10 -0
- package/src/llama.cpp/src/llama-cparams.h +2 -0
- package/src/llama.cpp/src/llama-graph.cpp +31 -43
- package/src/llama.cpp/src/llama-hparams.cpp +0 -36
- package/src/llama.cpp/src/llama-hparams.h +38 -1
- package/src/llama.cpp/src/llama-kv-cache.cpp +201 -59
- package/src/llama.cpp/src/llama-kv-cache.h +0 -2
- package/src/llama.cpp/src/llama-mmap.cpp +13 -6
- package/src/llama.cpp/src/llama-model-loader.cpp +21 -7
- package/src/llama.cpp/src/llama-model.cpp +215 -97
- package/src/llama.cpp/src/llama-model.h +3 -2
- package/src/llama.cpp/src/llama-sampling.cpp +170 -13
- package/src/llama.cpp/src/llama-vocab.cpp +37 -24
- package/src/llama.cpp/src/llama-vocab.h +1 -0
- package/src/llama.cpp/src/models/exaone-moe.cpp +146 -0
- package/src/llama.cpp/src/models/gemma3n-iswa.cpp +13 -3
- package/src/llama.cpp/src/models/models.h +13 -2
- package/src/llama.cpp/src/models/qwen3next.cpp +198 -182
|
@@ -0,0 +1,865 @@
|
|
|
1
|
+
#include "lexer.h"
|
|
2
|
+
#include "runtime.h"
|
|
3
|
+
#include "value.h"
|
|
4
|
+
#include "utils.h"
|
|
5
|
+
|
|
6
|
+
#include <string>
|
|
7
|
+
#include <vector>
|
|
8
|
+
#include <memory>
|
|
9
|
+
#include <cmath>
|
|
10
|
+
|
|
11
|
+
#define FILENAME "jinja-runtime"
|
|
12
|
+
|
|
13
|
+
bool g_jinja_debug = false;
|
|
14
|
+
|
|
15
|
+
namespace jinja {
|
|
16
|
+
|
|
17
|
+
void enable_debug(bool enable) {
|
|
18
|
+
g_jinja_debug = enable;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static value_string exec_statements(const statements & stmts, context & ctx) {
|
|
22
|
+
auto result = mk_val<value_array>();
|
|
23
|
+
for (const auto & stmt : stmts) {
|
|
24
|
+
JJ_DEBUG("Executing statement of type %s", stmt->type().c_str());
|
|
25
|
+
result->push_back(stmt->execute(ctx));
|
|
26
|
+
}
|
|
27
|
+
// convert to string parts
|
|
28
|
+
value_string str = mk_val<value_string>();
|
|
29
|
+
gather_string_parts_recursive(result, str);
|
|
30
|
+
return str;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static std::string get_line_col(const std::string & source, size_t pos) {
|
|
34
|
+
size_t line = 1;
|
|
35
|
+
size_t col = 1;
|
|
36
|
+
for (size_t i = 0; i < pos && i < source.size(); i++) {
|
|
37
|
+
if (source[i] == '\n') {
|
|
38
|
+
line++;
|
|
39
|
+
col = 1;
|
|
40
|
+
} else {
|
|
41
|
+
col++;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return "line " + std::to_string(line) + ", column " + std::to_string(col);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// execute with error handling
|
|
48
|
+
value statement::execute(context & ctx) {
|
|
49
|
+
try {
|
|
50
|
+
return execute_impl(ctx);
|
|
51
|
+
} catch (const continue_statement::signal & /* ex */) {
|
|
52
|
+
throw;
|
|
53
|
+
} catch (const break_statement::signal & /* ex */) {
|
|
54
|
+
throw;
|
|
55
|
+
} catch (const rethrown_exception & /* ex */) {
|
|
56
|
+
throw;
|
|
57
|
+
} catch (const not_implemented_exception & /* ex */) {
|
|
58
|
+
throw;
|
|
59
|
+
} catch (const std::exception & e) {
|
|
60
|
+
const std::string & source = *ctx.src;
|
|
61
|
+
if (source.empty()) {
|
|
62
|
+
std::ostringstream oss;
|
|
63
|
+
oss << "\nError executing " << type() << " at position " << pos << ": " << e.what();
|
|
64
|
+
throw rethrown_exception(oss.str());
|
|
65
|
+
} else {
|
|
66
|
+
std::ostringstream oss;
|
|
67
|
+
oss << "\n------------\n";
|
|
68
|
+
oss << "While executing " << type() << " at " << get_line_col(source, pos) << " in source:\n";
|
|
69
|
+
oss << peak_source(source, pos) << "\n";
|
|
70
|
+
oss << "Error: " << e.what();
|
|
71
|
+
// throw as another exception to avoid repeated formatting
|
|
72
|
+
throw rethrown_exception(oss.str());
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
value identifier::execute_impl(context & ctx) {
|
|
78
|
+
auto it = ctx.get_val(val);
|
|
79
|
+
auto builtins = global_builtins();
|
|
80
|
+
if (!it->is_undefined()) {
|
|
81
|
+
if (ctx.is_get_stats) {
|
|
82
|
+
it->stats.used = true;
|
|
83
|
+
}
|
|
84
|
+
JJ_DEBUG("Identifier '%s' found, type = %s", val.c_str(), it->type().c_str());
|
|
85
|
+
return it;
|
|
86
|
+
} else if (builtins.find(val) != builtins.end()) {
|
|
87
|
+
JJ_DEBUG("Identifier '%s' found in builtins", val.c_str());
|
|
88
|
+
return mk_val<value_func>(val, builtins.at(val));
|
|
89
|
+
} else {
|
|
90
|
+
JJ_DEBUG("Identifier '%s' not found, returning undefined", val.c_str());
|
|
91
|
+
return mk_val<value_undefined>(val);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
value object_literal::execute_impl(context & ctx) {
|
|
96
|
+
auto obj = mk_val<value_object>();
|
|
97
|
+
for (const auto & pair : val) {
|
|
98
|
+
value key_val = pair.first->execute(ctx);
|
|
99
|
+
if (!is_val<value_string>(key_val) && !is_val<value_int>(key_val)) {
|
|
100
|
+
throw std::runtime_error("Object literal: keys must be string or int values, got " + key_val->type());
|
|
101
|
+
}
|
|
102
|
+
std::string key = key_val->as_string().str();
|
|
103
|
+
value val = pair.second->execute(ctx);
|
|
104
|
+
JJ_DEBUG("Object literal: setting key '%s' with value type %s", key.c_str(), val->type().c_str());
|
|
105
|
+
obj->insert(key, val);
|
|
106
|
+
|
|
107
|
+
if (is_val<value_int>(key_val)) {
|
|
108
|
+
obj->val_obj.is_key_numeric = true;
|
|
109
|
+
} else if (obj->val_obj.is_key_numeric) {
|
|
110
|
+
throw std::runtime_error("Object literal: cannot mix numeric and non-numeric keys");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return obj;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
value binary_expression::execute_impl(context & ctx) {
|
|
117
|
+
value left_val = left->execute(ctx);
|
|
118
|
+
|
|
119
|
+
// Logical operators
|
|
120
|
+
if (op.value == "and") {
|
|
121
|
+
return left_val->as_bool() ? right->execute(ctx) : std::move(left_val);
|
|
122
|
+
} else if (op.value == "or") {
|
|
123
|
+
return left_val->as_bool() ? std::move(left_val) : right->execute(ctx);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Equality operators
|
|
127
|
+
value right_val = right->execute(ctx);
|
|
128
|
+
JJ_DEBUG("Executing binary expression %s '%s' %s", left_val->type().c_str(), op.value.c_str(), right_val->type().c_str());
|
|
129
|
+
if (op.value == "==") {
|
|
130
|
+
return mk_val<value_bool>(value_compare(left_val, right_val, value_compare_op::eq));
|
|
131
|
+
} else if (op.value == "!=") {
|
|
132
|
+
return mk_val<value_bool>(!value_compare(left_val, right_val, value_compare_op::eq));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
auto workaround_concat_null_with_str = [&](value & res) -> bool {
|
|
136
|
+
bool is_left_null = left_val->is_none() || left_val->is_undefined();
|
|
137
|
+
bool is_right_null = right_val->is_none() || right_val->is_undefined();
|
|
138
|
+
bool is_left_str = is_val<value_string>(left_val);
|
|
139
|
+
bool is_right_str = is_val<value_string>(right_val);
|
|
140
|
+
if ((is_left_null && is_right_str) || (is_right_null && is_left_str)) {
|
|
141
|
+
JJ_DEBUG("%s", "Workaround: treating null/undefined as empty string for string concatenation");
|
|
142
|
+
string left_str = is_left_null ? string() : left_val->as_string();
|
|
143
|
+
string right_str = is_right_null ? string() : right_val->as_string();
|
|
144
|
+
auto output = left_str.append(right_str);
|
|
145
|
+
res = mk_val<value_string>(std::move(output));
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
return false;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Handle undefined and null values
|
|
152
|
+
if (is_val<value_undefined>(left_val) || is_val<value_undefined>(right_val)) {
|
|
153
|
+
if (is_val<value_undefined>(right_val) && (op.value == "in" || op.value == "not in")) {
|
|
154
|
+
// Special case: `anything in undefined` is `false` and `anything not in undefined` is `true`
|
|
155
|
+
return mk_val<value_bool>(op.value == "not in");
|
|
156
|
+
}
|
|
157
|
+
if (op.value == "+" || op.value == "~") {
|
|
158
|
+
value res = mk_val<value_undefined>();
|
|
159
|
+
if (workaround_concat_null_with_str(res)) {
|
|
160
|
+
return res;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
throw std::runtime_error("Cannot perform operation " + op.value + " on undefined values");
|
|
164
|
+
} else if (is_val<value_none>(left_val) || is_val<value_none>(right_val)) {
|
|
165
|
+
if (op.value == "+" || op.value == "~") {
|
|
166
|
+
value res = mk_val<value_undefined>();
|
|
167
|
+
if (workaround_concat_null_with_str(res)) {
|
|
168
|
+
return res;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
throw std::runtime_error("Cannot perform operation on null values");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Float operations
|
|
175
|
+
if ((is_val<value_int>(left_val) || is_val<value_float>(left_val)) &&
|
|
176
|
+
(is_val<value_int>(right_val) || is_val<value_float>(right_val))) {
|
|
177
|
+
double a = left_val->as_float();
|
|
178
|
+
double b = right_val->as_float();
|
|
179
|
+
if (op.value == "+" || op.value == "-" || op.value == "*") {
|
|
180
|
+
double res = (op.value == "+") ? a + b : (op.value == "-") ? a - b : a * b;
|
|
181
|
+
JJ_DEBUG("Arithmetic operation: %f %s %f = %f", a, op.value.c_str(), b, res);
|
|
182
|
+
bool is_float = is_val<value_float>(left_val) || is_val<value_float>(right_val);
|
|
183
|
+
if (is_float) {
|
|
184
|
+
return mk_val<value_float>(res);
|
|
185
|
+
} else {
|
|
186
|
+
return mk_val<value_int>(static_cast<int64_t>(res));
|
|
187
|
+
}
|
|
188
|
+
} else if (op.value == "/") {
|
|
189
|
+
JJ_DEBUG("Division operation: %f / %f", a, b);
|
|
190
|
+
return mk_val<value_float>(a / b);
|
|
191
|
+
} else if (op.value == "%") {
|
|
192
|
+
double rem = std::fmod(a, b);
|
|
193
|
+
JJ_DEBUG("Modulo operation: %f %% %f = %f", a, b, rem);
|
|
194
|
+
bool is_float = is_val<value_float>(left_val) || is_val<value_float>(right_val);
|
|
195
|
+
if (is_float) {
|
|
196
|
+
return mk_val<value_float>(rem);
|
|
197
|
+
} else {
|
|
198
|
+
return mk_val<value_int>(static_cast<int64_t>(rem));
|
|
199
|
+
}
|
|
200
|
+
} else if (op.value == "<") {
|
|
201
|
+
JJ_DEBUG("Comparison operation: %f < %f is %d", a, b, a < b);
|
|
202
|
+
return mk_val<value_bool>(a < b);
|
|
203
|
+
} else if (op.value == ">") {
|
|
204
|
+
JJ_DEBUG("Comparison operation: %f > %f is %d", a, b, a > b);
|
|
205
|
+
return mk_val<value_bool>(a > b);
|
|
206
|
+
} else if (op.value == ">=") {
|
|
207
|
+
JJ_DEBUG("Comparison operation: %f >= %f is %d", a, b, a >= b);
|
|
208
|
+
return mk_val<value_bool>(a >= b);
|
|
209
|
+
} else if (op.value == "<=") {
|
|
210
|
+
JJ_DEBUG("Comparison operation: %f <= %f is %d", a, b, a <= b);
|
|
211
|
+
return mk_val<value_bool>(a <= b);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Array operations
|
|
216
|
+
if (is_val<value_array>(left_val) && is_val<value_array>(right_val)) {
|
|
217
|
+
if (op.value == "+") {
|
|
218
|
+
auto & left_arr = left_val->as_array();
|
|
219
|
+
auto & right_arr = right_val->as_array();
|
|
220
|
+
auto result = mk_val<value_array>();
|
|
221
|
+
for (const auto & item : left_arr) {
|
|
222
|
+
result->push_back(item);
|
|
223
|
+
}
|
|
224
|
+
for (const auto & item : right_arr) {
|
|
225
|
+
result->push_back(item);
|
|
226
|
+
}
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
} else if (is_val<value_array>(right_val)) {
|
|
230
|
+
auto & arr = right_val->as_array();
|
|
231
|
+
bool member = false;
|
|
232
|
+
for (const auto & item : arr) {
|
|
233
|
+
if (value_compare(left_val, item, value_compare_op::eq)) {
|
|
234
|
+
member = true;
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (op.value == "in") {
|
|
239
|
+
JJ_DEBUG("Checking membership: %s in Array is %d", left_val->type().c_str(), member);
|
|
240
|
+
return mk_val<value_bool>(member);
|
|
241
|
+
} else if (op.value == "not in") {
|
|
242
|
+
JJ_DEBUG("Checking non-membership: %s not in Array is %d", left_val->type().c_str(), !member);
|
|
243
|
+
return mk_val<value_bool>(!member);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// String concatenation with ~ and +
|
|
248
|
+
if ((is_val<value_string>(left_val) || is_val<value_string>(right_val)) &&
|
|
249
|
+
(op.value == "~" || op.value == "+")) {
|
|
250
|
+
JJ_DEBUG("String concatenation with %s operator", op.value.c_str());
|
|
251
|
+
auto output = left_val->as_string().append(right_val->as_string());
|
|
252
|
+
auto res = mk_val<value_string>();
|
|
253
|
+
res->val_str = std::move(output);
|
|
254
|
+
return res;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// String membership
|
|
258
|
+
if (is_val<value_string>(left_val) && is_val<value_string>(right_val)) {
|
|
259
|
+
auto left_str = left_val->as_string().str();
|
|
260
|
+
auto right_str = right_val->as_string().str();
|
|
261
|
+
if (op.value == "in") {
|
|
262
|
+
return mk_val<value_bool>(right_str.find(left_str) != std::string::npos);
|
|
263
|
+
} else if (op.value == "not in") {
|
|
264
|
+
return mk_val<value_bool>(right_str.find(left_str) == std::string::npos);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// String in object
|
|
269
|
+
if (is_val<value_string>(left_val) && is_val<value_object>(right_val)) {
|
|
270
|
+
auto key = left_val->as_string().str();
|
|
271
|
+
bool has_key = right_val->has_key(key);
|
|
272
|
+
if (op.value == "in") {
|
|
273
|
+
return mk_val<value_bool>(has_key);
|
|
274
|
+
} else if (op.value == "not in") {
|
|
275
|
+
return mk_val<value_bool>(!has_key);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
throw std::runtime_error("Unknown operator \"" + op.value + "\" between " + left_val->type() + " and " + right_val->type());
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
static value try_builtin_func(context & ctx, const std::string & name, value & input, bool undef_on_missing = false) {
|
|
283
|
+
JJ_DEBUG("Trying built-in function '%s' for type %s", name.c_str(), input->type().c_str());
|
|
284
|
+
if (ctx.is_get_stats) {
|
|
285
|
+
input->stats.used = true;
|
|
286
|
+
input->stats.ops.insert(name);
|
|
287
|
+
}
|
|
288
|
+
auto builtins = input->get_builtins();
|
|
289
|
+
auto it = builtins.find(name);
|
|
290
|
+
if (it != builtins.end()) {
|
|
291
|
+
JJ_DEBUG("Binding built-in '%s'", name.c_str());
|
|
292
|
+
return mk_val<value_func>(name, it->second, input);
|
|
293
|
+
}
|
|
294
|
+
if (undef_on_missing) {
|
|
295
|
+
return mk_val<value_undefined>(name);
|
|
296
|
+
}
|
|
297
|
+
throw std::runtime_error("Unknown (built-in) filter '" + name + "' for type " + input->type());
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
value filter_expression::execute_impl(context & ctx) {
|
|
301
|
+
value input = operand ? operand->execute(ctx) : val;
|
|
302
|
+
|
|
303
|
+
JJ_DEBUG("Applying filter to %s", input->type().c_str());
|
|
304
|
+
|
|
305
|
+
if (is_stmt<identifier>(filter)) {
|
|
306
|
+
auto filter_id = cast_stmt<identifier>(filter)->val;
|
|
307
|
+
|
|
308
|
+
if (filter_id == "trim") {
|
|
309
|
+
filter_id = "strip"; // alias
|
|
310
|
+
}
|
|
311
|
+
JJ_DEBUG("Applying filter '%s' to %s", filter_id.c_str(), input->type().c_str());
|
|
312
|
+
return try_builtin_func(ctx, filter_id, input)->invoke(func_args(ctx));
|
|
313
|
+
|
|
314
|
+
} else if (is_stmt<call_expression>(filter)) {
|
|
315
|
+
auto call = cast_stmt<call_expression>(filter);
|
|
316
|
+
if (!is_stmt<identifier>(call->callee)) {
|
|
317
|
+
throw std::runtime_error("Filter callee must be an identifier");
|
|
318
|
+
}
|
|
319
|
+
auto filter_id = cast_stmt<identifier>(call->callee)->val;
|
|
320
|
+
|
|
321
|
+
if (filter_id == "trim") {
|
|
322
|
+
filter_id = "strip"; // alias
|
|
323
|
+
}
|
|
324
|
+
JJ_DEBUG("Applying filter '%s' with arguments to %s", filter_id.c_str(), input->type().c_str());
|
|
325
|
+
func_args args(ctx);
|
|
326
|
+
for (const auto & arg_expr : call->args) {
|
|
327
|
+
args.push_back(arg_expr->execute(ctx));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return try_builtin_func(ctx, filter_id, input)->invoke(args);
|
|
331
|
+
|
|
332
|
+
} else {
|
|
333
|
+
throw std::runtime_error("Invalid filter expression");
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
value filter_statement::execute_impl(context & ctx) {
|
|
338
|
+
// eval body as string, then apply filter
|
|
339
|
+
auto body_val = exec_statements(body, ctx);
|
|
340
|
+
value_string parts = mk_val<value_string>();
|
|
341
|
+
gather_string_parts_recursive(body_val, parts);
|
|
342
|
+
|
|
343
|
+
JJ_DEBUG("FilterStatement: applying filter to body string of length %zu", parts->val_str.length());
|
|
344
|
+
filter_expression filter_expr(std::move(parts), std::move(filter));
|
|
345
|
+
value out = filter_expr.execute(ctx);
|
|
346
|
+
|
|
347
|
+
// this node can be reused later, make sure filter is preserved
|
|
348
|
+
this->filter = std::move(filter_expr.filter);
|
|
349
|
+
return out;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
value test_expression::execute_impl(context & ctx) {
|
|
353
|
+
// NOTE: "value is something" translates to function call "test_is_something(value)"
|
|
354
|
+
const auto & builtins = global_builtins();
|
|
355
|
+
|
|
356
|
+
std::string test_id;
|
|
357
|
+
value input = operand->execute(ctx);
|
|
358
|
+
|
|
359
|
+
func_args args(ctx);
|
|
360
|
+
args.push_back(input);
|
|
361
|
+
|
|
362
|
+
if (is_stmt<identifier>(test)) {
|
|
363
|
+
test_id = cast_stmt<identifier>(test)->val;
|
|
364
|
+
} else if (is_stmt<call_expression>(test)) {
|
|
365
|
+
auto call = cast_stmt<call_expression>(test);
|
|
366
|
+
if (!is_stmt<identifier>(call->callee)) {
|
|
367
|
+
throw std::runtime_error("Test callee must be an identifier");
|
|
368
|
+
}
|
|
369
|
+
test_id = cast_stmt<identifier>(call->callee)->val;
|
|
370
|
+
|
|
371
|
+
JJ_DEBUG("Applying test '%s' with arguments to %s", test_id.c_str(), input->type().c_str());
|
|
372
|
+
for (const auto & arg_expr : call->args) {
|
|
373
|
+
args.push_back(arg_expr->execute(ctx));
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
} else {
|
|
377
|
+
throw std::runtime_error("Invalid test expression");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
auto it = builtins.find("test_is_" + test_id);
|
|
381
|
+
JJ_DEBUG("Test expression %s '%s' %s (using function 'test_is_%s')", operand->type().c_str(), test_id.c_str(), negate ? "(negate)" : "", test_id.c_str());
|
|
382
|
+
if (it == builtins.end()) {
|
|
383
|
+
throw std::runtime_error("Unknown test '" + test_id + "'");
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
auto res = it->second(args);
|
|
387
|
+
|
|
388
|
+
if (negate) {
|
|
389
|
+
return mk_val<value_bool>(!res->as_bool());
|
|
390
|
+
} else {
|
|
391
|
+
return res;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
value unary_expression::execute_impl(context & ctx) {
|
|
396
|
+
value operand_val = argument->execute(ctx);
|
|
397
|
+
JJ_DEBUG("Executing unary expression with operator '%s'", op.value.c_str());
|
|
398
|
+
|
|
399
|
+
if (op.value == "not") {
|
|
400
|
+
return mk_val<value_bool>(!operand_val->as_bool());
|
|
401
|
+
} else if (op.value == "-") {
|
|
402
|
+
if (is_val<value_int>(operand_val)) {
|
|
403
|
+
return mk_val<value_int>(-operand_val->as_int());
|
|
404
|
+
} else if (is_val<value_float>(operand_val)) {
|
|
405
|
+
return mk_val<value_float>(-operand_val->as_float());
|
|
406
|
+
} else {
|
|
407
|
+
throw std::runtime_error("Unary - operator requires numeric operand");
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
throw std::runtime_error("Unknown unary operator '" + op.value + "'");
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
value if_statement::execute_impl(context & ctx) {
|
|
415
|
+
value test_val = test->execute(ctx);
|
|
416
|
+
|
|
417
|
+
auto out = mk_val<value_array>();
|
|
418
|
+
if (test_val->as_bool()) {
|
|
419
|
+
for (auto & stmt : body) {
|
|
420
|
+
JJ_DEBUG("IF --> Executing THEN body, current block: %s", stmt->type().c_str());
|
|
421
|
+
out->push_back(stmt->execute(ctx));
|
|
422
|
+
}
|
|
423
|
+
} else {
|
|
424
|
+
for (auto & stmt : alternate) {
|
|
425
|
+
JJ_DEBUG("IF --> Executing ELSE body, current block: %s", stmt->type().c_str());
|
|
426
|
+
out->push_back(stmt->execute(ctx));
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
// convert to string parts
|
|
430
|
+
value_string str = mk_val<value_string>();
|
|
431
|
+
gather_string_parts_recursive(out, str);
|
|
432
|
+
return str;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
value for_statement::execute_impl(context & ctx) {
|
|
436
|
+
context scope(ctx); // new scope for loop variables
|
|
437
|
+
|
|
438
|
+
jinja::select_expression * select_expr = cast_stmt<select_expression>(iterable);
|
|
439
|
+
statement_ptr test_expr_nullptr;
|
|
440
|
+
|
|
441
|
+
statement_ptr & iter_expr = [&]() -> statement_ptr & {
|
|
442
|
+
auto tmp = cast_stmt<select_expression>(iterable);
|
|
443
|
+
return tmp ? tmp->lhs : iterable;
|
|
444
|
+
}();
|
|
445
|
+
statement_ptr & test_expr = [&]() -> statement_ptr & {
|
|
446
|
+
auto tmp = cast_stmt<select_expression>(iterable);
|
|
447
|
+
return tmp ? tmp->test : test_expr_nullptr;
|
|
448
|
+
}();
|
|
449
|
+
|
|
450
|
+
JJ_DEBUG("Executing for statement, iterable type: %s", iter_expr->type().c_str());
|
|
451
|
+
|
|
452
|
+
value iterable_val = iter_expr->execute(scope);
|
|
453
|
+
|
|
454
|
+
if (iterable_val->is_undefined()) {
|
|
455
|
+
JJ_DEBUG("%s", "For loop iterable is undefined, skipping loop");
|
|
456
|
+
iterable_val = mk_val<value_array>();
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (!is_val<value_array>(iterable_val) && !is_val<value_object>(iterable_val)) {
|
|
460
|
+
throw std::runtime_error("Expected iterable or object type in for loop: got " + iterable_val->type());
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
std::vector<value> items;
|
|
464
|
+
if (is_val<value_object>(iterable_val)) {
|
|
465
|
+
JJ_DEBUG("%s", "For loop over object keys");
|
|
466
|
+
auto & obj = iterable_val->as_ordered_object();
|
|
467
|
+
for (auto & p : obj) {
|
|
468
|
+
auto tuple = mk_val<value_array>();
|
|
469
|
+
if (iterable_val->val_obj.is_key_numeric) {
|
|
470
|
+
tuple->push_back(mk_val<value_int>(std::stoll(p.first)));
|
|
471
|
+
} else {
|
|
472
|
+
tuple->push_back(mk_val<value_string>(p.first));
|
|
473
|
+
}
|
|
474
|
+
tuple->push_back(p.second);
|
|
475
|
+
items.push_back(tuple);
|
|
476
|
+
}
|
|
477
|
+
if (ctx.is_get_stats) {
|
|
478
|
+
iterable_val->stats.used = true;
|
|
479
|
+
iterable_val->stats.ops.insert("object_access");
|
|
480
|
+
}
|
|
481
|
+
} else {
|
|
482
|
+
JJ_DEBUG("%s", "For loop over array items");
|
|
483
|
+
auto & arr = iterable_val->as_array();
|
|
484
|
+
for (const auto & item : arr) {
|
|
485
|
+
items.push_back(item);
|
|
486
|
+
}
|
|
487
|
+
if (ctx.is_get_stats) {
|
|
488
|
+
iterable_val->stats.used = true;
|
|
489
|
+
iterable_val->stats.ops.insert("array_access");
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
std::vector<std::function<void(context &)>> scope_update_fns;
|
|
494
|
+
|
|
495
|
+
std::vector<value> filtered_items;
|
|
496
|
+
for (size_t i = 0; i < items.size(); ++i) {
|
|
497
|
+
context loop_scope(scope);
|
|
498
|
+
|
|
499
|
+
value current = items[i];
|
|
500
|
+
|
|
501
|
+
std::function<void(context&)> scope_update_fn = [](context &) { /* no-op */};
|
|
502
|
+
if (is_stmt<identifier>(loopvar)) {
|
|
503
|
+
auto id = cast_stmt<identifier>(loopvar)->val;
|
|
504
|
+
|
|
505
|
+
if (is_val<value_object>(iterable_val)) {
|
|
506
|
+
// case example: {% for key in dict %}
|
|
507
|
+
current = items[i]->as_array()[0];
|
|
508
|
+
scope_update_fn = [id, &items, i](context & ctx) {
|
|
509
|
+
ctx.set_val(id, items[i]->as_array()[0]);
|
|
510
|
+
};
|
|
511
|
+
} else {
|
|
512
|
+
// case example: {% for item in list %}
|
|
513
|
+
scope_update_fn = [id, &items, i](context & ctx) {
|
|
514
|
+
ctx.set_val(id, items[i]);
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
} else if (is_stmt<tuple_literal>(loopvar)) {
|
|
519
|
+
// case example: {% for key, value in dict %}
|
|
520
|
+
auto tuple = cast_stmt<tuple_literal>(loopvar);
|
|
521
|
+
if (!is_val<value_array>(current)) {
|
|
522
|
+
throw std::runtime_error("Cannot unpack non-iterable type: " + current->type());
|
|
523
|
+
}
|
|
524
|
+
auto & c_arr = current->as_array();
|
|
525
|
+
if (tuple->val.size() != c_arr.size()) {
|
|
526
|
+
throw std::runtime_error(std::string("Too ") + (tuple->val.size() > c_arr.size() ? "few" : "many") + " items to unpack");
|
|
527
|
+
}
|
|
528
|
+
scope_update_fn = [tuple, &items, i](context & ctx) {
|
|
529
|
+
auto & c_arr = items[i]->as_array();
|
|
530
|
+
for (size_t j = 0; j < tuple->val.size(); ++j) {
|
|
531
|
+
if (!is_stmt<identifier>(tuple->val[j])) {
|
|
532
|
+
throw std::runtime_error("Cannot unpack non-identifier type: " + tuple->val[j]->type());
|
|
533
|
+
}
|
|
534
|
+
auto id = cast_stmt<identifier>(tuple->val[j])->val;
|
|
535
|
+
ctx.set_val(id, c_arr[j]);
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
} else {
|
|
540
|
+
throw std::runtime_error("Invalid loop variable(s): " + loopvar->type());
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (select_expr && test_expr) {
|
|
544
|
+
scope_update_fn(loop_scope);
|
|
545
|
+
value test_val = test_expr->execute(loop_scope);
|
|
546
|
+
if (!test_val->as_bool()) {
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
JJ_DEBUG("For loop: adding item type %s at index %zu", current->type().c_str(), i);
|
|
551
|
+
filtered_items.push_back(current);
|
|
552
|
+
scope_update_fns.push_back(scope_update_fn);
|
|
553
|
+
}
|
|
554
|
+
JJ_DEBUG("For loop: %zu items after filtering", filtered_items.size());
|
|
555
|
+
|
|
556
|
+
auto result = mk_val<value_array>();
|
|
557
|
+
|
|
558
|
+
bool noIteration = true;
|
|
559
|
+
for (size_t i = 0; i < filtered_items.size(); i++) {
|
|
560
|
+
JJ_DEBUG("For loop iteration %zu/%zu", i + 1, filtered_items.size());
|
|
561
|
+
value_object loop_obj = mk_val<value_object>();
|
|
562
|
+
loop_obj->has_builtins = false; // loop object has no builtins
|
|
563
|
+
loop_obj->insert("index", mk_val<value_int>(i + 1));
|
|
564
|
+
loop_obj->insert("index0", mk_val<value_int>(i));
|
|
565
|
+
loop_obj->insert("revindex", mk_val<value_int>(filtered_items.size() - i));
|
|
566
|
+
loop_obj->insert("revindex0", mk_val<value_int>(filtered_items.size() - i - 1));
|
|
567
|
+
loop_obj->insert("first", mk_val<value_bool>(i == 0));
|
|
568
|
+
loop_obj->insert("last", mk_val<value_bool>(i == filtered_items.size() - 1));
|
|
569
|
+
loop_obj->insert("length", mk_val<value_int>(filtered_items.size()));
|
|
570
|
+
loop_obj->insert("previtem", i > 0 ? filtered_items[i - 1] : mk_val<value_undefined>("previtem"));
|
|
571
|
+
loop_obj->insert("nextitem", i < filtered_items.size() - 1 ? filtered_items[i + 1] : mk_val<value_undefined>("nextitem"));
|
|
572
|
+
scope.set_val("loop", loop_obj);
|
|
573
|
+
scope_update_fns[i](scope);
|
|
574
|
+
try {
|
|
575
|
+
for (auto & stmt : body) {
|
|
576
|
+
value val = stmt->execute(scope);
|
|
577
|
+
result->push_back(val);
|
|
578
|
+
}
|
|
579
|
+
} catch (const continue_statement::signal &) {
|
|
580
|
+
continue;
|
|
581
|
+
} catch (const break_statement::signal &) {
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
noIteration = false;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
JJ_DEBUG("For loop complete, total iterations: %zu", filtered_items.size());
|
|
588
|
+
if (noIteration) {
|
|
589
|
+
for (auto & stmt : default_block) {
|
|
590
|
+
value val = stmt->execute(ctx);
|
|
591
|
+
result->push_back(val);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// convert to string parts
|
|
596
|
+
value_string str = mk_val<value_string>();
|
|
597
|
+
gather_string_parts_recursive(result, str);
|
|
598
|
+
return str;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
value set_statement::execute_impl(context & ctx) {
|
|
602
|
+
auto rhs = val ? val->execute(ctx) : exec_statements(body, ctx);
|
|
603
|
+
|
|
604
|
+
if (is_stmt<identifier>(assignee)) {
|
|
605
|
+
auto var_name = cast_stmt<identifier>(assignee)->val;
|
|
606
|
+
JJ_DEBUG("Setting global variable '%s' with value type %s", var_name.c_str(), rhs->type().c_str());
|
|
607
|
+
ctx.set_val(var_name, rhs);
|
|
608
|
+
|
|
609
|
+
} else if (is_stmt<tuple_literal>(assignee)) {
|
|
610
|
+
auto tuple = cast_stmt<tuple_literal>(assignee);
|
|
611
|
+
if (!is_val<value_array>(rhs)) {
|
|
612
|
+
throw std::runtime_error("Cannot unpack non-iterable type in set: " + rhs->type());
|
|
613
|
+
}
|
|
614
|
+
auto & arr = rhs->as_array();
|
|
615
|
+
if (arr.size() != tuple->val.size()) {
|
|
616
|
+
throw std::runtime_error(std::string("Too ") + (tuple->val.size() > arr.size() ? "few" : "many") + " items to unpack in set");
|
|
617
|
+
}
|
|
618
|
+
for (size_t i = 0; i < tuple->val.size(); ++i) {
|
|
619
|
+
auto & elem = tuple->val[i];
|
|
620
|
+
if (!is_stmt<identifier>(elem)) {
|
|
621
|
+
throw std::runtime_error("Cannot unpack to non-identifier in set: " + elem->type());
|
|
622
|
+
}
|
|
623
|
+
auto var_name = cast_stmt<identifier>(elem)->val;
|
|
624
|
+
ctx.set_val(var_name, arr[i]);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
} else if (is_stmt<member_expression>(assignee)) {
|
|
628
|
+
auto member = cast_stmt<member_expression>(assignee);
|
|
629
|
+
if (member->computed) {
|
|
630
|
+
throw std::runtime_error("Cannot assign to computed member");
|
|
631
|
+
}
|
|
632
|
+
if (!is_stmt<identifier>(member->property)) {
|
|
633
|
+
throw std::runtime_error("Cannot assign to member with non-identifier property");
|
|
634
|
+
}
|
|
635
|
+
auto prop_name = cast_stmt<identifier>(member->property)->val;
|
|
636
|
+
|
|
637
|
+
value object = member->object->execute(ctx);
|
|
638
|
+
if (!is_val<value_object>(object)) {
|
|
639
|
+
throw std::runtime_error("Cannot assign to member of non-object");
|
|
640
|
+
}
|
|
641
|
+
auto obj_ptr = cast_val<value_object>(object);
|
|
642
|
+
JJ_DEBUG("Setting object property '%s' with value type %s", prop_name.c_str(), rhs->type().c_str());
|
|
643
|
+
obj_ptr->insert(prop_name, rhs);
|
|
644
|
+
|
|
645
|
+
} else {
|
|
646
|
+
throw std::runtime_error("Invalid LHS inside assignment expression: " + assignee->type());
|
|
647
|
+
}
|
|
648
|
+
return mk_val<value_undefined>();
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
value macro_statement::execute_impl(context & ctx) {
|
|
652
|
+
if (!is_stmt<identifier>(this->name)) {
|
|
653
|
+
throw std::runtime_error("Macro name must be an identifier");
|
|
654
|
+
}
|
|
655
|
+
std::string name = cast_stmt<identifier>(this->name)->val;
|
|
656
|
+
|
|
657
|
+
const func_handler func = [this, name, &ctx](const func_args & args) -> value {
|
|
658
|
+
size_t expected_count = this->args.size();
|
|
659
|
+
size_t input_count = args.count();
|
|
660
|
+
|
|
661
|
+
JJ_DEBUG("Invoking macro '%s' with %zu input arguments (expected %zu)", name.c_str(), input_count, expected_count);
|
|
662
|
+
context macro_ctx(ctx); // new scope for macro execution
|
|
663
|
+
|
|
664
|
+
// bind parameters
|
|
665
|
+
for (size_t i = 0; i < expected_count; ++i) {
|
|
666
|
+
if (i < input_count) {
|
|
667
|
+
if (is_stmt<identifier>(this->args[i])) {
|
|
668
|
+
// normal parameter
|
|
669
|
+
std::string param_name = cast_stmt<identifier>(this->args[i])->val;
|
|
670
|
+
JJ_DEBUG(" Binding parameter '%s' to argument of type %s", param_name.c_str(), args.get_pos(i)->type().c_str());
|
|
671
|
+
macro_ctx.set_val(param_name, args.get_pos(i));
|
|
672
|
+
} else if (is_stmt<keyword_argument_expression>(this->args[i])) {
|
|
673
|
+
// default argument used as normal parameter
|
|
674
|
+
auto kwarg = cast_stmt<keyword_argument_expression>(this->args[i]);
|
|
675
|
+
if (!is_stmt<identifier>(kwarg->key)) {
|
|
676
|
+
throw std::runtime_error("Keyword argument key must be an identifier in macro '" + name + "'");
|
|
677
|
+
}
|
|
678
|
+
std::string param_name = cast_stmt<identifier>(kwarg->key)->val;
|
|
679
|
+
JJ_DEBUG(" Binding parameter '%s' to argument of type %s", param_name.c_str(), args.get_pos(i)->type().c_str());
|
|
680
|
+
macro_ctx.set_val(param_name, args.get_pos(i));
|
|
681
|
+
} else {
|
|
682
|
+
throw std::runtime_error("Invalid parameter type in macro '" + name + "'");
|
|
683
|
+
}
|
|
684
|
+
} else {
|
|
685
|
+
auto & default_arg = this->args[i];
|
|
686
|
+
if (is_stmt<keyword_argument_expression>(default_arg)) {
|
|
687
|
+
auto kwarg = cast_stmt<keyword_argument_expression>(default_arg);
|
|
688
|
+
if (!is_stmt<identifier>(kwarg->key)) {
|
|
689
|
+
throw std::runtime_error("Keyword argument key must be an identifier in macro '" + name + "'");
|
|
690
|
+
}
|
|
691
|
+
std::string param_name = cast_stmt<identifier>(kwarg->key)->val;
|
|
692
|
+
JJ_DEBUG(" Binding parameter '%s' to default argument of type %s", param_name.c_str(), kwarg->val->type().c_str());
|
|
693
|
+
macro_ctx.set_val(param_name, kwarg->val->execute(ctx));
|
|
694
|
+
} else {
|
|
695
|
+
throw std::runtime_error("Not enough arguments provided to macro '" + name + "'");
|
|
696
|
+
}
|
|
697
|
+
//std::string param_name = cast_stmt<identifier>(default_args[i])->val;
|
|
698
|
+
//JJ_DEBUG(" Binding parameter '%s' to default", param_name.c_str());
|
|
699
|
+
//macro_ctx.var[param_name] = default_args[i]->execute(ctx);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// execute macro body
|
|
704
|
+
JJ_DEBUG("Executing macro '%s' body with %zu statements", name.c_str(), this->body.size());
|
|
705
|
+
auto res = exec_statements(this->body, macro_ctx);
|
|
706
|
+
JJ_DEBUG("Macro '%s' execution complete, result: %s", name.c_str(), res->val_str.str().c_str());
|
|
707
|
+
return res;
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
JJ_DEBUG("Defining macro '%s' with %zu parameters", name.c_str(), args.size());
|
|
711
|
+
ctx.set_val(name, mk_val<value_func>(name, func));
|
|
712
|
+
return mk_val<value_undefined>();
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
value member_expression::execute_impl(context & ctx) {
|
|
716
|
+
value object = this->object->execute(ctx);
|
|
717
|
+
|
|
718
|
+
value property;
|
|
719
|
+
if (this->computed) {
|
|
720
|
+
// syntax: obj[expr]
|
|
721
|
+
JJ_DEBUG("Member expression, computing property type %s", this->property->type().c_str());
|
|
722
|
+
|
|
723
|
+
int64_t arr_size = 0;
|
|
724
|
+
if (is_val<value_array>(object)) {
|
|
725
|
+
arr_size = object->as_array().size();
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
if (is_stmt<slice_expression>(this->property)) {
|
|
729
|
+
auto s = cast_stmt<slice_expression>(this->property);
|
|
730
|
+
value start_val = s->start_expr ? s->start_expr->execute(ctx) : mk_val<value_int>(0);
|
|
731
|
+
value stop_val = s->stop_expr ? s->stop_expr->execute(ctx) : mk_val<value_int>(arr_size);
|
|
732
|
+
value step_val = s->step_expr ? s->step_expr->execute(ctx) : mk_val<value_int>(1);
|
|
733
|
+
|
|
734
|
+
// translate to function call: obj.slice(start, stop, step)
|
|
735
|
+
JJ_DEBUG("Member expression is a slice: start %s, stop %s, step %s",
|
|
736
|
+
start_val->as_repr().c_str(),
|
|
737
|
+
stop_val->as_repr().c_str(),
|
|
738
|
+
step_val->as_repr().c_str());
|
|
739
|
+
auto slice_func = try_builtin_func(ctx, "slice", object);
|
|
740
|
+
func_args args(ctx);
|
|
741
|
+
args.push_back(start_val);
|
|
742
|
+
args.push_back(stop_val);
|
|
743
|
+
args.push_back(step_val);
|
|
744
|
+
return slice_func->invoke(args);
|
|
745
|
+
} else {
|
|
746
|
+
property = this->property->execute(ctx);
|
|
747
|
+
}
|
|
748
|
+
} else {
|
|
749
|
+
// syntax: obj.prop
|
|
750
|
+
if (!is_stmt<identifier>(this->property)) {
|
|
751
|
+
throw std::runtime_error("Static member property must be an identifier");
|
|
752
|
+
}
|
|
753
|
+
property = mk_val<value_string>(cast_stmt<identifier>(this->property)->val);
|
|
754
|
+
std::string prop = property->as_string().str();
|
|
755
|
+
JJ_DEBUG("Member expression, object type %s, static property '%s'", object->type().c_str(), prop.c_str());
|
|
756
|
+
|
|
757
|
+
// behavior of jinja2: obj having prop as a built-in function AND 'prop', as an object key,
|
|
758
|
+
// then obj.prop returns the built-in function, not the property value.
|
|
759
|
+
// while obj['prop'] returns the property value.
|
|
760
|
+
// example: {"obj": {"items": 123}} -> obj.items is the built-in function, obj['items'] is 123
|
|
761
|
+
|
|
762
|
+
value val = try_builtin_func(ctx, prop, object, true);
|
|
763
|
+
if (!is_val<value_undefined>(val)) {
|
|
764
|
+
return val;
|
|
765
|
+
}
|
|
766
|
+
// else, fallthrough to normal property access below
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
JJ_DEBUG("Member expression on object type %s, property type %s", object->type().c_str(), property->type().c_str());
|
|
770
|
+
|
|
771
|
+
value val = mk_val<value_undefined>("object_property");
|
|
772
|
+
|
|
773
|
+
if (is_val<value_undefined>(object)) {
|
|
774
|
+
JJ_DEBUG("%s", "Accessing property on undefined object, returning undefined");
|
|
775
|
+
return val;
|
|
776
|
+
} else if (is_val<value_object>(object)) {
|
|
777
|
+
if (!is_val<value_string>(property)) {
|
|
778
|
+
throw std::runtime_error("Cannot access object with non-string: got " + property->type());
|
|
779
|
+
}
|
|
780
|
+
auto key = property->as_string().str();
|
|
781
|
+
val = object->at(key, val);
|
|
782
|
+
if (is_val<value_undefined>(val)) {
|
|
783
|
+
val = try_builtin_func(ctx, key, object, true);
|
|
784
|
+
}
|
|
785
|
+
JJ_DEBUG("Accessed property '%s' value, got type: %s", key.c_str(), val->type().c_str());
|
|
786
|
+
} else if (is_val<value_array>(object) || is_val<value_string>(object)) {
|
|
787
|
+
if (is_val<value_int>(property)) {
|
|
788
|
+
int64_t index = property->as_int();
|
|
789
|
+
JJ_DEBUG("Accessing %s index %d", object->type().c_str(), (int)index);
|
|
790
|
+
if (is_val<value_array>(object)) {
|
|
791
|
+
auto & arr = object->as_array();
|
|
792
|
+
if (index < 0) {
|
|
793
|
+
index += static_cast<int64_t>(arr.size());
|
|
794
|
+
}
|
|
795
|
+
if (index >= 0 && index < static_cast<int64_t>(arr.size())) {
|
|
796
|
+
val = arr[index];
|
|
797
|
+
}
|
|
798
|
+
} else { // value_string
|
|
799
|
+
auto str = object->as_string().str();
|
|
800
|
+
if (index >= 0 && index < static_cast<int64_t>(str.size())) {
|
|
801
|
+
val = mk_val<value_string>(std::string(1, str[index]));
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
} else if (is_val<value_string>(property)) {
|
|
806
|
+
auto key = property->as_string().str();
|
|
807
|
+
JJ_DEBUG("Accessing %s built-in '%s'", is_val<value_array>(object) ? "array" : "string", key.c_str());
|
|
808
|
+
val = try_builtin_func(ctx, key, object, true);
|
|
809
|
+
} else {
|
|
810
|
+
throw std::runtime_error("Cannot access property with non-string/non-number: got " + property->type());
|
|
811
|
+
}
|
|
812
|
+
} else {
|
|
813
|
+
if (!is_val<value_string>(property)) {
|
|
814
|
+
throw std::runtime_error("Cannot access property with non-string: got " + property->type());
|
|
815
|
+
}
|
|
816
|
+
auto key = property->as_string().str();
|
|
817
|
+
val = try_builtin_func(ctx, key, object, true);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if (ctx.is_get_stats && val && object && property) {
|
|
821
|
+
val->stats.used = true;
|
|
822
|
+
object->stats.used = true;
|
|
823
|
+
if (is_val<value_int>(property)) {
|
|
824
|
+
object->stats.ops.insert("array_access");
|
|
825
|
+
} else if (is_val<value_string>(property)) {
|
|
826
|
+
object->stats.ops.insert("object_access");
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
return val;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
value call_expression::execute_impl(context & ctx) {
|
|
834
|
+
// gather arguments
|
|
835
|
+
func_args args(ctx);
|
|
836
|
+
for (auto & arg_stmt : this->args) {
|
|
837
|
+
auto arg_val = arg_stmt->execute(ctx);
|
|
838
|
+
JJ_DEBUG(" Argument type: %s", arg_val->type().c_str());
|
|
839
|
+
args.push_back(std::move(arg_val));
|
|
840
|
+
}
|
|
841
|
+
// execute callee
|
|
842
|
+
value callee_val = callee->execute(ctx);
|
|
843
|
+
if (!is_val<value_func>(callee_val)) {
|
|
844
|
+
throw std::runtime_error("Callee is not a function: got " + callee_val->type());
|
|
845
|
+
}
|
|
846
|
+
auto * callee_func = cast_val<value_func>(callee_val);
|
|
847
|
+
JJ_DEBUG("Calling function '%s' with %zu arguments", callee_func->name.c_str(), args.count());
|
|
848
|
+
return callee_func->invoke(args);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
value keyword_argument_expression::execute_impl(context & ctx) {
|
|
852
|
+
if (!is_stmt<identifier>(key)) {
|
|
853
|
+
throw std::runtime_error("Keyword argument key must be identifiers");
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
std::string k = cast_stmt<identifier>(key)->val;
|
|
857
|
+
JJ_DEBUG("Keyword argument expression key: %s, value: %s", k.c_str(), val->type().c_str());
|
|
858
|
+
|
|
859
|
+
value v = val->execute(ctx);
|
|
860
|
+
JJ_DEBUG("Keyword argument value executed, type: %s", v->type().c_str());
|
|
861
|
+
|
|
862
|
+
return mk_val<value_kwarg>(k, v);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
} // namespace jinja
|