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