Fun API Documentation 0.42.1
The programming language that makes you have fun!
Loading...
Searching...
No Matches
parser.c
Go to the documentation of this file.
1/*
2 * This file is part of the Fun programming language.
3 * https://fun-lang.xyz/
4 *
5 * Copyright 2025 Johannes Findeisen <you@hanez.org>
6 * Licensed under the terms of the Apache-2.0 license.
7 * https://opensource.org/license/apache-2-0
8 */
9
10/**
11 * @file parser.c
12 * @brief Implements the Fun language parser that converts source code to bytecode.
13 *
14 * This file contains the main parsing logic for the Fun programming language.
15 * It handles converting .fun source files into executable bytecode for the VM.
16 *
17 * Key Features:
18 * - Handles shebang lines
19 * - Skips whitespace and comments
20 * - Parses string literals with both single and double quotes
21 * - Supports basic function definitions
22 * - Compiles print statements
23 * - Generates bytecode with proper constants and instructions
24 *
25 * Functions:
26 * - parse_file_to_bytecode(): Main entry point for file parsing
27 * - parse_string_to_bytecode(): Parses code from string buffers
28 * - parser_last_error(): Retrieves parsing errors
29 *
30 * Error Handling:
31 * - Returns NULL on parse errors
32 * - Tracks error messages and positions
33 * - Validates syntax before bytecode generation
34 *
35 * Example:
36 * Bytecode *bc = parse_file_to_bytecode("example.fun");
37 * if (bc) {
38 * vm_run(&vm, bc);
39 * bytecode_free(bc);
40 * }
41 */
42
43#include "parser.h"
44#include "value.h"
45#include "vm.h"
46#include <ctype.h>
47#include <stdarg.h>
48#include <stdio.h>
49#include <stdlib.h>
50#include <string.h>
51
52/* from parser_utils.c */
53extern char *preprocess_includes_with_path(const char *src, const char *current_path);
54
55/* Forward declarations for helpers used before their definitions */
56static void skip_to_eol(const char *src, size_t len, size_t *pos);
57static int read_line_start(const char *src, size_t len, size_t *pos, int *out_indent);
58static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, int current_indent);
59static void calc_line_col(const char *src, size_t len, size_t pos, int *out_line, int *out_col);
60
61/* ---- parser error state ---- */
62static const char *g_current_source_path = NULL; /* for propagating filename into nested bytecodes */
63/* Track active source buffer for error line computation and cascade control */
64static const char *g_active_src = NULL;
65static size_t g_active_len = 0;
66static int g_last_err_line = -1;
67static int g_line_err_count = 0;
68static const int G_ERRS_PER_LINE_CAP = 3; /* suppress floods after this many per line */
69static int g_has_error = 0;
70static size_t g_err_pos = 0;
71static char g_err_msg[256];
72static int g_err_line = 0;
73static int g_err_col = 0;
74
75/* ---- compiler-generated temporary counter ---- */
76static int g_temp_counter = 0;
77
78/* ---- runtime debug control (for suppressing noisy stdout in production/CGI) ---- */
79/**
80 * @brief Interpret an environment variable as a boolean flag.
81 *
82 * Recognizes common truthy forms: 1, true/TRUE, yes/YES, on/ON.
83 * Any other value (including unset) is considered false.
84 *
85 * @param name Environment variable name (must not be NULL).
86 * @return 1 if the variable is set to a recognized truthy value, 0 otherwise.
87 */
88static int env_truthy(const char *name) {
89 const char *v = getenv(name);
90 if (!v) return 0;
91 if (strcmp(v, "1") == 0) return 1;
92 if (strcmp(v, "true") == 0) return 1;
93 if (strcmp(v, "TRUE") == 0) return 1;
94 if (strcmp(v, "yes") == 0) return 1;
95 if (strcmp(v, "YES") == 0) return 1;
96 if (strcmp(v, "on") == 0) return 1;
97 if (strcmp(v, "ON") == 0) return 1;
98 return 0;
99}
100
101/**
102 * @brief Determine whether parser/compiler debug tracing is enabled.
103 *
104 * Debugging can be enabled by setting either FUN_TRACE or FUN_DEBUG to a
105 * truthy environment value (see env_truthy).
106 *
107 * @return 1 if debug is enabled, 0 otherwise.
108 */
109static int fun_debug_enabled(void) {
110 /* Allow enabling debug dumps at runtime via environment.
111 * Default is OFF to avoid contaminating stdout (e.g., CGI responses).
112 */
113 if (env_truthy("FUN_TRACE")) return 1;
114 if (env_truthy("FUN_DEBUG")) return 1;
115 return 0;
116}
117
118/* Declared type metadata encoding in types[]:
119 0 = dynamic/untyped;
120 positive/negative 8/16/32/64 = integers (negative means signed);
121 TYPE_META_STRING/BOOLEAN/NIL mark non-integer enforced types;
122 TYPE_META_CLASS marks class instances (Map with "__class"). */
123/** @brief Type metadata tag used for string enforcement in declared types. */
124#define TYPE_META_STRING 10001
125/** @brief Type metadata tag used for boolean enforcement in declared types. */
126#define TYPE_META_BOOLEAN 10002
127/** @brief Type metadata tag indicating explicit nil type. */
128#define TYPE_META_NIL 10003
129/** @brief Type metadata tag marking class/instance values. */
130#define TYPE_META_CLASS 10004
131/** @brief Type metadata tag marking floating point numbers. */
132#define TYPE_META_FLOAT 10005
133/** @brief Type metadata tag marking array values. */
134#define TYPE_META_ARRAY 10006
135
136/**
137 * @brief Record a parser/compiler error at a given source position.
138 *
139 * Formats an error message into the parser's global error buffer and stores
140 * the offending byte position. Line and column are derived later on demand.
141 *
142 * @param pos Byte offset in the current (preprocessed) source.
143 * @param fmt printf-style format string for the message.
144 * @param ... Arguments matching fmt.
145 */
146static void parser_fail(size_t pos, const char *fmt, ...) {
147 /* Optional flood guard: suppress excessive errors on the same source line */
148 if (g_active_src && g_active_len > 0) {
149 int line = 0, col = 0;
150 calc_line_col(g_active_src, g_active_len, pos, &line, &col);
151 if (line == g_last_err_line) {
153 /* Too many errors on this line; ignore to avoid cascades */
154 return;
155 }
157 } else {
158 g_last_err_line = line;
160 }
161 }
162
163 g_has_error = 1;
164 g_err_pos = pos;
165 va_list ap;
166 va_start(ap, fmt);
167 vsnprintf(g_err_msg, sizeof(g_err_msg), fmt, ap);
168 va_end(ap);
169}
170
171/**
172 * @brief Compute one-based line and column from a byte position.
173 *
174 * Counts newlines up to the smaller of pos and len to derive a human-friendly
175 * (line, column) pair. Columns are one-based and reset after each newline.
176 *
177 * @param src Source buffer.
178 * @param len Source length in bytes.
179 * @param pos Target byte position within the source.
180 * @param out_line Optional out parameter for the computed line.
181 * @param out_col Optional out parameter for the computed column.
182 */
183static void calc_line_col(const char *src, size_t len, size_t pos, int *out_line, int *out_col) {
184 int line = 1, col = 1;
185 size_t limit = pos < len ? pos : len;
186 for (size_t i = 0; i < limit; ++i) {
187 if (src[i] == '\n') {
188 line++;
189 col = 1;
190 } else {
191 col++;
192 }
193 }
194 if (out_line) *out_line = line;
195 if (out_col) *out_col = col;
196}
197
198/* --------------------------- */
199
200/* Namespace alias tracking (for include-as):
201 We scan preprocessed source for lines starting with "// __ns_alias__: <name>"
202 and treat dot-calls on those identifiers as plain function calls (no implicit 'this'). */
203static char *g_ns_aliases[64];
204static int g_ns_alias_count = 0;
205
206/**
207 * @brief Reset and free all tracked namespace aliases.
208 */
209static void ns_aliases_reset(void) {
210 for (int i = 0; i < g_ns_alias_count; ++i) {
211 free(g_ns_aliases[i]);
212 g_ns_aliases[i] = NULL;
213 }
215}
216
217/**
218 * @brief Scan preprocessed source for namespace alias markers.
219 *
220 * Looks for lines starting with "// __ns_alias__: <name>" and stores <name>
221 * so that member-style calls on that identifier are parsed as plain calls
222 * (no implicit receiver).
223 *
224 * @param src Preprocessed source buffer.
225 * @param len Length of src in bytes.
226 */
227static void ns_aliases_scan(const char *src, size_t len) {
228 const char *marker = "// __ns_alias__: ";
229 size_t mlen = strlen(marker);
230 size_t i = 0;
231 while (i < len) {
232 /* find start of line */
233 size_t ls = i;
234 /* move to end of line first */
235 while (i < len && src[i] != '\n')
236 i++;
237 size_t le = i;
238 /* include trailing '\n' in next iteration */
239 if (i < len && src[i] == '\n') i++;
240
241 if (le - ls >= mlen && strncmp(src + ls, marker, mlen) == 0) {
242 size_t p = ls + mlen;
243 /* read identifier */
244 size_t start = p;
245 while (p < le && (src[p] == ' ' || src[p] == '\t'))
246 p++;
247 if (p < le && (isalpha((unsigned char)src[p]) || src[p] == '_')) {
248 size_t q = p + 1;
249 while (q < le && (isalnum((unsigned char)src[q]) || src[q] == '_'))
250 q++;
251 size_t n = q - p;
252 if (n > 0 && g_ns_alias_count < (int)(sizeof(g_ns_aliases) / sizeof(g_ns_aliases[0]))) {
253 char *name = (char *)malloc(n + 1);
254 if (name) {
255 memcpy(name, src + p, n);
256 name[n] = '\0';
258 }
259 }
260 }
261 }
262 }
263}
264
265/**
266 * @brief Check whether an identifier is a registered namespace alias.
267 *
268 * @param name Identifier to test.
269 * @return 1 if name is a known alias, 0 otherwise.
270 */
271static int is_ns_alias(const char *name) {
272 if (!name) return 0;
273 for (int i = 0; i < g_ns_alias_count; ++i) {
274 if (strcmp(g_ns_aliases[i], name) == 0) return 1;
275 }
276 return 0;
277}
278
279#include "parser_utils.c"
280
281/* very small global symbol table for LOAD_GLOBAL/STORE_GLOBAL */
282static struct {
284 int types[MAX_GLOBALS]; /* 0=untyped/number default; else bit width: 8/16/32/64; negative for signed */
285 int is_class[MAX_GLOBALS]; /* 1 if this global name denotes a class factory */
286 int count;
287} G = {{0}, {0}, {0}, 0};
288
289/**
290 * @brief Find a global symbol index by name.
291 *
292 * @param name Symbol name.
293 * @return Index in the global table, or -1 if not found.
294 */
295static int sym_find(const char *name) {
296 for (int i = 0; i < G.count; ++i) {
297 if (strcmp(G.names[i], name) == 0) return i;
298 }
299 return -1;
300}
301
302/**
303 * @brief Get or create a global symbol index for a name.
304 *
305 * Ensures the symbol exists in the global table, creating a new entry with
306 * default metadata when absent.
307 *
308 * @param name Symbol name.
309 * @return Index of the symbol (>=0). Returns 0 after reporting an error if
310 * the table is full.
311 */
312static int sym_index(const char *name) {
313 int existing = sym_find(name);
314 if (existing >= 0) return existing;
315 if (G.count >= MAX_GLOBALS) {
316 parser_fail(0, "Too many globals (max %d)", MAX_GLOBALS);
317 return 0;
318 }
319 G.names[G.count] = strdup(name);
320 G.types[G.count] = 0; /* default: untyped */
321 G.is_class[G.count] = 0; /* default: not a class */
322 return G.count++;
323}
324
325/* ---- locals environment for functions ---- */
326typedef struct {
328 int types[MAX_FRAME_LOCALS]; /* 0=untyped; else bit width: 8/16/32/64 */
329 int count;
330} LocalEnv;
331
332static LocalEnv *g_locals = NULL;
333
334/* --- nested function environment tracking (for no-capture enforcement) --- */
336static int g_func_env_depth = 0; /* number of valid outer env entries */
337
338/**
339 * @brief Check whether a local name exists in any outer function environment.
340 *
341 * Used to enforce no-capture semantics for nested functions.
342 *
343 * @param name Local identifier to search for.
344 * @return 1 if present in any outer environment, 0 otherwise.
345 */
346static int name_in_outer_envs(const char *name) {
347 if (g_func_env_depth <= 0) return 0;
348 for (int d = g_func_env_depth - 1; d >= 0; --d) {
350 if (!e) continue;
351 for (int i = 0; i < e->count; ++i) {
352 if (e->names[i] && strcmp(e->names[i], name) == 0) return 1;
353 }
354 }
355 return 0;
356}
357
358/* loop context for break/continue patching */
366
367static LoopCtx *g_loop_ctx = NULL;
368
369/**
370 * @brief Find the index of a local variable in the current function.
371 *
372 * @param name Local identifier to look up.
373 * @return Zero-based local index, or -1 if not found or outside a function.
374 */
375static int local_find(const char *name) {
376 if (!g_locals) return -1;
377 for (int i = 0; i < g_locals->count; ++i) {
378 if (strcmp(g_locals->names[i], name) == 0) return i;
379 }
380 return -1;
381}
382
383/**
384 * @brief Add a new local variable to the current function environment.
385 *
386 * @param name Local identifier to define.
387 * @return The assigned local index, or -1 if no function env exists or limit exceeded.
388 */
389static int local_add(const char *name) {
390 if (!g_locals) return -1;
391 if (g_locals->count >= MAX_FRAME_LOCALS) {
392 parser_fail(0, "Too many local variables/parameters (max %d)", MAX_FRAME_LOCALS);
393 return -1;
394 }
395 int idx = g_locals->count++;
396 g_locals->names[idx] = strdup(name);
397 return idx;
398}
399
400/* forward declaration so helpers can recurse */
401static int emit_expression(Bytecode *bc, const char *src, size_t len, size_t *pos);
402
403/* primary: (expr) | string | number | true/false | identifier */
404/**
405 * @brief Parse and emit bytecode for primary expressions.
406 *
407 * Handles literals, identifiers, parenthesized expressions, function literals,
408 * array/map literals, indexing, calls, member access, and related constructs.
409 *
410 * @param bc Target bytecode under construction.
411 * @param src Source buffer.
412 * @param len Source length in bytes.
413 * @param pos In/out byte position pointer; advanced past the parsed primary.
414 * @return 1 on success, 0 on parse error.
415 */
416static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) {
417 skip_spaces(src, len, pos);
418
419 /* anonymous function literal: fn(arg, ...) <newline> indented-body end */
420 if (*pos + 2 < len && starts_with(src, len, *pos, "fn") && (src[*pos + 2] == '(' || src[*pos + 2] == ' ' || src[*pos + 2] == '\t')) {
421 *pos += 2;
422 skip_spaces(src, len, pos);
423 if (!consume_char(src, len, pos, '(')) {
424 parser_fail(*pos, "Expected '(' after 'fn'");
425 return 0;
426 }
427
428 /* build locals from parameters */
429 LocalEnv env = {{0}, {0}, 0};
431 int saved_depth = g_func_env_depth;
432 /* push outer env for no-capture checks */
433 if (prev != NULL) {
434 if (g_func_env_depth >= (int)(sizeof(g_func_env_stack) / sizeof(g_func_env_stack[0]))) {
435 parser_fail(*pos, "Too many nested functions (env stack overflow)");
436 return 0;
437 }
439 }
440 g_locals = &env;
441
442 skip_spaces(src, len, pos);
443 if (*pos < len && src[*pos] != ')') {
444 for (;;) {
445 char *pname = NULL;
446 if (!read_identifier_into(src, len, pos, &pname)) {
447 parser_fail(*pos, "Expected parameter name");
448 g_locals = prev;
449 g_func_env_depth = saved_depth;
450 return 0;
451 }
452 if (local_find(pname) >= 0) {
453 parser_fail(*pos, "Duplicate parameter name '%s'", pname);
454 free(pname);
455 g_locals = prev;
456 g_func_env_depth = saved_depth;
457 return 0;
458 }
459 local_add(pname);
460 free(pname);
461 skip_spaces(src, len, pos);
462 if (*pos < len && src[*pos] == ',') {
463 (*pos)++;
464 skip_spaces(src, len, pos);
465 continue;
466 }
467 break;
468 }
469 }
470 if (!consume_char(src, len, pos, ')')) {
471 parser_fail(*pos, "Expected ')' after parameter list");
472 g_locals = prev;
473 g_func_env_depth = saved_depth;
474 return 0;
475 }
476 /* end header line */
477 skip_to_eol(src, len, pos);
478
479 /* compile body into separate Bytecode */
480 Bytecode *fn_bc = bytecode_new();
481 if (fn_bc) {
482 if (fn_bc->name) free((void *)fn_bc->name);
483 fn_bc->name = strdup("<fn>");
484 if (fn_bc->source_file) free((void *)fn_bc->source_file);
486 }
487
488 /* parse body at increased indent if present */
489 int body_indent = 0;
490 size_t look_body = *pos;
491 if (read_line_start(src, len, &look_body, &body_indent) && body_indent > 0) {
492 parse_block(fn_bc, src, len, pos, body_indent);
493 } else {
494 /* empty body ok */
495 }
497
498 /* restore env stack */
499 g_locals = prev;
500 g_func_env_depth = saved_depth;
501
502 int fci = bytecode_add_constant(bc, make_function(fn_bc));
504 return 1;
505 }
506
507 /* parenthesized */
508 if (*pos < len && src[*pos] == '(') {
509 (*pos)++; /* '(' */
510 if (!emit_expression(bc, src, len, pos)) {
511 parser_fail(*pos, "Expected expression after '('");
512 return 0;
513 }
514 if (!consume_char(src, len, pos, ')')) {
515 parser_fail(*pos, "Expected ')'");
516 return 0;
517 }
518 /* postfix indexing or slice */
519 for (;;) {
520 skip_spaces(src, len, pos);
521 if (*pos < len && src[*pos] == '[') {
522 (*pos)++;
523 if (!emit_expression(bc, src, len, pos)) {
524 parser_fail(*pos, "Expected start expression");
525 return 0;
526 }
527 skip_spaces(src, len, pos);
528 if (*pos < len && src[*pos] == ':') {
529 (*pos)++;
530 skip_spaces(src, len, pos);
531 size_t savep4 = *pos;
532 if (!emit_expression(bc, src, len, pos)) {
533 *pos = savep4;
534 int ci4 = bytecode_add_constant(bc, make_int(-1));
536 }
537 if (!consume_char(src, len, pos, ']')) {
538 parser_fail(*pos, "Expected ']' after slice");
539 return 0;
540 }
542 continue;
543 } else {
544 if (!consume_char(src, len, pos, ']')) {
545 parser_fail(*pos, "Expected ']' after index");
546 return 0;
547 }
549 continue;
550 }
551 }
552 break;
553 }
554 return 1;
555 }
556
557 /* string */
558 char *s = parse_string_literal_any_quote(src, len, pos);
559 if (s) {
560 int ci = bytecode_add_constant(bc, make_string(s));
561 free(s);
563 /* postfix indexing or slice */
564 for (;;) {
565 skip_spaces(src, len, pos);
566 if (*pos < len && src[*pos] == '[') {
567 (*pos)++;
568 if (!emit_expression(bc, src, len, pos)) {
569 parser_fail(*pos, "Expected start expression");
570 return 0;
571 }
572 skip_spaces(src, len, pos);
573 if (*pos < len && src[*pos] == ':') {
574 (*pos)++;
575 skip_spaces(src, len, pos);
576 /* end is optional; if missing, use -1 (till end) */
577 int has_end = 0;
578 size_t savep = *pos;
579 if (emit_expression(bc, src, len, pos)) {
580 has_end = 1;
581 } else {
582 *pos = savep;
583 int ci = bytecode_add_constant(bc, make_int(-1));
585 }
586 if (!consume_char(src, len, pos, ']')) {
587 parser_fail(*pos, "Expected ']' after slice");
588 return 0;
589 }
591 continue;
592 } else {
593 if (!consume_char(src, len, pos, ']')) {
594 parser_fail(*pos, "Expected ']' after index");
595 return 0;
596 }
598 continue;
599 }
600 }
601 break;
602 }
603 return 1;
604 }
605
606 /* array literal: [expr, expr, ...] */
607 skip_spaces(src, len, pos);
608 if (*pos < len && src[*pos] == '[') {
609 (*pos)++; /* '[' */
610 int count = 0;
611 skip_spaces(src, len, pos);
612 if (*pos < len && src[*pos] != ']') {
613 for (;;) {
614 if (!emit_expression(bc, src, len, pos)) {
615 parser_fail(*pos, "Expected expression in array literal");
616 return 0;
617 }
618 count++;
619 skip_spaces(src, len, pos);
620 if (*pos < len && src[*pos] == ',') {
621 (*pos)++;
622 skip_spaces(src, len, pos);
623 continue;
624 }
625 break;
626 }
627 }
628 if (!consume_char(src, len, pos, ']')) {
629 parser_fail(*pos, "Expected ']' to close array literal");
630 return 0;
631 }
633 /* postfix indexing or slice */
634 for (;;) {
635 skip_spaces(src, len, pos);
636 if (*pos < len && src[*pos] == '[') {
637 (*pos)++;
638 if (!emit_expression(bc, src, len, pos)) {
639 parser_fail(*pos, "Expected start expression");
640 return 0;
641 }
642 skip_spaces(src, len, pos);
643 if (*pos < len && src[*pos] == ':') {
644 (*pos)++;
645 skip_spaces(src, len, pos);
646 size_t savep3 = *pos;
647 if (!emit_expression(bc, src, len, pos)) {
648 *pos = savep3;
649 int ci3 = bytecode_add_constant(bc, make_int(-1));
651 }
652 if (!consume_char(src, len, pos, ']')) {
653 parser_fail(*pos, "Expected ']' after slice");
654 return 0;
655 }
657 continue;
658 } else {
659 if (!consume_char(src, len, pos, ']')) {
660 parser_fail(*pos, "Expected ']' after index");
661 return 0;
662 }
664 continue;
665 }
666 }
667 break;
668 }
669 return 1;
670 }
671
672 /* map literal: { "key": expr, ... } */
673 skip_spaces(src, len, pos);
674 if (*pos < len && src[*pos] == '{') {
675 (*pos)++; /* '{' */
676 int pairs = 0;
677 skip_spaces(src, len, pos);
678 if (*pos < len && src[*pos] != '}') {
679 for (;;) {
680 /* key must be a string literal */
681 char *k = parse_string_literal_any_quote(src, len, pos);
682 if (!k) {
683 parser_fail(*pos, "Expected string key in map literal");
684 return 0;
685 }
686 int kci = bytecode_add_constant(bc, make_string(k));
687 free(k);
689 skip_spaces(src, len, pos);
690 if (!consume_char(src, len, pos, ':')) {
691 parser_fail(*pos, "Expected ':' after map key");
692 return 0;
693 }
694 if (!emit_expression(bc, src, len, pos)) {
695 parser_fail(*pos, "Expected value expression in map literal");
696 return 0;
697 }
698 pairs++;
699 skip_spaces(src, len, pos);
700 if (*pos < len && src[*pos] == ',') {
701 (*pos)++;
702 skip_spaces(src, len, pos);
703 continue;
704 }
705 break;
706 }
707 }
708 if (!consume_char(src, len, pos, '}')) {
709 parser_fail(*pos, "Expected '}' to close map literal");
710 return 0;
711 }
713 return 1;
714 }
715
716 /* number (prefer float first to consume cases like 1.23 or 1e2) */
717 int ok = 0;
718 size_t save = *pos;
719 /* float literal */
720 double fval = parse_float_literal_value(src, len, pos, &ok);
721 if (ok) {
722 int ci = bytecode_add_constant(bc, make_float(fval));
724 /* postfix indexing or slice (not typical for floats but keep consistency) */
725 for (;;) {
726 skip_spaces(src, len, pos);
727 if (*pos < len && src[*pos] == '[') {
728 (*pos)++;
729 if (!emit_expression(bc, src, len, pos)) {
730 parser_fail(*pos, "Expected start expression");
731 return 0;
732 }
733 skip_spaces(src, len, pos);
734 if (*pos < len && src[*pos] == ':') {
735 (*pos)++;
736 skip_spaces(src, len, pos);
737 size_t savep2 = *pos;
738 if (!emit_expression(bc, src, len, pos)) {
739 *pos = savep2;
740 int ci2 = bytecode_add_constant(bc, make_int(-1));
742 }
743 if (!consume_char(src, len, pos, ']')) {
744 parser_fail(*pos, "Expected ']' after slice");
745 return 0;
746 }
748 continue;
749 } else {
750 if (!consume_char(src, len, pos, ']')) {
751 parser_fail(*pos, "Expected ']' after index");
752 return 0;
753 }
755 continue;
756 }
757 }
758 break;
759 }
760 return 1;
761 }
762 *pos = save;
763 /* integer literal */
764 int64_t ival = parse_int_literal_value(src, len, pos, &ok);
765 if (ok) {
766 int ci = bytecode_add_constant(bc, make_int(ival));
768 /* postfix indexing or slice */
769 for (;;) {
770 skip_spaces(src, len, pos);
771 if (*pos < len && src[*pos] == '[') {
772 (*pos)++;
773 if (!emit_expression(bc, src, len, pos)) {
774 parser_fail(*pos, "Expected start expression");
775 return 0;
776 }
777 skip_spaces(src, len, pos);
778 if (*pos < len && src[*pos] == ':') {
779 (*pos)++;
780 skip_spaces(src, len, pos);
781 int has_end = 0;
782 size_t savep2 = *pos;
783 if (emit_expression(bc, src, len, pos)) {
784 has_end = 1;
785 } else {
786 *pos = savep2;
787 int ci2 = bytecode_add_constant(bc, make_int(-1));
789 }
790 if (!consume_char(src, len, pos, ']')) {
791 parser_fail(*pos, "Expected ']' after slice");
792 return 0;
793 }
795 continue;
796 } else {
797 if (!consume_char(src, len, pos, ']')) {
798 parser_fail(*pos, "Expected ']' after index");
799 return 0;
800 }
802 continue;
803 }
804 }
805 break;
806 }
807 return 1;
808 }
809
810 /* identifier or keyword */
811 char *name = NULL;
812 if (read_identifier_into(src, len, pos, &name)) {
813 if (strcmp(name, "true") == 0 || strcmp(name, "false") == 0) {
814 int ci = bytecode_add_constant(bc, make_bool(strcmp(name, "true") == 0 ? 1 : 0));
815 free(name);
817 return 1;
818 }
819
820 /* call or variable load (locals preferred) */
821 skip_spaces(src, len, pos);
822 int local_idx = local_find(name);
823 int is_call = (*pos < len && src[*pos] == '(');
824
825 if (is_call) {
826 /* builtins */
827 if (strcmp(name, "len") == 0) {
828 (*pos)++; /* '(' */
829 if (!emit_expression(bc, src, len, pos)) {
830 parser_fail(*pos, "len expects 1 argument");
831 free(name);
832 return 0;
833 }
834 if (!consume_char(src, len, pos, ')')) {
835 parser_fail(*pos, "Expected ')' after len arg");
836 free(name);
837 return 0;
838 }
840 free(name);
841 return 1;
842 }
843 if (strcmp(name, "push") == 0) {
844 (*pos)++; /* '(' */
845 if (!emit_expression(bc, src, len, pos)) {
846 parser_fail(*pos, "push expects array");
847 free(name);
848 return 0;
849 }
850 if (*pos < len && src[*pos] == ',') {
851 (*pos)++;
852 skip_spaces(src, len, pos);
853 } else {
854 parser_fail(*pos, "push expects 2 args");
855 free(name);
856 return 0;
857 }
858 if (!emit_expression(bc, src, len, pos)) {
859 parser_fail(*pos, "push expects value");
860 free(name);
861 return 0;
862 }
863 if (!consume_char(src, len, pos, ')')) {
864 parser_fail(*pos, "Expected ')' after push args");
865 free(name);
866 return 0;
867 }
869 free(name);
870 return 1;
871 }
872 if (strcmp(name, "pop") == 0) {
873 (*pos)++; /* '(' */
874 if (!emit_expression(bc, src, len, pos)) {
875 parser_fail(*pos, "pop expects array");
876 free(name);
877 return 0;
878 }
879 if (!consume_char(src, len, pos, ')')) {
880 parser_fail(*pos, "Expected ')' after pop arg");
881 free(name);
882 return 0;
883 }
885 free(name);
886 return 1;
887 }
888 if (strcmp(name, "set") == 0) {
889 (*pos)++; /* '(' */
890 if (!emit_expression(bc, src, len, pos)) {
891 parser_fail(*pos, "set expects array");
892 free(name);
893 return 0;
894 }
895 if (*pos < len && src[*pos] == ',') {
896 (*pos)++;
897 skip_spaces(src, len, pos);
898 } else {
899 parser_fail(*pos, "set expects 3 args");
900 free(name);
901 return 0;
902 }
903 if (!emit_expression(bc, src, len, pos)) {
904 parser_fail(*pos, "set expects index");
905 free(name);
906 return 0;
907 }
908 if (*pos < len && src[*pos] == ',') {
909 (*pos)++;
910 skip_spaces(src, len, pos);
911 } else {
912 parser_fail(*pos, "set expects 3 args");
913 free(name);
914 return 0;
915 }
916 if (!emit_expression(bc, src, len, pos)) {
917 parser_fail(*pos, "set expects value");
918 free(name);
919 return 0;
920 }
921 if (!consume_char(src, len, pos, ')')) {
922 parser_fail(*pos, "Expected ')' after set args");
923 free(name);
924 return 0;
925 }
927 free(name);
928 return 1;
929 }
930 if (strcmp(name, "insert") == 0) {
931 (*pos)++; /* '(' */
932 if (!emit_expression(bc, src, len, pos)) {
933 parser_fail(*pos, "insert expects array");
934 free(name);
935 return 0;
936 }
937 if (*pos < len && src[*pos] == ',') {
938 (*pos)++;
939 skip_spaces(src, len, pos);
940 } else {
941 parser_fail(*pos, "insert expects 3 args");
942 free(name);
943 return 0;
944 }
945 if (!emit_expression(bc, src, len, pos)) {
946 parser_fail(*pos, "insert expects index");
947 free(name);
948 return 0;
949 }
950 if (*pos < len && src[*pos] == ',') {
951 (*pos)++;
952 skip_spaces(src, len, pos);
953 } else {
954 parser_fail(*pos, "insert expects 3 args");
955 free(name);
956 return 0;
957 }
958 if (!emit_expression(bc, src, len, pos)) {
959 parser_fail(*pos, "insert expects value");
960 free(name);
961 return 0;
962 }
963 if (!consume_char(src, len, pos, ')')) {
964 parser_fail(*pos, "Expected ')' after insert args");
965 free(name);
966 return 0;
967 }
969 free(name);
970 return 1;
971 }
972 if (strcmp(name, "remove") == 0) {
973 (*pos)++; /* '(' */
974 if (!emit_expression(bc, src, len, pos)) {
975 parser_fail(*pos, "remove expects array");
976 free(name);
977 return 0;
978 }
979 if (*pos < len && src[*pos] == ',') {
980 (*pos)++;
981 skip_spaces(src, len, pos);
982 } else {
983 parser_fail(*pos, "remove expects 2 args");
984 free(name);
985 return 0;
986 }
987 if (!emit_expression(bc, src, len, pos)) {
988 parser_fail(*pos, "remove expects index");
989 free(name);
990 return 0;
991 }
992 if (!consume_char(src, len, pos, ')')) {
993 parser_fail(*pos, "Expected ')' after remove args");
994 free(name);
995 return 0;
996 }
998 free(name);
999 return 1;
1000 }
1001 if (strcmp(name, "to_number") == 0) {
1002 (*pos)++; /* '(' */
1003 if (!emit_expression(bc, src, len, pos)) {
1004 parser_fail(*pos, "to_number expects 1 argument");
1005 free(name);
1006 return 0;
1007 }
1008 if (!consume_char(src, len, pos, ')')) {
1009 parser_fail(*pos, "Expected ')' after to_number arg");
1010 free(name);
1011 return 0;
1012 }
1014 free(name);
1015 return 1;
1016 }
1017 if (strcmp(name, "to_string") == 0) {
1018 (*pos)++; /* '(' */
1019 if (!emit_expression(bc, src, len, pos)) {
1020 parser_fail(*pos, "to_string expects 1 argument");
1021 free(name);
1022 return 0;
1023 }
1024 if (!consume_char(src, len, pos, ')')) {
1025 parser_fail(*pos, "Expected ')' after to_string arg");
1026 free(name);
1027 return 0;
1028 }
1030 free(name);
1031 return 1;
1032 }
1033 if (strcmp(name, "cast") == 0) {
1034 (*pos)++; /* '(' */
1035 /* cast(value, typeName) */
1036 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
1037 parser_fail(*pos, "cast expects (value, typeName)");
1038 free(name);
1039 return 0;
1040 }
1041 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
1042 parser_fail(*pos, "cast expects (value, typeName)");
1043 free(name);
1044 return 0;
1045 }
1047 free(name);
1048 return 1;
1049 }
1050 if (strcmp(name, "typeof") == 0) {
1051 (*pos)++; /* '(' */
1052 /* Special handling for typeof(<identifier>) to return declared subtype for integers */
1053 size_t peek = *pos;
1054 char *vname = NULL;
1055 int handled = 0;
1056 if (read_identifier_into(src, len, &peek, &vname)) {
1057 skip_spaces(src, len, &peek);
1058 if (peek < len && src[peek] == ')') {
1059 int meta = 0;
1060 int lidx = local_find(vname);
1061 if (lidx >= 0) {
1062 meta = g_locals->types[lidx];
1063 } else {
1064 int gi = sym_index(vname);
1065 if (gi >= 0) meta = G.types[gi];
1066 }
1067
1068 if (meta != 0 && meta != TYPE_META_STRING && meta != TYPE_META_BOOLEAN && meta != TYPE_META_NIL && meta != TYPE_META_CLASS && meta != TYPE_META_FLOAT) {
1069 /* Integer subtype: ±bits */
1070 int abs_bits = meta < 0 ? -meta : meta;
1071 const char *tname = (meta < 0)
1072 ? (abs_bits == 64 ? "Sint64" : (abs_bits == 32 ? "Sint32" : (abs_bits == 16 ? "Sint16" : "Sint8")))
1073 : (abs_bits == 64 ? "Uint64" : (abs_bits == 32 ? "Uint32" : (abs_bits == 16 ? "Uint16" : "Uint8")));
1074 int ci = bytecode_add_constant(bc, make_string(tname));
1076 *pos = peek + 1; /* consume name and ')' */
1077 handled = 1;
1078 }
1079 free(vname);
1080 } else {
1081 free(vname);
1082 }
1083 }
1084
1085 if (!handled) {
1086 /* General case: typeof(expression)
1087 If the value is a Map with "__class" key, return that string; else return base typeof.
1088 */
1089 if (!emit_expression(bc, src, len, pos)) {
1090 parser_fail(*pos, "typeof expects 1 argument");
1091 free(name);
1092 return 0;
1093 }
1094 if (!consume_char(src, len, pos, ')')) {
1095 parser_fail(*pos, "Expected ')' after typeof arg");
1096 free(name);
1097 return 0;
1098 }
1099
1100 /* [v] */
1101 bytecode_add_instruction(bc, OP_DUP, 0); /* [v, v] */
1102 bytecode_add_instruction(bc, OP_TYPEOF, 0); /* [v, tname] */
1103 {
1104 int ciMap = bytecode_add_constant(bc, make_string("Map"));
1105 bytecode_add_instruction(bc, OP_LOAD_CONST, ciMap); /* [v, tname, "Map"] */
1106 }
1107 bytecode_add_instruction(bc, OP_EQ, 0); /* [v, isMap] */
1108 int j_if_not_map = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
1109
1110 /* Map branch: [v] */
1111 bytecode_add_instruction(bc, OP_DUP, 0); /* [v, v] */
1112 {
1113 int kci = bytecode_add_constant(bc, make_string("__class"));
1114 bytecode_add_instruction(bc, OP_LOAD_CONST, kci); /* [v, v, "__class"] */
1115 }
1116 bytecode_add_instruction(bc, OP_HAS_KEY, 0); /* [v, has] */
1117 int j_no_meta = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
1118 /* has __class: try toString() -> return its result */
1119 bytecode_add_instruction(bc, OP_DUP, 0); /* [v, v] */
1120 {
1121 int kcits = bytecode_add_constant(bc, make_string("toString"));
1122 bytecode_add_instruction(bc, OP_LOAD_CONST, kcits); /* [v, v, "toString"] */
1123 }
1124 bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* [v, func] */
1125 bytecode_add_instruction(bc, OP_SWAP, 0); /* [func, v] */
1126 bytecode_add_instruction(bc, OP_CALL, 1); /* [string] */
1127 int j_end = bytecode_add_instruction(bc, OP_JUMP, 0);
1128
1129 /* no meta: drop v and return "Map" */
1130 bytecode_set_operand(bc, j_no_meta, bc->instr_count);
1131 bytecode_add_instruction(bc, OP_POP, 0); /* [] */
1132 {
1133 int ciMap2 = bytecode_add_constant(bc, make_string("Map"));
1134 bytecode_add_instruction(bc, OP_LOAD_CONST, ciMap2); /* ["Map"] */
1135 }
1136 int j_end2 = bytecode_add_instruction(bc, OP_JUMP, 0);
1137 int after_map = bc->instr_count;
1138
1139 /* not map: compute typeof(v) */
1140 bytecode_set_operand(bc, j_if_not_map, after_map);
1141 bytecode_add_instruction(bc, OP_TYPEOF, 0); /* [tname] */
1142
1143 /* end */
1144 bytecode_set_operand(bc, j_end, bc->instr_count);
1145 bytecode_set_operand(bc, j_end2, bc->instr_count);
1146 }
1147
1148 free(name);
1149 return 1;
1150 }
1151 if (strcmp(name, "keys") == 0) {
1152 (*pos)++; /* '(' */
1153 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
1154 parser_fail(*pos, "keys expects 1 arg");
1155 free(name);
1156 return 0;
1157 }
1159 free(name);
1160 return 1;
1161 }
1162 if (strcmp(name, "values") == 0) {
1163 (*pos)++; /* '(' */
1164 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
1165 parser_fail(*pos, "values expects 1 arg");
1166 free(name);
1167 return 0;
1168 }
1170 free(name);
1171 return 1;
1172 }
1173 if (strcmp(name, "has") == 0) {
1174 (*pos)++; /* '(' */
1175 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
1176 parser_fail(*pos, "has expects (map, key)");
1177 free(name);
1178 return 0;
1179 }
1180 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
1181 parser_fail(*pos, "has expects (map, key)");
1182 free(name);
1183 return 0;
1184 }
1186 free(name);
1187 return 1;
1188 }
1189 if (strcmp(name, "read_file") == 0) {
1190 (*pos)++; /* '(' */
1191 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
1192 parser_fail(*pos, "read_file expects 1 arg");
1193 free(name);
1194 return 0;
1195 }
1197 free(name);
1198 return 1;
1199 }
1200 if (strcmp(name, "write_file") == 0) {
1201 (*pos)++; /* '(' */
1202 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
1203 parser_fail(*pos, "write_file expects 2 args");
1204 free(name);
1205 return 0;
1206 }
1207 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
1208 parser_fail(*pos, "write_file expects 2 args");
1209 free(name);
1210 return 0;
1211 }
1213 free(name);
1214 return 1;
1215 }
1216 if (strcmp(name, "input") == 0) {
1217 (*pos)++; /* '(' */
1218 int hasPrompt = 0;
1219 skip_spaces(src, len, pos);
1220 if (*pos < len && src[*pos] != ')') {
1221 if (!emit_expression(bc, src, len, pos)) {
1222 parser_fail(*pos, "input expects 0 or 1 argument");
1223 free(name);
1224 return 0;
1225 }
1226 hasPrompt = 1;
1227 }
1228 if (!consume_char(src, len, pos, ')')) {
1229 parser_fail(*pos, "Expected ')' after input arg(s)");
1230 free(name);
1231 return 0;
1232 }
1233 bytecode_add_instruction(bc, OP_INPUT_LINE, hasPrompt ? 1 : 0);
1234 free(name);
1235 return 1;
1236 }
1237 if (strcmp(name, "input_hidden") == 0) {
1238 (*pos)++; /* '(' */
1239 int hasPrompt = 0;
1240 skip_spaces(src, len, pos);
1241 if (*pos < len && src[*pos] != ')') {
1242 if (!emit_expression(bc, src, len, pos)) {
1243 parser_fail(*pos, "input_hidden expects 0 or 1 argument");
1244 free(name);
1245 return 0;
1246 }
1247 hasPrompt = 1;
1248 }
1249 if (!consume_char(src, len, pos, ')')) {
1250 parser_fail(*pos, "Expected ')' after input_hidden arg(s)");
1251 free(name);
1252 return 0;
1253 }
1254 /* operand bit0 = hasPrompt, bit1 = hidden */
1255 bytecode_add_instruction(bc, OP_INPUT_LINE, (hasPrompt ? 1 : 0) | 2);
1256 free(name);
1257 return 1;
1258 }
1259 if (strcmp(name, "proc_run") == 0) {
1260 (*pos)++; /* '(' */
1261 if (!emit_expression(bc, src, len, pos)) {
1262 parser_fail(*pos, "proc_run expects 1 argument (command string)");
1263 free(name);
1264 return 0;
1265 }
1266 if (!consume_char(src, len, pos, ')')) {
1267 parser_fail(*pos, "Expected ')' after proc_run arg");
1268 free(name);
1269 return 0;
1270 }
1272 free(name);
1273 return 1;
1274 }
1275 if (strcmp(name, "system") == 0) {
1276 (*pos)++; /* '(' */
1277 if (!emit_expression(bc, src, len, pos)) {
1278 parser_fail(*pos, "system expects 1 argument (command string)");
1279 free(name);
1280 return 0;
1281 }
1282 if (!consume_char(src, len, pos, ')')) {
1283 parser_fail(*pos, "Expected ')' after system arg");
1284 free(name);
1285 return 0;
1286 }
1288 free(name);
1289 return 1;
1290 }
1291 if (strcmp(name, "time_now_ms") == 0) {
1292 (*pos)++; /* '(' */
1293 if (!consume_char(src, len, pos, ')')) {
1294 parser_fail(*pos, "time_now_ms expects ()");
1295 free(name);
1296 return 0;
1297 }
1299 free(name);
1300 return 1;
1301 }
1302 if (strcmp(name, "clock_mono_ms") == 0) {
1303 (*pos)++; /* '(' */
1304 if (!consume_char(src, len, pos, ')')) {
1305 parser_fail(*pos, "clock_mono_ms expects ()");
1306 free(name);
1307 return 0;
1308 }
1310 free(name);
1311 return 1;
1312 }
1313 if (strcmp(name, "date_format") == 0) {
1314 (*pos)++; /* '(' */
1315 if (!emit_expression(bc, src, len, pos)) {
1316 parser_fail(*pos, "date_format expects (ms:int, fmt:string)");
1317 free(name);
1318 return 0;
1319 }
1320 if (!consume_char(src, len, pos, ',')) {
1321 parser_fail(*pos, "date_format expects 2 args");
1322 free(name);
1323 return 0;
1324 }
1325 if (!emit_expression(bc, src, len, pos)) {
1326 parser_fail(*pos, "date_format expects (ms:int, fmt:string)");
1327 free(name);
1328 return 0;
1329 }
1330 if (!consume_char(src, len, pos, ')')) {
1331 parser_fail(*pos, "Expected ')' after date_format args");
1332 free(name);
1333 return 0;
1334 }
1336 free(name);
1337 return 1;
1338 }
1339 if (strcmp(name, "env") == 0) {
1340 (*pos)++; /* '(' */
1341 if (!emit_expression(bc, src, len, pos)) {
1342 parser_fail(*pos, "env expects 1 argument");
1343 free(name);
1344 return 0;
1345 }
1346 if (!consume_char(src, len, pos, ')')) {
1347 parser_fail(*pos, "Expected ')' after env arg");
1348 free(name);
1349 return 0;
1350 }
1352 free(name);
1353 return 1;
1354 }
1355 if (strcmp(name, "env_all") == 0) {
1356 (*pos)++; /* '(' */
1357 if (!consume_char(src, len, pos, ')')) {
1358 parser_fail(*pos, "env_all expects ()");
1359 free(name);
1360 return 0;
1361 }
1363 free(name);
1364 return 1;
1365 }
1366 if (strcmp(name, "fun_version") == 0) {
1367 (*pos)++; /* '(' */
1368 if (!consume_char(src, len, pos, ')')) {
1369 parser_fail(*pos, "fun_version expects ()");
1370 free(name);
1371 return 0;
1372 }
1374 free(name);
1375 return 1;
1376 }
1377 /* KCGI intrinsics (optional feature; opcodes are safe no-ops when disabled) */
1378 if (strcmp(name, "kcgi_parse") == 0) {
1379 (*pos)++; /* '(' */
1380 if (!consume_char(src, len, pos, ')')) {
1381 parser_fail(*pos, "kcgi_parse expects ()");
1382 free(name);
1383 return 0;
1384 }
1386 free(name);
1387 return 1;
1388 }
1389 if (strcmp(name, "kcgi_reply_start") == 0) {
1390 (*pos)++; /* '(' */
1391 if (!emit_expression(bc, src, len, pos)) {
1392 parser_fail(*pos, "kcgi_reply_start expects (code:int, content_type:string)");
1393 free(name);
1394 return 0;
1395 }
1396 if (!consume_char(src, len, pos, ',')) {
1397 parser_fail(*pos, "kcgi_reply_start expects 2 args");
1398 free(name);
1399 return 0;
1400 }
1401 if (!emit_expression(bc, src, len, pos)) {
1402 parser_fail(*pos, "kcgi_reply_start expects (code:int, content_type:string)");
1403 free(name);
1404 return 0;
1405 }
1406 if (!consume_char(src, len, pos, ')')) {
1407 parser_fail(*pos, "Expected ')' after kcgi_reply_start args");
1408 free(name);
1409 return 0;
1410 }
1412 free(name);
1413 return 1;
1414 }
1415 if (strcmp(name, "kcgi_write") == 0) {
1416 (*pos)++; /* '(' */
1417 if (!emit_expression(bc, src, len, pos)) {
1418 parser_fail(*pos, "kcgi_write expects (chunk:string)");
1419 free(name);
1420 return 0;
1421 }
1422 if (!consume_char(src, len, pos, ')')) {
1423 parser_fail(*pos, "Expected ')' after kcgi_write arg");
1424 free(name);
1425 return 0;
1426 }
1428 free(name);
1429 return 1;
1430 }
1431 if (strcmp(name, "kcgi_end") == 0) {
1432 (*pos)++; /* '(' */
1433 if (!consume_char(src, len, pos, ')')) {
1434 parser_fail(*pos, "kcgi_end expects ()");
1435 free(name);
1436 return 0;
1437 }
1439 free(name);
1440 return 1;
1441 }
1442 if (strcmp(name, "rust_hello") == 0) {
1443 (*pos)++; /* '(' */
1444 if (!consume_char(src, len, pos, ')')) {
1445 parser_fail(*pos, "rust_hello expects ()");
1446 free(name);
1447 return 0;
1448 }
1450 free(name);
1451 return 1;
1452 }
1453 if (strcmp(name, "rust_hello_args") == 0) {
1454 (*pos)++; /* '(' */
1455 if (!emit_expression(bc, src, len, pos)) {
1456 parser_fail(*pos, "rust_hello_args expects (message:string)");
1457 free(name);
1458 return 0;
1459 }
1460 if (!consume_char(src, len, pos, ')')) {
1461 parser_fail(*pos, "Expected ')' after rust_hello_args arg");
1462 free(name);
1463 return 0;
1464 }
1466 free(name);
1467 return 1;
1468 }
1469 if (strcmp(name, "rust_hello_args_return") == 0) {
1470 (*pos)++; /* '(' */
1471 if (!emit_expression(bc, src, len, pos)) {
1472 parser_fail(*pos, "rust_hello_args_return expects (message:string)");
1473 free(name);
1474 return 0;
1475 }
1476 if (!consume_char(src, len, pos, ')')) {
1477 parser_fail(*pos, "Expected ')' after rust_hello_args_return arg");
1478 free(name);
1479 return 0;
1480 }
1482 free(name);
1483 return 1;
1484 }
1485 if (strcmp(name, "rust_get_sp") == 0) {
1486 (*pos)++; /* '(' */
1487 if (!consume_char(src, len, pos, ')')) {
1488 parser_fail(*pos, "rust_get_sp expects ()");
1489 free(name);
1490 return 0;
1491 }
1493 free(name);
1494 return 1;
1495 }
1496 if (strcmp(name, "rust_set_exit") == 0) {
1497 (*pos)++; /* '(' */
1498 if (!emit_expression(bc, src, len, pos)) {
1499 parser_fail(*pos, "rust_set_exit expects (code:int)");
1500 free(name);
1501 return 0;
1502 }
1503 if (!consume_char(src, len, pos, ')')) {
1504 parser_fail(*pos, "Expected ')' after rust_set_exit arg");
1505 free(name);
1506 return 0;
1507 }
1509 free(name);
1510 return 1;
1511 }
1512 if (strcmp(name, "cpp_add") == 0) {
1513 (*pos)++; /* '(' */
1514 if (!emit_expression(bc, src, len, pos)) {
1515 parser_fail(*pos, "cpp_add expects (a:int, b:int)");
1516 free(name);
1517 return 0;
1518 }
1519 if (!consume_char(src, len, pos, ',')) {
1520 parser_fail(*pos, "cpp_add expects two arguments");
1521 free(name);
1522 return 0;
1523 }
1524 if (!emit_expression(bc, src, len, pos)) {
1525 parser_fail(*pos, "cpp_add expects (a:int, b:int)");
1526 free(name);
1527 return 0;
1528 }
1529 if (!consume_char(src, len, pos, ')')) {
1530 parser_fail(*pos, "Expected ')' after cpp_add args");
1531 free(name);
1532 return 0;
1533 }
1535 free(name);
1536 return 1;
1537 }
1538 if (strcmp(name, "os_list_dir") == 0) {
1539 (*pos)++; /* '(' */
1540 if (!emit_expression(bc, src, len, pos)) {
1541 parser_fail(*pos, "os_list_dir expects (path)");
1542 free(name);
1543 return 0;
1544 }
1545 if (!consume_char(src, len, pos, ')')) {
1546 parser_fail(*pos, "Expected ')' after os_list_dir arg");
1547 free(name);
1548 return 0;
1549 }
1551 free(name);
1552 return 1;
1553 }
1554 /* JSON builtins */
1555 if (strcmp(name, "json_parse") == 0) {
1556 (*pos)++; /* '(' */
1557 if (!emit_expression(bc, src, len, pos)) {
1558 parser_fail(*pos, "json_parse expects (text)");
1559 free(name);
1560 return 0;
1561 }
1562 if (!consume_char(src, len, pos, ')')) {
1563 parser_fail(*pos, "Expected ')' after json_parse arg");
1564 free(name);
1565 return 0;
1566 }
1568 free(name);
1569 return 1;
1570 }
1571 /* XML builtins (minimal) */
1572 if (strcmp(name, "xml_parse") == 0) {
1573 (*pos)++; /* '(' */
1574 if (!emit_expression(bc, src, len, pos)) {
1575 parser_fail(*pos, "xml_parse expects (text)");
1576 free(name);
1577 return 0;
1578 }
1579 if (!consume_char(src, len, pos, ')')) {
1580 parser_fail(*pos, "Expected ')' after xml_parse arg");
1581 free(name);
1582 return 0;
1583 }
1585 free(name);
1586 return 1;
1587 }
1588 if (strcmp(name, "xml_root") == 0) {
1589 (*pos)++; /* '(' */
1590 if (!emit_expression(bc, src, len, pos)) {
1591 parser_fail(*pos, "xml_root expects (doc_handle)");
1592 free(name);
1593 return 0;
1594 }
1595 if (!consume_char(src, len, pos, ')')) {
1596 parser_fail(*pos, "Expected ')' after xml_root arg");
1597 free(name);
1598 return 0;
1599 }
1601 free(name);
1602 return 1;
1603 }
1604 if (strcmp(name, "xml_name") == 0) {
1605 (*pos)++; /* '(' */
1606 if (!emit_expression(bc, src, len, pos)) {
1607 parser_fail(*pos, "xml_name expects (node_handle)");
1608 free(name);
1609 return 0;
1610 }
1611 if (!consume_char(src, len, pos, ')')) {
1612 parser_fail(*pos, "Expected ')' after xml_name arg");
1613 free(name);
1614 return 0;
1615 }
1617 free(name);
1618 return 1;
1619 }
1620 if (strcmp(name, "xml_text") == 0) {
1621 (*pos)++; /* '(' */
1622 if (!emit_expression(bc, src, len, pos)) {
1623 parser_fail(*pos, "xml_text expects (node_handle)");
1624 free(name);
1625 return 0;
1626 }
1627 if (!consume_char(src, len, pos, ')')) {
1628 parser_fail(*pos, "Expected ')' after xml_text arg");
1629 free(name);
1630 return 0;
1631 }
1633 free(name);
1634 return 1;
1635 }
1636 if (strcmp(name, "json_stringify") == 0) {
1637 (*pos)++; /* '(' */
1638 if (!emit_expression(bc, src, len, pos)) {
1639 parser_fail(*pos, "json_stringify expects (value, pretty)");
1640 free(name);
1641 return 0;
1642 }
1643 if (!consume_char(src, len, pos, ',')) {
1644 parser_fail(*pos, "json_stringify expects (value, pretty)");
1645 free(name);
1646 return 0;
1647 }
1648 if (!emit_expression(bc, src, len, pos)) {
1649 parser_fail(*pos, "json_stringify expects (value, pretty)");
1650 free(name);
1651 return 0;
1652 }
1653 if (!consume_char(src, len, pos, ')')) {
1654 parser_fail(*pos, "Expected ')' after json_stringify args");
1655 free(name);
1656 return 0;
1657 }
1659 free(name);
1660 return 1;
1661 }
1662 if (strcmp(name, "json_from_file") == 0) {
1663 (*pos)++; /* '(' */
1664 if (!emit_expression(bc, src, len, pos)) {
1665 parser_fail(*pos, "json_from_file expects (path)");
1666 free(name);
1667 return 0;
1668 }
1669 if (!consume_char(src, len, pos, ')')) {
1670 parser_fail(*pos, "Expected ')' after json_from_file arg");
1671 free(name);
1672 return 0;
1673 }
1675 free(name);
1676 return 1;
1677 }
1678 if (strcmp(name, "json_to_file") == 0) {
1679 (*pos)++; /* '(' */
1680 if (!emit_expression(bc, src, len, pos)) {
1681 parser_fail(*pos, "json_to_file expects (path, value, pretty)");
1682 free(name);
1683 return 0;
1684 }
1685 if (!consume_char(src, len, pos, ',')) {
1686 parser_fail(*pos, "json_to_file expects (path, value, pretty)");
1687 free(name);
1688 return 0;
1689 }
1690 if (!emit_expression(bc, src, len, pos)) {
1691 parser_fail(*pos, "json_to_file expects (path, value, pretty)");
1692 free(name);
1693 return 0;
1694 }
1695 if (!consume_char(src, len, pos, ',')) {
1696 parser_fail(*pos, "json_to_file expects (path, value, pretty)");
1697 free(name);
1698 return 0;
1699 }
1700 if (!emit_expression(bc, src, len, pos)) {
1701 parser_fail(*pos, "json_to_file expects (path, value, pretty)");
1702 free(name);
1703 return 0;
1704 }
1705 if (!consume_char(src, len, pos, ')')) {
1706 parser_fail(*pos, "Expected ')' after json_to_file args");
1707 free(name);
1708 return 0;
1709 }
1711 free(name);
1712 return 1;
1713 }
1714 /* INI (iniparser 4.2.6) builtins */
1715 if (strcmp(name, "ini_load") == 0) {
1716 (*pos)++; /* '(' */
1717 if (!emit_expression(bc, src, len, pos)) {
1718 parser_fail(*pos, "ini_load expects (path)");
1719 free(name);
1720 return 0;
1721 }
1722 if (!consume_char(src, len, pos, ')')) {
1723 parser_fail(*pos, "Expected ')' after ini_load arg");
1724 free(name);
1725 return 0;
1726 }
1728 free(name);
1729 return 1;
1730 }
1731 if (strcmp(name, "ini_free") == 0) {
1732 (*pos)++; /* '(' */
1733 if (!emit_expression(bc, src, len, pos)) {
1734 parser_fail(*pos, "ini_free expects (handle)");
1735 free(name);
1736 return 0;
1737 }
1738 if (!consume_char(src, len, pos, ')')) {
1739 parser_fail(*pos, "Expected ')' after ini_free arg");
1740 free(name);
1741 return 0;
1742 }
1744 free(name);
1745 return 1;
1746 }
1747 if (strcmp(name, "ini_get_string") == 0) {
1748 (*pos)++; /* '(' */
1749 if (!emit_expression(bc, src, len, pos)) {
1750 parser_fail(*pos, "ini_get_string expects (handle, section, key, default)");
1751 free(name);
1752 return 0;
1753 }
1754 if (!consume_char(src, len, pos, ',')) {
1755 parser_fail(*pos, "ini_get_string expects 4 args");
1756 free(name);
1757 return 0;
1758 }
1759 if (!emit_expression(bc, src, len, pos)) {
1760 parser_fail(*pos, "ini_get_string expects 4 args");
1761 free(name);
1762 return 0;
1763 }
1764 if (!consume_char(src, len, pos, ',')) {
1765 parser_fail(*pos, "ini_get_string expects 4 args");
1766 free(name);
1767 return 0;
1768 }
1769 if (!emit_expression(bc, src, len, pos)) {
1770 parser_fail(*pos, "ini_get_string expects 4 args");
1771 free(name);
1772 return 0;
1773 }
1774 if (!consume_char(src, len, pos, ',')) {
1775 parser_fail(*pos, "ini_get_string expects 4 args");
1776 free(name);
1777 return 0;
1778 }
1779 if (!emit_expression(bc, src, len, pos)) {
1780 parser_fail(*pos, "ini_get_string expects 4 args");
1781 free(name);
1782 return 0;
1783 }
1784 if (!consume_char(src, len, pos, ')')) {
1785 parser_fail(*pos, "Expected ')' after ini_get_string args");
1786 free(name);
1787 return 0;
1788 }
1790 free(name);
1791 return 1;
1792 }
1793 if (strcmp(name, "ini_get_int") == 0) {
1794 (*pos)++; /* '(' */
1795 if (!emit_expression(bc, src, len, pos)) {
1796 parser_fail(*pos, "ini_get_int expects (handle, section, key, default)");
1797 free(name);
1798 return 0;
1799 }
1800 if (!consume_char(src, len, pos, ',')) {
1801 parser_fail(*pos, "ini_get_int expects 4 args");
1802 free(name);
1803 return 0;
1804 }
1805 if (!emit_expression(bc, src, len, pos)) {
1806 parser_fail(*pos, "ini_get_int expects 4 args");
1807 free(name);
1808 return 0;
1809 }
1810 if (!consume_char(src, len, pos, ',')) {
1811 parser_fail(*pos, "ini_get_int expects 4 args");
1812 free(name);
1813 return 0;
1814 }
1815 if (!emit_expression(bc, src, len, pos)) {
1816 parser_fail(*pos, "ini_get_int expects 4 args");
1817 free(name);
1818 return 0;
1819 }
1820 if (!consume_char(src, len, pos, ',')) {
1821 parser_fail(*pos, "ini_get_int expects 4 args");
1822 free(name);
1823 return 0;
1824 }
1825 if (!emit_expression(bc, src, len, pos)) {
1826 parser_fail(*pos, "ini_get_int expects 4 args");
1827 free(name);
1828 return 0;
1829 }
1830 if (!consume_char(src, len, pos, ')')) {
1831 parser_fail(*pos, "Expected ')' after ini_get_int args");
1832 free(name);
1833 return 0;
1834 }
1836 free(name);
1837 return 1;
1838 }
1839 if (strcmp(name, "ini_get_double") == 0) {
1840 (*pos)++; /* '(' */
1841 if (!emit_expression(bc, src, len, pos)) {
1842 parser_fail(*pos, "ini_get_double expects (handle, section, key, default)");
1843 free(name);
1844 return 0;
1845 }
1846 if (!consume_char(src, len, pos, ',')) {
1847 parser_fail(*pos, "ini_get_double expects 4 args");
1848 free(name);
1849 return 0;
1850 }
1851 if (!emit_expression(bc, src, len, pos)) {
1852 parser_fail(*pos, "ini_get_double expects 4 args");
1853 free(name);
1854 return 0;
1855 }
1856 if (!consume_char(src, len, pos, ',')) {
1857 parser_fail(*pos, "ini_get_double expects 4 args");
1858 free(name);
1859 return 0;
1860 }
1861 if (!emit_expression(bc, src, len, pos)) {
1862 parser_fail(*pos, "ini_get_double expects 4 args");
1863 free(name);
1864 return 0;
1865 }
1866 if (!consume_char(src, len, pos, ',')) {
1867 parser_fail(*pos, "ini_get_double expects 4 args");
1868 free(name);
1869 return 0;
1870 }
1871 if (!emit_expression(bc, src, len, pos)) {
1872 parser_fail(*pos, "ini_get_double expects 4 args");
1873 free(name);
1874 return 0;
1875 }
1876 if (!consume_char(src, len, pos, ')')) {
1877 parser_fail(*pos, "Expected ')' after ini_get_double args");
1878 free(name);
1879 return 0;
1880 }
1882 free(name);
1883 return 1;
1884 }
1885 if (strcmp(name, "ini_get_bool") == 0) {
1886 (*pos)++; /* '(' */
1887 if (!emit_expression(bc, src, len, pos)) {
1888 parser_fail(*pos, "ini_get_bool expects (handle, section, key, default)");
1889 free(name);
1890 return 0;
1891 }
1892 if (!consume_char(src, len, pos, ',')) {
1893 parser_fail(*pos, "ini_get_bool expects 4 args");
1894 free(name);
1895 return 0;
1896 }
1897 if (!emit_expression(bc, src, len, pos)) {
1898 parser_fail(*pos, "ini_get_bool expects 4 args");
1899 free(name);
1900 return 0;
1901 }
1902 if (!consume_char(src, len, pos, ',')) {
1903 parser_fail(*pos, "ini_get_bool expects 4 args");
1904 free(name);
1905 return 0;
1906 }
1907 if (!emit_expression(bc, src, len, pos)) {
1908 parser_fail(*pos, "ini_get_bool expects 4 args");
1909 free(name);
1910 return 0;
1911 }
1912 if (!consume_char(src, len, pos, ',')) {
1913 parser_fail(*pos, "ini_get_bool expects 4 args");
1914 free(name);
1915 return 0;
1916 }
1917 if (!emit_expression(bc, src, len, pos)) {
1918 parser_fail(*pos, "ini_get_bool expects 4 args");
1919 free(name);
1920 return 0;
1921 }
1922 if (!consume_char(src, len, pos, ')')) {
1923 parser_fail(*pos, "Expected ')' after ini_get_bool args");
1924 free(name);
1925 return 0;
1926 }
1928 free(name);
1929 return 1;
1930 }
1931 if (strcmp(name, "ini_set") == 0) {
1932 (*pos)++; /* '(' */
1933 if (!emit_expression(bc, src, len, pos)) {
1934 parser_fail(*pos, "ini_set expects (handle, section, key, value)");
1935 free(name);
1936 return 0;
1937 }
1938 if (!consume_char(src, len, pos, ',')) {
1939 parser_fail(*pos, "ini_set expects 4 args");
1940 free(name);
1941 return 0;
1942 }
1943 if (!emit_expression(bc, src, len, pos)) {
1944 parser_fail(*pos, "ini_set expects 4 args");
1945 free(name);
1946 return 0;
1947 }
1948 if (!consume_char(src, len, pos, ',')) {
1949 parser_fail(*pos, "ini_set expects 4 args");
1950 free(name);
1951 return 0;
1952 }
1953 if (!emit_expression(bc, src, len, pos)) {
1954 parser_fail(*pos, "ini_set expects 4 args");
1955 free(name);
1956 return 0;
1957 }
1958 if (!consume_char(src, len, pos, ',')) {
1959 parser_fail(*pos, "ini_set expects 4 args");
1960 free(name);
1961 return 0;
1962 }
1963 if (!emit_expression(bc, src, len, pos)) {
1964 parser_fail(*pos, "ini_set expects 4 args");
1965 free(name);
1966 return 0;
1967 }
1968 if (!consume_char(src, len, pos, ')')) {
1969 parser_fail(*pos, "Expected ')' after ini_set args");
1970 free(name);
1971 return 0;
1972 }
1974 free(name);
1975 return 1;
1976 }
1977 if (strcmp(name, "ini_unset") == 0) {
1978 (*pos)++; /* '(' */
1979 if (!emit_expression(bc, src, len, pos)) {
1980 parser_fail(*pos, "ini_unset expects (handle, section, key)");
1981 free(name);
1982 return 0;
1983 }
1984 if (!consume_char(src, len, pos, ',')) {
1985 parser_fail(*pos, "ini_unset expects 3 args");
1986 free(name);
1987 return 0;
1988 }
1989 if (!emit_expression(bc, src, len, pos)) {
1990 parser_fail(*pos, "ini_unset expects 3 args");
1991 free(name);
1992 return 0;
1993 }
1994 if (!consume_char(src, len, pos, ',')) {
1995 parser_fail(*pos, "ini_unset expects 3 args");
1996 free(name);
1997 return 0;
1998 }
1999 if (!emit_expression(bc, src, len, pos)) {
2000 parser_fail(*pos, "ini_unset expects 3 args");
2001 free(name);
2002 return 0;
2003 }
2004 if (!consume_char(src, len, pos, ')')) {
2005 parser_fail(*pos, "Expected ')' after ini_unset args");
2006 free(name);
2007 return 0;
2008 }
2010 free(name);
2011 return 1;
2012 }
2013 if (strcmp(name, "ini_save") == 0) {
2014 (*pos)++; /* '(' */
2015 if (!emit_expression(bc, src, len, pos)) {
2016 parser_fail(*pos, "ini_save expects (handle, path)");
2017 free(name);
2018 return 0;
2019 }
2020 if (!consume_char(src, len, pos, ',')) {
2021 parser_fail(*pos, "ini_save expects 2 args");
2022 free(name);
2023 return 0;
2024 }
2025 if (!emit_expression(bc, src, len, pos)) {
2026 parser_fail(*pos, "ini_save expects 2 args");
2027 free(name);
2028 return 0;
2029 }
2030 if (!consume_char(src, len, pos, ')')) {
2031 parser_fail(*pos, "Expected ')' after ini_save args");
2032 free(name);
2033 return 0;
2034 }
2036 free(name);
2037 return 1;
2038 }
2039 /* CURL builtins (minimal interface like JSON) */
2040 if (strcmp(name, "curl_get") == 0) {
2041 (*pos)++; /* '(' */
2042 if (!emit_expression(bc, src, len, pos)) {
2043 parser_fail(*pos, "curl_get expects (url)");
2044 free(name);
2045 return 0;
2046 }
2047 if (!consume_char(src, len, pos, ')')) {
2048 parser_fail(*pos, "Expected ')' after curl_get arg");
2049 free(name);
2050 return 0;
2051 }
2053 free(name);
2054 return 1;
2055 }
2056 /* SQLite builtins */
2057 if (strcmp(name, "sqlite_open") == 0) {
2058 (*pos)++; /* '(' */
2059 if (!emit_expression(bc, src, len, pos)) {
2060 parser_fail(*pos, "sqlite_open expects (path)");
2061 free(name);
2062 return 0;
2063 }
2064 if (!consume_char(src, len, pos, ')')) {
2065 parser_fail(*pos, "Expected ')' after sqlite_open arg");
2066 free(name);
2067 return 0;
2068 }
2070 free(name);
2071 return 1;
2072 }
2073 /* Redis builtins (hiredis) */
2074 if (strcmp(name, "redis_connect") == 0) {
2075 (*pos)++; /* '(' */
2076 if (!emit_expression(bc, src, len, pos)) {
2077 parser_fail(*pos, "redis_connect expects (host, port)");
2078 free(name);
2079 return 0;
2080 }
2081 if (!consume_char(src, len, pos, ',')) {
2082 parser_fail(*pos, "redis_connect expects (host, port)");
2083 free(name);
2084 return 0;
2085 }
2086 if (!emit_expression(bc, src, len, pos)) {
2087 parser_fail(*pos, "redis_connect expects (host, port)");
2088 free(name);
2089 return 0;
2090 }
2091 if (!consume_char(src, len, pos, ')')) {
2092 parser_fail(*pos, "Expected ')' after redis_connect args");
2093 free(name);
2094 return 0;
2095 }
2097 free(name);
2098 return 1;
2099 }
2100 if (strcmp(name, "redis_cmd") == 0) {
2101 (*pos)++; /* '(' */
2102 if (!emit_expression(bc, src, len, pos)) {
2103 parser_fail(*pos, "redis_cmd expects (handle, cmd)");
2104 free(name);
2105 return 0;
2106 }
2107 if (!consume_char(src, len, pos, ',')) {
2108 parser_fail(*pos, "redis_cmd expects (handle, cmd)");
2109 free(name);
2110 return 0;
2111 }
2112 if (!emit_expression(bc, src, len, pos)) {
2113 parser_fail(*pos, "redis_cmd expects (handle, cmd)");
2114 free(name);
2115 return 0;
2116 }
2117 if (!consume_char(src, len, pos, ')')) {
2118 parser_fail(*pos, "Expected ')' after redis_cmd args");
2119 free(name);
2120 return 0;
2121 }
2123 free(name);
2124 return 1;
2125 }
2126 if (strcmp(name, "redis_close") == 0) {
2127 (*pos)++; /* '(' */
2128 if (!emit_expression(bc, src, len, pos)) {
2129 parser_fail(*pos, "redis_close expects (handle)");
2130 free(name);
2131 return 0;
2132 }
2133 if (!consume_char(src, len, pos, ')')) {
2134 parser_fail(*pos, "Expected ')' after redis_close arg");
2135 free(name);
2136 return 0;
2137 }
2139 free(name);
2140 return 1;
2141 }
2142 if (strcmp(name, "sqlite_close") == 0) {
2143 (*pos)++; /* '(' */
2144 if (!emit_expression(bc, src, len, pos)) {
2145 parser_fail(*pos, "sqlite_close expects (handle)");
2146 free(name);
2147 return 0;
2148 }
2149 if (!consume_char(src, len, pos, ')')) {
2150 parser_fail(*pos, "Expected ')' after sqlite_close arg");
2151 free(name);
2152 return 0;
2153 }
2155 free(name);
2156 return 1;
2157 }
2158 if (strcmp(name, "sqlite_exec") == 0) {
2159 (*pos)++; /* '(' */
2160 if (!emit_expression(bc, src, len, pos)) {
2161 parser_fail(*pos, "sqlite_exec expects (handle, sql)");
2162 free(name);
2163 return 0;
2164 }
2165 if (!consume_char(src, len, pos, ',')) {
2166 parser_fail(*pos, "sqlite_exec expects (handle, sql)");
2167 free(name);
2168 return 0;
2169 }
2170 if (!emit_expression(bc, src, len, pos)) {
2171 parser_fail(*pos, "sqlite_exec expects (handle, sql)");
2172 free(name);
2173 return 0;
2174 }
2175 if (!consume_char(src, len, pos, ')')) {
2176 parser_fail(*pos, "Expected ')' after sqlite_exec args");
2177 free(name);
2178 return 0;
2179 }
2181 free(name);
2182 return 1;
2183 }
2184 if (strcmp(name, "sqlite_query") == 0) {
2185 (*pos)++; /* '(' */
2186 if (!emit_expression(bc, src, len, pos)) {
2187 parser_fail(*pos, "sqlite_query expects (handle, sql)");
2188 free(name);
2189 return 0;
2190 }
2191 if (!consume_char(src, len, pos, ',')) {
2192 parser_fail(*pos, "sqlite_query expects (handle, sql)");
2193 free(name);
2194 return 0;
2195 }
2196 if (!emit_expression(bc, src, len, pos)) {
2197 parser_fail(*pos, "sqlite_query expects (handle, sql)");
2198 free(name);
2199 return 0;
2200 }
2201 if (!consume_char(src, len, pos, ')')) {
2202 parser_fail(*pos, "Expected ')' after sqlite_query args");
2203 free(name);
2204 return 0;
2205 }
2207 free(name);
2208 return 1;
2209 }
2210 if (strcmp(name, "curl_post") == 0) {
2211 (*pos)++; /* '(' */
2212 if (!emit_expression(bc, src, len, pos)) {
2213 parser_fail(*pos, "curl_post expects (url, body)");
2214 free(name);
2215 return 0;
2216 }
2217 if (!consume_char(src, len, pos, ',')) {
2218 parser_fail(*pos, "curl_post expects (url, body)");
2219 free(name);
2220 return 0;
2221 }
2222 if (!emit_expression(bc, src, len, pos)) {
2223 parser_fail(*pos, "curl_post expects (url, body)");
2224 free(name);
2225 return 0;
2226 }
2227 if (!consume_char(src, len, pos, ')')) {
2228 parser_fail(*pos, "Expected ')' after curl_post args");
2229 free(name);
2230 return 0;
2231 }
2233 free(name);
2234 return 1;
2235 }
2236 if (strcmp(name, "curl_download") == 0) {
2237 (*pos)++; /* '(' */
2238 if (!emit_expression(bc, src, len, pos)) {
2239 parser_fail(*pos, "curl_download expects (url, path)");
2240 free(name);
2241 return 0;
2242 }
2243 if (!consume_char(src, len, pos, ',')) {
2244 parser_fail(*pos, "curl_download expects (url, path)");
2245 free(name);
2246 return 0;
2247 }
2248 if (!emit_expression(bc, src, len, pos)) {
2249 parser_fail(*pos, "curl_download expects (url, path)");
2250 free(name);
2251 return 0;
2252 }
2253 if (!consume_char(src, len, pos, ')')) {
2254 parser_fail(*pos, "Expected ')' after curl_download args");
2255 free(name);
2256 return 0;
2257 }
2259 free(name);
2260 return 1;
2261 }
2262 /* OpenSSL (md5/sha256/sha512) */
2263 if (strcmp(name, "openssl_md5") == 0) {
2264 (*pos)++; /* '(' */
2265 if (!emit_expression(bc, src, len, pos)) {
2266 parser_fail(*pos, "openssl_md5 expects (data)");
2267 free(name);
2268 return 0;
2269 }
2270 if (!consume_char(src, len, pos, ')')) {
2271 parser_fail(*pos, "Expected ')' after openssl_md5 arg");
2272 free(name);
2273 return 0;
2274 }
2276 free(name);
2277 return 1;
2278 }
2279 if (strcmp(name, "openssl_sha256") == 0) {
2280 (*pos)++; /* '(' */
2281 if (!emit_expression(bc, src, len, pos)) {
2282 parser_fail(*pos, "openssl_sha256 expects (data)");
2283 free(name);
2284 return 0;
2285 }
2286 if (!consume_char(src, len, pos, ')')) {
2287 parser_fail(*pos, "Expected ')' after openssl_sha256 arg");
2288 free(name);
2289 return 0;
2290 }
2292 free(name);
2293 return 1;
2294 }
2295 if (strcmp(name, "openssl_sha512") == 0) {
2296 (*pos)++; /* '(' */
2297 if (!emit_expression(bc, src, len, pos)) {
2298 parser_fail(*pos, "openssl_sha512 expects (data)");
2299 free(name);
2300 return 0;
2301 }
2302 if (!consume_char(src, len, pos, ')')) {
2303 parser_fail(*pos, "Expected ')' after openssl_sha512 arg");
2304 free(name);
2305 return 0;
2306 }
2308 free(name);
2309 return 1;
2310 }
2311 if (strcmp(name, "openssl_ripemd160") == 0) {
2312 (*pos)++; /* '(' */
2313 if (!emit_expression(bc, src, len, pos)) {
2314 parser_fail(*pos, "openssl_ripemd160 expects (data)");
2315 free(name);
2316 return 0;
2317 }
2318 if (!consume_char(src, len, pos, ')')) {
2319 parser_fail(*pos, "Expected ')' after openssl_ripemd160 arg");
2320 free(name);
2321 return 0;
2322 }
2324 free(name);
2325 return 1;
2326 }
2327 /* LibreSSL builtins removed */
2328 /* PCSC builtins */
2329 if (strcmp(name, "pcsc_establish") == 0) {
2330 (*pos)++; /* '(' */
2331 /* no args */
2332 if (!consume_char(src, len, pos, ')')) {
2333 parser_fail(*pos, "pcsc_establish expects ()");
2334 free(name);
2335 return 0;
2336 }
2338 free(name);
2339 return 1;
2340 }
2341 /* PCRE2 builtins */
2342 if (strcmp(name, "pcre2_test") == 0) {
2343 (*pos)++; /* '(' */
2344 /* (pattern, text, flags) */
2345 if (!emit_expression(bc, src, len, pos)) {
2346 parser_fail(*pos, "pcre2_test expects (pattern, text, flags)");
2347 free(name);
2348 return 0;
2349 }
2350 if (!consume_char(src, len, pos, ',')) {
2351 parser_fail(*pos, "pcre2_test expects (pattern, text, flags)");
2352 free(name);
2353 return 0;
2354 }
2355 if (!emit_expression(bc, src, len, pos)) {
2356 parser_fail(*pos, "pcre2_test expects (pattern, text, flags)");
2357 free(name);
2358 return 0;
2359 }
2360 if (!consume_char(src, len, pos, ',')) {
2361 parser_fail(*pos, "pcre2_test expects (pattern, text, flags)");
2362 free(name);
2363 return 0;
2364 }
2365 if (!emit_expression(bc, src, len, pos)) {
2366 parser_fail(*pos, "pcre2_test expects (pattern, text, flags)");
2367 free(name);
2368 return 0;
2369 }
2370 if (!consume_char(src, len, pos, ')')) {
2371 parser_fail(*pos, "Expected ')' after pcre2_test args");
2372 free(name);
2373 return 0;
2374 }
2376 free(name);
2377 return 1;
2378 }
2379 if (strcmp(name, "pcre2_match") == 0) {
2380 (*pos)++; /* '(' */
2381 if (!emit_expression(bc, src, len, pos)) {
2382 parser_fail(*pos, "pcre2_match expects (pattern, text, flags)");
2383 free(name);
2384 return 0;
2385 }
2386 if (!consume_char(src, len, pos, ',')) {
2387 parser_fail(*pos, "pcre2_match expects (pattern, text, flags)");
2388 free(name);
2389 return 0;
2390 }
2391 if (!emit_expression(bc, src, len, pos)) {
2392 parser_fail(*pos, "pcre2_match expects (pattern, text, flags)");
2393 free(name);
2394 return 0;
2395 }
2396 if (!consume_char(src, len, pos, ',')) {
2397 parser_fail(*pos, "pcre2_match expects (pattern, text, flags)");
2398 free(name);
2399 return 0;
2400 }
2401 if (!emit_expression(bc, src, len, pos)) {
2402 parser_fail(*pos, "pcre2_match expects (pattern, text, flags)");
2403 free(name);
2404 return 0;
2405 }
2406 if (!consume_char(src, len, pos, ')')) {
2407 parser_fail(*pos, "Expected ')' after pcre2_match args");
2408 free(name);
2409 return 0;
2410 }
2412 free(name);
2413 return 1;
2414 }
2415 if (strcmp(name, "pcre2_findall") == 0) {
2416 (*pos)++; /* '(' */
2417 if (!emit_expression(bc, src, len, pos)) {
2418 parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)");
2419 free(name);
2420 return 0;
2421 }
2422 if (!consume_char(src, len, pos, ',')) {
2423 parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)");
2424 free(name);
2425 return 0;
2426 }
2427 if (!emit_expression(bc, src, len, pos)) {
2428 parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)");
2429 free(name);
2430 return 0;
2431 }
2432 if (!consume_char(src, len, pos, ',')) {
2433 parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)");
2434 free(name);
2435 return 0;
2436 }
2437 if (!emit_expression(bc, src, len, pos)) {
2438 parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)");
2439 free(name);
2440 return 0;
2441 }
2442 if (!consume_char(src, len, pos, ')')) {
2443 parser_fail(*pos, "Expected ')' after pcre2_findall args");
2444 free(name);
2445 return 0;
2446 }
2448 free(name);
2449 return 1;
2450 }
2451 /* PCSC builtins (optional) */
2452 if (strcmp(name, "pcsc_release") == 0) {
2453 (*pos)++; /* '(' */
2454 if (!emit_expression(bc, src, len, pos)) {
2455 parser_fail(*pos, "pcsc_release expects 1 argument (ctx)");
2456 free(name);
2457 return 0;
2458 }
2459 if (!consume_char(src, len, pos, ')')) {
2460 parser_fail(*pos, "Expected ')' after pcsc_release arg");
2461 free(name);
2462 return 0;
2463 }
2465 free(name);
2466 return 1;
2467 }
2468 if (strcmp(name, "pcsc_list_readers") == 0) {
2469 (*pos)++; /* '(' */
2470 if (!emit_expression(bc, src, len, pos)) {
2471 parser_fail(*pos, "pcsc_list_readers expects 1 argument (ctx)");
2472 free(name);
2473 return 0;
2474 }
2475 if (!consume_char(src, len, pos, ')')) {
2476 parser_fail(*pos, "Expected ')' after pcsc_list_readers arg");
2477 free(name);
2478 return 0;
2479 }
2481 free(name);
2482 return 1;
2483 }
2484 if (strcmp(name, "pcsc_connect") == 0) {
2485 (*pos)++; /* '(' */
2486 /* ctx, reader */
2487 if (!emit_expression(bc, src, len, pos)) {
2488 parser_fail(*pos, "pcsc_connect expects (ctx, reader)");
2489 free(name);
2490 return 0;
2491 }
2492 if (!consume_char(src, len, pos, ',')) {
2493 parser_fail(*pos, "pcsc_connect expects (ctx, reader)");
2494 free(name);
2495 return 0;
2496 }
2497 if (!emit_expression(bc, src, len, pos)) {
2498 parser_fail(*pos, "pcsc_connect expects (ctx, reader)");
2499 free(name);
2500 return 0;
2501 }
2502 if (!consume_char(src, len, pos, ')')) {
2503 parser_fail(*pos, "Expected ')' after pcsc_connect args");
2504 free(name);
2505 return 0;
2506 }
2508 free(name);
2509 return 1;
2510 }
2511 if (strcmp(name, "pcsc_disconnect") == 0) {
2512 (*pos)++; /* '(' */
2513 if (!emit_expression(bc, src, len, pos)) {
2514 parser_fail(*pos, "pcsc_disconnect expects 1 argument (handle)");
2515 free(name);
2516 return 0;
2517 }
2518 if (!consume_char(src, len, pos, ')')) {
2519 parser_fail(*pos, "Expected ')' after pcsc_disconnect arg");
2520 free(name);
2521 return 0;
2522 }
2524 free(name);
2525 return 1;
2526 }
2527 if (strcmp(name, "pcsc_transmit") == 0) {
2528 (*pos)++; /* '(' */
2529 /* handle, bytes */
2530 if (!emit_expression(bc, src, len, pos)) {
2531 parser_fail(*pos, "pcsc_transmit expects (handle, bytes)");
2532 free(name);
2533 return 0;
2534 }
2535 if (!consume_char(src, len, pos, ',')) {
2536 parser_fail(*pos, "pcsc_transmit expects (handle, bytes)");
2537 free(name);
2538 return 0;
2539 }
2540 if (!emit_expression(bc, src, len, pos)) {
2541 parser_fail(*pos, "pcsc_transmit expects (handle, bytes)");
2542 free(name);
2543 return 0;
2544 }
2545 if (!consume_char(src, len, pos, ')')) {
2546 parser_fail(*pos, "Expected ')' after pcsc_transmit args");
2547 free(name);
2548 return 0;
2549 }
2551 free(name);
2552 return 1;
2553 }
2554 /* Socket builtins */
2555 if (strcmp(name, "tcp_listen") == 0) {
2556 (*pos)++; /* '(' */
2557 if (!emit_expression(bc, src, len, pos)) {
2558 parser_fail(*pos, "tcp_listen expects (port, backlog)");
2559 free(name);
2560 return 0;
2561 }
2562 if (!consume_char(src, len, pos, ',')) {
2563 parser_fail(*pos, "tcp_listen expects (port, backlog)");
2564 free(name);
2565 return 0;
2566 }
2567 if (!emit_expression(bc, src, len, pos)) {
2568 parser_fail(*pos, "tcp_listen expects (port, backlog)");
2569 free(name);
2570 return 0;
2571 }
2572 if (!consume_char(src, len, pos, ')')) {
2573 parser_fail(*pos, "Expected ')' after tcp_listen args");
2574 free(name);
2575 return 0;
2576 }
2578 free(name);
2579 return 1;
2580 }
2581 if (strcmp(name, "tcp_accept") == 0) {
2582 (*pos)++; /* '(' */
2583 if (!emit_expression(bc, src, len, pos)) {
2584 parser_fail(*pos, "tcp_accept expects (listen_fd)");
2585 free(name);
2586 return 0;
2587 }
2588 if (!consume_char(src, len, pos, ')')) {
2589 parser_fail(*pos, "Expected ')' after tcp_accept arg");
2590 free(name);
2591 return 0;
2592 }
2594 free(name);
2595 return 1;
2596 }
2597 if (strcmp(name, "tcp_connect") == 0) {
2598 (*pos)++; /* '(' */
2599 if (!emit_expression(bc, src, len, pos)) {
2600 parser_fail(*pos, "tcp_connect expects (host, port)");
2601 free(name);
2602 return 0;
2603 }
2604 if (!consume_char(src, len, pos, ',')) {
2605 parser_fail(*pos, "tcp_connect expects (host, port)");
2606 free(name);
2607 return 0;
2608 }
2609 if (!emit_expression(bc, src, len, pos)) {
2610 parser_fail(*pos, "tcp_connect expects (host, port)");
2611 free(name);
2612 return 0;
2613 }
2614 if (!consume_char(src, len, pos, ')')) {
2615 parser_fail(*pos, "Expected ')' after tcp_connect args");
2616 free(name);
2617 return 0;
2618 }
2620 free(name);
2621 return 1;
2622 }
2623 if (strcmp(name, "sock_send") == 0) {
2624 (*pos)++; /* '(' */
2625 if (!emit_expression(bc, src, len, pos)) {
2626 parser_fail(*pos, "sock_send expects (fd, data)");
2627 free(name);
2628 return 0;
2629 }
2630 if (!consume_char(src, len, pos, ',')) {
2631 parser_fail(*pos, "sock_send expects (fd, data)");
2632 free(name);
2633 return 0;
2634 }
2635 if (!emit_expression(bc, src, len, pos)) {
2636 parser_fail(*pos, "sock_send expects (fd, data)");
2637 free(name);
2638 return 0;
2639 }
2640 if (!consume_char(src, len, pos, ')')) {
2641 parser_fail(*pos, "Expected ')' after sock_send args");
2642 free(name);
2643 return 0;
2644 }
2646 free(name);
2647 return 1;
2648 }
2649 if (strcmp(name, "sock_recv") == 0) {
2650 (*pos)++; /* '(' */
2651 if (!emit_expression(bc, src, len, pos)) {
2652 parser_fail(*pos, "sock_recv expects (fd, maxlen)");
2653 free(name);
2654 return 0;
2655 }
2656 if (!consume_char(src, len, pos, ',')) {
2657 parser_fail(*pos, "sock_recv expects (fd, maxlen)");
2658 free(name);
2659 return 0;
2660 }
2661 if (!emit_expression(bc, src, len, pos)) {
2662 parser_fail(*pos, "sock_recv expects (fd, maxlen)");
2663 free(name);
2664 return 0;
2665 }
2666 if (!consume_char(src, len, pos, ')')) {
2667 parser_fail(*pos, "Expected ')' after sock_recv args");
2668 free(name);
2669 return 0;
2670 }
2672 free(name);
2673 return 1;
2674 }
2675 if (strcmp(name, "sock_close") == 0) {
2676 (*pos)++; /* '(' */
2677 if (!emit_expression(bc, src, len, pos)) {
2678 parser_fail(*pos, "sock_close expects (fd)");
2679 free(name);
2680 return 0;
2681 }
2682 if (!consume_char(src, len, pos, ')')) {
2683 parser_fail(*pos, "Expected ')' after sock_close arg");
2684 free(name);
2685 return 0;
2686 }
2688 free(name);
2689 return 1;
2690 }
2691 if (strcmp(name, "unix_listen") == 0) {
2692 (*pos)++; /* '(' */
2693 if (!emit_expression(bc, src, len, pos)) {
2694 parser_fail(*pos, "unix_listen expects (path, backlog)");
2695 free(name);
2696 return 0;
2697 }
2698 if (!consume_char(src, len, pos, ',')) {
2699 parser_fail(*pos, "unix_listen expects (path, backlog)");
2700 free(name);
2701 return 0;
2702 }
2703 if (!emit_expression(bc, src, len, pos)) {
2704 parser_fail(*pos, "unix_listen expects (path, backlog)");
2705 free(name);
2706 return 0;
2707 }
2708 if (!consume_char(src, len, pos, ')')) {
2709 parser_fail(*pos, "Expected ')' after unix_listen args");
2710 free(name);
2711 return 0;
2712 }
2714 free(name);
2715 return 1;
2716 }
2717 if (strcmp(name, "unix_connect") == 0) {
2718 (*pos)++; /* '(' */
2719 if (!emit_expression(bc, src, len, pos)) {
2720 parser_fail(*pos, "unix_connect expects (path)");
2721 free(name);
2722 return 0;
2723 }
2724 if (!consume_char(src, len, pos, ')')) {
2725 parser_fail(*pos, "Expected ')' after unix_connect arg");
2726 free(name);
2727 return 0;
2728 }
2730 free(name);
2731 return 1;
2732 }
2733 /* Async-friendly FD helpers */
2734 if (strcmp(name, "fd_set_nonblock") == 0) {
2735 (*pos)++; /* '(' */
2736 /* Expect (fd, on) -> push fd then on so VM pops on first */
2737 if (!emit_expression(bc, src, len, pos)) {
2738 parser_fail(*pos, "fd_set_nonblock expects (fd, on)");
2739 free(name);
2740 return 0;
2741 }
2742 if (!consume_char(src, len, pos, ',')) {
2743 parser_fail(*pos, "fd_set_nonblock expects (fd, on)");
2744 free(name);
2745 return 0;
2746 }
2747 if (!emit_expression(bc, src, len, pos)) {
2748 parser_fail(*pos, "fd_set_nonblock expects (fd, on)");
2749 free(name);
2750 return 0;
2751 }
2752 if (!consume_char(src, len, pos, ')')) {
2753 parser_fail(*pos, "Expected ')' after fd_set_nonblock args");
2754 free(name);
2755 return 0;
2756 }
2758 free(name);
2759 return 1;
2760 }
2761 if (strcmp(name, "fd_poll_read") == 0) {
2762 (*pos)++; /* '(' */
2763 /* Expect (fd, timeout_ms) -> push fd then timeout so VM pops timeout first */
2764 if (!emit_expression(bc, src, len, pos)) {
2765 parser_fail(*pos, "fd_poll_read expects (fd, timeout_ms)");
2766 free(name);
2767 return 0;
2768 }
2769 if (!consume_char(src, len, pos, ',')) {
2770 parser_fail(*pos, "fd_poll_read expects (fd, timeout_ms)");
2771 free(name);
2772 return 0;
2773 }
2774 if (!emit_expression(bc, src, len, pos)) {
2775 parser_fail(*pos, "fd_poll_read expects (fd, timeout_ms)");
2776 free(name);
2777 return 0;
2778 }
2779 if (!consume_char(src, len, pos, ')')) {
2780 parser_fail(*pos, "Expected ')' after fd_poll_read args");
2781 free(name);
2782 return 0;
2783 }
2785 free(name);
2786 return 1;
2787 }
2788 if (strcmp(name, "fd_poll_write") == 0) {
2789 (*pos)++; /* '(' */
2790 /* Expect (fd, timeout_ms) */
2791 if (!emit_expression(bc, src, len, pos)) {
2792 parser_fail(*pos, "fd_poll_write expects (fd, timeout_ms)");
2793 free(name);
2794 return 0;
2795 }
2796 if (!consume_char(src, len, pos, ',')) {
2797 parser_fail(*pos, "fd_poll_write expects (fd, timeout_ms)");
2798 free(name);
2799 return 0;
2800 }
2801 if (!emit_expression(bc, src, len, pos)) {
2802 parser_fail(*pos, "fd_poll_write expects (fd, timeout_ms)");
2803 free(name);
2804 return 0;
2805 }
2806 if (!consume_char(src, len, pos, ')')) {
2807 parser_fail(*pos, "Expected ')' after fd_poll_write args");
2808 free(name);
2809 return 0;
2810 }
2812 free(name);
2813 return 1;
2814 }
2815 /* Serial builtins */
2816 if (strcmp(name, "serial_open") == 0) {
2817 (*pos)++; /* '(' */
2818 if (!emit_expression(bc, src, len, pos)) {
2819 parser_fail(*pos, "serial_open expects (path, baud)");
2820 free(name);
2821 return 0;
2822 }
2823 if (!consume_char(src, len, pos, ',')) {
2824 parser_fail(*pos, "serial_open expects (path, baud)");
2825 free(name);
2826 return 0;
2827 }
2828 if (!emit_expression(bc, src, len, pos)) {
2829 parser_fail(*pos, "serial_open expects (path, baud)");
2830 free(name);
2831 return 0;
2832 }
2833 if (!consume_char(src, len, pos, ')')) {
2834 parser_fail(*pos, "Expected ')' after serial_open args");
2835 free(name);
2836 return 0;
2837 }
2839 free(name);
2840 return 1;
2841 }
2842 if (strcmp(name, "serial_config") == 0) {
2843 (*pos)++; /* '(' */
2844 // fd, data_bits, parity, stop_bits, flow_control
2845 for (int i = 0; i < 5; ++i) {
2846 if (!emit_expression(bc, src, len, pos)) {
2847 parser_fail(*pos, "serial_config expects 5 arguments");
2848 free(name);
2849 return 0;
2850 }
2851 if (i < 4) {
2852 if (!consume_char(src, len, pos, ',')) {
2853 parser_fail(*pos, "serial_config expects 5 arguments");
2854 free(name);
2855 return 0;
2856 }
2857 }
2858 }
2859 if (!consume_char(src, len, pos, ')')) {
2860 parser_fail(*pos, "Expected ')' after serial_config args");
2861 free(name);
2862 return 0;
2863 }
2865 free(name);
2866 return 1;
2867 }
2868 if (strcmp(name, "serial_send") == 0) {
2869 (*pos)++; /* '(' */
2870 if (!emit_expression(bc, src, len, pos)) {
2871 parser_fail(*pos, "serial_send expects (fd, data)");
2872 free(name);
2873 return 0;
2874 }
2875 if (!consume_char(src, len, pos, ',')) {
2876 parser_fail(*pos, "serial_send expects (fd, data)");
2877 free(name);
2878 return 0;
2879 }
2880 if (!emit_expression(bc, src, len, pos)) {
2881 parser_fail(*pos, "serial_send expects (fd, data)");
2882 free(name);
2883 return 0;
2884 }
2885 if (!consume_char(src, len, pos, ')')) {
2886 parser_fail(*pos, "Expected ')' after serial_send args");
2887 free(name);
2888 return 0;
2889 }
2891 free(name);
2892 return 1;
2893 }
2894 if (strcmp(name, "serial_recv") == 0) {
2895 (*pos)++; /* '(' */
2896 if (!emit_expression(bc, src, len, pos)) {
2897 parser_fail(*pos, "serial_recv expects (fd, maxlen)");
2898 free(name);
2899 return 0;
2900 }
2901 if (!consume_char(src, len, pos, ',')) {
2902 parser_fail(*pos, "serial_recv expects (fd, maxlen)");
2903 free(name);
2904 return 0;
2905 }
2906 if (!emit_expression(bc, src, len, pos)) {
2907 parser_fail(*pos, "serial_recv expects (fd, maxlen)");
2908 free(name);
2909 return 0;
2910 }
2911 if (!consume_char(src, len, pos, ')')) {
2912 parser_fail(*pos, "Expected ')' after serial_recv args");
2913 free(name);
2914 return 0;
2915 }
2917 free(name);
2918 return 1;
2919 }
2920 if (strcmp(name, "serial_close") == 0) {
2921 (*pos)++; /* '(' */
2922 if (!emit_expression(bc, src, len, pos)) {
2923 parser_fail(*pos, "serial_close expects (fd)");
2924 free(name);
2925 return 0;
2926 }
2927 if (!consume_char(src, len, pos, ')')) {
2928 parser_fail(*pos, "Expected ')' after serial_close arg");
2929 free(name);
2930 return 0;
2931 }
2933 free(name);
2934 return 1;
2935 }
2936 /* string ops */
2937 if (strcmp(name, "split") == 0) {
2938 (*pos)++; /* '(' */
2939 if (!emit_expression(bc, src, len, pos)) {
2940 parser_fail(*pos, "split expects string");
2941 free(name);
2942 return 0;
2943 }
2944 if (*pos < len && src[*pos] == ',') {
2945 (*pos)++;
2946 skip_spaces(src, len, pos);
2947 } else {
2948 parser_fail(*pos, "split expects 2 args");
2949 free(name);
2950 return 0;
2951 }
2952 if (!emit_expression(bc, src, len, pos)) {
2953 parser_fail(*pos, "split expects separator");
2954 free(name);
2955 return 0;
2956 }
2957 if (!consume_char(src, len, pos, ')')) {
2958 parser_fail(*pos, "Expected ')' after split args");
2959 free(name);
2960 return 0;
2961 }
2963 free(name);
2964 return 1;
2965 }
2966 if (strcmp(name, "join") == 0) {
2967 (*pos)++; /* '(' */
2968 if (!emit_expression(bc, src, len, pos)) {
2969 parser_fail(*pos, "join expects array");
2970 free(name);
2971 return 0;
2972 }
2973 if (*pos < len && src[*pos] == ',') {
2974 (*pos)++;
2975 skip_spaces(src, len, pos);
2976 } else {
2977 parser_fail(*pos, "join expects 2 args");
2978 free(name);
2979 return 0;
2980 }
2981 if (!emit_expression(bc, src, len, pos)) {
2982 parser_fail(*pos, "join expects separator");
2983 free(name);
2984 return 0;
2985 }
2986 if (!consume_char(src, len, pos, ')')) {
2987 parser_fail(*pos, "Expected ')' after join args");
2988 free(name);
2989 return 0;
2990 }
2992 free(name);
2993 return 1;
2994 }
2995 if (strcmp(name, "substr") == 0) {
2996 (*pos)++; /* '(' */
2997 if (!emit_expression(bc, src, len, pos)) {
2998 parser_fail(*pos, "substr expects string");
2999 free(name);
3000 return 0;
3001 }
3002 if (*pos < len && src[*pos] == ',') {
3003 (*pos)++;
3004 skip_spaces(src, len, pos);
3005 } else {
3006 parser_fail(*pos, "substr expects 3 args");
3007 free(name);
3008 return 0;
3009 }
3010 if (!emit_expression(bc, src, len, pos)) {
3011 parser_fail(*pos, "substr expects start");
3012 free(name);
3013 return 0;
3014 }
3015 if (*pos < len && src[*pos] == ',') {
3016 (*pos)++;
3017 skip_spaces(src, len, pos);
3018 } else {
3019 parser_fail(*pos, "substr expects 3 args");
3020 free(name);
3021 return 0;
3022 }
3023 if (!emit_expression(bc, src, len, pos)) {
3024 parser_fail(*pos, "substr expects len");
3025 free(name);
3026 return 0;
3027 }
3028 if (!consume_char(src, len, pos, ')')) {
3029 parser_fail(*pos, "Expected ')' after substr args");
3030 free(name);
3031 return 0;
3032 }
3034 free(name);
3035 return 1;
3036 }
3037 if (strcmp(name, "find") == 0) {
3038 (*pos)++; /* '(' */
3039 if (!emit_expression(bc, src, len, pos)) {
3040 parser_fail(*pos, "find expects haystack");
3041 free(name);
3042 return 0;
3043 }
3044 if (*pos < len && src[*pos] == ',') {
3045 (*pos)++;
3046 skip_spaces(src, len, pos);
3047 } else {
3048 parser_fail(*pos, "find expects 2 args");
3049 free(name);
3050 return 0;
3051 }
3052 if (!emit_expression(bc, src, len, pos)) {
3053 parser_fail(*pos, "find expects needle");
3054 free(name);
3055 return 0;
3056 }
3057 if (!consume_char(src, len, pos, ')')) {
3058 parser_fail(*pos, "Expected ')' after find args");
3059 free(name);
3060 return 0;
3061 }
3063 free(name);
3064 return 1;
3065 }
3066 /* regex ops */
3067 if (strcmp(name, "regex_match") == 0) {
3068 (*pos)++; /* '(' */
3069 if (!emit_expression(bc, src, len, pos)) {
3070 parser_fail(*pos, "regex_match expects text");
3071 free(name);
3072 return 0;
3073 }
3074 if (*pos < len && src[*pos] == ',') {
3075 (*pos)++;
3076 skip_spaces(src, len, pos);
3077 } else {
3078 parser_fail(*pos, "regex_match expects 2 args");
3079 free(name);
3080 return 0;
3081 }
3082 if (!emit_expression(bc, src, len, pos)) {
3083 parser_fail(*pos, "regex_match expects pattern");
3084 free(name);
3085 return 0;
3086 }
3087 if (!consume_char(src, len, pos, ')')) {
3088 parser_fail(*pos, "Expected ')' after regex_match args");
3089 free(name);
3090 return 0;
3091 }
3093 free(name);
3094 return 1;
3095 }
3096 if (strcmp(name, "regex_search") == 0) {
3097 (*pos)++; /* '(' */
3098 if (!emit_expression(bc, src, len, pos)) {
3099 parser_fail(*pos, "regex_search expects text");
3100 free(name);
3101 return 0;
3102 }
3103 if (*pos < len && src[*pos] == ',') {
3104 (*pos)++;
3105 skip_spaces(src, len, pos);
3106 } else {
3107 parser_fail(*pos, "regex_search expects 2 args");
3108 free(name);
3109 return 0;
3110 }
3111 if (!emit_expression(bc, src, len, pos)) {
3112 parser_fail(*pos, "regex_search expects pattern");
3113 free(name);
3114 return 0;
3115 }
3116 if (!consume_char(src, len, pos, ')')) {
3117 parser_fail(*pos, "Expected ')' after regex_search args");
3118 free(name);
3119 return 0;
3120 }
3122 free(name);
3123 return 1;
3124 }
3125 if (strcmp(name, "regex_replace") == 0) {
3126 (*pos)++; /* '(' */
3127 if (!emit_expression(bc, src, len, pos)) {
3128 parser_fail(*pos, "regex_replace expects text");
3129 free(name);
3130 return 0;
3131 }
3132 if (*pos < len && src[*pos] == ',') {
3133 (*pos)++;
3134 skip_spaces(src, len, pos);
3135 } else {
3136 parser_fail(*pos, "regex_replace expects 3 args");
3137 free(name);
3138 return 0;
3139 }
3140 if (!emit_expression(bc, src, len, pos)) {
3141 parser_fail(*pos, "regex_replace expects pattern");
3142 free(name);
3143 return 0;
3144 }
3145 if (*pos < len && src[*pos] == ',') {
3146 (*pos)++;
3147 skip_spaces(src, len, pos);
3148 } else {
3149 parser_fail(*pos, "regex_replace expects 3 args");
3150 free(name);
3151 return 0;
3152 }
3153 if (!emit_expression(bc, src, len, pos)) {
3154 parser_fail(*pos, "regex_replace expects replacement");
3155 free(name);
3156 return 0;
3157 }
3158 if (!consume_char(src, len, pos, ')')) {
3159 parser_fail(*pos, "Expected ')' after regex_replace args");
3160 free(name);
3161 return 0;
3162 }
3164 free(name);
3165 return 1;
3166 }
3167 /* array utils */
3168 if (strcmp(name, "contains") == 0) {
3169 (*pos)++; /* '(' */
3170 if (!emit_expression(bc, src, len, pos)) {
3171 parser_fail(*pos, "contains expects array");
3172 free(name);
3173 return 0;
3174 }
3175 if (*pos < len && src[*pos] == ',') {
3176 (*pos)++;
3177 skip_spaces(src, len, pos);
3178 } else {
3179 parser_fail(*pos, "contains expects 2 args");
3180 free(name);
3181 return 0;
3182 }
3183 if (!emit_expression(bc, src, len, pos)) {
3184 parser_fail(*pos, "contains expects value");
3185 free(name);
3186 return 0;
3187 }
3188 if (!consume_char(src, len, pos, ')')) {
3189 parser_fail(*pos, "Expected ')' after contains args");
3190 free(name);
3191 return 0;
3192 }
3194 free(name);
3195 return 1;
3196 }
3197 if (strcmp(name, "indexOf") == 0) {
3198 (*pos)++; /* '(' */
3199 if (!emit_expression(bc, src, len, pos)) {
3200 parser_fail(*pos, "indexOf expects array");
3201 free(name);
3202 return 0;
3203 }
3204 if (*pos < len && src[*pos] == ',') {
3205 (*pos)++;
3206 skip_spaces(src, len, pos);
3207 } else {
3208 parser_fail(*pos, "indexOf expects 2 args");
3209 free(name);
3210 return 0;
3211 }
3212 if (!emit_expression(bc, src, len, pos)) {
3213 parser_fail(*pos, "indexOf expects value");
3214 free(name);
3215 return 0;
3216 }
3217 if (!consume_char(src, len, pos, ')')) {
3218 parser_fail(*pos, "Expected ')' after indexOf args");
3219 free(name);
3220 return 0;
3221 }
3223 free(name);
3224 return 1;
3225 }
3226 if (strcmp(name, "clear") == 0) {
3227 (*pos)++; /* '(' */
3228 if (!emit_expression(bc, src, len, pos)) {
3229 parser_fail(*pos, "clear expects array");
3230 free(name);
3231 return 0;
3232 }
3233 if (!consume_char(src, len, pos, ')')) {
3234 parser_fail(*pos, "Expected ')' after clear arg");
3235 free(name);
3236 return 0;
3237 }
3239 free(name);
3240 return 1;
3241 }
3242 /* iteration helpers */
3243 if (strcmp(name, "enumerate") == 0) {
3244 (*pos)++; /* '(' */
3245 if (!emit_expression(bc, src, len, pos)) {
3246 parser_fail(*pos, "enumerate expects array");
3247 free(name);
3248 return 0;
3249 }
3250 if (!consume_char(src, len, pos, ')')) {
3251 parser_fail(*pos, "Expected ')' after enumerate arg");
3252 free(name);
3253 return 0;
3254 }
3256 free(name);
3257 return 1;
3258 }
3259 if (strcmp(name, "map") == 0) {
3260 (*pos)++; /* '(' */
3261 /* arr */
3262 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
3263 parser_fail(*pos, "map expects (array, function)");
3264 free(name);
3265 return 0;
3266 }
3267 /* store arr -> __map_arr */
3268 char tarr[64];
3269 snprintf(tarr, sizeof(tarr), "__map_arr_%d", g_temp_counter++);
3270 int larr = -1, garr = -1;
3271 if (g_locals) {
3272 larr = local_add(tarr);
3274 } else {
3275 garr = sym_index(tarr);
3277 }
3278 /* func */
3279 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
3280 parser_fail(*pos, "map expects (array, function)");
3281 free(name);
3282 return 0;
3283 }
3284 char tfn[64];
3285 snprintf(tfn, sizeof(tfn), "__map_fn_%d", g_temp_counter++);
3286 int lfn = -1, gfn = -1;
3287 if (g_locals) {
3288 lfn = local_add(tfn);
3290 } else {
3291 gfn = sym_index(tfn);
3293 }
3294 /* res array */
3296 char tres[64];
3297 snprintf(tres, sizeof(tres), "__map_res_%d", g_temp_counter++);
3298 int lres = -1, gres = -1;
3299 if (g_locals) {
3300 lres = local_add(tres);
3302 } else {
3303 gres = sym_index(tres);
3305 }
3306 /* i=0 */
3307 int c0 = bytecode_add_constant(bc, make_int(0));
3309 char ti[64];
3310 snprintf(ti, sizeof(ti), "__map_i_%d", g_temp_counter++);
3311 int li = -1, gi = -1;
3312 if (g_locals) {
3313 li = local_add(ti);
3315 } else {
3316 gi = sym_index(ti);
3318 }
3319 /* loop start */
3320 int loop_start = bc->instr_count;
3321 /* i < len(arr) */
3322 if (g_locals) {
3325 } else {
3328 }
3332 /* elem = arr[i] */
3333 if (g_locals) {
3336 } else {
3339 }
3341 /* call fn(elem) */
3342 if (g_locals) {
3344 } else {
3346 }
3347 /* reorder: we need fn below arg -> push fn first then arg already on stack? We currently have elem on stack; push fn now results top=fn. We want top args then function; OP_CALL expects fn then pops args? Our OP_CALL pops function after args; earlier compile of calls: they push function then args then OP_CALL. So we need to swap: push fn, then swap to make function below arg */
3350 /* Append to result via indexed assignment: res[len(res)] = value */
3351 /* Store computed value to a temp */
3352 char tv[64];
3353 snprintf(tv, sizeof(tv), "__map_v_%d", g_temp_counter++);
3354 int lv = -1, gv = -1;
3355 if (g_locals) {
3356 lv = local_add(tv);
3358 } else {
3359 gv = sym_index(tv);
3361 }
3362
3363 /* Push array (for INDEX_SET we need stack: value, index, array; we will build array, index, then value) */
3364 if (g_locals) {
3366 } else {
3368 }
3369
3370 /* Compute index = len(res) */
3371 if (g_locals) {
3373 } else {
3375 }
3377
3378 /* Load value back on top */
3379 if (g_locals) {
3381 } else {
3383 }
3384
3385 /* Append via insert: res.insert(index=len(res), value) */
3387 /* dApache-2.0ard returned new length */
3389
3390 /* i++ */
3391 int c1 = bytecode_add_constant(bc, make_int(1));
3392 if (g_locals) {
3397 } else {
3402 }
3403 bytecode_add_instruction(bc, OP_JUMP, loop_start);
3404 bytecode_set_operand(bc, jf, bc->instr_count);
3405 /* result value on stack */
3406 if (g_locals) {
3408 } else {
3410 }
3411 free(name);
3412 return 1;
3413 }
3414 if (strcmp(name, "filter") == 0) {
3415 (*pos)++; /* '(' */
3416 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
3417 parser_fail(*pos, "filter expects (array, function)");
3418 free(name);
3419 return 0;
3420 }
3421 char tarr[64];
3422 snprintf(tarr, sizeof(tarr), "__flt_arr_%d", g_temp_counter++);
3423 int larr = -1, garr = -1;
3424 if (g_locals) {
3425 larr = local_add(tarr);
3427 } else {
3428 garr = sym_index(tarr);
3430 }
3431 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
3432 parser_fail(*pos, "filter expects (array, function)");
3433 free(name);
3434 return 0;
3435 }
3436 char tfn[64];
3437 snprintf(tfn, sizeof(tfn), "__flt_fn_%d", g_temp_counter++);
3438 int lfn = -1, gfn = -1;
3439 if (g_locals) {
3440 lfn = local_add(tfn);
3442 } else {
3443 gfn = sym_index(tfn);
3445 }
3447 char tres[64];
3448 snprintf(tres, sizeof(tres), "__flt_res_%d", g_temp_counter++);
3449 int lres = -1, gres = -1;
3450 if (g_locals) {
3451 lres = local_add(tres);
3453 } else {
3454 gres = sym_index(tres);
3456 }
3457 int c0 = bytecode_add_constant(bc, make_int(0));
3459 char ti[64];
3460 snprintf(ti, sizeof(ti), "__flt_i_%d", g_temp_counter++);
3461 int li = -1, gi = -1;
3462 if (g_locals) {
3463 li = local_add(ti);
3465 } else {
3466 gi = sym_index(ti);
3468 }
3469 int loop_start = bc->instr_count;
3470 if (g_locals) {
3473 } else {
3476 }
3480 if (g_locals) {
3483 } else {
3486 }
3488 if (g_locals) {
3490 } else {
3492 }
3495 /* if truthy then push elem to res */
3496 int jskip = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
3497 /* Append element to result: res[len(res)] = elem */
3498 /* Reload element into a temp */
3499 if (g_locals) {
3502 } else {
3505 }
3507
3508 char tvf[64];
3509 snprintf(tvf, sizeof(tvf), "__flt_v_%d", g_temp_counter++);
3510 int lvf = -1, gvf = -1;
3511 if (g_locals) {
3512 lvf = local_add(tvf);
3514 } else {
3515 gvf = sym_index(tvf);
3517 }
3518
3519 /* Push array */
3520 if (g_locals) {
3522 } else {
3524 }
3525
3526 /* index = len(res) */
3527 if (g_locals) {
3529 } else {
3531 }
3533
3534 /* value */
3535 if (g_locals) {
3537 } else {
3539 }
3540
3541 /* Append via insert: res.insert(index=len(res), value) */
3543 /* dApache-2.0ard returned new length */
3545
3546 int c1 = bytecode_add_constant(bc, make_int(1));
3547 /* patch skip over append */
3548 bytecode_set_operand(bc, jskip, bc->instr_count);
3549 /* i++ */
3550 if (g_locals) {
3555 } else {
3560 }
3561 bytecode_add_instruction(bc, OP_JUMP, loop_start);
3562 bytecode_set_operand(bc, jf, bc->instr_count);
3563 if (g_locals) {
3565 } else {
3567 }
3568 free(name);
3569 return 1;
3570 }
3571 if (strcmp(name, "reduce") == 0) {
3572 (*pos)++; /* '(' */
3573 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
3574 parser_fail(*pos, "reduce expects (array, init, function)");
3575 free(name);
3576 return 0;
3577 }
3578 char tarr[64];
3579 snprintf(tarr, sizeof(tarr), "__red_arr_%d", g_temp_counter++);
3580 int larr = -1, garr = -1;
3581 if (g_locals) {
3582 larr = local_add(tarr);
3584 } else {
3585 garr = sym_index(tarr);
3587 }
3588 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
3589 parser_fail(*pos, "reduce expects (array, init, function)");
3590 free(name);
3591 return 0;
3592 }
3593 char tacc[64];
3594 snprintf(tacc, sizeof(tacc), "__red_acc_%d", g_temp_counter++);
3595 int lacc = -1, gacc = -1;
3596 if (g_locals) {
3597 lacc = local_add(tacc);
3599 } else {
3600 gacc = sym_index(tacc);
3602 }
3603 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
3604 parser_fail(*pos, "reduce expects (array, init, function)");
3605 free(name);
3606 return 0;
3607 }
3608 char tfn[64];
3609 snprintf(tfn, sizeof(tfn), "__red_fn_%d", g_temp_counter++);
3610 int lfn = -1, gfn = -1;
3611 if (g_locals) {
3612 lfn = local_add(tfn);
3614 } else {
3615 gfn = sym_index(tfn);
3617 }
3618 /* loop */
3619 int c0 = bytecode_add_constant(bc, make_int(0));
3621 char ti[64];
3622 snprintf(ti, sizeof(ti), "__red_i_%d", g_temp_counter++);
3623 int li = -1, gi = -1;
3624 if (g_locals) {
3625 li = local_add(ti);
3627 } else {
3628 gi = sym_index(ti);
3630 }
3631 int loop_start = bc->instr_count;
3632 if (g_locals) {
3635 } else {
3638 }
3642 /* elem */
3643 if (g_locals) {
3646 } else {
3649 }
3651
3652 /* Store elem to a temp so we can build stack as: fn, acc, elem */
3653 char telem[64];
3654 snprintf(telem, sizeof(telem), "__red_elem_%d", g_temp_counter++);
3655 int lelem = -1, gelem = -1;
3656 if (g_locals) {
3657 lelem = local_add(telem);
3659 } else {
3660 gelem = sym_index(telem);
3662 }
3663
3664 /* push function */
3665 if (g_locals) {
3667 } else {
3669 }
3670
3671 /* push accumulator (arg1) */
3672 if (g_locals) {
3674 } else {
3676 }
3677
3678 /* push element (arg2) */
3679 if (g_locals) {
3681 } else {
3683 }
3684
3685 /* call fn(acc, elem) -> result */
3687 /* store to acc */
3688 if (g_locals) {
3690 } else {
3692 }
3693 int c1 = bytecode_add_constant(bc, make_int(1));
3694 if (g_locals) {
3699 } else {
3704 }
3705 bytecode_add_instruction(bc, OP_JUMP, loop_start);
3706 bytecode_set_operand(bc, jf, bc->instr_count);
3707 /* result = acc on stack */
3708 if (g_locals) {
3710 } else {
3712 }
3713 free(name);
3714 return 1;
3715 }
3716 if (strcmp(name, "zip") == 0) {
3717 (*pos)++; /* '(' */
3718 if (!emit_expression(bc, src, len, pos)) {
3719 parser_fail(*pos, "zip expects first array");
3720 free(name);
3721 return 0;
3722 }
3723 if (*pos < len && src[*pos] == ',') {
3724 (*pos)++;
3725 skip_spaces(src, len, pos);
3726 } else {
3727 parser_fail(*pos, "zip expects 2 args");
3728 free(name);
3729 return 0;
3730 }
3731 if (!emit_expression(bc, src, len, pos)) {
3732 parser_fail(*pos, "zip expects second array");
3733 free(name);
3734 return 0;
3735 }
3736 if (!consume_char(src, len, pos, ')')) {
3737 parser_fail(*pos, "Expected ')' after zip args");
3738 free(name);
3739 return 0;
3740 }
3742 free(name);
3743 return 1;
3744 }
3745 /* math */
3746 if (strcmp(name, "min") == 0) {
3747 (*pos)++; /* '(' */
3748 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
3749 parser_fail(*pos, "min expects 2 args");
3750 free(name);
3751 return 0;
3752 }
3753 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
3754 parser_fail(*pos, "min expects 2 args");
3755 free(name);
3756 return 0;
3757 }
3759 free(name);
3760 return 1;
3761 }
3762 if (strcmp(name, "max") == 0) {
3763 (*pos)++; /* '(' */
3764 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
3765 parser_fail(*pos, "max expects 2 args");
3766 free(name);
3767 return 0;
3768 }
3769 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
3770 parser_fail(*pos, "max expects 2 args");
3771 free(name);
3772 return 0;
3773 }
3775 free(name);
3776 return 1;
3777 }
3778 if (strcmp(name, "fmin") == 0) {
3779 (*pos)++; /* '(' */
3780 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
3781 parser_fail(*pos, "fmin expects 2 args");
3782 free(name);
3783 return 0;
3784 }
3785 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
3786 parser_fail(*pos, "fmin expects 2 args");
3787 free(name);
3788 return 0;
3789 }
3791 free(name);
3792 return 1;
3793 }
3794 if (strcmp(name, "fmax") == 0) {
3795 (*pos)++; /* '(' */
3796 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
3797 parser_fail(*pos, "fmax expects 2 args");
3798 free(name);
3799 return 0;
3800 }
3801 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
3802 parser_fail(*pos, "fmax expects 2 args");
3803 free(name);
3804 return 0;
3805 }
3807 free(name);
3808 return 1;
3809 }
3810 if (strcmp(name, "clamp") == 0) {
3811 (*pos)++; /* '(' */
3812 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
3813 parser_fail(*pos, "clamp expects 3 args");
3814 free(name);
3815 return 0;
3816 }
3817 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
3818 parser_fail(*pos, "clamp expects 3 args");
3819 free(name);
3820 return 0;
3821 }
3822 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
3823 parser_fail(*pos, "clamp expects 3 args");
3824 free(name);
3825 return 0;
3826 }
3828 free(name);
3829 return 1;
3830 }
3831 if (strcmp(name, "abs") == 0) {
3832 (*pos)++; /* '(' */
3833 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
3834 parser_fail(*pos, "abs expects 1 arg");
3835 free(name);
3836 return 0;
3837 }
3839 free(name);
3840 return 1;
3841 }
3842 if (strcmp(name, "floor") == 0) {
3843 (*pos)++; /* '(' */
3844 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
3845 parser_fail(*pos, "floor expects 1 arg");
3846 free(name);
3847 return 0;
3848 }
3850 free(name);
3851 return 1;
3852 }
3853 if (strcmp(name, "ceil") == 0) {
3854 (*pos)++; /* '(' */
3855 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
3856 parser_fail(*pos, "ceil expects 1 arg");
3857 free(name);
3858 return 0;
3859 }
3861 free(name);
3862 return 1;
3863 }
3864 if (strcmp(name, "trunc") == 0) {
3865 (*pos)++; /* '(' */
3866 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
3867 parser_fail(*pos, "trunc expects 1 arg");
3868 free(name);
3869 return 0;
3870 }
3872 free(name);
3873 return 1;
3874 }
3875 if (strcmp(name, "round") == 0) {
3876 (*pos)++; /* '(' */
3877 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
3878 parser_fail(*pos, "round expects 1 arg");
3879 free(name);
3880 return 0;
3881 }
3883 free(name);
3884 return 1;
3885 }
3886 if (strcmp(name, "sin") == 0) {
3887 (*pos)++; /* '(' */
3888 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
3889 parser_fail(*pos, "sin expects 1 arg");
3890 free(name);
3891 return 0;
3892 }
3894 free(name);
3895 return 1;
3896 }
3897 if (strcmp(name, "cos") == 0) {
3898 (*pos)++; /* '(' */
3899 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
3900 parser_fail(*pos, "cos expects 1 arg");
3901 free(name);
3902 return 0;
3903 }
3905 free(name);
3906 return 1;
3907 }
3908 if (strcmp(name, "tan") == 0) {
3909 (*pos)++; /* '(' */
3910 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
3911 parser_fail(*pos, "tan expects 1 arg");
3912 free(name);
3913 return 0;
3914 }
3916 free(name);
3917 return 1;
3918 }
3919 if (strcmp(name, "exp") == 0) {
3920 (*pos)++; /* '(' */
3921 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
3922 parser_fail(*pos, "exp expects 1 arg");
3923 free(name);
3924 return 0;
3925 }
3927 free(name);
3928 return 1;
3929 }
3930 if (strcmp(name, "log") == 0) {
3931 (*pos)++; /* '(' */
3932 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
3933 parser_fail(*pos, "log expects 1 arg");
3934 free(name);
3935 return 0;
3936 }
3938 free(name);
3939 return 1;
3940 }
3941 if (strcmp(name, "log10") == 0) {
3942 (*pos)++; /* '(' */
3943 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
3944 parser_fail(*pos, "log10 expects 1 arg");
3945 free(name);
3946 return 0;
3947 }
3949 free(name);
3950 return 1;
3951 }
3952 if (strcmp(name, "sqrt") == 0) {
3953 (*pos)++; /* '(' */
3954 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
3955 parser_fail(*pos, "sqrt expects 1 arg");
3956 free(name);
3957 return 0;
3958 }
3960 free(name);
3961 return 1;
3962 }
3963 if (strcmp(name, "gcd") == 0) {
3964 (*pos)++; /* '(' */
3965 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
3966 parser_fail(*pos, "gcd expects 2 args");
3967 free(name);
3968 return 0;
3969 }
3970 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
3971 parser_fail(*pos, "gcd expects 2 args");
3972 free(name);
3973 return 0;
3974 }
3976 free(name);
3977 return 1;
3978 }
3979 if (strcmp(name, "lcm") == 0) {
3980 (*pos)++; /* '(' */
3981 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
3982 parser_fail(*pos, "lcm expects 2 args");
3983 free(name);
3984 return 0;
3985 }
3986 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
3987 parser_fail(*pos, "lcm expects 2 args");
3988 free(name);
3989 return 0;
3990 }
3992 free(name);
3993 return 1;
3994 }
3995 if (strcmp(name, "isqrt") == 0) {
3996 (*pos)++; /* '(' */
3997 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
3998 parser_fail(*pos, "isqrt expects 1 arg");
3999 free(name);
4000 return 0;
4001 }
4003 free(name);
4004 return 1;
4005 }
4006 if (strcmp(name, "sign") == 0) {
4007 (*pos)++; /* '(' */
4008 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
4009 parser_fail(*pos, "sign expects 1 arg");
4010 free(name);
4011 return 0;
4012 }
4014 free(name);
4015 return 1;
4016 }
4017 if (strcmp(name, "pow") == 0) {
4018 (*pos)++; /* '(' */
4019 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
4020 parser_fail(*pos, "pow expects 2 args");
4021 free(name);
4022 return 0;
4023 }
4024 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
4025 parser_fail(*pos, "pow expects 2 args");
4026 free(name);
4027 return 0;
4028 }
4030 free(name);
4031 return 1;
4032 }
4033
4034 if (strcmp(name, "random_seed") == 0) {
4035 (*pos)++; /* '(' */
4036 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
4037 parser_fail(*pos, "random_seed expects 1 arg");
4038 free(name);
4039 return 0;
4040 }
4042 free(name);
4043 return 1;
4044 }
4045 if (strcmp(name, "random_int") == 0) {
4046 (*pos)++; /* '(' */
4047 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
4048 parser_fail(*pos, "random_int expects 2 args");
4049 free(name);
4050 return 0;
4051 }
4052 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
4053 parser_fail(*pos, "random_int expects 2 args");
4054 free(name);
4055 return 0;
4056 }
4058 free(name);
4059 return 1;
4060 }
4061
4062 if (strcmp(name, "random_number") == 0) {
4063 (*pos)++; /* '(' */
4064 /* expects exactly 1 arg: length */
4065 if (!emit_expression(bc, src, len, pos)) {
4066 parser_fail(*pos, "random_number expects 1 arg (length)");
4067 free(name);
4068 return 0;
4069 }
4070 if (!consume_char(src, len, pos, ')')) {
4071 parser_fail(*pos, "Expected ')' after random_number arg");
4072 free(name);
4073 return 0;
4074 }
4076 free(name);
4077 return 1;
4078 }
4079
4080 /* threading */
4081 if (strcmp(name, "thread_spawn") == 0) {
4082 (*pos)++; /* '(' */
4083 /* thread_spawn(fn [, args]) */
4084 if (!emit_expression(bc, src, len, pos)) {
4085 parser_fail(*pos, "thread_spawn expects function as first arg");
4086 free(name);
4087 return 0;
4088 }
4089 int hasArgs = 0;
4090 skip_spaces(src, len, pos);
4091 if (*pos < len && src[*pos] == ',') {
4092 (*pos)++;
4093 skip_spaces(src, len, pos);
4094 if (!emit_expression(bc, src, len, pos)) {
4095 parser_fail(*pos, "thread_spawn second arg must be array or value");
4096 free(name);
4097 return 0;
4098 }
4099 hasArgs = 1;
4100 }
4101 if (!consume_char(src, len, pos, ')')) {
4102 parser_fail(*pos, "Expected ')' after thread_spawn args");
4103 free(name);
4104 return 0;
4105 }
4106 bytecode_add_instruction(bc, OP_THREAD_SPAWN, hasArgs ? 1 : 0);
4107 free(name);
4108 return 1;
4109 }
4110 if (strcmp(name, "thread_join") == 0) {
4111 (*pos)++; /* '(' */
4112 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
4113 parser_fail(*pos, "thread_join expects 1 arg (thread id)");
4114 free(name);
4115 return 0;
4116 }
4118 free(name);
4119 return 1;
4120 }
4121 if (strcmp(name, "sleep") == 0) {
4122 (*pos)++; /* '(' */
4123 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
4124 parser_fail(*pos, "sleep expects 1 arg (milliseconds)");
4125 free(name);
4126 return 0;
4127 }
4129 free(name);
4130 return 1;
4131 }
4132
4133 /* bitwise ops (32-bit) */
4134 if (strcmp(name, "band") == 0) {
4135 (*pos)++; /* '(' */
4136 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
4137 parser_fail(*pos, "band expects 2 args");
4138 free(name);
4139 return 0;
4140 }
4141 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
4142 parser_fail(*pos, "band expects 2 args");
4143 free(name);
4144 return 0;
4145 }
4147 free(name);
4148 return 1;
4149 }
4150 if (strcmp(name, "bor") == 0) {
4151 (*pos)++; /* '(' */
4152 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
4153 parser_fail(*pos, "bor expects 2 args");
4154 free(name);
4155 return 0;
4156 }
4157 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
4158 parser_fail(*pos, "bor expects 2 args");
4159 free(name);
4160 return 0;
4161 }
4163 free(name);
4164 return 1;
4165 }
4166 if (strcmp(name, "bxor") == 0) {
4167 (*pos)++; /* '(' */
4168 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
4169 parser_fail(*pos, "bxor expects 2 args");
4170 free(name);
4171 return 0;
4172 }
4173 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
4174 parser_fail(*pos, "bxor expects 2 args");
4175 free(name);
4176 return 0;
4177 }
4179 free(name);
4180 return 1;
4181 }
4182 if (strcmp(name, "bnot") == 0) {
4183 (*pos)++; /* '(' */
4184 if (!emit_expression(bc, src, len, pos)) {
4185 parser_fail(*pos, "bnot expects 1 arg");
4186 free(name);
4187 return 0;
4188 }
4189 if (!consume_char(src, len, pos, ')')) {
4190 parser_fail(*pos, "bnot expects 1 arg");
4191 free(name);
4192 return 0;
4193 }
4195 free(name);
4196 return 1;
4197 }
4198 if (strcmp(name, "shl") == 0) {
4199 (*pos)++; /* '(' */
4200 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
4201 parser_fail(*pos, "shl expects 2 args");
4202 free(name);
4203 return 0;
4204 }
4205 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
4206 parser_fail(*pos, "shl expects 2 args");
4207 free(name);
4208 return 0;
4209 }
4211 free(name);
4212 return 1;
4213 }
4214 if (strcmp(name, "shr") == 0) {
4215 (*pos)++; /* '(' */
4216 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
4217 parser_fail(*pos, "shr expects 2 args");
4218 free(name);
4219 return 0;
4220 }
4221 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
4222 parser_fail(*pos, "shr expects 2 args");
4223 free(name);
4224 return 0;
4225 }
4227 free(name);
4228 return 1;
4229 }
4230 if (strcmp(name, "rol") == 0) {
4231 (*pos)++; /* '(' */
4232 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
4233 parser_fail(*pos, "rol expects 2 args");
4234 free(name);
4235 return 0;
4236 }
4237 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
4238 parser_fail(*pos, "rol expects 2 args");
4239 free(name);
4240 return 0;
4241 }
4243 free(name);
4244 return 1;
4245 }
4246 if (strcmp(name, "ror") == 0) {
4247 (*pos)++; /* '(' */
4248 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
4249 parser_fail(*pos, "ror expects 2 args");
4250 free(name);
4251 return 0;
4252 }
4253 if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
4254 parser_fail(*pos, "ror expects 2 args");
4255 free(name);
4256 return 0;
4257 }
4259 free(name);
4260 return 1;
4261 }
4262
4263 /* push function value first */
4264 if (local_idx < 0 && name_in_outer_envs(name)) {
4265 const char *fname = (bc && bc->name) ? bc->name : "<function>";
4266 parser_fail(*pos, "Nested function '%s' cannot access outer local '%s'. Pass it as a parameter instead.", fname, name);
4267 free(name);
4268 return 0;
4269 }
4270 if (local_idx >= 0) {
4272 } else {
4273 int gi = sym_index(name);
4275 }
4276 /* Track namespace alias only for the initial receiver; after any call it's no longer an alias value */
4277 int __ns_ctx = is_ns_alias(name);
4278 /* parse arguments */
4279 (*pos)++; /* '(' */
4280 int argc = 0;
4281 skip_spaces(src, len, pos);
4282 if (*pos < len && src[*pos] != ')') {
4283 do {
4284 if (!emit_expression(bc, src, len, pos)) {
4285 parser_fail(*pos, "Expected expression as function argument");
4286 free(name);
4287 return 0;
4288 }
4289 argc++;
4290 skip_spaces(src, len, pos);
4291 } while (*pos < len && src[*pos] == ',' && (++(*pos), skip_spaces(src, len, pos), 1));
4292 }
4293 if (!consume_char(src, len, pos, ')')) {
4294 parser_fail(*pos, "Expected ')' after arguments");
4295 free(name);
4296 return 0;
4297 }
4298#ifdef FUN_DEBUG
4299 /* DEBUG: show compiled call (only when FUN_DEBUG/FUN_TRACE enabled at runtime) */
4300 if (fun_debug_enabled()) {
4301 printf("compile: CALL %s with %d arg(s)\n", name, argc);
4302 }
4303#endif
4305 /* postfix indexing, slice, and dot access/method calls */
4306 for (;;) {
4307 skip_spaces(src, len, pos);
4308
4309 /* index/slice */
4310 if (*pos < len && src[*pos] == '[') {
4311 (*pos)++;
4312 if (!emit_expression(bc, src, len, pos)) {
4313 parser_fail(*pos, "Expected start expression");
4314 free(name);
4315 return 0;
4316 }
4317 skip_spaces(src, len, pos);
4318 if (*pos < len && src[*pos] == ':') {
4319 (*pos)++;
4320 skip_spaces(src, len, pos);
4321 size_t svp = *pos;
4322 if (!emit_expression(bc, src, len, pos)) {
4323 *pos = svp;
4324 int ci = bytecode_add_constant(bc, make_int(-1));
4326 }
4327 if (!consume_char(src, len, pos, ']')) {
4328 parser_fail(*pos, "Expected ']' after slice");
4329 free(name);
4330 return 0;
4331 }
4333 continue;
4334 } else {
4335 if (!consume_char(src, len, pos, ']')) {
4336 parser_fail(*pos, "Expected ']' after index");
4337 free(name);
4338 return 0;
4339 }
4341 continue;
4342 }
4343 }
4344
4345 /* dot property access and method-call sugar: obj.field or obj.method(...) */
4346 if (*pos < len && src[*pos] == '.') {
4347 (*pos)++; /* '.' */
4348 skip_spaces(src, len, pos);
4349 char *mname = NULL;
4350 if (!read_identifier_into(src, len, pos, &mname)) {
4351 parser_fail(*pos, "Expected identifier after '.'");
4352 free(name);
4353 return 0;
4354 }
4355 int is_private = (mname && mname[0] == '_');
4356 int kci = bytecode_add_constant(bc, make_string(mname));
4357
4358 /* Peek for immediate call: obj.method( ... ) */
4359 size_t callp = *pos;
4360 skip_spaces(src, len, &callp);
4361 if (callp < len && src[callp] == '(') {
4362 /* If private method on non-'this' receiver in this context -> error */
4363 if (is_private) {
4364 char msg[160];
4365 snprintf(msg, sizeof(msg), "AccessError: private method '%s' is not accessible here", mname);
4366 int ci = bytecode_add_constant(bc, make_string(msg));
4370 free(mname);
4371 continue;
4372 }
4373
4374 /* After a function call, the receiver is a value (not a namespace alias);
4375 always treat dot-call as a method with implicit 'this'. */
4376 int is_ns = 0;
4377
4378 /* Method sugar with implicit 'this' */
4381 bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* -> stack: obj, func */
4382 bytecode_add_instruction(bc, OP_SWAP, 0); /* -> stack: func, obj (this) */
4383
4384 /* Consume '(' and parse args */
4385 *pos = callp + 1;
4386 int argc = 0;
4387 skip_spaces(src, len, pos);
4388 if (*pos < len && src[*pos] != ')') {
4389 do {
4390 if (!emit_expression(bc, src, len, pos)) {
4391 parser_fail(*pos, "Expected expression as method argument");
4392 free(mname);
4393 free(name);
4394 return 0;
4395 }
4396 argc++;
4397 skip_spaces(src, len, pos);
4398 } while (*pos < len && src[*pos] == ',' && (++(*pos), skip_spaces(src, len, pos), 1));
4399 }
4400 if (!consume_char(src, len, pos, ')')) {
4401 parser_fail(*pos, "Expected ')' after arguments");
4402 free(mname);
4403 free(name);
4404 return 0;
4405 }
4406
4407 /* Call */
4408 bytecode_add_instruction(bc, OP_CALL, is_ns ? argc : (argc + 1));
4409 free(mname);
4410 continue;
4411 } else {
4412 /* Plain property get: obj["field"] */
4415 free(mname);
4416 continue;
4417 }
4418 }
4419
4420 break;
4421 }
4422 free(name);
4423 return 1;
4424 } else {
4425 if (local_idx < 0 && name_in_outer_envs(name)) {
4426 const char *fname = (bc && bc->name) ? bc->name : "<function>";
4427 parser_fail(*pos, "Nested function '%s' cannot access outer local '%s'. Pass it as a parameter instead.", fname, name);
4428 free(name);
4429 return 0;
4430 }
4431 if (local_idx >= 0) {
4433 } else {
4434 int gi = sym_index(name);
4436 }
4437 /* track namespace alias only for the initial receiver; after any call it's no longer an alias value */
4438 int __ns_ctx = is_ns_alias(name);
4439 /* postfix indexing, slice, and dot access/method calls */
4440 for (;;) {
4441 skip_spaces(src, len, pos);
4442
4443 /* index/slice */
4444 if (*pos < len && src[*pos] == '[') {
4445 (*pos)++;
4446 if (!emit_expression(bc, src, len, pos)) {
4447 parser_fail(*pos, "Expected start expression");
4448 free(name);
4449 return 0;
4450 }
4451 skip_spaces(src, len, pos);
4452 if (*pos < len && src[*pos] == ':') {
4453 (*pos)++;
4454 skip_spaces(src, len, pos);
4455 size_t svp = *pos;
4456 if (!emit_expression(bc, src, len, pos)) {
4457 *pos = svp;
4458 int ci = bytecode_add_constant(bc, make_int(-1));
4460 }
4461 if (!consume_char(src, len, pos, ']')) {
4462 parser_fail(*pos, "Expected ']' after slice");
4463 free(name);
4464 return 0;
4465 }
4467 continue;
4468 } else {
4469 if (!consume_char(src, len, pos, ']')) {
4470 parser_fail(*pos, "Expected ']' after index");
4471 free(name);
4472 return 0;
4473 }
4475 continue;
4476 }
4477 }
4478
4479 /* dot property access and method-call sugar */
4480 if (*pos < len && src[*pos] == '.') {
4481 (*pos)++;
4482 skip_spaces(src, len, pos);
4483 char *mname = NULL;
4484 if (!read_identifier_into(src, len, pos, &mname)) {
4485 parser_fail(*pos, "Expected identifier after '.'");
4486 free(name);
4487 return 0;
4488 }
4489 int is_private = (mname && mname[0] == '_');
4490 int kci = bytecode_add_constant(bc, make_string(mname));
4491
4492 /* Peek for call */
4493 size_t callp = *pos;
4494 skip_spaces(src, len, &callp);
4495 if (callp < len && src[callp] == '(') {
4496 /* If private and receiver is not 'this' -> error */
4497 if (is_private && !(strcmp(name, "this") == 0)) {
4498 char msg[160];
4499 snprintf(msg, sizeof(msg), "AccessError: private method '%s' is not accessible", mname);
4500 int ci = bytecode_add_constant(bc, make_string(msg));
4504 free(mname);
4505 continue;
4506 }
4507
4508 /* Use initial alias context for only the first dot-call; reset after call */
4509 int is_ns = __ns_ctx;
4510
4511 if (!is_ns) {
4512 /* Method sugar with implicit 'this' */
4515 bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* -> obj, func */
4516 bytecode_add_instruction(bc, OP_SWAP, 0); /* -> func, obj */
4517 } else {
4518 /* Plain property function call */
4520 bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* -> func */
4521 }
4522
4523 *pos = callp + 1;
4524 int argc = 0;
4525 skip_spaces(src, len, pos);
4526 if (*pos < len && src[*pos] != ')') {
4527 do {
4528 if (!emit_expression(bc, src, len, pos)) {
4529 parser_fail(*pos, "Expected expression as method argument");
4530 free(mname);
4531 free(name);
4532 return 0;
4533 }
4534 argc++;
4535 skip_spaces(src, len, pos);
4536 } while (*pos < len && src[*pos] == ',' && (++(*pos), skip_spaces(src, len, pos), 1));
4537 }
4538 if (!consume_char(src, len, pos, ')')) {
4539 parser_fail(*pos, "Expected ')' after arguments");
4540 free(mname);
4541 free(name);
4542 return 0;
4543 }
4544
4545 bytecode_add_instruction(bc, OP_CALL, is_ns ? argc : (argc + 1));
4546 /* After any call, the receiver is now a value, not a namespace alias */
4547 __ns_ctx = 0;
4548
4549 free(mname);
4550 continue;
4551 } else {
4552 /* plain property get (allowed even for private name) */
4555 free(mname);
4556 continue;
4557 }
4558 }
4559
4560 break;
4561 }
4562 free(name);
4563 return 1;
4564 }
4565 }
4566
4567 return 0;
4568}
4569
4570/**
4571 * @brief Parse and emit unary expressions.
4572 *
4573 * Supports logical not (!) and unary minus (-) with right associativity,
4574 * falling back to primary expressions.
4575 *
4576 * @param bc Target bytecode.
4577 * @param src Source buffer.
4578 * @param len Source length.
4579 * @param pos In/out byte position pointer.
4580 * @return 1 on success, 0 on error.
4581 */
4582static int emit_unary(Bytecode *bc, const char *src, size_t len, size_t *pos) {
4583 skip_spaces(src, len, pos);
4584 if (*pos < len && src[*pos] == '!') {
4585 (*pos)++;
4586 if (!emit_unary(bc, src, len, pos)) {
4587 parser_fail(*pos, "Expected expression after '!'");
4588 return 0;
4589 }
4591 return 1;
4592 }
4593 if (*pos < len && src[*pos] == '-') {
4594 (*pos)++;
4595 /* unary minus -> 0 - expr */
4596 int ci = bytecode_add_constant(bc, make_int(0));
4598 if (!emit_unary(bc, src, len, pos)) {
4599 parser_fail(*pos, "Expected expression after unary '-'");
4600 return 0;
4601 }
4603 return 1;
4604 }
4605 return emit_primary(bc, src, len, pos);
4606}
4607
4608/**
4609 * @brief Parse and emit multiplicative expressions.
4610 *
4611 * Grammar: unary (('*' | '/' | '%') unary)*
4612 *
4613 * @param bc Target bytecode.
4614 * @param src Source buffer.
4615 * @param len Source length.
4616 * @param pos In/out byte position pointer.
4617 * @return 1 on success, 0 on error.
4618 */
4619static int emit_multiplicative(Bytecode *bc, const char *src, size_t len, size_t *pos) {
4620 if (!emit_unary(bc, src, len, pos)) return 0;
4621 for (;;) {
4622 skip_spaces(src, len, pos);
4623
4624 /* Stop expression at start of inline comment */
4625 if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '/') {
4626 break;
4627 }
4628 /* Skip block comments inside expressions */
4629 if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '*') {
4630 size_t p = *pos + 2;
4631 while (p + 1 < len && !(src[p] == '*' && src[p + 1] == '/')) {
4632 p++;
4633 }
4634 if (p + 1 < len) p += 2; /* consume closing marker */
4635 *pos = p;
4636 continue;
4637 }
4638
4639 if (*pos < len && src[*pos] == '*') {
4640 (*pos)++;
4641 if (!emit_unary(bc, src, len, pos)) {
4642 parser_fail(*pos, "Expected expression after '*'");
4643 return 0;
4644 }
4646 continue;
4647 }
4648 if (*pos < len && src[*pos] == '/') {
4649 (*pos)++;
4650 if (!emit_unary(bc, src, len, pos)) {
4651 parser_fail(*pos, "Expected expression after '/'");
4652 return 0;
4653 }
4655 continue;
4656 }
4657 if (*pos < len && src[*pos] == '%') {
4658 (*pos)++;
4659 if (!emit_unary(bc, src, len, pos)) {
4660 parser_fail(*pos, "Expected expression after '%'");
4661 return 0;
4662 }
4664 continue;
4665 }
4666 break;
4667 }
4668 return 1;
4669}
4670
4671/**
4672 * @brief Parse and emit additive expressions.
4673 *
4674 * Grammar: multiplicative (('+' | '-') multiplicative)*
4675 *
4676 * @param bc Target bytecode.
4677 * @param src Source buffer.
4678 * @param len Source length.
4679 * @param pos In/out byte position pointer.
4680 * @return 1 on success, 0 on error.
4681 */
4682static int emit_additive(Bytecode *bc, const char *src, size_t len, size_t *pos) {
4683 if (!emit_multiplicative(bc, src, len, pos)) return 0;
4684 for (;;) {
4685 skip_spaces(src, len, pos);
4686 if (*pos < len && src[*pos] == '+') {
4687 (*pos)++;
4688 if (!emit_multiplicative(bc, src, len, pos)) {
4689 parser_fail(*pos, "Expected expression after '+'");
4690 return 0;
4691 }
4693 continue;
4694 }
4695 if (*pos < len && src[*pos] == '-') {
4696 (*pos)++;
4697 if (!emit_multiplicative(bc, src, len, pos)) {
4698 parser_fail(*pos, "Expected expression after '-'");
4699 return 0;
4700 }
4702 continue;
4703 }
4704 break;
4705 }
4706 return 1;
4707}
4708
4709/**
4710 * @brief Parse and emit relational expressions.
4711 *
4712 * Grammar: additive (('<' | '<=' | '>' | '>=') additive)*
4713 *
4714 * @param bc Target bytecode.
4715 * @param src Source buffer.
4716 * @param len Source length.
4717 * @param pos In/out byte position pointer.
4718 * @return 1 on success, 0 on error.
4719 */
4720static int emit_relational(Bytecode *bc, const char *src, size_t len, size_t *pos) {
4721 if (!emit_additive(bc, src, len, pos)) return 0;
4722 for (;;) {
4723 skip_spaces(src, len, pos);
4724 if (*pos + 1 < len && src[*pos] == '<' && src[*pos + 1] == '=') {
4725 *pos += 2;
4726 if (!emit_additive(bc, src, len, pos)) {
4727 parser_fail(*pos, "Expected expression after '<='");
4728 return 0;
4729 }
4731 continue;
4732 }
4733 if (*pos + 1 < len && src[*pos] == '>' && src[*pos + 1] == '=') {
4734 *pos += 2;
4735 if (!emit_additive(bc, src, len, pos)) {
4736 parser_fail(*pos, "Expected expression after '>='");
4737 return 0;
4738 }
4740 continue;
4741 }
4742 if (*pos < len && src[*pos] == '<') {
4743 (*pos)++;
4744 if (!emit_additive(bc, src, len, pos)) {
4745 parser_fail(*pos, "Expected expression after '<'");
4746 return 0;
4747 }
4749 continue;
4750 }
4751 if (*pos < len && src[*pos] == '>') {
4752 (*pos)++;
4753 if (!emit_additive(bc, src, len, pos)) {
4754 parser_fail(*pos, "Expected expression after '>'");
4755 return 0;
4756 }
4758 continue;
4759 }
4760 break;
4761 }
4762 return 1;
4763}
4764
4765/**
4766 * @brief Parse and emit equality/inequality expressions.
4767 *
4768 * Grammar: relational (('==' | '!=') relational)*
4769 *
4770 * @param bc Target bytecode.
4771 * @param src Source buffer.
4772 * @param len Source length.
4773 * @param pos In/out byte position pointer.
4774 * @return 1 on success, 0 on error.
4775 */
4776static int emit_equality(Bytecode *bc, const char *src, size_t len, size_t *pos) {
4777 if (!emit_relational(bc, src, len, pos)) return 0;
4778 for (;;) {
4779 skip_spaces(src, len, pos);
4780 if (*pos + 1 < len && src[*pos] == '=' && src[*pos + 1] == '=') {
4781 *pos += 2;
4782 if (!emit_relational(bc, src, len, pos)) {
4783 parser_fail(*pos, "Expected expression after '=='");
4784 return 0;
4785 }
4787 continue;
4788 }
4789 if (*pos + 1 < len && src[*pos] == '!' && src[*pos + 1] == '=') {
4790 *pos += 2;
4791 if (!emit_relational(bc, src, len, pos)) {
4792 parser_fail(*pos, "Expected expression after '!='");
4793 return 0;
4794 }
4796 continue;
4797 }
4798 break;
4799 }
4800 return 1;
4801}
4802
4803/**
4804 * @brief Parse and emit logical AND (&&) with short-circuiting.
4805 *
4806 * Evaluates left-to-right, jumping around subsequent operands when a false
4807 * operand is encountered.
4808 *
4809 * @param bc Target bytecode.
4810 * @param src Source buffer.
4811 * @param len Source length.
4812 * @param pos In/out byte position pointer.
4813 * @return 1 on success, 0 on error.
4814 */
4815static int emit_and_expr(Bytecode *bc, const char *src, size_t len, size_t *pos) {
4816 int jf_idxs[64];
4817 int jf_count = 0;
4818 int has_and = 0;
4819
4820 /* first operand */
4821 if (!emit_equality(bc, src, len, pos)) return 0;
4822
4823 for (;;) {
4824 skip_spaces(src, len, pos);
4825 if (!(*pos + 1 < len && src[*pos] == '&' && src[*pos + 1] == '&')) break;
4826 *pos += 2;
4827 has_and = 1;
4828
4829 /* if current value is false -> jump to false label (patched later) */
4830 if (jf_count < (int)(sizeof(jf_idxs) / sizeof(jf_idxs[0]))) {
4831 jf_idxs[jf_count++] = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
4832 } else {
4833 parser_fail(*pos, "Too many operands in '&&' chain");
4834 return 0;
4835 }
4836
4837 /* evaluate next operand */
4838 if (!emit_equality(bc, src, len, pos)) {
4839 parser_fail(*pos, "Expected expression after '&&'");
4840 return 0;
4841 }
4842 }
4843
4844 if (has_and) {
4845 /* final: if last operand is false -> jump false */
4846 if (jf_count < (int)(sizeof(jf_idxs) / sizeof(jf_idxs[0]))) {
4847 jf_idxs[jf_count++] = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
4848 } else {
4849 parser_fail(*pos, "Too many operands in '&&' chain");
4850 return 0;
4851 }
4852
4853 /* all were truthy -> result true */
4854 int c1 = bytecode_add_constant(bc, make_bool(1));
4856 int j_end = bytecode_add_instruction(bc, OP_JUMP, 0);
4857
4858 /* false label: patch all false jumps here, result false */
4859 int l_false = bc->instr_count;
4860 for (int i = 0; i < jf_count; ++i) {
4861 bytecode_set_operand(bc, jf_idxs[i], l_false);
4862 }
4863 int c0 = bytecode_add_constant(bc, make_bool(0));
4865
4866 /* end */
4867 int l_end = bc->instr_count;
4868 bytecode_set_operand(bc, j_end, l_end);
4869 }
4870
4871 return 1;
4872}
4873
4874/**
4875 * @brief Parse and emit logical OR (||) with short-circuiting.
4876 *
4877 * Evaluates left-to-right; when a true operand is encountered, remaining
4878 * operands are skipped and the expression yields true.
4879 *
4880 * @param bc Target bytecode.
4881 * @param src Source buffer.
4882 * @param len Source length.
4883 * @param pos In/out byte position pointer.
4884 * @return 1 on success, 0 on error.
4885 */
4886static int emit_or_expr(Bytecode *bc, const char *src, size_t len, size_t *pos) {
4887 int true_jumps[64];
4888 int tj_count = 0;
4889 int has_or = 0;
4890
4891 /* first operand */
4892 if (!emit_and_expr(bc, src, len, pos)) return 0;
4893
4894 for (;;) {
4895 skip_spaces(src, len, pos);
4896 if (!(*pos + 1 < len && src[*pos] == '|' && src[*pos + 1] == '|')) break;
4897 *pos += 2;
4898 has_or = 1;
4899
4900 /* if current value is false -> proceed to next; else -> result true */
4901 int jf_proceed = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
4902
4903 /* true path: push true and jump to end */
4904 int c1 = bytecode_add_constant(bc, make_bool(1));
4906 if (tj_count < (int)(sizeof(true_jumps) / sizeof(true_jumps[0]))) {
4907 true_jumps[tj_count++] = bytecode_add_instruction(bc, OP_JUMP, 0);
4908 } else {
4909 parser_fail(*pos, "Too many operands in '||' chain");
4910 return 0;
4911 }
4912
4913 /* patch to start of next operand */
4914 bytecode_set_operand(bc, jf_proceed, bc->instr_count);
4915
4916 /* evaluate next operand */
4917 if (!emit_and_expr(bc, src, len, pos)) {
4918 parser_fail(*pos, "Expected expression after '||'");
4919 return 0;
4920 }
4921 }
4922
4923 if (has_or) {
4924 /* After evaluating the last operand: test it and produce 1/0 */
4925 int jf_last = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
4926
4927 int c1 = bytecode_add_constant(bc, make_int(1));
4929 int j_end_single = bytecode_add_instruction(bc, OP_JUMP, 0);
4930
4931 int l_false = bc->instr_count;
4932 bytecode_set_operand(bc, jf_last, l_false);
4933 int c0 = bytecode_add_constant(bc, make_int(0));
4935
4936 int l_end = bc->instr_count;
4937 bytecode_set_operand(bc, j_end_single, l_end);
4938 for (int i = 0; i < tj_count; ++i) {
4939 bytecode_set_operand(bc, true_jumps[i], l_end);
4940 }
4941 }
4942
4943 return 1;
4944}
4945
4946/**
4947 * @brief Parse and emit the ternary conditional operator.
4948 *
4949 * Grammar (right-associative): logical_or ('?' conditional ':' conditional)?
4950 *
4951 * @param bc Target bytecode.
4952 * @param src Source buffer.
4953 * @param len Source length.
4954 * @param pos In/out byte position pointer.
4955 * @return 1 on success, 0 on error.
4956 */
4957static int emit_conditional(Bytecode *bc, const char *src, size_t len, size_t *pos) {
4958 /* parse condition (logical OR precedence or higher) */
4959 if (!emit_or_expr(bc, src, len, pos)) return 0;
4960
4961 for (;;) {
4962 skip_spaces(src, len, pos);
4963 if (!(*pos < len && src[*pos] == '?')) break;
4964 (*pos)++; /* consume '?' */
4965
4966 /* If condition is false -> jump to false arm */
4967 int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
4968
4969 /* true arm (right-assoc: allow nested ternaries) */
4970 skip_spaces(src, len, pos);
4971 if (!emit_conditional(bc, src, len, pos)) {
4972 parser_fail(*pos, "Expected expression after '?'");
4973 return 0;
4974 }
4975
4976 /* After true arm, unconditionally skip false arm */
4977 int jmp_end = bytecode_add_instruction(bc, OP_JUMP, 0);
4978
4979 /* false arm label */
4980 bytecode_set_operand(bc, jmp_false, bc->instr_count);
4981
4982 /* require ':' */
4983 skip_spaces(src, len, pos);
4984 if (!(*pos < len && src[*pos] == ':')) {
4985 parser_fail(*pos, "Expected ':' in conditional expression");
4986 return 0;
4987 }
4988 (*pos)++; /* consume ':' */
4989
4990 /* false arm (right-assoc) */
4991 skip_spaces(src, len, pos);
4992 if (!emit_conditional(bc, src, len, pos)) {
4993 parser_fail(*pos, "Expected expression after ':'");
4994 return 0;
4995 }
4996
4997 /* end label */
4998 bytecode_set_operand(bc, jmp_end, bc->instr_count);
4999 /* loop to allow chaining like a ? b : c ? d : e (right-assoc) */
5000 }
5001 return 1;
5002}
5003
5004/* top-level expression */
5005/**
5006 * @brief Parse and emit a full expression using precedence climbing.
5007 *
5008 * Delegates to emit_conditional which handles the highest-level precedence
5009 * (ternary). This serves as the common entry for expression parsing at
5010 * various grammar positions.
5011 *
5012 * @param bc Target bytecode.
5013 * @param src Source buffer.
5014 * @param len Source length in bytes.
5015 * @param pos In/out byte position pointer.
5016 * @return 1 on success, 0 on parse error.
5017 */
5018static int emit_expression(Bytecode *bc, const char *src, size_t len, size_t *pos) {
5019 return emit_conditional(bc, src, len, pos);
5020}
5021
5022/*
5023 * Indentation-aware compiler (2 spaces):
5024 * - Optional shebang and a single fun <ident>() { ... } wrapper.
5025 * - Statements supported: print(expr), ident = expr, and if <expr> with an indented block.
5026 * - Literals: strings, integers, booleans; identifiers are globals.
5027 * - Ends with HALT.
5028 */
5029
5030/* line/indent utilities */
5031/**
5032 * @brief Advance position to the end of the current line, validating tail.
5033 *
5034 * Skips spaces and inline/block comments until CR/LF/CRLF or end-of-input.
5035 * Reports an error if trailing non-space/comment characters are found.
5036 *
5037 * @param src Source buffer.
5038 * @param len Source length.
5039 * @param pos In/out byte position pointer; set to first char of next line.
5040 */
5041static void skip_to_eol(const char *src, size_t len, size_t *pos) {
5042 /* Strict mode: only allow trailing spaces and comments until end-of-line. */
5043 size_t p = *pos;
5044
5045 for (;;) {
5046 /* skip spaces */
5047 while (p < len && src[p] == ' ')
5048 p++;
5049
5050 if (p >= len) {
5051 *pos = p;
5052 return;
5053 }
5054 /* Treat CR, LF, and CRLF as end-of-line */
5055 if (src[p] == '\r') {
5056 p++;
5057 if (p < len && src[p] == '\n') p++;
5058 *pos = p;
5059 return;
5060 }
5061 if (src[p] == '\n') {
5062 *pos = p + 1;
5063 return;
5064 }
5065
5066 /* line or block comments allowed */
5067 if (p + 1 < len && src[p] == '/' && src[p + 1] == '/') {
5068 /* consume rest of line up to CR or LF */
5069 p += 2;
5070 while (p < len && src[p] != '\n' && src[p] != '\r')
5071 p++;
5072 if (p < len && src[p] == '\r') {
5073 p++;
5074 if (p < len && src[p] == '\n') p++;
5075 } else if (p < len && src[p] == '\n') {
5076 p++;
5077 }
5078 *pos = p;
5079 return;
5080 }
5081
5082 if (p + 1 < len && src[p] == '/' && src[p + 1] == '*') {
5083 /* consume block comment, then loop again for spaces till EOL */
5084 p += 2;
5085 while (p + 1 < len && !(src[p] == '*' && src[p + 1] == '/')) {
5086 p++;
5087 }
5088 if (p + 1 < len) {
5089 p += 2; /* consume closing */
5090 continue;
5091 } else {
5092 parser_fail(p, "Unterminated block comment at end of file");
5093 *pos = p;
5094 return;
5095 }
5096 }
5097
5098 /* Any other character here is unexpected trailing garbage */
5099 parser_fail(p, "Unexpected trailing characters at end of line");
5100 *pos = p;
5101 return;
5102 }
5103}
5104
5105/**
5106 * @brief Read the start of a logical line and compute indentation.
5107 *
5108 * Consumes blank lines and comment-only lines. Tabs are forbidden for
5109 * indentation; only spaces are allowed, counted in units of 2.
5110 *
5111 * @param src Source buffer.
5112 * @param len Source length.
5113 * @param pos In/out byte position pointer; advanced to first non-space.
5114 * @param out_indent Receives the number of leading spaces on the line.
5115 * @return 1 if a non-empty logical line was found, 0 if end reached.
5116 */
5117/**
5118 * @brief Read the start of a logical line and compute indentation.
5119 *
5120 * Consumes blank lines and comment-only lines. Tabs are forbidden for
5121 * indentation; only spaces are allowed, counted in units of 2.
5122 *
5123 * @param src Source buffer.
5124 * @param len Source length.
5125 * @param pos In/out byte position pointer; advanced to first non-space.
5126 * @param out_indent Receives the number of leading spaces on the line.
5127 * @return 1 if a non-empty logical line was found, 0 if end reached.
5128 */
5129static int read_line_start(const char *src, size_t len, size_t *pos, int *out_indent) {
5130 while (*pos < len) {
5131 size_t p = *pos;
5132 int spaces = 0;
5133 while (p < len && src[p] == ' ') {
5134 spaces++;
5135 p++;
5136 }
5137 if (p < len && src[p] == '\t') {
5138 parser_fail(p, "Tabs are forbidden for indentation");
5139 return 0;
5140 }
5141 if (p >= len) {
5142 *pos = p;
5143 return 0;
5144 }
5145
5146 /* empty line: handle CR, LF, and CRLF */
5147 if (src[p] == '\r') {
5148 p++;
5149 if (p < len && src[p] == '\n') p++;
5150 *pos = p;
5151 continue;
5152 }
5153 if (src[p] == '\n') {
5154 p++;
5155 *pos = p;
5156 continue;
5157 }
5158
5159 /* // comment-only line */
5160 if (p + 1 < len && src[p] == '/' && src[p + 1] == '/') {
5161 /* skip entire line up to CR/LF */
5162 p += 2;
5163 while (p < len && src[p] != '\n' && src[p] != '\r')
5164 p++;
5165 if (p < len && src[p] == '\r') {
5166 p++;
5167 if (p < len && src[p] == '\n') p++;
5168 } else if (p < len && src[p] == '\n') {
5169 p++;
5170 }
5171 *pos = p;
5172 continue;
5173 }
5174
5175 // block comment starting at line (treat as comment-only line)
5176 if (p + 1 < len && src[p] == '/' && src[p + 1] == '*') {
5177 p += 2;
5178 /* advance until we find closing block comment marker */
5179 while (p + 1 < len && !(src[p] == '*' && src[p + 1] == '/')) {
5180 p++;
5181 }
5182 if (p + 1 < len) p += 2; /* consume closing block comment marker */
5183 /* consume to end of current line (if any leftover) */
5184 while (p < len && src[p] != '\n' && src[p] != '\r')
5185 p++;
5186 if (p < len && src[p] == '\r') {
5187 p++;
5188 if (p < len && src[p] == '\n') p++;
5189 } else if (p < len && src[p] == '\n') {
5190 p++;
5191 }
5192 *pos = p;
5193 continue;
5194 }
5195
5196 if (spaces % 2 != 0) {
5197 parser_fail(p, "Indentation must be multiples of two spaces");
5198 return 0;
5199 }
5200 *out_indent = spaces / 2;
5201 *pos = p; /* point to first code char */
5202 return 1;
5203 }
5204 return 0;
5205}
5206
5207/* forward decl */
5208static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, int current_indent);
5209
5210/* parse and emit a single simple (non-if) statement on the current line */
5211/**
5212 * @brief Parse a single statement on the current line and emit bytecode.
5213 *
5214 * Handles assignments, function calls, control flow starters, print/debug
5215 * statements and other simple constructs that fit on one logical line.
5216 *
5217 * @param bc Target bytecode.
5218 * @param src Source buffer.
5219 * @param len Source length.
5220 * @param pos In/out byte position pointer at start of the statement line.
5221 */
5222static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, size_t *pos) {
5223 size_t local_pos = *pos;
5224 char *name = NULL;
5225 if (read_identifier_into(src, len, &local_pos, &name)) {
5226
5227 /* alias: accept 'sint*' as synonyms for 'int*' */
5228 if (strcmp(name, "sint8") == 0) {
5229 free(name);
5230 name = strdup("int8");
5231 } else if (strcmp(name, "sint16") == 0) {
5232 free(name);
5233 name = strdup("int16");
5234 } else if (strcmp(name, "sint32") == 0) {
5235 free(name);
5236 name = strdup("int32");
5237 } else if (strcmp(name, "sint64") == 0) {
5238 free(name);
5239 name = strdup("int64");
5240 }
5241
5242 /* return statement */
5243 if (strcmp(name, "return") == 0) {
5244 free(name);
5245 skip_spaces(src, len, &local_pos);
5246 /* optional expression */
5247 size_t save_pos = local_pos;
5248 if (emit_expression(bc, src, len, &local_pos)) {
5249 /* expression result already on stack */
5250 } else {
5251 /* no expression: return nil */
5252 local_pos = save_pos;
5253 int ci = bytecode_add_constant(bc, make_nil());
5255 }
5257 *pos = local_pos;
5258 skip_to_eol(src, len, pos);
5259 return;
5260 }
5261
5262 /* exit statement: exit [expr]? */
5263 if (strcmp(name, "exit") == 0) {
5264 free(name);
5265 skip_spaces(src, len, &local_pos);
5266 size_t save_pos = local_pos;
5267 if (emit_expression(bc, src, len, &local_pos)) {
5268 /* expression result already on stack */
5269 } else {
5270 /* default exit code 0 */
5271 local_pos = save_pos;
5272 int ci = bytecode_add_constant(bc, make_int(0));
5274 }
5276 *pos = local_pos;
5277 skip_to_eol(src, len, pos);
5278 return;
5279 }
5280
5281 /* break / continue */
5282 if (strcmp(name, "break") == 0) {
5283 free(name);
5284 if (!g_loop_ctx) {
5285 parser_fail(local_pos, "break used outside of loop");
5286 return;
5287 }
5288 int j = bytecode_add_instruction(bc, OP_JUMP, 0);
5289 if (g_loop_ctx->break_count < (int)(sizeof(g_loop_ctx->break_jumps) / sizeof(g_loop_ctx->break_jumps[0]))) {
5290 g_loop_ctx->break_jumps[g_loop_ctx->break_count++] = j;
5291 } else {
5292 parser_fail(local_pos, "Too many 'break' in one loop");
5293 return;
5294 }
5295 *pos = local_pos;
5296 skip_to_eol(src, len, pos);
5297 return;
5298 }
5299 if (strcmp(name, "continue") == 0) {
5300 free(name);
5301 if (!g_loop_ctx) {
5302 parser_fail(local_pos, "continue used outside of loop");
5303 return;
5304 }
5305 int j = bytecode_add_instruction(bc, OP_JUMP, 0);
5306 if (g_loop_ctx->cont_count < (int)(sizeof(g_loop_ctx->continue_jumps) / sizeof(g_loop_ctx->continue_jumps[0]))) {
5307 g_loop_ctx->continue_jumps[g_loop_ctx->cont_count++] = j;
5308 } else {
5309 parser_fail(local_pos, "Too many 'continue' in one loop");
5310 return;
5311 }
5312 *pos = local_pos;
5313 skip_to_eol(src, len, pos);
5314 return;
5315 }
5316
5317 /* typed declarations:
5318 number|string|boolean|nil|Class|byte|uint8|uint16|uint32|uint64|int8|int16|int32|int64 <ident> (= expr)?
5319 Note: 'number' maps to signed 64-bit here. 'byte' is an alias of unsigned 8-bit. 'Class' restricts to class instances.
5320 */
5321 if (strcmp(name, "number") == 0 || strcmp(name, "string") == 0 || strcmp(name, "boolean") == 0 || strcmp(name, "nil") == 0 || strcmp(name, "class") == 0 || strcmp(name, "float") == 0 || strcmp(name, "array") == 0 || strcmp(name, "byte") == 0 || strcmp(name, "uint8") == 0 || strcmp(name, "uint16") == 0 || strcmp(name, "uint32") == 0 || strcmp(name, "uint64") == 0 || strcmp(name, "int8") == 0 || strcmp(name, "int16") == 0 || strcmp(name, "int32") == 0 || strcmp(name, "int64") == 0) {
5322 int is_number = (strcmp(name, "number") == 0);
5323 int is_string = (strcmp(name, "string") == 0);
5324 int is_boolean = (strcmp(name, "boolean") == 0);
5325 int is_nil = (strcmp(name, "nil") == 0);
5326 int is_class = (strcmp(name, "class") == 0);
5327 int is_float = (strcmp(name, "float") == 0);
5328 int is_array = (strcmp(name, "array") == 0);
5329 int is_byte = (strcmp(name, "byte") == 0);
5330 int is_u8 = (strcmp(name, "uint8") == 0) || is_byte;
5331 int is_u16 = (strcmp(name, "uint16") == 0);
5332 int is_u32 = (strcmp(name, "uint32") == 0);
5333 int is_u64 = (strcmp(name, "uint64") == 0);
5334 int is_s8 = (strcmp(name, "int8") == 0);
5335 int is_s16 = (strcmp(name, "int16") == 0);
5336 int is_s32 = (strcmp(name, "int32") == 0);
5337 int is_s64 = (strcmp(name, "int64") == 0) || is_number; /* number maps to int64 (signed) */
5338 int decl_bits = is_u8 ? 8 : is_u16 ? 16
5339 : is_u32 ? 32
5340 : is_u64 ? 64
5341 : is_s8 ? 8
5342 : is_s16 ? 16
5343 : is_s32 ? 32
5344 : is_s64 ? 64
5345 : 0;
5346 int decl_signed = (is_s8 || is_s16 || is_s32 || is_s64) ? 1 : 0;
5347 /* store decl bits with sign encoded: negative means signed (number is signed 64-bit) */
5348 if (decl_signed) decl_bits = -decl_bits;
5349
5350 /* declared type metadata: integers use decl_bits; string/boolean/nil/Class/float/array use special markers */
5351 int decl_meta = decl_bits;
5352 if (is_string) {
5353 decl_meta = TYPE_META_STRING;
5354 } else if (is_boolean) {
5355 decl_meta = TYPE_META_BOOLEAN;
5356 } else if (is_nil) {
5357 decl_meta = TYPE_META_NIL;
5358 } else if (is_class) {
5359 decl_meta = TYPE_META_CLASS;
5360 } else if (is_float) {
5361 decl_meta = TYPE_META_FLOAT;
5362 } else if (is_array) {
5363 decl_meta = TYPE_META_ARRAY;
5364 }
5365
5366 free(name);
5367
5368 /* read variable name */
5369 char *varname = NULL;
5370 skip_spaces(src, len, &local_pos);
5371 if (!read_identifier_into(src, len, &local_pos, &varname)) {
5372 parser_fail(local_pos, "Expected identifier after type declaration");
5373 return;
5374 }
5375
5376 /* decide local vs global */
5377 int lidx = -1;
5378 int gi = -1;
5379 if (g_locals) {
5380 int existing = local_find(varname);
5381 if (existing >= 0)
5382 lidx = existing;
5383 else
5384 lidx = local_add(varname);
5385 if (lidx >= 0) {
5386 g_locals->types[lidx] = decl_meta; /* encoding: ±bits for integers; TYPE_META_* for non-integer enforced types; 0 = dynamic */
5387 }
5388 } else {
5389 gi = sym_index(varname);
5390 if (gi >= 0) {
5391 G.types[gi] = decl_meta;
5392 }
5393 }
5394 free(varname);
5395
5396 skip_spaces(src, len, &local_pos);
5397 if (local_pos < len && src[local_pos] == '=') {
5398 local_pos++; /* '=' */
5399 if (!emit_expression(bc, src, len, &local_pos)) {
5400 parser_fail(local_pos, "Expected initializer expression after '='");
5401 return;
5402 }
5403
5404 /* Enforce declared type on initializer */
5405 if (decl_meta == TYPE_META_STRING) {
5406 /* expect String */
5409 int ciExp = bytecode_add_constant(bc, make_string("String"));
5412 int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
5413 int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
5414 /* error block */
5415 bytecode_set_operand(bc, j_to_error, bc->instr_count);
5416 {
5417 int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected String"));
5421 }
5422 bytecode_set_operand(bc, j_skip_err, bc->instr_count);
5423 } else if (decl_meta == TYPE_META_CLASS) {
5424 /* expect Class instance: Map with "__class" key */
5425 /* Check typeof(v) == "Map" */
5428 {
5429 int ciMap = bytecode_add_constant(bc, make_string("Map"));
5431 }
5433 int j_err1 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
5434 /* Check v has "__class" */
5436 {
5437 int kci = bytecode_add_constant(bc, make_string("__class"));
5439 }
5441 int j_err2 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
5442 /* Success path jumps over error block */
5443 int j_ok = bytecode_add_instruction(bc, OP_JUMP, 0);
5444 /* Error block */
5445 int err_lbl = bc->instr_count;
5446 bytecode_set_operand(bc, j_err1, err_lbl);
5447 bytecode_set_operand(bc, j_err2, err_lbl);
5448 {
5449 int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Class"));
5453 }
5454 /* Continue after checks */
5455 bytecode_set_operand(bc, j_ok, bc->instr_count);
5456 } else if (decl_meta == TYPE_META_FLOAT) {
5457 /* expect Float */
5460 int ciF = bytecode_add_constant(bc, make_string("Float"));
5463 int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
5464 int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
5465 bytecode_set_operand(bc, j_to_error, bc->instr_count);
5466 {
5467 int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Float"));
5471 }
5472 bytecode_set_operand(bc, j_skip_err, bc->instr_count);
5473 } else if (decl_meta == TYPE_META_ARRAY) {
5474 /* expect Array */
5477 int ciArr = bytecode_add_constant(bc, make_string("Array"));
5480 int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
5481 int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
5482 bytecode_set_operand(bc, j_to_error, bc->instr_count);
5483 {
5484 int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Array"));
5488 }
5489 bytecode_set_operand(bc, j_skip_err, bc->instr_count);
5490 } else if (decl_meta == TYPE_META_BOOLEAN) {
5491 /* accept Boolean literal or Number; if Number, clamp to 0/1 */
5492 /* check if value is Boolean */
5495 int ciBool = bytecode_add_constant(bc, make_string("Boolean"));
5498 int j_not_bool = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
5499 /* it is Boolean -> OK, skip number check */
5500 int j_done = bytecode_add_instruction(bc, OP_JUMP, 0);
5501 /* not Boolean: check Number */
5502 bytecode_set_operand(bc, j_not_bool, bc->instr_count);
5505 int ciNum = bytecode_add_constant(bc, make_string("Number"));
5508 int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
5509 int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
5510 bytecode_set_operand(bc, j_to_error, bc->instr_count);
5511 {
5512 int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Boolean or Number for boolean"));
5516 }
5517 bytecode_set_operand(bc, j_skip_err, bc->instr_count);
5518 /* if Number, clamp to 0/1 */
5520 /* common continuation */
5521 bytecode_set_operand(bc, j_done, bc->instr_count);
5522 } else if (decl_meta == TYPE_META_NIL) {
5523 /* expect Nil */
5526 int ciNil = bytecode_add_constant(bc, make_string("Nil"));
5529 int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
5530 int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
5531 bytecode_set_operand(bc, j_to_error, bc->instr_count);
5532 {
5533 int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Nil"));
5537 }
5538 bytecode_set_operand(bc, j_skip_err, bc->instr_count);
5539 } else {
5540 /* integer widths: expect Number then range-check */
5541 int abs_bits = decl_bits < 0 ? -decl_bits : decl_bits;
5542 if (abs_bits > 0) {
5543 /* typeof == Number */
5546 int ciNum = bytecode_add_constant(bc, make_string("Number"));
5549 int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
5550 int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
5551 bytecode_set_operand(bc, j_to_error, bc->instr_count);
5552 {
5553 int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Number"));
5557 }
5558 bytecode_set_operand(bc, j_skip_err, bc->instr_count);
5559
5560 if (abs_bits > 0) {
5561 bytecode_add_instruction(bc, (decl_bits < 0) ? OP_SCLAMP : OP_UCLAMP, abs_bits);
5562 }
5563 }
5564 }
5565
5566 if (lidx >= 0) {
5568 } else {
5570 }
5571 } else {
5572 /* default initialize if no '=' given */
5573 int ci = -1;
5574 if (is_string) {
5575 ci = bytecode_add_constant(bc, make_string(""));
5576 } else if (is_nil) {
5577 ci = bytecode_add_constant(bc, make_nil());
5578 } else if (is_class) {
5579 /* Class-typed variable defaults to Nil until assigned an instance */
5580 ci = bytecode_add_constant(bc, make_nil());
5581 } else if (is_boolean) {
5582 /* booleans default to false */
5583 ci = bytecode_add_constant(bc, make_bool(0));
5584 } else if (is_number || (decl_bits != 0)) {
5585 /* integers default to 0 */
5586 ci = bytecode_add_constant(bc, make_int(0));
5587 }
5588 if (ci >= 0) {
5590 int abs_bits2 = decl_bits < 0 ? -decl_bits : decl_bits;
5591 if (abs_bits2 > 0) {
5592 bytecode_add_instruction(bc, (decl_bits < 0) ? OP_SCLAMP : OP_UCLAMP, abs_bits2);
5593 }
5594 if (lidx >= 0) {
5596 } else {
5598 }
5599 }
5600 }
5601 *pos = local_pos;
5602 skip_to_eol(src, len, pos);
5603 return;
5604 }
5605
5606 if (strcmp(name, "print") == 0) {
5607 free(name);
5608 skip_spaces(src, len, &local_pos);
5609 (void)consume_char(src, len, &local_pos, '(');
5610 if (emit_expression(bc, src, len, &local_pos)) {
5611 (void)consume_char(src, len, &local_pos, ')');
5613 } else {
5614 (void)consume_char(src, len, &local_pos, ')');
5615 }
5616 *pos = local_pos;
5617 skip_to_eol(src, len, pos);
5618 return;
5619 }
5620
5621 if (strcmp(name, "echo") == 0) {
5622 free(name);
5623 skip_spaces(src, len, &local_pos);
5624 (void)consume_char(src, len, &local_pos, '(');
5625 if (emit_expression(bc, src, len, &local_pos)) {
5626 (void)consume_char(src, len, &local_pos, ')');
5628 } else {
5629 (void)consume_char(src, len, &local_pos, ')');
5630 }
5631 *pos = local_pos;
5632 skip_to_eol(src, len, pos);
5633 return;
5634 }
5635
5636 /* assignment or simple call */
5637 int lidx = local_find(name);
5638 int gi = (lidx < 0) ? sym_find(name) : -1;
5639 if (lidx < 0 && gi < 0 && g_locals) {
5640 /* Auto-declare as local if we're in a function and it's not known as local or global */
5641 lidx = local_add(name);
5642 }
5643 if (lidx < 0 && gi < 0) {
5644 /* Not local, not existing global -> it's a new global (or a new local if we were in a function but lidx still < 0?)
5645 Actually if g_locals was non-null we already added it to lidx.
5646 If g_locals is NULL, we create it as global. */
5647 gi = sym_index(name);
5648 }
5649 skip_spaces(src, len, &local_pos);
5650
5651 /* object field assignment: supports
5652 - name.field = expr
5653 - name.field[expr] = expr
5654 - name.field[expr1][expr2] = expr
5655 If pattern doesn't match an assignment, fall back to expression stmt (e.g., method call).
5656 */
5657 if (local_pos < len && src[local_pos] == '.') {
5658 size_t stmt_start = *pos; /* for expression fallback */
5659 size_t look = local_pos + 1; /* point after '.' */
5660 skip_spaces(src, len, &look);
5661 char *fname = NULL;
5662 if (!read_identifier_into(src, len, &look, &fname)) {
5663 parser_fail(look, "Expected field name after '.'");
5664 free(name);
5665 return;
5666 }
5667 skip_spaces(src, len, &look);
5668 if (look < len && src[look] == '[') {
5669 /* Handle name.field[...][...] = value */
5670 /* Load base container and resolve field: base[field] -> inner container on stack */
5671 if (lidx >= 0) {
5673 } else {
5675 }
5676 int fci = bytecode_add_constant(bc, make_string(fname));
5677 free(fname);
5680
5681 /* Now parse one or more [expr] */
5682 for (;;) {
5683 if (!(look < len && src[look] == '[')) break;
5684 look++; /* consume '[' */
5685 if (!emit_expression(bc, src, len, &look)) {
5686 parser_fail(look, "Expected index expression after '['");
5687 free(name);
5688 return;
5689 }
5690 if (!consume_char(src, len, &look, ']')) {
5691 parser_fail(look, "Expected ']' after index");
5692 free(name);
5693 return;
5694 }
5695 skip_spaces(src, len, &look);
5696 if (look < len && src[look] == '[') {
5697 /* Need to dereference one level: inner = inner[index] */
5699 continue;
5700 }
5701 break;
5702 }
5703 /* Expect '=' to assign into the last container with last index on stack */
5704 if (look >= len || src[look] != '=') {
5705 /* Not an assignment: fallback to expression statement */
5706 free(name);
5707 size_t expr_pos = stmt_start;
5708 if (emit_expression(bc, src, len, &expr_pos)) {
5710 }
5711 *pos = expr_pos;
5712 skip_to_eol(src, len, pos);
5713 return;
5714 }
5715 look++; /* skip '=' */
5716 if (!emit_expression(bc, src, len, &look)) {
5717 parser_fail(look, "Expected expression after '='");
5718 free(name);
5719 return;
5720 }
5722 free(name);
5723 *pos = look;
5724 skip_to_eol(src, len, pos);
5725 return;
5726 } else if (look < len && src[look] == '=') {
5727 /* Simple name.field = value
5728 * Previous implementation pushed (container, key) first and then evaluated value.
5729 * That relies on CALL preserving underlying stack entries. To be robust,
5730 * we first evaluate the value into a temporary local, then push (container, key),
5731 * then load the temporary and perform INDEX_SET.
5732 */
5733
5734 /* Advance to after '=' and parse value into a temporary local */
5735 local_pos = look + 1;
5736 if (!emit_expression(bc, src, len, &local_pos)) {
5737 parser_fail(local_pos, "Expected expression after '='");
5738 free(fname);
5739 free(name);
5740 return;
5741 }
5742 /* store value to a hidden temp local */
5743 int tmp_local = local_add("__assign_tmp");
5745
5746 /* Load container variable */
5747 if (lidx >= 0) {
5749 } else {
5751 }
5752 /* Push key */
5753 int kci = bytecode_add_constant(bc, make_string(fname));
5754 free(fname);
5756
5757 /* Load value from temp and set */
5760
5761 free(name);
5762 *pos = local_pos;
5763 skip_to_eol(src, len, pos);
5764 return;
5765 } else {
5766 /* Not an assignment: treat as expression statement (e.g., obj.method(...)) */
5767 free(fname);
5768 free(name);
5769 size_t expr_pos = stmt_start;
5770 if (emit_expression(bc, src, len, &expr_pos)) {
5772 }
5773 *pos = expr_pos;
5774 skip_to_eol(src, len, pos);
5775 return;
5776 }
5777 }
5778
5779 /* array element assignment: name[expr] = expr and nested: name[expr1][expr2] = expr */
5780 if (local_pos < len && src[local_pos] == '[') {
5781 /* load array/map variable */
5782 if (lidx >= 0) {
5784 } else {
5786 }
5787 local_pos++; /* '[' */
5788 if (!emit_expression(bc, src, len, &local_pos)) {
5789 parser_fail(local_pos, "Expected index expression after '['");
5790 free(name);
5791 return;
5792 }
5793 if (!consume_char(src, len, &local_pos, ']')) {
5794 parser_fail(local_pos, "Expected ']' after index");
5795 free(name);
5796 return;
5797 }
5798 skip_spaces(src, len, &local_pos);
5799
5800 /* Nested index: name[expr1][expr2] = value */
5801 if (local_pos < len && src[local_pos] == '[') {
5802 /* Reduce base: stack currently has container, index1 -> get inner container */
5804
5805 local_pos++; /* second '[' */
5806 if (!emit_expression(bc, src, len, &local_pos)) {
5807 parser_fail(local_pos, "Expected nested index expression after '['");
5808 free(name);
5809 return;
5810 }
5811 if (!consume_char(src, len, &local_pos, ']')) {
5812 parser_fail(local_pos, "Expected ']' after nested index");
5813 free(name);
5814 return;
5815 }
5816 skip_spaces(src, len, &local_pos);
5817 if (local_pos >= len || src[local_pos] != '=') {
5818 parser_fail(local_pos, "Expected '=' after nested array index");
5819 free(name);
5820 return;
5821 }
5822 local_pos++; /* '=' */
5823 if (!emit_expression(bc, src, len, &local_pos)) {
5824 parser_fail(local_pos, "Expected expression after '='");
5825 free(name);
5826 return;
5827 }
5828 /* perform set into inner container */
5830 free(name);
5831 *pos = local_pos;
5832 skip_to_eol(src, len, pos);
5833 return;
5834 }
5835
5836 /* Single-level: name[expr] = value */
5837 if (local_pos >= len || src[local_pos] != '=') {
5838 parser_fail(local_pos, "Expected '=' after array index");
5839 free(name);
5840 return;
5841 }
5842 local_pos++; /* '=' */
5843 if (!emit_expression(bc, src, len, &local_pos)) {
5844 parser_fail(local_pos, "Expected expression after '='");
5845 free(name);
5846 return;
5847 }
5848 /* perform set */
5850 free(name);
5851 *pos = local_pos;
5852 skip_to_eol(src, len, pos);
5853 return;
5854 }
5855
5856 free(name);
5857 if (local_pos < len && src[local_pos] == '=') {
5858 local_pos++; /* '=' */
5859 if (emit_expression(bc, src, len, &local_pos)) {
5860 /* enforce declared type if present (0 = dynamic) */
5861 int meta = 0;
5862 if (lidx >= 0 && g_locals) {
5863 meta = g_locals->types[lidx];
5864 } else if (gi >= 0) {
5865 meta = G.types[gi];
5866 }
5867
5868 if (meta == TYPE_META_STRING) {
5869 /* expect String */
5872 int ciStr = bytecode_add_constant(bc, make_string("String"));
5875 int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
5876 int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
5877 bytecode_set_operand(bc, j_to_error, bc->instr_count);
5878 {
5879 int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected String"));
5883 }
5884 bytecode_set_operand(bc, j_skip_err, bc->instr_count);
5885 } else if (meta == TYPE_META_CLASS) {
5886 /* expect Class instance: Map with "__class" key */
5887 /* Check typeof(v) == "Map" */
5890 {
5891 int ciMap = bytecode_add_constant(bc, make_string("Map"));
5893 }
5895 int j_err1 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
5896 /* Check v has "__class" */
5898 {
5899 int kci = bytecode_add_constant(bc, make_string("__class"));
5901 }
5903 int j_err2 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
5904 /* Success path jumps over error block */
5905 int j_ok = bytecode_add_instruction(bc, OP_JUMP, 0);
5906 /* Error block */
5907 int err_lbl = bc->instr_count;
5908 bytecode_set_operand(bc, j_err1, err_lbl);
5909 bytecode_set_operand(bc, j_err2, err_lbl);
5910 {
5911 int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Class"));
5915 }
5916 /* Continue after checks */
5917 bytecode_set_operand(bc, j_ok, bc->instr_count);
5918 } else if (meta == TYPE_META_FLOAT) {
5919 /* expect Float */
5922 int ciF = bytecode_add_constant(bc, make_string("Float"));
5925 int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
5926 int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
5927 bytecode_set_operand(bc, j_to_error, bc->instr_count);
5928 {
5929 int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Float"));
5933 }
5934 bytecode_set_operand(bc, j_skip_err, bc->instr_count);
5935 } else if (meta == TYPE_META_ARRAY) {
5936 /* expect Array */
5939 int ciArr = bytecode_add_constant(bc, make_string("Array"));
5942 int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
5943 int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
5944 bytecode_set_operand(bc, j_to_error, bc->instr_count);
5945 {
5946 int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Array"));
5950 }
5951 bytecode_set_operand(bc, j_skip_err, bc->instr_count);
5952 } else if (meta == TYPE_META_BOOLEAN) {
5953 /* expect Number then clamp to 1 bit (unsigned) */
5956 int ciNum = bytecode_add_constant(bc, make_string("Number"));
5959 int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
5960 int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
5961 bytecode_set_operand(bc, j_to_error, bc->instr_count);
5962 {
5963 int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Number for boolean"));
5967 }
5968 bytecode_set_operand(bc, j_skip_err, bc->instr_count);
5970 } else if (meta == TYPE_META_NIL) {
5971 /* expect Nil */
5974 int ciNil = bytecode_add_constant(bc, make_string("Nil"));
5977 int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
5978 int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
5979 bytecode_set_operand(bc, j_to_error, bc->instr_count);
5980 {
5981 int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Nil"));
5985 }
5986 bytecode_set_operand(bc, j_skip_err, bc->instr_count);
5987 } else if (meta != 0) {
5988 /* integer widths: expect Number then range-check to declared width */
5989 int abs_bits = meta < 0 ? -meta : meta;
5990 /* typeof == Number */
5993 int ciNum = bytecode_add_constant(bc, make_string("Number"));
5996 int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
5997 int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
5998 bytecode_set_operand(bc, j_to_error, bc->instr_count);
5999 {
6000 int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Number"));
6004 }
6005 bytecode_set_operand(bc, j_skip_err, bc->instr_count);
6006
6007 if (abs_bits > 0) {
6008 bytecode_add_instruction(bc, (meta < 0) ? OP_SCLAMP : OP_UCLAMP, abs_bits);
6009 }
6010 }
6011 /* dynamic (meta==0): no enforcement */
6012
6013 if (lidx >= 0) {
6015 } else {
6017 }
6018 }
6019 *pos = local_pos;
6020 skip_to_eol(src, len, pos);
6021 return;
6022 } else {
6023 /* Treat as a generic expression-statement (e.g., method calls like obj.method(...),
6024 property access chains, or builtin calls behind wrappers). We rewind to the
6025 start of the statement, emit the full expression, and drop its result. */
6026 local_pos = *pos;
6027 if (emit_expression(bc, src, len, &local_pos)) {
6028 bytecode_add_instruction(bc, OP_POP, 0); /* discard return value of standalone expr */
6029 *pos = local_pos;
6030 skip_to_eol(src, len, pos);
6031 return;
6032 }
6033 /* if it wasn't a valid expression either, report a meaningful error */
6034 parser_fail(local_pos, "Expected assignment '=' or call '(...)' after identifier");
6035 return;
6036 }
6037 }
6038
6039 /* fallback: print(expr) without identifier read (unlikely) */
6040 if (starts_with(src, len, *pos, "print")) {
6041 *pos += 5;
6042 skip_spaces(src, len, pos);
6043 (void)consume_char(src, len, pos, '(');
6044 if (emit_expression(bc, src, len, pos)) {
6045 (void)consume_char(src, len, pos, ')');
6047 } else {
6048 (void)consume_char(src, len, pos, ')');
6049 }
6050 skip_to_eol(src, len, pos);
6051 return;
6052 }
6053
6054 /* echo(expr): like print but does not add a newline (immediate output) */
6055 if (starts_with(src, len, *pos, "echo")) {
6056 *pos += 4;
6057 skip_spaces(src, len, pos);
6058 (void)consume_char(src, len, pos, '(');
6059 if (emit_expression(bc, src, len, pos)) {
6060 (void)consume_char(src, len, pos, ')');
6062 } else {
6063 (void)consume_char(src, len, pos, ')');
6064 }
6065 skip_to_eol(src, len, pos);
6066 return;
6067 }
6068
6069 /* unknown token: report error */
6070 parser_fail(*pos, "Unknown token at start of statement");
6071}
6072
6073/* parse a block with lines at indentation >= current_indent; stop at dedent */
6074/**
6075 * @brief Parse a block of statements at a given indentation level.
6076 *
6077 * Continues parsing lines while their indentation is >= current_indent. On
6078 * dedent, returns control to the caller so upstream constructs (e.g., if/while)
6079 * can close. Inserts OP_LINE markers to improve runtime diagnostics.
6080 *
6081 * @param bc Target bytecode.
6082 * @param src Source buffer.
6083 * @param len Source length.
6084 * @param pos In/out byte position pointer.
6085 * @param current_indent Indentation level (spaces) of the enclosing block.
6086 */
6087static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, int current_indent) {
6088 while (*pos < len) {
6089 if (g_has_error) return;
6090 size_t line_start = *pos; /* remember raw line start for dedent backtracking */
6091 int indent = 0;
6092 if (!read_line_start(src, len, pos, &indent)) {
6093 /* EOF or error */
6094 return;
6095 }
6096 if (indent < current_indent) {
6097 /* dedent -> let caller handle this line */
6098 *pos = line_start;
6099 return;
6100 }
6101 if (indent > current_indent) {
6102 /* nested block without a header (tolerate by parsing it and continuing) */
6103 parse_block(bc, src, len, pos, indent);
6104 continue;
6105 }
6106
6107 /* at same indent -> parse statement */
6108 /* Insert a line marker for better runtime error reporting. Use the current
6109 * position (start of the actual statement) returned by read_line_start(),
6110 * not the pre-scan position which may point to a comment/blank line. */
6111 {
6112 int stmt_line = 1, stmt_col = 1;
6113 calc_line_col(src, len, *pos, &stmt_line, &stmt_col);
6114 bytecode_add_instruction(bc, OP_LINE, stmt_line);
6115 }
6116
6117 /* class definition -> factory function */
6118 if (starts_with(src, len, *pos, "class") && (*pos + 5 < len) && (src[*pos + 5] == ' ' || src[*pos + 5] == '\t')) {
6119 *pos += 5;
6120 skip_spaces(src, len, pos);
6121 /* class name */
6122 char *cname = NULL;
6123 if (!read_identifier_into(src, len, pos, &cname)) {
6124 parser_fail(*pos, "Expected class name after 'class'");
6125 return;
6126 }
6127 int cgi = sym_index(cname);
6128
6129 /* optional extends Parent */
6130 char *parent_name = NULL;
6131
6132 /* Optional typed parameter list: class Name(type ident, ...) */
6133 char *param_names[64];
6134 int param_kind[64];
6135 int pcount = 0;
6136 memset(param_names, 0, sizeof(param_names));
6137 memset(param_kind, 0, sizeof(param_kind));
6138
6139/* kind: 1=Number (numeric types incl. boolean), 2=String, 3=Nil */
6140/* helper macro instead of nested function (C99 compliant) */
6141#define MAP_TYPE_KIND(t) ( \
6142 ((t) && strcmp((t), "string") == 0) ? 2 : ((t) && strcmp((t), "nil") == 0) ? 3 \
6143 : ((t) && (strcmp((t), "boolean") == 0 || strcmp((t), "number") == 0 || strcmp((t), "byte") == 0 || strncmp((t), "uint", 4) == 0 || strncmp((t), "sint", 4) == 0 || strncmp((t), "int", 3) == 0)) ? 1 \
6144 : 0)
6145
6146 skip_spaces(src, len, pos);
6147 if (*pos < len && src[*pos] == '(') {
6148 (*pos)++;
6149 skip_spaces(src, len, pos);
6150 if (*pos < len && src[*pos] != ')') {
6151 for (;;) {
6152 /* read type token */
6153 char *tname = NULL;
6154 if (!read_identifier_into(src, len, pos, &tname)) {
6155 parser_fail(*pos, "Expected type in class parameter list");
6156 free(cname);
6157 return;
6158 }
6159 /* read param name */
6160 skip_spaces(src, len, pos);
6161 char *pname = NULL;
6162 if (!read_identifier_into(src, len, pos, &pname)) {
6163 parser_fail(*pos, "Expected parameter name after type");
6164 free(tname);
6165 free(cname);
6166 return;
6167 }
6168 if (pcount >= (int)(sizeof(param_names) / sizeof(param_names[0]))) {
6169 parser_fail(*pos, "Too many class parameters");
6170 free(tname);
6171 free(pname);
6172 free(cname);
6173 return;
6174 }
6175 param_names[pcount] = pname;
6176 param_kind[pcount] = MAP_TYPE_KIND(tname);
6177 free(tname);
6178 pcount++;
6179
6180 skip_spaces(src, len, pos);
6181 if (*pos < len && src[*pos] == ',') {
6182 (*pos)++;
6183 skip_spaces(src, len, pos);
6184 continue;
6185 }
6186 break;
6187 }
6188 }
6189 if (!consume_char(src, len, pos, ')')) {
6190 parser_fail(*pos, "Expected ')' after class parameter list");
6191 for (int i = 0; i < pcount; ++i)
6192 free(param_names[i]);
6193 free(cname);
6194 return;
6195 }
6196 }
6197
6198 /* optional 'extends Parent' after parameter list */
6199 skip_spaces(src, len, pos);
6200 if (starts_with(src, len, *pos, "extends")) {
6201 *pos += 7; /* consume 'extends' */
6202 skip_spaces(src, len, pos);
6203 if (!read_identifier_into(src, len, pos, &parent_name)) {
6204 parser_fail(*pos, "Expected parent class name after 'extends'");
6205 for (int i = 0; i < pcount; ++i)
6206 free(param_names[i]);
6207 free(cname);
6208 return;
6209 }
6210 }
6211
6212 /* end of class header line */
6213 skip_to_eol(src, len, pos);
6214
6215 /* Build factory function: Name(...) -> instance map with fields and methods */
6216 Bytecode *ctor_bc = bytecode_new();
6217 /* set debug metadata for class factory */
6218 if (ctor_bc) {
6219 if (ctor_bc->name) free((void *)ctor_bc->name);
6220 ctor_bc->name = strdup(cname);
6221 if (ctor_bc->source_file) free((void *)ctor_bc->source_file);
6223 }
6224 /* local env for the factory to allow temp locals */
6225 LocalEnv ctor_env;
6226 memset(&ctor_env, 0, sizeof(ctor_env));
6227 LocalEnv *prev_env = g_locals;
6228 g_locals = &ctor_env;
6229
6230 /* track if _construct is defined in this class */
6231 int ctor_present = 0;
6232
6233 /* Register parameter locals first so args land at 0..pcount-1 */
6234 for (int i = 0; i < pcount; ++i) {
6235 local_add(param_names[i]);
6236 }
6237
6238 /* Guard local to detect extra argument at index == pcount */
6239 int l_extra = local_add("__extra");
6240
6241 /* Runtime checks: missing args and type checks */
6242 for (int i = 0; i < pcount; ++i) {
6243 /* missing arg: local i must not be Nil */
6246 int ci_nil = bytecode_add_constant(ctor_bc, make_string("Nil"));
6247 bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_nil);
6248 bytecode_add_instruction(ctor_bc, OP_EQ, 0);
6249 int j_ok_present = bytecode_add_instruction(ctor_bc, OP_JUMP_IF_FALSE, 0);
6250 /* then -> error */
6251 {
6252 char msg[128];
6253 snprintf(msg, sizeof(msg), "TypeError: missing argument '%s' in %s()", param_names[i], cname);
6254 int ci_msg = bytecode_add_constant(ctor_bc, make_string(msg));
6255 bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_msg);
6257 bytecode_add_instruction(ctor_bc, OP_HALT, 0);
6258 }
6259 /* patch continue if present */
6260 bytecode_set_operand(ctor_bc, j_ok_present, ctor_bc->instr_count);
6261
6262 /* type check for known kinds */
6263 int kind = param_kind[i];
6264 if (kind == 1 || kind == 2 || kind == 3) {
6265 /* typeof(local i) == Expected ? skip error : go to error */
6268 const char *exp = (kind == 1) ? "Number" : (kind == 2) ? "String"
6269 : "Nil";
6270 int ci_exp = bytecode_add_constant(ctor_bc, make_string(exp));
6271 bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_exp);
6272 bytecode_add_instruction(ctor_bc, OP_EQ, 0);
6273 /* if false -> jump to error */
6274 int j_to_error = bytecode_add_instruction(ctor_bc, OP_JUMP_IF_FALSE, 0);
6275 /* on success, skip error block */
6276 int j_skip_err = bytecode_add_instruction(ctor_bc, OP_JUMP, 0);
6277 /* error block */
6278 {
6279 int err_label = ctor_bc->instr_count;
6280 bytecode_set_operand(ctor_bc, j_to_error, err_label);
6281 char msg2[160];
6282 snprintf(msg2, sizeof(msg2), "TypeError: %s() expects %s for '%s'", cname, exp, param_names[i]);
6283 int ci_msg2 = bytecode_add_constant(ctor_bc, make_string(msg2));
6284 bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_msg2);
6286 bytecode_add_instruction(ctor_bc, OP_HALT, 0);
6287 }
6288 /* continue label after error block */
6289 bytecode_set_operand(ctor_bc, j_skip_err, ctor_bc->instr_count);
6290 }
6291 }
6292
6293 /* Extra args check: guard local must be Nil; if not Nil -> error */
6294 {
6295 bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_extra);
6297 int ci_nil2 = bytecode_add_constant(ctor_bc, make_string("Nil"));
6298 bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_nil2);
6299 bytecode_add_instruction(ctor_bc, OP_EQ, 0);
6300 /* if false (not Nil) -> jump to error */
6301 int j_to_error = bytecode_add_instruction(ctor_bc, OP_JUMP_IF_FALSE, 0);
6302 /* if true (Nil) -> skip error */
6303 int j_skip_err = bytecode_add_instruction(ctor_bc, OP_JUMP, 0);
6304 {
6305 int err_label = ctor_bc->instr_count;
6306 bytecode_set_operand(ctor_bc, j_to_error, err_label);
6307 char msg3[128];
6308 snprintf(msg3, sizeof(msg3), "TypeError: %s() received too many arguments", cname);
6309 int ci_msg3 = bytecode_add_constant(ctor_bc, make_string(msg3));
6310 bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_msg3);
6312 bytecode_add_instruction(ctor_bc, OP_HALT, 0);
6313 }
6314 bytecode_set_operand(ctor_bc, j_skip_err, ctor_bc->instr_count);
6315 }
6316
6317 /* instance map: __this = {} (placed after param guard) */
6318 int l_this = local_add("__this");
6320 bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_this);
6321
6322 /* tag instance with its class name: this["__class"] = "<ClassName>" */
6323 bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this);
6324 {
6325 int kci_cls = bytecode_add_constant(ctor_bc, make_string("__class"));
6326 bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, kci_cls);
6327 int vci_cls = bytecode_add_constant(ctor_bc, make_string(cname));
6328 bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, vci_cls);
6329 }
6331
6332 /* Inheritance: if extends Parent, create Parent(header args...) and merge its keys into this */
6333 if (parent_name) {
6334 /* parent_inst = Parent(args...) */
6335 int parent_gi = sym_index(parent_name);
6336 bytecode_add_instruction(ctor_bc, OP_LOAD_GLOBAL, parent_gi);
6337 for (int i = 0; i < pcount; ++i) {
6339 }
6340 bytecode_add_instruction(ctor_bc, OP_CALL, pcount);
6341 int l_parent = local_add("__parent_inst");
6342 bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_parent);
6343
6344 /* keys = keys(parent_inst) */
6345 bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_parent);
6346 bytecode_add_instruction(ctor_bc, OP_KEYS, 0);
6347 int l_keys = local_add("__parent_keys");
6348 bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_keys);
6349
6350 /* i = 0 */
6351 int c0_inh = bytecode_add_constant(ctor_bc, make_int(0));
6352 bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, c0_inh);
6353 int l_i = local_add("__inh_i");
6355
6356 /* loop: while (i < len(keys)) */
6357 int loop_start = ctor_bc->instr_count;
6359 bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_keys);
6360 bytecode_add_instruction(ctor_bc, OP_LEN, 0);
6361 bytecode_add_instruction(ctor_bc, OP_LT, 0);
6362 int jmp_false = bytecode_add_instruction(ctor_bc, OP_JUMP_IF_FALSE, 0);
6363
6364 /* key = keys[i] */
6365 bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_keys);
6368 int l_k = local_add("__inh_k");
6370
6371 /* if this has key -> skip set */
6372 bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this);
6375 int j_skip_set = bytecode_add_instruction(ctor_bc, OP_JUMP_IF_FALSE, 0);
6376 /* has key -> nothing to do, jump over set sequence */
6377 int j_after_maybe_set = bytecode_add_instruction(ctor_bc, OP_JUMP, 0);
6378
6379 /* not has key: set this[key] = parent_inst[key] */
6380 bytecode_set_operand(ctor_bc, j_skip_set, ctor_bc->instr_count);
6381 bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this);
6383 bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_parent);
6387
6388 /* continue after maybe-set */
6389 bytecode_set_operand(ctor_bc, j_after_maybe_set, ctor_bc->instr_count);
6390
6391 /* i++ */
6392 int c1_inh = bytecode_add_constant(ctor_bc, make_int(1));
6394 bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, c1_inh);
6395 bytecode_add_instruction(ctor_bc, OP_ADD, 0);
6397
6398 /* back to loop */
6399 bytecode_add_instruction(ctor_bc, OP_JUMP, loop_start);
6400 /* end loop */
6401 bytecode_set_operand(ctor_bc, jmp_false, ctor_bc->instr_count);
6402 }
6403
6404 /* Parse class body at increased indent */
6405 int body_indent = 0;
6406 size_t look_body = *pos;
6407 if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) {
6408 /* iterate over class members at body_indent */
6409 for (;;) {
6410 size_t member_line_start = *pos;
6411 int member_indent = 0;
6412 if (!read_line_start(src, len, pos, &member_indent)) {
6413 /* EOF */
6414 break;
6415 }
6416 if (member_indent < body_indent) {
6417 /* end of class body */
6418 *pos = member_line_start;
6419 break;
6420 }
6421 if (member_indent > body_indent) {
6422 /* skip nested blocks that are part of previous method parsing */
6423 parse_block(ctor_bc, src, len, pos, member_indent);
6424 continue;
6425 }
6426
6427 /* at body_indent: member declaration (field = expr) or method 'fun name(...)' */
6428 if (starts_with(src, len, *pos, "fun")) {
6429 /* method definition: fun m(this, ...) ... */
6430 *pos += 3;
6431 skip_spaces(src, len, pos);
6432 char *mname = NULL;
6433 if (!read_identifier_into(src, len, pos, &mname)) {
6434 parser_fail(*pos, "Expected method name after 'fun' in class");
6435 g_locals = prev_env;
6436 free(cname);
6437 return;
6438 }
6439 int is_ctor_method = (strcmp(mname, "_construct") == 0);
6440
6441 skip_spaces(src, len, pos);
6442 if (!consume_char(src, len, pos, '(')) {
6443 parser_fail(*pos, "Expected '(' after method name");
6444 free(mname);
6445 g_locals = prev_env;
6446 free(cname);
6447 return;
6448 }
6449
6450 /* Build method function bytecode */
6451 Bytecode *m_bc = bytecode_new();
6452 if (m_bc) {
6453 if (m_bc->name) free((void *)m_bc->name);
6454 /* method qualified name: Class.method */
6455 size_t qlen = strlen(cname) + 1 + strlen(mname) + 1;
6456 char *q = (char *)malloc(qlen);
6457 if (q) {
6458 snprintf(q, qlen, "%s.%s", cname, mname);
6459 m_bc->name = q;
6460 }
6461 if (m_bc->source_file) free((void *)m_bc->source_file);
6463 }
6464 LocalEnv m_env;
6465 memset(&m_env, 0, sizeof(m_env));
6466 LocalEnv *saved = g_locals;
6467 g_locals = &m_env;
6468
6469 /* Parse params, ensure first is 'this' (insert if missing) */
6470 int saw_param = 0;
6471 int param_count = 0;
6472 skip_spaces(src, len, pos);
6473 if (*pos < len && src[*pos] != ')') {
6474 for (;;) {
6475 char *pname = NULL;
6476 if (!read_identifier_into(src, len, pos, &pname)) {
6477 parser_fail(*pos, "Expected parameter name");
6478 free(mname);
6479 g_locals = saved;
6480 g_locals = prev_env;
6481 free(cname);
6482 return;
6483 }
6484 if (param_count == 0 && strcmp(pname, "this") != 0) {
6485 /* Require explicit 'this' as first parameter */
6486 if (is_ctor_method) {
6487 parser_fail(*pos, "Constructor '_construct' must declare 'this' as its first parameter");
6488 } else {
6489 parser_fail(*pos, "First parameter of a method must be 'this'");
6490 }
6491 free(pname);
6492 free(mname);
6493 g_locals = saved;
6494 g_locals = prev_env;
6495 free(cname);
6496 return;
6497 }
6498 local_add(pname);
6499 free(pname);
6500 param_count++;
6501 skip_spaces(src, len, pos);
6502 if (*pos < len && src[*pos] == ',') {
6503 (*pos)++;
6504 skip_spaces(src, len, pos);
6505 continue;
6506 }
6507 break;
6508 }
6509 } else {
6510 /* no params: enforce (this) */
6511 if (is_ctor_method) {
6512 parser_fail(*pos, "Constructor '_construct' must declare 'this' as its first parameter");
6513 } else {
6514 parser_fail(*pos, "Method must declare at least 'this' parameter");
6515 }
6516 free(mname);
6517 g_locals = saved;
6518 g_locals = prev_env;
6519 free(cname);
6520 return;
6521 }
6522
6523 if (!consume_char(src, len, pos, ')')) {
6524 parser_fail(*pos, "Expected ')' after method parameter list");
6525 free(mname);
6526 g_locals = saved;
6527 g_locals = prev_env;
6528 free(cname);
6529 return;
6530 }
6531 /* end header line */
6532 skip_to_eol(src, len, pos);
6533
6534 /* parse method body at increased indent */
6535 int m_body_indent = 0;
6536 size_t look_m = *pos;
6537 if (read_line_start(src, len, &look_m, &m_body_indent) && m_body_indent > body_indent) {
6538 parse_block(m_bc, src, len, pos, m_body_indent);
6539 } else {
6540 /* empty method body allowed -> return */
6541 }
6542 /* ensure return */
6544
6545 /* restore env to factory */
6546 g_locals = saved;
6547
6548 /* Insert method function into instance: this["mname"] = <function> */
6549 bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this);
6550 int kci = bytecode_add_constant(ctor_bc, make_string(mname));
6552 int mci = bytecode_add_constant(ctor_bc, make_function(m_bc));
6555
6556 /* mark constructor presence if name matches */
6557 if (strcmp(mname, "_construct") == 0) {
6558 ctor_present = 1;
6559 }
6560
6561 free(mname);
6562 continue;
6563 }
6564
6565 /* field initializer: ident = expr */
6566 size_t lp = *pos;
6567 char *fname = NULL;
6568 if (!read_identifier_into(src, len, &lp, &fname)) {
6569 parser_fail(*pos, "Expected field or 'fun' in class body");
6570 g_locals = prev_env;
6571 free(cname);
6572 return;
6573 }
6574 size_t tmp = lp;
6575 skip_spaces(src, len, &tmp);
6576 if (tmp >= len || src[tmp] != '=') {
6577 free(fname);
6578 parser_fail(tmp, "Expected '=' in field initializer");
6579 g_locals = prev_env;
6580 free(cname);
6581 return;
6582 }
6583 /* commit position and consume '=' */
6584 *pos = tmp + 1;
6585 /* emit: this["fname"] = (expr) */
6586 bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this);
6587 int fkey = bytecode_add_constant(ctor_bc, make_string(fname));
6589 free(fname);
6590 if (!emit_expression(ctor_bc, src, len, pos)) {
6591 parser_fail(*pos, "Expected expression in field initializer");
6592 g_locals = prev_env;
6593 free(cname);
6594 return;
6595 }
6597 /* end of line */
6598 skip_to_eol(src, len, pos);
6599 }
6600 } else {
6601 /* empty class body allowed */
6602 }
6603
6604 /* Override defaults with constructor parameters: this["name"] = local i */
6605 for (int i = 0; i < pcount; ++i) {
6606 bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this);
6607 int kci = bytecode_add_constant(ctor_bc, make_string(param_names[i]));
6611 }
6612
6613 /* If a constructor exists, invoke: this._construct(this, params...) and drop its return */
6614 if (ctor_present) {
6615 /* fetch method: duplicate 'this' so we keep it for the call */
6616 bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this);
6617 bytecode_add_instruction(ctor_bc, OP_DUP, 0); /* -> this, this */
6618 {
6619 int kci_ctor = bytecode_add_constant(ctor_bc, make_string("_construct"));
6620 bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, kci_ctor);
6621 }
6622 bytecode_add_instruction(ctor_bc, OP_INDEX_GET, 0); /* -> this, func */
6623 bytecode_add_instruction(ctor_bc, OP_SWAP, 0); /* -> func, this */
6624
6625 /* push all header parameters as additional args in order */
6626 for (int i = 0; i < pcount; ++i) {
6628 }
6629 /* call with implicit 'this' (+1) plus pcount params */
6630 bytecode_add_instruction(ctor_bc, OP_CALL, pcount + 1);
6631 /* discard any return value from constructor */
6632 bytecode_add_instruction(ctor_bc, OP_POP, 0);
6633 }
6634
6635 /* return instance */
6636 bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this);
6638
6639 /* restore outer locals env */
6640 g_locals = prev_env;
6641
6642 /* bind factory function globally under class name */
6643 int cci = bytecode_add_constant(bc, make_function(ctor_bc));
6646 /* mark this global as a class for typeof(identifier) */
6647 G.is_class[cgi] = 1;
6648
6649 for (int i = 0; i < pcount; ++i)
6650 free(param_names[i]);
6651 if (parent_name) free(parent_name);
6652 free(cname);
6653 continue;
6654 }
6655
6656 if (starts_with(src, len, *pos, "fun") && (*pos + 3 < len) && (src[*pos + 3] == ' ' || src[*pos + 3] == '\t')) {
6657 /* parse header: fun name(arg, ...) */
6658 *pos += 3;
6659 skip_spaces(src, len, pos);
6660 char *fname = NULL;
6661 if (!read_identifier_into(src, len, pos, &fname)) {
6662 parser_fail(*pos, "Expected function name after 'fun'");
6663 return;
6664 }
6665 int fgi = sym_index(fname);
6666 skip_spaces(src, len, pos);
6667 if (!consume_char(src, len, pos, '(')) {
6668 parser_fail(*pos, "Expected '(' after function name");
6669 free(fname);
6670 return;
6671 }
6672
6673 /* build locals from parameters */
6674 LocalEnv env = {{0}, {0}, 0};
6676 int saved_depth = g_func_env_depth;
6677 if (prev != NULL) {
6678 if (g_func_env_depth >= (int)(sizeof(g_func_env_stack) / sizeof(g_func_env_stack[0]))) {
6679 parser_fail(*pos, "Too many nested functions (env stack overflow)");
6680 free(fname);
6681 return;
6682 }
6684 }
6685 g_locals = &env;
6686
6687 skip_spaces(src, len, pos);
6688 if (*pos < len && src[*pos] != ')') {
6689 for (;;) {
6690 char *pname = NULL;
6691 if (!read_identifier_into(src, len, pos, &pname)) {
6692 parser_fail(*pos, "Expected parameter name");
6693 g_locals = prev;
6694 free(fname);
6695 return;
6696 }
6697 if (local_find(pname) >= 0) {
6698 parser_fail(*pos, "Duplicate parameter name '%s'", pname);
6699 free(pname);
6700 g_locals = prev;
6701 free(fname);
6702 return;
6703 }
6704 local_add(pname);
6705 free(pname);
6706 skip_spaces(src, len, pos);
6707 if (*pos < len && src[*pos] == ',') {
6708 (*pos)++;
6709 skip_spaces(src, len, pos);
6710 continue;
6711 }
6712 break;
6713 }
6714 }
6715 if (!consume_char(src, len, pos, ')')) {
6716 parser_fail(*pos, "Expected ')' after parameter list");
6717 g_locals = prev;
6718 free(fname);
6719 return;
6720 }
6721 /* end header line */
6722 skip_to_eol(src, len, pos);
6723
6724 /* compile body into separate Bytecode */
6725 Bytecode *fn_bc = bytecode_new();
6726 if (fn_bc) {
6727 if (fn_bc->name) free((void *)fn_bc->name);
6728 fn_bc->name = strdup(fname);
6729 if (fn_bc->source_file) free((void *)fn_bc->source_file);
6731 }
6732
6733 /* parse body at increased indent if present */
6734 int body_indent = 0;
6735 size_t look_body = *pos;
6736 if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) {
6737 /* do not advance pos here; let parse_block consume the line */
6738 parse_block(fn_bc, src, len, pos, body_indent);
6739 } else {
6740 /* empty body: ok */
6741 }
6742 /* ensure function returns */
6744
6745#ifdef FUN_DEBUG
6746 /* DEBUG: dump compiled function bytecode (guarded by runtime env) */
6747 if (fun_debug_enabled()) {
6748 printf("=== compiled function %s (%d params) ===\n", fname, env.count);
6749 bytecode_dump(fn_bc);
6750 printf("=== end function %s ===\n", fname);
6751 }
6752#endif
6753
6754 /* If we are inside another function (prev != NULL), bind this function as a local
6755 * so that it is only visible within the current function scope. Otherwise, bind
6756 * it as a global (top-level behavior unchanged). */
6757 int fci = bytecode_add_constant(bc, make_function(fn_bc));
6759 if (prev != NULL) {
6760 /* Bind into the OUTER function's local env (prev), not the inner temp env. */
6761 LocalEnv *save_env = g_locals;
6762 g_locals = prev;
6763 /* declare a local with the function name if not present */
6764 int lidx = local_find(fname);
6765 if (lidx < 0) {
6766 lidx = local_add(fname);
6767 }
6768 if (lidx < 0) {
6769 /* failed to allocate local slot */
6770 g_locals = save_env; /* restore before returning */
6771 g_locals = prev;
6772 free(fname);
6773 return;
6774 }
6775 /* We want sibling inner functions to be able to call this nested function
6776 * without closures. Strategy: also expose it as a global symbol so that
6777 * inner functions (compiled into separate frames) can resolve the name
6778 * via global lookup. Keep the local binding for the outer function body. */
6779 /* Duplicate the function value so we can store to both local and global. */
6782 /* Bind a global under the same name for call resolution inside deeper nested functions. */
6783 int gsym = sym_index(fname);
6785 /* restore current env (will be reset to prev below) */
6786 g_locals = save_env;
6787 } else {
6788 /* top-level: global binding (backward compatible) */
6790 }
6791
6792 g_locals = prev;
6793 g_func_env_depth = saved_depth;
6794 free(fname);
6795 continue;
6796 }
6797
6798 /* for-sugar:
6799 * - for <ident> in range(a, b)
6800 * - for <ident> in <array-expr>
6801 * - for (<keyIdent>, <valIdent>) in <map-expr>
6802 */
6803 if (starts_with(src, len, *pos, "for")) {
6804 *pos += 3;
6805 skip_spaces(src, len, pos);
6806
6807 /* Optional tuple '(k, v)' for map iteration */
6808 int tuple_mode = 0; /* 0 = single var, 1 = (k,v) */
6809 char *ivar = NULL; /* single variable name OR key variable when tuple */
6810 char *vvar = NULL; /* value variable when tuple */
6811
6812 if (*pos < len && src[*pos] == '(') {
6813 /* Parse '(k, v)' */
6814 (*pos)++;
6815 skip_spaces(src, len, pos);
6816 if (!read_identifier_into(src, len, pos, &ivar)) {
6817 parser_fail(*pos, "Expected identifier after '(' in for tuple");
6818 return;
6819 }
6820 skip_spaces(src, len, pos);
6821 if (!consume_char(src, len, pos, ',')) {
6822 parser_fail(*pos, "Expected ',' between key and value in for tuple");
6823 free(ivar);
6824 return;
6825 }
6826 skip_spaces(src, len, pos);
6827 if (!read_identifier_into(src, len, pos, &vvar)) {
6828 parser_fail(*pos, "Expected value identifier after ',' in for tuple");
6829 free(ivar);
6830 return;
6831 }
6832 skip_spaces(src, len, pos);
6833 if (!consume_char(src, len, pos, ')')) {
6834 parser_fail(*pos, "Expected ')' to close for tuple");
6835 free(ivar);
6836 free(vvar);
6837 return;
6838 }
6839 tuple_mode = 1;
6840 } else {
6841 /* loop variable name */
6842 if (!read_identifier_into(src, len, pos, &ivar)) {
6843 parser_fail(*pos, "Expected loop variable after 'for'");
6844 return;
6845 }
6846 }
6847
6848 skip_spaces(src, len, pos);
6849 if (!starts_with(src, len, *pos, "in")) {
6850 parser_fail(*pos, "Expected 'in' after loop variable");
6851 free(ivar);
6852 return;
6853 }
6854 *pos += 2;
6855 skip_spaces(src, len, pos);
6856
6857 if (starts_with(src, len, *pos, "range")) {
6858 /* ===== range(a, b) variant ===== */
6859 *pos += 5;
6860 if (!consume_char(src, len, pos, '(')) {
6861 parser_fail(*pos, "Expected '(' after range");
6862 free(ivar);
6863 return;
6864 }
6865
6866 /* Parse start expression */
6867 if (!emit_expression(bc, src, len, pos)) {
6868 parser_fail(*pos, "Expected start expression in range");
6869 free(ivar);
6870 return;
6871 }
6872
6873 /* Determine loop variable storage and store start value */
6874 int lidx = local_find(ivar);
6875 int gi = -1;
6876 if (lidx < 0) {
6877 if (g_locals)
6878 lidx = local_add(ivar);
6879 else
6880 gi = sym_index(ivar);
6881 }
6882 if (lidx >= 0) {
6884 } else {
6886 }
6887
6888 /* comma */
6889 skip_spaces(src, len, pos);
6890 if (*pos >= len || src[*pos] != ',') {
6891 parser_fail(*pos, "Expected ',' between range start and end");
6892 free(ivar);
6893 return;
6894 }
6895 (*pos)++; /* consume ',' */
6896 skip_spaces(src, len, pos);
6897
6898 /* Parse end expression and store in a temp (local or global) */
6899 if (!emit_expression(bc, src, len, pos)) {
6900 parser_fail(*pos, "Expected end expression in range");
6901 free(ivar);
6902 return;
6903 }
6904
6905 char tmpname[64];
6906 snprintf(tmpname, sizeof(tmpname), "__for_end_%d", g_temp_counter++);
6907
6908 int lend = -1, gend = -1;
6909 if (g_locals) {
6910 lend = local_add(tmpname);
6912 } else {
6913 gend = sym_index(tmpname);
6915 }
6916
6917 if (!consume_char(src, len, pos, ')')) {
6918 parser_fail(*pos, "Expected ')' after range arguments");
6919 free(ivar);
6920 return;
6921 }
6922
6923 /* end of header line */
6924 skip_to_eol(src, len, pos);
6925
6926 /* emit loop */
6927 int loop_start = bc->instr_count;
6928
6929 /* condition: ivar < end_tmp */
6930 if (lidx >= 0) {
6932 } else {
6934 }
6935 if (lend >= 0) {
6937 } else {
6939 }
6941 int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
6942
6943 /* enter loop context for break/continue */
6944 LoopCtx ctx = {{0}, 0, {0}, 0, g_loop_ctx};
6945 g_loop_ctx = &ctx;
6946
6947 /* parse body at increased indent (peek) */
6948 int body_indent = 0;
6949 size_t look_body = *pos;
6950 if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) {
6951 parse_block(bc, src, len, pos, body_indent);
6952 } else {
6953 /* empty body ok */
6954 }
6955
6956 /* continue target: start of increment */
6957 int cont_label = bc->instr_count;
6958
6959 /* i = i + 1 */
6960 int c1 = bytecode_add_constant(bc, make_int(1));
6961 if (lidx >= 0) {
6966 } else {
6971 }
6972
6973 /* back edge and patch */
6974 bytecode_add_instruction(bc, OP_JUMP, loop_start);
6975
6976 /* end label (after loop) */
6977 int end_label = bc->instr_count;
6978 bytecode_set_operand(bc, jmp_false, end_label);
6979
6980 /* patch continue/break jumps */
6981 for (int bi = 0; bi < ctx.cont_count; ++bi) {
6982 bytecode_set_operand(bc, ctx.continue_jumps[bi], cont_label);
6983 }
6984 for (int bi = 0; bi < ctx.break_count; ++bi) {
6985 bytecode_set_operand(bc, ctx.break_jumps[bi], end_label);
6986 }
6987 g_loop_ctx = ctx.prev;
6988
6989 free(ivar);
6990 continue;
6991 } else if (!tuple_mode) {
6992 /* ===== array iteration: for ivar in <expr> ===== */
6993 /* Evaluate the iterable once and store in a temp */
6994 if (!emit_expression(bc, src, len, pos)) {
6995 parser_fail(*pos, "Expected iterable expression after 'in'");
6996 free(ivar);
6997 return;
6998 }
6999 char arrname[64];
7000 snprintf(arrname, sizeof(arrname), "__for_arr_%d", g_temp_counter++);
7001 int larr = -1, garr = -1;
7002 if (g_locals) {
7003 larr = local_add(arrname);
7005 } else {
7006 garr = sym_index(arrname);
7008 }
7009
7010 /* Compute length once: len(arr) -> store temp */
7011 if (larr >= 0) {
7013 } else {
7015 }
7017 char lenname[64];
7018 snprintf(lenname, sizeof(lenname), "__for_len_%d", g_temp_counter++);
7019 int llen = -1, glen = -1;
7020 if (g_locals) {
7021 llen = local_add(lenname);
7023 } else {
7024 glen = sym_index(lenname);
7026 }
7027
7028 /* Index temp: i = 0 */
7029 int c0 = bytecode_add_constant(bc, make_int(0));
7031 char iname[64];
7032 snprintf(iname, sizeof(iname), "__for_i_%d", g_temp_counter++);
7033 int li = -1, gi = -1;
7034 if (g_locals) {
7035 li = local_add(iname);
7037 } else {
7038 gi = sym_index(iname);
7040 }
7041
7042 /* end of header line */
7043 skip_to_eol(src, len, pos);
7044
7045 /* loop start label */
7046 int loop_start = bc->instr_count;
7047
7048 /* condition: i < len */
7049 if (li >= 0) {
7051 } else {
7053 }
7054 if (llen >= 0) {
7056 } else {
7058 }
7060 int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
7061
7062 /* element: ivar = arr[i] */
7063 if (larr >= 0) {
7065 } else {
7067 }
7068 if (li >= 0) {
7070 } else {
7072 }
7074
7075 /* assign to ivar (local preferred) */
7076 int ldst = local_find(ivar);
7077 int gdst = -1;
7078 if (ldst < 0) {
7079 if (g_locals)
7080 ldst = local_add(ivar);
7081 else
7082 gdst = sym_index(ivar);
7083 }
7084 if (ldst >= 0) {
7086 } else {
7088 }
7089
7090 /* enter loop context for break/continue */
7091 LoopCtx ctx = {{0}, 0, {0}, 0, g_loop_ctx};
7092 g_loop_ctx = &ctx;
7093
7094 /* parse body at increased indent */
7095 int body_indent = 0;
7096 size_t look_body = *pos;
7097 if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) {
7098 parse_block(bc, src, len, pos, body_indent);
7099 } else {
7100 /* empty body ok */
7101 }
7102
7103 /* continue target: start of increment */
7104 int cont_label = bc->instr_count;
7105
7106 /* i = i + 1 */
7107 int c1 = bytecode_add_constant(bc, make_int(1));
7108 if (li >= 0) {
7113 } else {
7118 }
7119
7120 /* back edge and patch */
7121 bytecode_add_instruction(bc, OP_JUMP, loop_start);
7122
7123 /* end label (after loop) */
7124 int end_label = bc->instr_count;
7125 bytecode_set_operand(bc, jmp_false, end_label);
7126
7127 /* patch continue/break jumps */
7128 for (int bi = 0; bi < ctx.cont_count; ++bi) {
7129 bytecode_set_operand(bc, ctx.continue_jumps[bi], cont_label);
7130 }
7131 for (int bi = 0; bi < ctx.break_count; ++bi) {
7132 bytecode_set_operand(bc, ctx.break_jumps[bi], end_label);
7133 }
7134 g_loop_ctx = ctx.prev;
7135
7136 free(ivar);
7137 continue;
7138 } else {
7139 /* ===== map iteration with tuple: for (k, v) in <expr> ===== */
7140 /* Evaluate the map expr once: store in temp */
7141 if (!emit_expression(bc, src, len, pos)) {
7142 parser_fail(*pos, "Expected map expression after 'in'");
7143 free(ivar);
7144 free(vvar);
7145 return;
7146 }
7147 char mapname[64];
7148 snprintf(mapname, sizeof(mapname), "__for_map_%d", g_temp_counter++);
7149 int lmap = -1, gmap = -1;
7150 if (g_locals) {
7151 lmap = local_add(mapname);
7153 } else {
7154 gmap = sym_index(mapname);
7156 }
7157
7158 /* keys = keys(map) */
7159 if (lmap >= 0) {
7161 } else {
7163 }
7165 char keysname[64];
7166 snprintf(keysname, sizeof(keysname), "__for_keys_%d", g_temp_counter++);
7167 int lkeys = -1, gkeys = -1;
7168 if (g_locals) {
7169 lkeys = local_add(keysname);
7171 } else {
7172 gkeys = sym_index(keysname);
7174 }
7175
7176 /* len(keys) */
7177 if (lkeys >= 0) {
7179 } else {
7181 }
7183 char lenname[64];
7184 snprintf(lenname, sizeof(lenname), "__for_klen_%d", g_temp_counter++);
7185 int llen = -1, glen = -1;
7186 if (g_locals) {
7187 llen = local_add(lenname);
7189 } else {
7190 glen = sym_index(lenname);
7192 }
7193
7194 /* i = 0 */
7195 int c0m = bytecode_add_constant(bc, make_int(0));
7197 char iname[64];
7198 snprintf(iname, sizeof(iname), "__for_ki_%d", g_temp_counter++);
7199 int li = -1, gi = -1;
7200 if (g_locals) {
7201 li = local_add(iname);
7203 } else {
7204 gi = sym_index(iname);
7206 }
7207
7208 /* end of header line */
7209 skip_to_eol(src, len, pos);
7210
7211 /* loop start */
7212 int loop_start = bc->instr_count;
7213 /* condition: i < len */
7214 if (li >= 0) {
7216 } else {
7218 }
7219 if (llen >= 0) {
7221 } else {
7223 }
7225 int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
7226
7227 /* key = keys[i] */
7228 if (lkeys >= 0) {
7230 } else {
7232 }
7233 if (li >= 0) {
7235 } else {
7237 }
7239
7240 /* assign to key variable (ivar) */
7241 int lk = local_find(ivar);
7242 int gk = -1;
7243 if (lk < 0) {
7244 if (g_locals)
7245 lk = local_add(ivar);
7246 else
7247 gk = sym_index(ivar);
7248 }
7249 if (lk >= 0) {
7251 } else {
7253 }
7254
7255 /* value = map[key] */
7256 if (lmap >= 0) {
7258 } else {
7260 }
7261 /* load key again */
7262 if (lk >= 0) {
7264 } else {
7266 }
7268
7269 /* assign to value variable (vvar) */
7270 int lv = local_find(vvar);
7271 int gv = -1;
7272 if (lv < 0) {
7273 if (g_locals)
7274 lv = local_add(vvar);
7275 else
7276 gv = sym_index(vvar);
7277 }
7278 if (lv >= 0) {
7280 } else {
7282 }
7283
7284 /* enter loop context */
7285 LoopCtx ctx = {{0}, 0, {0}, 0, g_loop_ctx};
7286 g_loop_ctx = &ctx;
7287
7288 /* body */
7289 int body_indent = 0;
7290 size_t look_body = *pos;
7291 if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) {
7292 parse_block(bc, src, len, pos, body_indent);
7293 } else {
7294 /* empty body ok */
7295 }
7296
7297 /* continue target: i = i + 1 */
7298 int cont_label = bc->instr_count;
7299 int c1m = bytecode_add_constant(bc, make_int(1));
7300 if (li >= 0) {
7305 } else {
7310 }
7311
7312 /* back edge */
7313 bytecode_add_instruction(bc, OP_JUMP, loop_start);
7314
7315 /* end label */
7316 int end_label = bc->instr_count;
7317 bytecode_set_operand(bc, jmp_false, end_label);
7318
7319 /* patch continue/break */
7320 for (int bi = 0; bi < ctx.cont_count; ++bi) {
7321 bytecode_set_operand(bc, ctx.continue_jumps[bi], cont_label);
7322 }
7323 for (int bi = 0; bi < ctx.break_count; ++bi) {
7324 bytecode_set_operand(bc, ctx.break_jumps[bi], end_label);
7325 }
7326 g_loop_ctx = ctx.prev;
7327
7328 free(ivar);
7329 free(vvar);
7330 continue;
7331 }
7332 }
7333
7334 if (starts_with(src, len, *pos, "if")) {
7335 int end_jumps[64];
7336 int end_count = 0;
7337
7338 for (;;) {
7339 /* consume 'if' or 'else if' condition */
7340 if (starts_with(src, len, *pos, "if")) {
7341 *pos += 2;
7342 } else {
7343 /* for 'else if' we arrive here with *pos already after 'if' */
7344 }
7345
7346 /* require at least one space before condition if present */
7347 skip_spaces(src, len, pos);
7348 if (!emit_expression(bc, src, len, pos)) {
7349 /* no condition -> treat as false */
7350 int ci = bytecode_add_constant(bc, make_int(0));
7352 }
7353
7354 /* Decide between inline single-statement and indented block on next line */
7355 size_t ppeek = *pos;
7356 /* skip spaces after condition */
7357 while (ppeek < len && src[ppeek] == ' ')
7358 ppeek++;
7359 int inline_stmt = 0;
7360 if (ppeek < len) {
7361 if (src[ppeek] == '\r' || src[ppeek] == '\n') {
7362 inline_stmt = 0; /* EOL -> no inline body */
7363 } else if (ppeek + 1 < len && src[ppeek] == '/' && src[ppeek + 1] == '/') {
7364 inline_stmt = 0; /* line comment -> no inline body */
7365 } else if (ppeek + 1 < len && src[ppeek] == '/' && src[ppeek + 1] == '*') {
7366 inline_stmt = 0; /* block comment at EOL -> no inline body */
7367 } else {
7368 inline_stmt = 1; /* there's code after condition on same line */
7369 }
7370 }
7371
7372 /* conditional jump over this clause's inline/body */
7373 int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
7374
7375 if (inline_stmt) {
7376 /* Compile a single inline statement on the same line:
7377 if (cond) <statement> */
7378 *pos = ppeek;
7379 parse_simple_statement(bc, src, len, pos);
7380 /* Skip the inline body when condition is false */
7381 bytecode_set_operand(bc, jmp_false, bc->instr_count);
7382 /* one-liner form has no else/elseif on the same line; end the chain */
7383 break;
7384 }
7385
7386 /* No inline statement on this line: consume up to EOL and parse indented block or else-if/else */
7387 skip_to_eol(src, len, pos);
7388
7389 /* parse nested block if next line is indented */
7390 int next_indent = 0;
7391 size_t look_next = *pos;
7392 if (read_line_start(src, len, &look_next, &next_indent)) {
7393 if (next_indent > current_indent) {
7394 /* parse body at increased indent (let parse_block consume the line) */
7395 parse_block(bc, src, len, pos, next_indent);
7396 } else {
7397 /* empty body; keep *pos at start of that line */
7398 }
7399 } else {
7400 /* EOF -> empty body */
7401 }
7402
7403 /* after body, unconditionally jump to end of the whole chain */
7404 int jmp_end = bytecode_add_instruction(bc, OP_JUMP, 0);
7405 if (end_count < (int)(sizeof(end_jumps) / sizeof(end_jumps[0]))) {
7406 end_jumps[end_count++] = jmp_end;
7407 } else {
7408 parser_fail(*pos, "Too many chained else/if clauses");
7409 return;
7410 }
7411
7412 /* patch false-jump target to start of next clause (or fallthrough) */
7413 bytecode_set_operand(bc, jmp_false, bc->instr_count);
7414
7415 /* look for else or else if at the same indentation */
7416 size_t look = *pos;
7417 int look_indent = 0;
7418 if (!read_line_start(src, len, &look, &look_indent)) {
7419 /* EOF: break and patch end jumps */
7420 break;
7421 }
7422 if (look_indent != current_indent) {
7423 /* dedent or deeper indent means no 'else' clause here */
7424 break;
7425 }
7426 if (starts_with(src, len, look, "else")) {
7427 /* consume 'else' */
7428 *pos = look + 4;
7429 skip_spaces(src, len, pos);
7430
7431 if (starts_with(src, len, *pos, "if")) {
7432 /* else if -> consume 'if' token and continue loop to parse condition */
7433 *pos += 2;
7434 continue;
7435 } else {
7436 /* plain else: parse its block and finish the chain */
7437 skip_to_eol(src, len, pos);
7438 int else_indent = 0;
7439 size_t look_else = *pos;
7440 if (read_line_start(src, len, &look_else, &else_indent) && else_indent > current_indent) {
7441 /* let parse_block consume the line */
7442 parse_block(bc, src, len, pos, else_indent);
7443 } else {
7444 /* empty else-body */
7445 }
7446 /* end of chain after else */
7447 break;
7448 }
7449 } else {
7450 /* next line is not an else/else if */
7451 break;
7452 }
7453 }
7454
7455 /* patch all end-of-clause jumps to the end of the chain */
7456 for (int i = 0; i < end_count; ++i) {
7457 bytecode_set_operand(bc, end_jumps[i], bc->instr_count);
7458 }
7459 continue;
7460 }
7461
7462 /* while loop */
7463 if (starts_with(src, len, *pos, "while")) {
7464 *pos += 5;
7465 skip_spaces(src, len, pos);
7466
7467 int loop_start = bc->instr_count;
7468
7469 /* condition */
7470 if (!emit_expression(bc, src, len, pos)) {
7471 int ci = bytecode_add_constant(bc, make_int(0));
7473 }
7474 /* end of condition */
7475 skip_to_eol(src, len, pos);
7476
7477 /* jump over body if false */
7478 int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
7479
7480 /* enter loop context (continue -> condition) */
7481 LoopCtx ctx = {{0}, 0, {0}, 0, g_loop_ctx};
7482 g_loop_ctx = &ctx;
7483
7484 /* parse body at increased indent (peek indent without advancing pos) */
7485 int body_indent = 0;
7486 size_t look_body = *pos;
7487 if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) {
7488 parse_block(bc, src, len, pos, body_indent);
7489 } else {
7490 /* empty body allowed */
7491 }
7492
7493 /* patch pending continues to loop_start (re-evaluate condition) */
7494 for (int bi = 0; bi < ctx.cont_count; ++bi) {
7495 bytecode_set_operand(bc, ctx.continue_jumps[bi], loop_start);
7496 }
7497
7498 /* back edge to loop start */
7499 bytecode_add_instruction(bc, OP_JUMP, loop_start);
7500
7501 /* end label and patches */
7502 int end_label = bc->instr_count;
7503 bytecode_set_operand(bc, jmp_false, end_label);
7504 for (int bi = 0; bi < ctx.break_count; ++bi) {
7505 bytecode_set_operand(bc, ctx.break_jumps[bi], end_label);
7506 }
7507 g_loop_ctx = ctx.prev;
7508 continue;
7509 }
7510
7511 /* try/catch/finally */
7512 if (starts_with(src, len, *pos, "try")) {
7513 /* consume 'try' */
7514 *pos += 3;
7515 /* end of header line */
7516 skip_to_eol(src, len, pos);
7517
7518 /* Install a handler placeholder; will be patched to catch label (or a rethrow stub) */
7519 int try_push_idx = bytecode_add_instruction(bc, OP_TRY_PUSH, 0);
7520
7521 /* parse try body at increased indent (if any) */
7522 int try_body_indent = 0;
7523 size_t look_try = *pos;
7524 if (read_line_start(src, len, &look_try, &try_body_indent) && try_body_indent > current_indent) {
7525 parse_block(bc, src, len, pos, try_body_indent);
7526 } else {
7527 /* empty try body allowed */
7528 }
7529
7530 /* After try body, pop handler for normal (non-exceptional) flow */
7532
7533 /* on normal completion, jump over catch body */
7534 int jmp_over_catch_finally = bytecode_add_instruction(bc, OP_JUMP, 0);
7535
7536 /* Optional: catch and/or finally clauses at same indentation */
7537 int seen_catch = 0;
7538 int seen_finally = 0;
7539 int catch_label = -1;
7540 for (;;) {
7541 size_t look = *pos;
7542 int look_indent = 0;
7543 if (!read_line_start(src, len, &look, &look_indent)) break; /* EOF */
7544 if (look_indent != current_indent) break; /* different indentation -> stop */
7545
7546 if (!seen_catch && starts_with(src, len, look, "catch")) {
7547 /* consume 'catch' */
7548 *pos = look + 5;
7549 /* optional variable name */
7550 skip_spaces(src, len, pos);
7551 char *ex_name = NULL;
7552 size_t tmp = *pos;
7553 int have_name = 0;
7554 if (read_identifier_into(src, len, &tmp, &ex_name)) {
7555 *pos = tmp;
7556 have_name = 1;
7557 }
7558 /* end of header line */
7559 skip_to_eol(src, len, pos);
7560
7561 /* Mark catch label and patch try handler target */
7562 catch_label = bc->instr_count;
7563 bytecode_set_operand(bc, try_push_idx, catch_label);
7564
7565 /* On entering catch, the thrown error is on stack. Bind to name if provided, else pop. */
7566 if (have_name) {
7567 int lidx = -1, gi = -1;
7568 if (g_locals) {
7569 int existing = local_find(ex_name);
7570 if (existing >= 0)
7571 lidx = existing;
7572 else
7573 lidx = local_add(ex_name);
7574 } else {
7575 gi = sym_index(ex_name);
7576 }
7577 if (lidx >= 0)
7579 else if (gi >= 0)
7581 else
7583 } else {
7585 }
7586 if (ex_name) free(ex_name);
7587
7588 /* parse catch body at increased indent (if any) */
7589 int catch_indent = 0;
7590 size_t look_catch = *pos;
7591 if (read_line_start(src, len, &look_catch, &catch_indent) && catch_indent > current_indent) {
7592 parse_block(bc, src, len, pos, catch_indent);
7593 } else {
7594 /* empty catch body allowed */
7595 }
7596 seen_catch = 1;
7597 continue;
7598 }
7599
7600 if (!seen_finally && starts_with(src, len, look, "finally")) {
7601 /* consume 'finally' */
7602 *pos = look + 7;
7603 /* end of header line */
7604 skip_to_eol(src, len, pos);
7605
7606 /* parse finally body at increased indent (if any) */
7607 int finally_indent = 0;
7608 size_t look_fin = *pos;
7609 if (read_line_start(src, len, &look_fin, &finally_indent) && finally_indent > current_indent) {
7610 parse_block(bc, src, len, pos, finally_indent);
7611 } else {
7612 /* empty finally body allowed */
7613 }
7614
7615 seen_finally = 1;
7616 continue;
7617 }
7618
7619 /* no recognized clause at this indentation */
7620 break;
7621 }
7622
7623 /* If no catch clause was present, make handler rethrow */
7624 if (!seen_catch) {
7625 int rethrow_label = bc->instr_count;
7626 bytecode_set_operand(bc, try_push_idx, rethrow_label);
7627 /* at handler: immediately rethrow the incoming error */
7629 }
7630
7631 /* patch normal-flow jump to here (after catch/finally) */
7632 bytecode_set_operand(bc, jmp_over_catch_finally, bc->instr_count);
7633 continue;
7634 }
7635
7636 /* otherwise: simple statement on this line */
7637 parse_simple_statement(bc, src, len, pos);
7638 }
7639}
7640
7641/**
7642 * @brief Compile a full source buffer into bytecode.
7643 *
7644 * Preprocesses namespace aliases, handles shebangs and top-level constructs,
7645 * parses the indentation-aware block and appends a final HALT instruction.
7646 *
7647 * @param src Source buffer to compile (preprocessed when used via file API).
7648 * @param len Length of the source buffer in bytes.
7649 * @return Newly allocated Bytecode on success; never NULL here (errors are
7650 * recorded globally and may lead to incomplete bytecode).
7651 */
7652static Bytecode *compile_minimal(const char *src, size_t len) {
7653 Bytecode *bc = bytecode_new();
7654 size_t pos = 0;
7655
7656 /* Refresh namespace alias table for this compilation unit */
7658 ns_aliases_scan(src, len);
7659
7660 skip_shebang_if_present(src, len, &pos);
7661
7662 /* Allow top-of-file function definitions; do not skip any leading 'fun' line */
7663 skip_comments(src, len, &pos);
7664 skip_ws(src, len, &pos);
7665
7666 /* parse the top-level block at indent 0 */
7667 parse_block(bc, src, len, &pos, 0);
7668
7670 return bc;
7671}
7672
7673/**
7674 * @brief Parse a .fun source file and return compiled bytecode.
7675 *
7676 * Reads the file, preprocesses includes (tracking original file paths),
7677 * resets the global error state, and compiles to Bytecode while attaching
7678 * source metadata (name, source_file).
7679 *
7680 * @param path Filesystem path to the source file.
7681 * @return Bytecode pointer on success; NULL on I/O or parse error.
7682 */
7684 size_t len = 0;
7685 char *src = read_file_all(path, &len);
7686 if (!src) {
7687 fprintf(stderr, "Error: cannot read file: %s\n", path);
7688 return NULL;
7689 }
7690
7691 /* Preprocess includes before compiling (with path for accurate markers) */
7692 char *prep = preprocess_includes_with_path(src, path);
7693 const char *compile_src = prep ? prep : src;
7694 size_t compile_len = strlen(compile_src);
7695
7696 /* reset error state */
7697 g_has_error = 0;
7698 g_err_pos = 0;
7699 g_err_msg[0] = '\0';
7700 g_err_line = 0;
7701 g_err_col = 0;
7702
7703 /* Set active source for error mapping/cascade control and current source path for nested bytecodes */
7704 const char *prev_source = g_current_source_path;
7705 const char *prev_active_src = g_active_src;
7706 size_t prev_active_len = g_active_len;
7707 int prev_last_line = g_last_err_line;
7708 int prev_line_count = g_line_err_count;
7709 g_active_src = compile_src;
7710 g_active_len = compile_len;
7711 g_last_err_line = -1;
7712 g_line_err_count = 0;
7713 g_current_source_path = path;
7714 Bytecode *bc = compile_minimal(compile_src, compile_len);
7715 /* assign debug metadata to module bytecode */
7716 if (bc) {
7717 if (bc->source_file) free((void *)bc->source_file);
7718 bc->source_file = path ? strdup(path) : strdup("<input>");
7719 if (bc->name) free((void *)bc->name);
7720 /* derive name from basename of path */
7721 const char *bn = path ? strrchr(path, '/') : NULL;
7722 const char *base = bn ? bn + 1 : (path ? path : "<input>");
7723 bc->name = strdup(base);
7724 }
7725 /* restore previous */
7726 g_current_source_path = prev_source;
7727 g_active_src = prev_active_src;
7728 g_active_len = prev_active_len;
7729 g_last_err_line = prev_last_line;
7730 g_line_err_count = prev_line_count;
7731
7732 if (g_has_error) {
7733 int line = 1, col = 1;
7734 calc_line_col(compile_src, compile_len, g_err_pos, &line, &col);
7735
7736 /* If the preprocessor injected an initial include marker line, compensate
7737 * it in the outward-reported top-level line number so it matches the
7738 * physical file. The marker may appear on the first line OR on the second
7739 * line if a shebang was preserved as line 1. We therefore check the first
7740 * non-shebang line for the marker and, if the error lies after that line,
7741 * subtract exactly one from the reported line. */
7742 {
7743 const char *marker0 = "// __include_begin__: ";
7744 size_t m0 = strlen(marker0);
7745
7746 /* Determine start of first logical line to examine (skip shebang) */
7747 size_t start = 0;
7748 if (compile_len >= 2 && compile_src[0] == '#' && compile_src[1] == '!') {
7749 /* skip to end of shebang line (handle CR/LF/CRLF) */
7750 while (start < compile_len && compile_src[start] != '\n' && compile_src[start] != '\r') start++;
7751 if (start < compile_len && compile_src[start] == '\r') {
7752 start++;
7753 if (start < compile_len && compile_src[start] == '\n') start++;
7754 } else if (start < compile_len && compile_src[start] == '\n') {
7755 start++;
7756 }
7757 }
7758
7759 /* Find beginning and end of that (first non-shebang) line */
7760 size_t ls = start;
7761 size_t eol0 = ls;
7762 while (eol0 < compile_len && compile_src[eol0] != '\n') eol0++;
7763
7764 if (ls + m0 <= compile_len && strncmp(compile_src + ls, marker0, m0) == 0) {
7765 /* If the error is positioned after the synthetic marker line, reduce the outward line */
7766 if (g_err_pos > eol0) {
7767 if (line > 1) line -= 1;
7768 }
7769 }
7770 }
7771
7772 g_err_line = line;
7773 g_err_col = col;
7774
7775 /* Try to locate include context marker preceding error */
7776 const char *marker = "// __include_begin__: ";
7777 size_t mlen = strlen(marker);
7778 int inner_line = -1;
7779 int base_line = 1;
7780 char inc_path[512];
7781 inc_path[0] = '\0';
7782 /* scan backward to find last marker line */
7783 size_t scan = g_err_pos;
7784 while (scan > 0) {
7785 /* find start of current line */
7786 size_t ls = scan;
7787 while (ls > 0 && compile_src[ls - 1] != '\n')
7788 ls--;
7789 /* check if this line starts with marker */
7790 if (ls + mlen <= compile_len && strncmp(compile_src + ls, marker, mlen) == 0) {
7791 /* Parse marker: path [as alias] [@line N] */
7792 size_t p = ls + mlen;
7793 size_t eol = p;
7794 while (eol < compile_len && compile_src[eol] != '\n') eol++;
7795 /* locate separators */
7796 size_t pos_as = eol, pos_line = eol;
7797 for (size_t t = p; t + 3 < eol; ++t) {
7798 if (compile_src[t] == ' ' && strncmp(compile_src + t, " as ", 4) == 0) { pos_as = t; break; }
7799 }
7800 for (size_t t = p; t + 6 < eol; ++t) {
7801 if (compile_src[t] == ' ' && strncmp(compile_src + t, " @line ", 7) == 0) { pos_line = t; break; }
7802 }
7803 size_t path_end = pos_as < pos_line ? pos_as : pos_line;
7804 if (path_end < p) path_end = eol;
7805 size_t copy = (path_end - p) < sizeof(inc_path) - 1 ? (path_end - p) : sizeof(inc_path) - 1;
7806 memcpy(inc_path, compile_src + p, copy);
7807 inc_path[copy] = '\0';
7808
7809 /* parse optional base line value */
7810 base_line = 1;
7811 if (pos_line < eol) {
7812 size_t num_start = pos_line + 7;
7813 while (num_start < eol && compile_src[num_start] == ' ') num_start++;
7814 int v = 0;
7815 while (num_start < eol && compile_src[num_start] >= '0' && compile_src[num_start] <= '9') {
7816 v = v * 10 + (compile_src[num_start] - '0');
7817 num_start++;
7818 }
7819 if (v > 0) base_line = v;
7820 }
7821
7822 /* compute inner line as number of newlines from (eol+1) to error position */
7823 int count = 1;
7824 size_t q = (eol < compile_len && compile_src[eol] == '\n') ? (eol + 1) : eol;
7825 while (q < g_err_pos) {
7826 if (compile_src[q] == '\n') count++;
7827 q++;
7828 }
7829 inner_line = count;
7830 break;
7831 }
7832 /* move to previous line */
7833 if (ls == 0) break;
7834 scan = ls - 1;
7835 }
7836
7837 if (inner_line > 0 && inc_path[0] != '\0') {
7838 /* Adjust for shebang in included physical file */
7839 int shebang_adjust = 0;
7840 FILE *sf = fopen(inc_path, "rb");
7841 if (sf) {
7842 int c1 = fgetc(sf);
7843 int c2 = fgetc(sf);
7844 if (c1 == '#' && c2 == '!') shebang_adjust = 1;
7845 fclose(sf);
7846 }
7847 int mapped_inner = inner_line + (base_line - 1) + shebang_adjust;
7848 /* If the include context points to the same top-level path, suppress the redundant trailer. */
7849 if (path && strcmp(path, inc_path) == 0) {
7850 fprintf(stderr, "Parse error %s:%d:%d: %s\n",
7851 path, line, col, g_err_msg);
7852 } else {
7853 fprintf(stderr, "Parse error %s:%d:%d: %s (in %s:%d)\n",
7854 path ? path : "<input>", line, col, g_err_msg, inc_path, mapped_inner);
7855 }
7856 } else {
7857 fprintf(stderr, "Parse error %s:%d:%d: %s\n", path ? path : "<input>", line, col, g_err_msg);
7858 }
7859
7860 if (bc) bytecode_free(bc);
7861 if (prep) free(prep);
7862 free(src);
7863 return NULL;
7864 }
7865
7866 if (prep) free(prep);
7867 free(src);
7868 return bc;
7869}
7870
7871/**
7872 * @brief Parse a source string and return compiled bytecode.
7873 *
7874 * Suitable for REPL or tests. Performs include preprocessing with no base
7875 * path, compiles, and attaches generic source metadata.
7876 *
7877 * @param source NUL-terminated source code string.
7878 * @return Bytecode pointer on success; NULL on parse error.
7879 */
7881 if (!source) {
7882 fprintf(stderr, "Error: null source provided\n");
7883 return NULL;
7884 }
7885
7886 /* Preprocess includes before compiling */
7887 char *prep = preprocess_includes(source);
7888 const char *compile_src = prep ? prep : source;
7889 size_t len = strlen(compile_src);
7890
7891 /* reset error state */
7892 g_has_error = 0;
7893 g_err_pos = 0;
7894 g_err_msg[0] = '\0';
7895 g_err_line = 0;
7896 g_err_col = 0;
7897
7898 /* Set active source for error mapping/cascade control and current source path to <input> for nested bytecodes */
7899 const char *prev_src = g_current_source_path;
7900 const char *prev_active_src = g_active_src;
7901 size_t prev_active_len = g_active_len;
7902 int prev_last_line = g_last_err_line;
7903 int prev_line_count = g_line_err_count;
7904 g_active_src = compile_src;
7905 g_active_len = len;
7906 g_last_err_line = -1;
7907 g_line_err_count = 0;
7908 g_current_source_path = NULL;
7909 Bytecode *bc = compile_minimal(compile_src, len);
7910 if (bc) {
7911 if (bc->source_file) free((void *)bc->source_file);
7912 bc->source_file = strdup("<input>");
7913 if (bc->name) free((void *)bc->name);
7914 bc->name = strdup("<input>");
7915 }
7916 g_current_source_path = prev_src;
7917 g_active_src = prev_active_src;
7918 g_active_len = prev_active_len;
7919 g_last_err_line = prev_last_line;
7920 g_line_err_count = prev_line_count;
7921
7922 if (g_has_error) {
7923 int line = 1, col = 1;
7924 calc_line_col(compile_src, len, g_err_pos, &line, &col);
7925 g_err_line = line;
7926 g_err_col = col;
7927 if (bc) bytecode_free(bc);
7928 if (prep) free(prep);
7929 return NULL;
7930 }
7931 if (prep) free(prep);
7932 return bc;
7933}
7934
7935/**
7936 * @brief Retrieve the last parser/compiler error information, if any.
7937 *
7938 * Copies the error message into msgBuf (truncated to msgCap-1), and returns
7939 * the one-based line and column where available. If no error is pending,
7940 * returns 0 and leaves outputs unchanged.
7941 *
7942 * @param msgBuf Destination buffer for the error message (may be NULL).
7943 * @param msgCap Capacity of msgBuf in bytes.
7944 * @param outLine Optional out param for one-based line number.
7945 * @param outCol Optional out param for one-based column number.
7946 * @return 1 if an error was available and copied, 0 otherwise.
7947 */
7948int parser_last_error(char *msgBuf, unsigned long msgCap, int *outLine, int *outCol) {
7949 if (!g_has_error) return 0;
7950 if (msgBuf && msgCap > 0) {
7951 snprintf(msgBuf, msgCap, "%s", g_err_msg);
7952 }
7953 if (outLine) *outLine = g_err_line;
7954 if (outCol) *outCol = g_err_col;
7955 return 1;
7956}
Bytecode * bytecode_new(void)
Allocate and initialize an empty Bytecode object.
Definition bytecode.c:27
int bytecode_add_instruction(Bytecode *bc, OpCode op, int32_t operand)
Append a single instruction to the instruction stream.
Definition bytecode.c:74
void bytecode_free(Bytecode *bc)
Free a Bytecode and all memory it owns.
Definition bytecode.c:104
void bytecode_dump(const Bytecode *bc)
Print a human-readable dump of constants and instructions to stdout.
Definition bytecode.c:435
void bytecode_set_operand(Bytecode *bc, int idx, int32_t operand)
Patch the operand of a previously emitted instruction.
Definition bytecode.c:90
int bytecode_add_constant(Bytecode *bc, Value v)
Append a constant to a Bytecode's constant table with de-duplication.
Definition bytecode.c:51
@ OP_OPENSSL_SHA512
Definition bytecode.h:210
@ OP_PCSC_RELEASE
Definition bytecode.h:196
@ OP_GCD
Definition bytecode.h:279
@ OP_JSON_PARSE
Definition bytecode.h:173
@ OP_READ_FILE
Definition bytecode.h:136
@ OP_KCGI_REPLY_START
Definition bytecode.h:149
@ OP_CLOCK_MONO_MS
Definition bytecode.h:145
@ OP_CALL
Definition bytecode.h:60
@ OP_RUST_HELLO_ARGS_RETURN
Definition bytecode.h:291
@ OP_ROTL
Definition bytecode.h:169
@ OP_TAN
Definition bytecode.h:272
@ OP_OPENSSL_SHA256
Definition bytecode.h:209
@ OP_JSON_STRINGIFY
Definition bytecode.h:174
@ OP_PCSC_TRANSMIT
Definition bytecode.h:200
@ OP_TRUNC
Definition bytecode.h:266
@ OP_CEIL
Definition bytecode.h:265
@ OP_SPLIT
Definition bytecode.h:101
@ OP_CAST
Definition bytecode.h:95
@ OP_EQ
Definition bytecode.h:53
@ OP_KCGI_END
Definition bytecode.h:151
@ OP_PCRE2_MATCH
Definition bytecode.h:204
@ OP_RANDOM_INT
Definition bytecode.h:127
@ OP_TRY_POP
Definition bytecode.h:260
@ OP_LTE
Definition bytecode.h:50
@ OP_SOCK_TCP_ACCEPT
Definition bytecode.h:232
@ OP_NEQ
Definition bytecode.h:54
@ OP_ISQRT
Definition bytecode.h:281
@ OP_FMAX
Definition bytecode.h:286
@ OP_CPP_ADD
Definition bytecode.h:296
@ OP_PCSC_ESTABLISH
Definition bytecode.h:195
@ OP_SQLITE_EXEC
Definition bytecode.h:186
@ OP_FMIN
Definition bytecode.h:285
@ OP_SWAP
Definition bytecode.h:76
@ OP_SOCK_TCP_LISTEN
Definition bytecode.h:231
@ OP_OPENSSL_RIPEMD160
Definition bytecode.h:211
@ OP_PCSC_LIST_READERS
Definition bytecode.h:197
@ OP_BOR
Definition bytecode.h:164
@ OP_CURL_POST
Definition bytecode.h:180
@ OP_XML_NAME
Definition bytecode.h:227
@ OP_KCGI_PARSE
Definition bytecode.h:148
@ OP_REGEX_SEARCH
Definition bytecode.h:108
@ OP_POW
Definition bytecode.h:125
@ OP_NOT
Definition bytecode.h:73
@ OP_SQRT
Definition bytecode.h:276
@ OP_SOCK_RECV
Definition bytecode.h:235
@ OP_MOD
Definition bytecode.h:70
@ OP_REDIS_CMD
Definition bytecode.h:191
@ OP_INI_UNSET
Definition bytecode.h:221
@ OP_WRITE_FILE
Definition bytecode.h:137
@ OP_SQLITE_CLOSE
Definition bytecode.h:185
@ OP_FIND
Definition bytecode.h:104
@ OP_SIGN
Definition bytecode.h:282
@ OP_DUP
Definition bytecode.h:75
@ OP_OS_LIST_DIR
Definition bytecode.h:249
@ OP_KCGI_WRITE
Definition bytecode.h:150
@ OP_ENV_ALL
Definition bytecode.h:153
@ OP_VALUES
Definition bytecode.h:132
@ OP_RANDOM_NUMBER
Definition bytecode.h:160
@ OP_INDEX_GET
Definition bytecode.h:80
@ OP_LCM
Definition bytecode.h:280
@ OP_LEN
Definition bytecode.h:84
@ OP_THREAD_SPAWN
Definition bytecode.h:157
@ OP_INDEX_OF
Definition bytecode.h:113
@ OP_LINE
Definition bytecode.h:67
@ OP_MIN
Definition bytecode.h:121
@ OP_SLEEP_MS
Definition bytecode.h:159
@ OP_REGEX_REPLACE
Definition bytecode.h:109
@ OP_MAX
Definition bytecode.h:122
@ OP_SQLITE_OPEN
Definition bytecode.h:184
@ OP_XML_PARSE
Definition bytecode.h:225
@ OP_STORE_LOCAL
Definition bytecode.h:39
@ OP_SLICE
Definition bytecode.h:90
@ OP_INI_GET_DOUBLE
Definition bytecode.h:218
@ OP_JOIN
Definition bytecode.h:102
@ OP_COS
Definition bytecode.h:271
@ OP_BAND
Definition bytecode.h:163
@ OP_ECHO
Definition bytecode.h:64
@ OP_ROTR
Definition bytecode.h:170
@ OP_DATE_FORMAT
Definition bytecode.h:152
@ OP_PROC_SYSTEM
Definition bytecode.h:143
@ OP_OPENSSL_MD5
Definition bytecode.h:208
@ OP_ZIP
Definition bytecode.h:118
@ OP_SUB
Definition bytecode.h:45
@ OP_INI_GET_INT
Definition bytecode.h:217
@ OP_XML_ROOT
Definition bytecode.h:226
@ OP_JUMP
Definition bytecode.h:57
@ OP_DIV
Definition bytecode.h:47
@ OP_SHR
Definition bytecode.h:168
@ OP_SERIAL_OPEN
Definition bytecode.h:252
@ OP_JSON_TO_FILE
Definition bytecode.h:176
@ OP_LT
Definition bytecode.h:49
@ OP_INI_GET_BOOL
Definition bytecode.h:219
@ OP_ROUND
Definition bytecode.h:267
@ OP_PRINT
Definition bytecode.h:63
@ OP_INI_GET_STRING
Definition bytecode.h:216
@ OP_SHL
Definition bytecode.h:167
@ OP_KEYS
Definition bytecode.h:131
@ OP_RUST_HELLO
Definition bytecode.h:289
@ OP_LOAD_LOCAL
Definition bytecode.h:38
@ OP_CURL_DOWNLOAD
Definition bytecode.h:181
@ OP_PCSC_CONNECT
Definition bytecode.h:198
@ OP_RUST_SET_EXIT
Definition bytecode.h:293
@ OP_ENV
Definition bytecode.h:140
@ OP_PUSH
Definition bytecode.h:85
@ OP_TO_NUMBER
Definition bytecode.h:93
@ OP_LOAD_GLOBAL
Definition bytecode.h:41
@ OP_FUN_VERSION
Definition bytecode.h:154
@ OP_SERIAL_SEND
Definition bytecode.h:254
@ OP_LOAD_CONST
Definition bytecode.h:37
@ OP_ADD
Definition bytecode.h:44
@ OP_CONTAINS
Definition bytecode.h:112
@ OP_SOCK_SEND
Definition bytecode.h:234
@ OP_INPUT_LINE
Definition bytecode.h:141
@ OP_FD_POLL_READ
Definition bytecode.h:242
@ OP_MUL
Definition bytecode.h:46
@ OP_SERIAL_CONFIG
Definition bytecode.h:253
@ OP_SOCK_UNIX_CONNECT
Definition bytecode.h:238
@ OP_PCRE2_FINDALL
Definition bytecode.h:205
@ OP_SET
Definition bytecode.h:87
@ OP_PROC_RUN
Definition bytecode.h:142
@ OP_LOG10
Definition bytecode.h:275
@ OP_ENUMERATE
Definition bytecode.h:117
@ OP_FD_POLL_WRITE
Definition bytecode.h:243
@ OP_PCRE2_TEST
Definition bytecode.h:203
@ OP_SIN
Definition bytecode.h:270
@ OP_TIME_NOW_MS
Definition bytecode.h:144
@ OP_SCLAMP
Definition bytecode.h:98
@ OP_XML_TEXT
Definition bytecode.h:228
@ OP_FD_SET_NONBLOCK
Definition bytecode.h:241
@ OP_STORE_GLOBAL
Definition bytecode.h:42
@ OP_SOCK_TCP_CONNECT
Definition bytecode.h:233
@ OP_SUBSTR
Definition bytecode.h:103
@ OP_UCLAMP
Definition bytecode.h:97
@ OP_REDIS_CONNECT
Definition bytecode.h:190
@ OP_INI_SET
Definition bytecode.h:220
@ OP_TO_STRING
Definition bytecode.h:94
@ OP_SOCK_CLOSE
Definition bytecode.h:236
@ OP_GT
Definition bytecode.h:51
@ OP_INI_SAVE
Definition bytecode.h:222
@ OP_SERIAL_RECV
Definition bytecode.h:255
@ OP_GTE
Definition bytecode.h:52
@ OP_HALT
Definition bytecode.h:65
@ OP_ABS
Definition bytecode.h:124
@ OP_INI_LOAD
Definition bytecode.h:214
@ OP_TYPEOF
Definition bytecode.h:96
@ OP_RETURN
Definition bytecode.h:61
@ OP_REGEX_MATCH
Definition bytecode.h:107
@ OP_EXIT
Definition bytecode.h:246
@ OP_SQLITE_QUERY
Definition bytecode.h:187
@ OP_APOP
Definition bytecode.h:86
@ OP_SOCK_UNIX_LISTEN
Definition bytecode.h:237
@ OP_BNOT
Definition bytecode.h:166
@ OP_THREAD_JOIN
Definition bytecode.h:158
@ OP_CLAMP
Definition bytecode.h:123
@ OP_SERIAL_CLOSE
Definition bytecode.h:256
@ OP_RUST_HELLO_ARGS
Definition bytecode.h:290
@ OP_CURL_GET
Definition bytecode.h:179
@ OP_MAKE_ARRAY
Definition bytecode.h:79
@ OP_REDIS_CLOSE
Definition bytecode.h:192
@ OP_TRY_PUSH
Definition bytecode.h:259
@ OP_CLEAR
Definition bytecode.h:114
@ OP_RANDOM_SEED
Definition bytecode.h:126
@ OP_INDEX_SET
Definition bytecode.h:81
@ OP_LOG
Definition bytecode.h:274
@ OP_INI_FREE
Definition bytecode.h:215
@ OP_REMOVE
Definition bytecode.h:89
@ OP_THROW
Definition bytecode.h:261
@ OP_HAS_KEY
Definition bytecode.h:133
@ OP_PCSC_DISCONNECT
Definition bytecode.h:199
@ OP_FLOOR
Definition bytecode.h:264
@ OP_JSON_FROM_FILE
Definition bytecode.h:175
@ OP_INSERT
Definition bytecode.h:88
@ OP_EXP
Definition bytecode.h:273
@ OP_RUST_GET_SP
Definition bytecode.h:292
@ OP_JUMP_IF_FALSE
Definition bytecode.h:58
@ OP_MAKE_MAP
Definition bytecode.h:130
@ OP_POP
Definition bytecode.h:56
@ OP_BXOR
Definition bytecode.h:165
int is_class[MAX_GLOBALS]
Definition parser.c:285
static void parser_fail(size_t pos, const char *fmt,...)
Record a parser/compiler error at a given source position.
Definition parser.c:146
#define TYPE_META_NIL
Type metadata tag indicating explicit nil type.
Definition parser.c:128
static LocalEnv * g_func_env_stack[64]
Definition parser.c:335
static int local_find(const char *name)
Find the index of a local variable in the current function.
Definition parser.c:375
static int fun_debug_enabled(void)
Determine whether parser/compiler debug tracing is enabled.
Definition parser.c:109
static int emit_and_expr(Bytecode *bc, const char *src, size_t len, size_t *pos)
Parse and emit logical AND (&&) with short-circuiting.
Definition parser.c:4815
static int g_temp_counter
Definition parser.c:76
char * names[MAX_GLOBALS]
Definition parser.c:283
static int emit_relational(Bytecode *bc, const char *src, size_t len, size_t *pos)
Parse and emit relational expressions.
Definition parser.c:4720
int types[MAX_GLOBALS]
Definition parser.c:284
static int local_add(const char *name)
Add a new local variable to the current function environment.
Definition parser.c:389
#define TYPE_META_BOOLEAN
Type metadata tag used for boolean enforcement in declared types.
Definition parser.c:126
static size_t g_err_pos
Definition parser.c:70
static int env_truthy(const char *name)
Interpret an environment variable as a boolean flag.
Definition parser.c:88
static struct @204206037126360267375134217342340021000343236145 G
static void calc_line_col(const char *src, size_t len, size_t pos, int *out_line, int *out_col)
Compute one-based line and column from a byte position.
Definition parser.c:183
static int emit_or_expr(Bytecode *bc, const char *src, size_t len, size_t *pos)
Parse and emit logical OR (||) with short-circuiting.
Definition parser.c:4886
static int g_err_line
Definition parser.c:72
static const char * g_active_src
Definition parser.c:64
static int is_ns_alias(const char *name)
Check whether an identifier is a registered namespace alias.
Definition parser.c:271
static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
Parse and emit bytecode for primary expressions.
Definition parser.c:416
#define TYPE_META_ARRAY
Type metadata tag marking array values.
Definition parser.c:134
static int sym_find(const char *name)
Find a global symbol index by name.
Definition parser.c:295
#define TYPE_META_FLOAT
Type metadata tag marking floating point numbers.
Definition parser.c:132
static int g_last_err_line
Definition parser.c:66
Bytecode * parse_string_to_bytecode(const char *source)
Parse a source string and return compiled bytecode.
Definition parser.c:7880
static int emit_equality(Bytecode *bc, const char *src, size_t len, size_t *pos)
Parse and emit equality/inequality expressions.
Definition parser.c:4776
static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, int current_indent)
Parse a block of statements at a given indentation level.
Definition parser.c:6087
int parser_last_error(char *msgBuf, unsigned long msgCap, int *outLine, int *outCol)
Retrieve the last parser/compiler error information, if any.
Definition parser.c:7948
#define MAP_TYPE_KIND(t)
static int emit_additive(Bytecode *bc, const char *src, size_t len, size_t *pos)
Parse and emit additive expressions.
Definition parser.c:4682
#define TYPE_META_CLASS
Type metadata tag marking class/instance values.
Definition parser.c:130
static int emit_conditional(Bytecode *bc, const char *src, size_t len, size_t *pos)
Parse and emit the ternary conditional operator.
Definition parser.c:4957
static void ns_aliases_reset(void)
Reset and free all tracked namespace aliases.
Definition parser.c:209
static int g_line_err_count
Definition parser.c:67
static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, size_t *pos)
Parse a single statement on the current line and emit bytecode.
Definition parser.c:5222
static int emit_multiplicative(Bytecode *bc, const char *src, size_t len, size_t *pos)
Parse and emit multiplicative expressions.
Definition parser.c:4619
#define TYPE_META_STRING
Type metadata tag used for string enforcement in declared types.
Definition parser.c:124
static char * g_ns_aliases[64]
Definition parser.c:203
static int read_line_start(const char *src, size_t len, size_t *pos, int *out_indent)
Read the start of a logical line and compute indentation.
Definition parser.c:5129
static Bytecode * compile_minimal(const char *src, size_t len)
Compile a full source buffer into bytecode.
Definition parser.c:7652
static char g_err_msg[256]
Definition parser.c:71
static LocalEnv * g_locals
Definition parser.c:332
static void ns_aliases_scan(const char *src, size_t len)
Scan preprocessed source for namespace alias markers.
Definition parser.c:227
static int emit_unary(Bytecode *bc, const char *src, size_t len, size_t *pos)
Parse and emit unary expressions.
Definition parser.c:4582
static int sym_index(const char *name)
Get or create a global symbol index for a name.
Definition parser.c:312
static int emit_expression(Bytecode *bc, const char *src, size_t len, size_t *pos)
Parse and emit a full expression using precedence climbing.
Definition parser.c:5018
static const int G_ERRS_PER_LINE_CAP
Definition parser.c:68
int count
Definition parser.c:286
static int g_ns_alias_count
Definition parser.c:204
static const char * g_current_source_path
Definition parser.c:62
char * preprocess_includes_with_path(const char *src, const char *current_path)
Preprocess includes with a known file path to improve span markers.
static int g_func_env_depth
Definition parser.c:336
static LoopCtx * g_loop_ctx
Definition parser.c:367
static int g_err_col
Definition parser.c:73
static void skip_to_eol(const char *src, size_t len, size_t *pos)
Advance position to the end of the current line, validating tail.
Definition parser.c:5041
static size_t g_active_len
Definition parser.c:65
static int name_in_outer_envs(const char *name)
Check whether a local name exists in any outer function environment.
Definition parser.c:346
static int g_has_error
Definition parser.c:69
Bytecode * parse_file_to_bytecode(const char *path)
Parse a .fun source file and return compiled bytecode.
Definition parser.c:7683
Public API for parsing Fun source into bytecode.
Low-level parsing helpers and include preprocessor for the Fun parser.
static double parse_float_literal_value(const char *src, size_t len, size_t *pos, int *ok)
Parse a floating-point literal (supports . and scientific notation).
static int read_identifier_into(const char *src, size_t len, size_t *pos, char **out_name)
Read an identifier starting at pos and allocate its name.
static void skip_ws(const char *src, size_t len, size_t *pos)
Skip spaces, tabs, carriage returns and newlines.
static uint64_t parse_int_literal_value(const char *src, size_t len, size_t *pos, int *ok)
Parse an integer literal (decimal or 0x-hex) with optional sign.
static int consume_char(const char *src, size_t len, size_t *pos, char expected)
Consume expected character after skipping whitespace.
static void skip_comments(const char *src, size_t len, size_t *pos)
Skip whitespace, then line and block comments. Continues until the next non-comment,...
static void skip_spaces(const char *src, size_t len, size_t *pos)
Skip only spaces, tabs and carriage returns (not newlines).
static void skip_shebang_if_present(const char *src, size_t len, size_t *pos)
Skip a top-of-file shebang line that starts with "#!" if present.
static char * read_file_all(const char *path, size_t *out_len)
Read entire file into a newly allocated buffer.
static int starts_with(const char *src, size_t len, size_t pos, const char *kw)
Check if src starting at pos begins with kw and fits in len.
static char * parse_string_literal_any_quote(const char *src, size_t len, size_t *pos)
Parse a single-quoted or double-quoted string literal.
char * preprocess_includes(const char *src)
Public wrapper to preprocess includes without a current path.
const char * source_file
Definition bytecode.h:316
const char * name
Definition bytecode.h:315
int instr_count
Definition bytecode.h:309
int types[MAX_FRAME_LOCALS]
Definition parser.c:328
char * names[MAX_FRAME_LOCALS]
Definition parser.c:327
int count
Definition parser.c:329
struct LoopCtx * prev
Definition parser.c:364
int break_count
Definition parser.c:361
int cont_count
Definition parser.c:363
int continue_jumps[64]
Definition parser.c:362
int break_jumps[64]
Definition parser.c:360
Value make_bool(int v)
Construct a boolean Value.
Definition value.c:79
Value make_nil(void)
Construct a nil Value.
Definition value.c:126
Value make_string(const char *s)
Construct a string Value by duplicating the given C string.
Definition value.c:95
Value make_function(struct Bytecode *fn)
Construct a function Value referencing bytecode.
Definition value.c:114
Value make_float(double v)
Construct a Value representing a double-precision float.
Definition value.c:64
Value make_int(int64_t v)
Construct a Value representing a 64-bit integer.
Definition value.c:51
Defines the Value type and associated functions for the Fun VM.
#define fprintf
Definition vm.c:194
Core virtual machine data structures and public VM API.
#define MAX_GLOBALS
Definition vm.h:34
#define MAX_FRAME_LOCALS
Definition vm.h:30