7730 lines
266 KiB
C
7730 lines
266 KiB
C
/**
|
|
* This file is part of the Fun programming language.
|
|
* https://fun-lang.xyz/
|
|
*
|
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
|
* Licensed under the terms of the Apache-2.0 license.
|
|
* https://opensource.org/license/apache-2-0
|
|
*/
|
|
|
|
/**
|
|
* @file parser.c
|
|
* @brief Implements the Fun language parser that converts source code to bytecode.
|
|
*
|
|
* This file contains the main parsing logic for the Fun programming language.
|
|
* It handles converting .fun source files into executable bytecode for the VM.
|
|
*
|
|
* Key Features:
|
|
* - Handles shebang lines
|
|
* - Skips whitespace and comments
|
|
* - Parses string literals with both single and double quotes
|
|
* - Supports basic function definitions
|
|
* - Compiles print statements
|
|
* - Generates bytecode with proper constants and instructions
|
|
*
|
|
* Functions:
|
|
* - parse_file_to_bytecode(): Main entry point for file parsing
|
|
* - parse_string_to_bytecode(): Parses code from string buffers
|
|
* - parser_last_error(): Retrieves parsing errors
|
|
*
|
|
* Error Handling:
|
|
* - Returns NULL on parse errors
|
|
* - Tracks error messages and positions
|
|
* - Validates syntax before bytecode generation
|
|
*
|
|
* Example:
|
|
* Bytecode *bc = parse_file_to_bytecode("example.fun");
|
|
* if (bc) {
|
|
* vm_run(&vm, bc);
|
|
* bytecode_free(bc);
|
|
* }
|
|
*
|
|
* @author Johannes Findeisen
|
|
* @date 2025-09-16
|
|
*/
|
|
|
|
#include "parser.h"
|
|
#include "value.h"
|
|
#include "vm.h"
|
|
#include <ctype.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/* ---- parser error state ---- */
|
|
static const char *g_current_source_path = NULL; /* for propagating filename into nested bytecodes */
|
|
static int g_has_error = 0;
|
|
static size_t g_err_pos = 0;
|
|
static char g_err_msg[256];
|
|
static int g_err_line = 0;
|
|
static int g_err_col = 0;
|
|
|
|
/* ---- compiler-generated temporary counter ---- */
|
|
static int g_temp_counter = 0;
|
|
|
|
/* ---- runtime debug control (for suppressing noisy stdout in production/CGI) ---- */
|
|
static int env_truthy(const char *name) {
|
|
const char *v = getenv(name);
|
|
if (!v) return 0;
|
|
if (strcmp(v, "1") == 0) return 1;
|
|
if (strcmp(v, "true") == 0) return 1;
|
|
if (strcmp(v, "TRUE") == 0) return 1;
|
|
if (strcmp(v, "yes") == 0) return 1;
|
|
if (strcmp(v, "YES") == 0) return 1;
|
|
if (strcmp(v, "on") == 0) return 1;
|
|
if (strcmp(v, "ON") == 0) return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int fun_debug_enabled(void) {
|
|
/* Allow enabling debug dumps at runtime via environment.
|
|
* Default is OFF to avoid contaminating stdout (e.g., CGI responses).
|
|
*/
|
|
if (env_truthy("FUN_TRACE")) return 1;
|
|
if (env_truthy("FUN_DEBUG")) return 1;
|
|
return 0;
|
|
}
|
|
|
|
/* Declared type metadata encoding in types[]:
|
|
0 = dynamic/untyped;
|
|
positive/negative 8/16/32/64 = integers (negative means signed);
|
|
TYPE_META_STRING/BOOLEAN/NIL mark non-integer enforced types;
|
|
TYPE_META_CLASS marks class instances (Map with "__class"). */
|
|
#define TYPE_META_STRING 10001
|
|
#define TYPE_META_BOOLEAN 10002
|
|
#define TYPE_META_NIL 10003
|
|
#define TYPE_META_CLASS 10004
|
|
#define TYPE_META_FLOAT 10005
|
|
#define TYPE_META_ARRAY 10006
|
|
|
|
static void parser_fail(size_t pos, const char *fmt, ...) {
|
|
g_has_error = 1;
|
|
g_err_pos = pos;
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
vsnprintf(g_err_msg, sizeof(g_err_msg), fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
static void calc_line_col(const char *src, size_t len, size_t pos, int *out_line, int *out_col) {
|
|
int line = 1, col = 1;
|
|
size_t limit = pos < len ? pos : len;
|
|
for (size_t i = 0; i < limit; ++i) {
|
|
if (src[i] == '\n') {
|
|
line++;
|
|
col = 1;
|
|
} else {
|
|
col++;
|
|
}
|
|
}
|
|
if (out_line) *out_line = line;
|
|
if (out_col) *out_col = col;
|
|
}
|
|
|
|
/* --------------------------- */
|
|
|
|
/* Namespace alias tracking (for include-as):
|
|
We scan preprocessed source for lines starting with "// __ns_alias__: <name>"
|
|
and treat dot-calls on those identifiers as plain function calls (no implicit 'this'). */
|
|
static char *g_ns_aliases[64];
|
|
static int g_ns_alias_count = 0;
|
|
|
|
static void ns_aliases_reset(void) {
|
|
for (int i = 0; i < g_ns_alias_count; ++i) {
|
|
free(g_ns_aliases[i]);
|
|
g_ns_aliases[i] = NULL;
|
|
}
|
|
g_ns_alias_count = 0;
|
|
}
|
|
|
|
static void ns_aliases_scan(const char *src, size_t len) {
|
|
const char *marker = "// __ns_alias__: ";
|
|
size_t mlen = strlen(marker);
|
|
size_t i = 0;
|
|
while (i < len) {
|
|
/* find start of line */
|
|
size_t ls = i;
|
|
/* move to end of line first */
|
|
while (i < len && src[i] != '\n')
|
|
i++;
|
|
size_t le = i;
|
|
/* include trailing '\n' in next iteration */
|
|
if (i < len && src[i] == '\n') i++;
|
|
|
|
if (le - ls >= mlen && strncmp(src + ls, marker, mlen) == 0) {
|
|
size_t p = ls + mlen;
|
|
/* read identifier */
|
|
size_t start = p;
|
|
while (p < le && (src[p] == ' ' || src[p] == '\t'))
|
|
p++;
|
|
if (p < le && (isalpha((unsigned char)src[p]) || src[p] == '_')) {
|
|
size_t q = p + 1;
|
|
while (q < le && (isalnum((unsigned char)src[q]) || src[q] == '_'))
|
|
q++;
|
|
size_t n = q - p;
|
|
if (n > 0 && g_ns_alias_count < (int)(sizeof(g_ns_aliases) / sizeof(g_ns_aliases[0]))) {
|
|
char *name = (char *)malloc(n + 1);
|
|
if (name) {
|
|
memcpy(name, src + p, n);
|
|
name[n] = '\0';
|
|
g_ns_aliases[g_ns_alias_count++] = name;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int is_ns_alias(const char *name) {
|
|
if (!name) return 0;
|
|
for (int i = 0; i < g_ns_alias_count; ++i) {
|
|
if (strcmp(g_ns_aliases[i], name) == 0) return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#include "parser_utils.c"
|
|
|
|
/* very small global symbol table for LOAD_GLOBAL/STORE_GLOBAL */
|
|
static struct {
|
|
char *names[MAX_GLOBALS];
|
|
int types[MAX_GLOBALS]; /* 0=untyped/number default; else bit width: 8/16/32/64; negative for signed */
|
|
int is_class[MAX_GLOBALS]; /* 1 if this global name denotes a class factory */
|
|
int count;
|
|
} G = {{0}, {0}, {0}, 0};
|
|
|
|
static int sym_index(const char *name) {
|
|
for (int i = 0; i < G.count; ++i) {
|
|
if (strcmp(G.names[i], name) == 0) return i;
|
|
}
|
|
if (G.count >= MAX_GLOBALS) {
|
|
parser_fail(0, "Too many globals (max %d)", MAX_GLOBALS);
|
|
return 0;
|
|
}
|
|
G.names[G.count] = strdup(name);
|
|
G.types[G.count] = 0; /* default: untyped */
|
|
G.is_class[G.count] = 0; /* default: not a class */
|
|
return G.count++;
|
|
}
|
|
|
|
/* ---- locals environment for functions ---- */
|
|
typedef struct {
|
|
char *names[MAX_FRAME_LOCALS];
|
|
int types[MAX_FRAME_LOCALS]; /* 0=untyped; else bit width: 8/16/32/64 */
|
|
int count;
|
|
} LocalEnv;
|
|
|
|
static LocalEnv *g_locals = NULL;
|
|
|
|
/* loop context for break/continue patching */
|
|
typedef struct LoopCtx {
|
|
int break_jumps[64];
|
|
int break_count;
|
|
int continue_jumps[64];
|
|
int cont_count;
|
|
struct LoopCtx *prev;
|
|
} LoopCtx;
|
|
|
|
static LoopCtx *g_loop_ctx = NULL;
|
|
|
|
static int local_find(const char *name) {
|
|
if (!g_locals) return -1;
|
|
for (int i = 0; i < g_locals->count; ++i) {
|
|
if (strcmp(g_locals->names[i], name) == 0) return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int local_add(const char *name) {
|
|
if (!g_locals) return -1;
|
|
if (g_locals->count >= MAX_FRAME_LOCALS) {
|
|
parser_fail(0, "Too many local variables/parameters (max %d)", MAX_FRAME_LOCALS);
|
|
return -1;
|
|
}
|
|
int idx = g_locals->count++;
|
|
g_locals->names[idx] = strdup(name);
|
|
return idx;
|
|
}
|
|
|
|
/* forward declaration so helpers can recurse */
|
|
static int emit_expression(Bytecode *bc, const char *src, size_t len, size_t *pos);
|
|
|
|
/* primary: (expr) | string | number | true/false | identifier */
|
|
static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
|
skip_spaces(src, len, pos);
|
|
|
|
/* parenthesized */
|
|
if (*pos < len && src[*pos] == '(') {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected expression after '('");
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')'");
|
|
return 0;
|
|
}
|
|
/* postfix indexing or slice */
|
|
for (;;) {
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] == '[') {
|
|
(*pos)++;
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected start expression");
|
|
return 0;
|
|
}
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] == ':') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
size_t savep4 = *pos;
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
*pos = savep4;
|
|
int ci4 = bytecode_add_constant(bc, make_int(-1));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ci4);
|
|
}
|
|
if (!consume_char(src, len, pos, ']')) {
|
|
parser_fail(*pos, "Expected ']' after slice");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SLICE, 0);
|
|
continue;
|
|
} else {
|
|
if (!consume_char(src, len, pos, ']')) {
|
|
parser_fail(*pos, "Expected ']' after index");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
|
|
continue;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* string */
|
|
char *s = parse_string_literal_any_quote(src, len, pos);
|
|
if (s) {
|
|
int ci = bytecode_add_constant(bc, make_string(s));
|
|
free(s);
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
|
|
/* postfix indexing or slice */
|
|
for (;;) {
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] == '[') {
|
|
(*pos)++;
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected start expression");
|
|
return 0;
|
|
}
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] == ':') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
/* end is optional; if missing, use -1 (till end) */
|
|
int has_end = 0;
|
|
size_t savep = *pos;
|
|
if (emit_expression(bc, src, len, pos)) {
|
|
has_end = 1;
|
|
} else {
|
|
*pos = savep;
|
|
int ci = bytecode_add_constant(bc, make_int(-1));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
|
|
}
|
|
if (!consume_char(src, len, pos, ']')) {
|
|
parser_fail(*pos, "Expected ']' after slice");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SLICE, 0);
|
|
continue;
|
|
} else {
|
|
if (!consume_char(src, len, pos, ']')) {
|
|
parser_fail(*pos, "Expected ']' after index");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
|
|
continue;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* array literal: [expr, expr, ...] */
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] == '[') {
|
|
(*pos)++; /* '[' */
|
|
int count = 0;
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] != ']') {
|
|
for (;;) {
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected expression in array literal");
|
|
return 0;
|
|
}
|
|
count++;
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (!consume_char(src, len, pos, ']')) {
|
|
parser_fail(*pos, "Expected ']' to close array literal");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_MAKE_ARRAY, count);
|
|
/* postfix indexing or slice */
|
|
for (;;) {
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] == '[') {
|
|
(*pos)++;
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected start expression");
|
|
return 0;
|
|
}
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] == ':') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
size_t savep3 = *pos;
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
*pos = savep3;
|
|
int ci3 = bytecode_add_constant(bc, make_int(-1));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ci3);
|
|
}
|
|
if (!consume_char(src, len, pos, ']')) {
|
|
parser_fail(*pos, "Expected ']' after slice");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SLICE, 0);
|
|
continue;
|
|
} else {
|
|
if (!consume_char(src, len, pos, ']')) {
|
|
parser_fail(*pos, "Expected ']' after index");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
|
|
continue;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* map literal: { "key": expr, ... } */
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] == '{') {
|
|
(*pos)++; /* '{' */
|
|
int pairs = 0;
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] != '}') {
|
|
for (;;) {
|
|
/* key must be a string literal */
|
|
char *k = parse_string_literal_any_quote(src, len, pos);
|
|
if (!k) {
|
|
parser_fail(*pos, "Expected string key in map literal");
|
|
return 0;
|
|
}
|
|
int kci = bytecode_add_constant(bc, make_string(k));
|
|
free(k);
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, kci);
|
|
skip_spaces(src, len, pos);
|
|
if (!consume_char(src, len, pos, ':')) {
|
|
parser_fail(*pos, "Expected ':' after map key");
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected value expression in map literal");
|
|
return 0;
|
|
}
|
|
pairs++;
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (!consume_char(src, len, pos, '}')) {
|
|
parser_fail(*pos, "Expected '}' to close map literal");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_MAKE_MAP, pairs);
|
|
return 1;
|
|
}
|
|
|
|
/* number (prefer float first to consume cases like 1.23 or 1e2) */
|
|
int ok = 0;
|
|
size_t save = *pos;
|
|
/* float literal */
|
|
double fval = parse_float_literal_value(src, len, pos, &ok);
|
|
if (ok) {
|
|
int ci = bytecode_add_constant(bc, make_float(fval));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
|
|
/* postfix indexing or slice (not typical for floats but keep consistency) */
|
|
for (;;) {
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] == '[') {
|
|
(*pos)++;
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected start expression");
|
|
return 0;
|
|
}
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] == ':') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
size_t savep2 = *pos;
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
*pos = savep2;
|
|
int ci2 = bytecode_add_constant(bc, make_int(-1));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ci2);
|
|
}
|
|
if (!consume_char(src, len, pos, ']')) {
|
|
parser_fail(*pos, "Expected ']' after slice");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SLICE, 0);
|
|
continue;
|
|
} else {
|
|
if (!consume_char(src, len, pos, ']')) {
|
|
parser_fail(*pos, "Expected ']' after index");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
|
|
continue;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|
|
*pos = save;
|
|
/* integer literal */
|
|
int64_t ival = parse_int_literal_value(src, len, pos, &ok);
|
|
if (ok) {
|
|
int ci = bytecode_add_constant(bc, make_int(ival));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
|
|
/* postfix indexing or slice */
|
|
for (;;) {
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] == '[') {
|
|
(*pos)++;
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected start expression");
|
|
return 0;
|
|
}
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] == ':') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
int has_end = 0;
|
|
size_t savep2 = *pos;
|
|
if (emit_expression(bc, src, len, pos)) {
|
|
has_end = 1;
|
|
} else {
|
|
*pos = savep2;
|
|
int ci2 = bytecode_add_constant(bc, make_int(-1));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ci2);
|
|
}
|
|
if (!consume_char(src, len, pos, ']')) {
|
|
parser_fail(*pos, "Expected ']' after slice");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SLICE, 0);
|
|
continue;
|
|
} else {
|
|
if (!consume_char(src, len, pos, ']')) {
|
|
parser_fail(*pos, "Expected ']' after index");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
|
|
continue;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* identifier or keyword */
|
|
char *name = NULL;
|
|
if (read_identifier_into(src, len, pos, &name)) {
|
|
if (strcmp(name, "true") == 0 || strcmp(name, "false") == 0) {
|
|
int ci = bytecode_add_constant(bc, make_bool(strcmp(name, "true") == 0 ? 1 : 0));
|
|
free(name);
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
|
|
return 1;
|
|
}
|
|
|
|
/* call or variable load (locals preferred) */
|
|
skip_spaces(src, len, pos);
|
|
int local_idx = local_find(name);
|
|
int is_call = (*pos < len && src[*pos] == '(');
|
|
|
|
if (is_call) {
|
|
/* builtins */
|
|
if (strcmp(name, "len") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "len expects 1 argument");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after len arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_LEN, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "push") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "push expects array");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
} else {
|
|
parser_fail(*pos, "push expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "push expects value");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after push args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_PUSH, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "pop") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "pop expects array");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after pop arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_APOP, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "set") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "set expects array");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
} else {
|
|
parser_fail(*pos, "set expects 3 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "set expects index");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
} else {
|
|
parser_fail(*pos, "set expects 3 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "set expects value");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after set args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SET, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "insert") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "insert expects array");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
} else {
|
|
parser_fail(*pos, "insert expects 3 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "insert expects index");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
} else {
|
|
parser_fail(*pos, "insert expects 3 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "insert expects value");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after insert args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_INSERT, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "remove") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "remove expects array");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
} else {
|
|
parser_fail(*pos, "remove expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "remove expects index");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after remove args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_REMOVE, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "to_number") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "to_number expects 1 argument");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after to_number arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_TO_NUMBER, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "to_string") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "to_string expects 1 argument");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after to_string arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_TO_STRING, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "cast") == 0) {
|
|
(*pos)++; /* '(' */
|
|
/* cast(value, typeName) */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "cast expects (value, typeName)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "cast expects (value, typeName)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_CAST, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "typeof") == 0) {
|
|
(*pos)++; /* '(' */
|
|
/* Special handling for typeof(<identifier>) to return declared subtype for integers */
|
|
size_t peek = *pos;
|
|
char *vname = NULL;
|
|
int handled = 0;
|
|
if (read_identifier_into(src, len, &peek, &vname)) {
|
|
skip_spaces(src, len, &peek);
|
|
if (peek < len && src[peek] == ')') {
|
|
int meta = 0;
|
|
int lidx = local_find(vname);
|
|
if (lidx >= 0) {
|
|
meta = g_locals->types[lidx];
|
|
} else {
|
|
int gi = sym_index(vname);
|
|
if (gi >= 0) meta = G.types[gi];
|
|
}
|
|
|
|
if (meta != 0 && meta != TYPE_META_STRING && meta != TYPE_META_BOOLEAN && meta != TYPE_META_NIL && meta != TYPE_META_CLASS && meta != TYPE_META_FLOAT) {
|
|
/* Integer subtype: ±bits */
|
|
int abs_bits = meta < 0 ? -meta : meta;
|
|
const char *tname = (meta < 0)
|
|
? (abs_bits == 64 ? "Sint64" : (abs_bits == 32 ? "Sint32" : (abs_bits == 16 ? "Sint16" : "Sint8")))
|
|
: (abs_bits == 64 ? "Uint64" : (abs_bits == 32 ? "Uint32" : (abs_bits == 16 ? "Uint16" : "Uint8")));
|
|
int ci = bytecode_add_constant(bc, make_string(tname));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
|
|
*pos = peek + 1; /* consume name and ')' */
|
|
handled = 1;
|
|
}
|
|
free(vname);
|
|
} else {
|
|
free(vname);
|
|
}
|
|
}
|
|
|
|
if (!handled) {
|
|
/* General case: typeof(expression)
|
|
If the value is a Map with "__class" key, return that string; else return base typeof.
|
|
*/
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "typeof expects 1 argument");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after typeof arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
|
|
/* [v] */
|
|
bytecode_add_instruction(bc, OP_DUP, 0); /* [v, v] */
|
|
bytecode_add_instruction(bc, OP_TYPEOF, 0); /* [v, tname] */
|
|
{
|
|
int ciMap = bytecode_add_constant(bc, make_string("Map"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMap); /* [v, tname, "Map"] */
|
|
}
|
|
bytecode_add_instruction(bc, OP_EQ, 0); /* [v, isMap] */
|
|
int j_if_not_map = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
|
|
/* Map branch: [v] */
|
|
bytecode_add_instruction(bc, OP_DUP, 0); /* [v, v] */
|
|
{
|
|
int kci = bytecode_add_constant(bc, make_string("__class"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, kci); /* [v, v, "__class"] */
|
|
}
|
|
bytecode_add_instruction(bc, OP_HAS_KEY, 0); /* [v, has] */
|
|
int j_no_meta = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
/* has __class: try toString() -> return its result */
|
|
bytecode_add_instruction(bc, OP_DUP, 0); /* [v, v] */
|
|
{
|
|
int kcits = bytecode_add_constant(bc, make_string("toString"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, kcits); /* [v, v, "toString"] */
|
|
}
|
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* [v, func] */
|
|
bytecode_add_instruction(bc, OP_SWAP, 0); /* [func, v] */
|
|
bytecode_add_instruction(bc, OP_CALL, 1); /* [string] */
|
|
int j_end = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
|
|
/* no meta: drop v and return "Map" */
|
|
bytecode_set_operand(bc, j_no_meta, bc->instr_count);
|
|
bytecode_add_instruction(bc, OP_POP, 0); /* [] */
|
|
{
|
|
int ciMap2 = bytecode_add_constant(bc, make_string("Map"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMap2); /* ["Map"] */
|
|
}
|
|
int j_end2 = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
int after_map = bc->instr_count;
|
|
|
|
/* not map: compute typeof(v) */
|
|
bytecode_set_operand(bc, j_if_not_map, after_map);
|
|
bytecode_add_instruction(bc, OP_TYPEOF, 0); /* [tname] */
|
|
|
|
/* end */
|
|
bytecode_set_operand(bc, j_end, bc->instr_count);
|
|
bytecode_set_operand(bc, j_end2, bc->instr_count);
|
|
}
|
|
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "keys") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "keys expects 1 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_KEYS, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "values") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "values expects 1 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_VALUES, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "has") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "has expects (map, key)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "has expects (map, key)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_HAS_KEY, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "read_file") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "read_file expects 1 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_READ_FILE, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "write_file") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "write_file expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "write_file expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_WRITE_FILE, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "input") == 0) {
|
|
(*pos)++; /* '(' */
|
|
int hasPrompt = 0;
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] != ')') {
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "input expects 0 or 1 argument");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
hasPrompt = 1;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after input arg(s)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_INPUT_LINE, hasPrompt ? 1 : 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "input_hidden") == 0) {
|
|
(*pos)++; /* '(' */
|
|
int hasPrompt = 0;
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] != ')') {
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "input_hidden expects 0 or 1 argument");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
hasPrompt = 1;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after input_hidden arg(s)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
/* operand bit0 = hasPrompt, bit1 = hidden */
|
|
bytecode_add_instruction(bc, OP_INPUT_LINE, (hasPrompt ? 1 : 0) | 2);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "proc_run") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "proc_run expects 1 argument (command string)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after proc_run arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_PROC_RUN, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "system") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "system expects 1 argument (command string)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after system arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_PROC_SYSTEM, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "time_now_ms") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "time_now_ms expects ()");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_TIME_NOW_MS, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "clock_mono_ms") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "clock_mono_ms expects ()");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_CLOCK_MONO_MS, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "date_format") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "date_format expects (ms:int, fmt:string)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "date_format expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "date_format expects (ms:int, fmt:string)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after date_format args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_DATE_FORMAT, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "env") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "env expects 1 argument");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after env arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_ENV, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "env_all") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "env_all expects ()");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_ENV_ALL, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "fun_version") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "fun_version expects ()");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_FUN_VERSION, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "rust_hello") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "rust_hello expects ()");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_RUST_HELLO, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "rust_hello_args") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "rust_hello_args expects (message:string)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after rust_hello_args arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_RUST_HELLO_ARGS, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "rust_hello_args_return") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "rust_hello_args_return expects (message:string)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after rust_hello_args_return arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_RUST_HELLO_ARGS_RETURN, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "rust_get_sp") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "rust_get_sp expects ()");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_RUST_GET_SP, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "rust_set_exit") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "rust_set_exit expects (code:int)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after rust_set_exit arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_RUST_SET_EXIT, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "cpp_add") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "cpp_add expects (a:int, b:int)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "cpp_add expects two arguments");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "cpp_add expects (a:int, b:int)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after cpp_add args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_CPP_ADD, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "os_list_dir") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "os_list_dir expects (path)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after os_list_dir arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_OS_LIST_DIR, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
/* JSON builtins */
|
|
if (strcmp(name, "json_parse") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "json_parse expects (text)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after json_parse arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_JSON_PARSE, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
/* XML builtins (minimal) */
|
|
if (strcmp(name, "xml_parse") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "xml_parse expects (text)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after xml_parse arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_XML_PARSE, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "xml_root") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "xml_root expects (doc_handle)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after xml_root arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_XML_ROOT, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "xml_name") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "xml_name expects (node_handle)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after xml_name arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_XML_NAME, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "xml_text") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "xml_text expects (node_handle)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after xml_text arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_XML_TEXT, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "json_stringify") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "json_stringify expects (value, pretty)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "json_stringify expects (value, pretty)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "json_stringify expects (value, pretty)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after json_stringify args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_JSON_STRINGIFY, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
/* Tk (GUI) builtins (no raw Tcl exposed) */
|
|
if (strcmp(name, "tk_loop") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "tk_loop expects ()");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_TK_LOOP, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "tk_title") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "tk_title expects (title:string)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after tk_title arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_TK_WM_TITLE, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "tk_label") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "tk_label expects (id:string, text:string)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "tk_label expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "tk_label expects (id:string, text:string)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after tk_label args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_TK_LABEL, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "tk_button") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "tk_button expects (id:string, text:string)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "tk_button expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "tk_button expects (id:string, text:string)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after tk_button args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_TK_BUTTON, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "tk_pack") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "tk_pack expects (id:string)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after tk_pack arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_TK_PACK, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "tk_bind") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "tk_bind expects (id, event, cmd)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "tk_bind expects 3 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "tk_bind expects 3 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "tk_bind expects 3 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "tk_bind expects 3 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after tk_bind args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_TK_BIND, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "tk_eval") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "tk_eval expects (script)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after tk_eval arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_TK_EVAL, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "tk_result") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "tk_result expects ()");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_TK_RESULT, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "json_from_file") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "json_from_file expects (path)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after json_from_file arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_JSON_FROM_FILE, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "json_to_file") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "json_to_file expects (path, value, pretty)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "json_to_file expects (path, value, pretty)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "json_to_file expects (path, value, pretty)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "json_to_file expects (path, value, pretty)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "json_to_file expects (path, value, pretty)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after json_to_file args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_JSON_TO_FILE, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
/* INI (iniparser 4.2.6) builtins */
|
|
if (strcmp(name, "ini_load") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_load expects (path)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after ini_load arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_INI_LOAD, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "ini_free") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_free expects (handle)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after ini_free arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_INI_FREE, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "ini_get_string") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_get_string expects (handle, section, key, default)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "ini_get_string expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_get_string expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "ini_get_string expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_get_string expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "ini_get_string expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_get_string expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after ini_get_string args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_INI_GET_STRING, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "ini_get_int") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_get_int expects (handle, section, key, default)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "ini_get_int expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_get_int expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "ini_get_int expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_get_int expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "ini_get_int expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_get_int expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after ini_get_int args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_INI_GET_INT, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "ini_get_double") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_get_double expects (handle, section, key, default)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "ini_get_double expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_get_double expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "ini_get_double expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_get_double expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "ini_get_double expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_get_double expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after ini_get_double args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_INI_GET_DOUBLE, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "ini_get_bool") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_get_bool expects (handle, section, key, default)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "ini_get_bool expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_get_bool expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "ini_get_bool expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_get_bool expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "ini_get_bool expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_get_bool expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after ini_get_bool args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_INI_GET_BOOL, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "ini_set") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_set expects (handle, section, key, value)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "ini_set expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_set expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "ini_set expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_set expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "ini_set expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_set expects 4 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after ini_set args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_INI_SET, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "ini_unset") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_unset expects (handle, section, key)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "ini_unset expects 3 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_unset expects 3 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "ini_unset expects 3 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_unset expects 3 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after ini_unset args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_INI_UNSET, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "ini_save") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_save expects (handle, path)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "ini_save expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "ini_save expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after ini_save args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_INI_SAVE, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
/* CURL builtins (minimal interface like JSON) */
|
|
if (strcmp(name, "curl_get") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "curl_get expects (url)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after curl_get arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_CURL_GET, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
/* SQLite builtins */
|
|
if (strcmp(name, "sqlite_open") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "sqlite_open expects (path)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after sqlite_open arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SQLITE_OPEN, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "sqlite_close") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "sqlite_close expects (handle)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after sqlite_close arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SQLITE_CLOSE, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "sqlite_exec") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "sqlite_exec expects (handle, sql)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "sqlite_exec expects (handle, sql)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "sqlite_exec expects (handle, sql)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after sqlite_exec args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SQLITE_EXEC, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "sqlite_query") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "sqlite_query expects (handle, sql)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "sqlite_query expects (handle, sql)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "sqlite_query expects (handle, sql)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after sqlite_query args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SQLITE_QUERY, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
/* libsql builtins (independent extension) */
|
|
if (strcmp(name, "libsql_open") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "libsql_open expects (url_or_path)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after libsql_open arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_LIBSQL_OPEN, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "libsql_close") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "libsql_close expects (handle)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after libsql_close arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_LIBSQL_CLOSE, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "libsql_exec") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "libsql_exec expects (handle, sql)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "libsql_exec expects (handle, sql)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "libsql_exec expects (handle, sql)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after libsql_exec args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_LIBSQL_EXEC, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "libsql_query") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "libsql_query expects (handle, sql)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "libsql_query expects (handle, sql)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "libsql_query expects (handle, sql)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after libsql_query args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_LIBSQL_QUERY, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "curl_post") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "curl_post expects (url, body)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "curl_post expects (url, body)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "curl_post expects (url, body)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after curl_post args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_CURL_POST, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "curl_download") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "curl_download expects (url, path)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "curl_download expects (url, path)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "curl_download expects (url, path)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after curl_download args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_CURL_DOWNLOAD, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
/* OpenSSL (md5/sha256/sha512) */
|
|
if (strcmp(name, "openssl_md5") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "openssl_md5 expects (data)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after openssl_md5 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_OPENSSL_MD5, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "openssl_sha256") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "openssl_sha256 expects (data)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after openssl_sha256 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_OPENSSL_SHA256, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "openssl_sha512") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "openssl_sha512 expects (data)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after openssl_sha512 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_OPENSSL_SHA512, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "openssl_ripemd160") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "openssl_ripemd160 expects (data)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after openssl_ripemd160 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_OPENSSL_RIPEMD160, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
/* LibreSSL (md5/sha256/sha512/ripemd160) */
|
|
if (strcmp(name, "libressl_md5") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "libressl_md5 expects (data)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after libressl_md5 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_LIBRESSL_MD5, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "libressl_sha256") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "libressl_sha256 expects (data)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after libressl_sha256 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_LIBRESSL_SHA256, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "libressl_sha512") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "libressl_sha512 expects (data)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after libressl_sha512 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_LIBRESSL_SHA512, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "libressl_ripemd160") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "libressl_ripemd160 expects (data)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after libressl_ripemd160 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_LIBRESSL_RIPEMD160, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
/* PCSC builtins */
|
|
if (strcmp(name, "pcsc_establish") == 0) {
|
|
(*pos)++; /* '(' */
|
|
/* no args */
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "pcsc_establish expects ()");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_PCSC_ESTABLISH, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
/* PCRE2 builtins */
|
|
if (strcmp(name, "pcre2_test") == 0) {
|
|
(*pos)++; /* '(' */
|
|
/* (pattern, text, flags) */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "pcre2_test expects (pattern, text, flags)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "pcre2_test expects (pattern, text, flags)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "pcre2_test expects (pattern, text, flags)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "pcre2_test expects (pattern, text, flags)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "pcre2_test expects (pattern, text, flags)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after pcre2_test args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_PCRE2_TEST, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "pcre2_match") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "pcre2_match expects (pattern, text, flags)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "pcre2_match expects (pattern, text, flags)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "pcre2_match expects (pattern, text, flags)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "pcre2_match expects (pattern, text, flags)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "pcre2_match expects (pattern, text, flags)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after pcre2_match args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_PCRE2_MATCH, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "pcre2_findall") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after pcre2_findall args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_PCRE2_FINDALL, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
/* Notcurses builtins (optional) */
|
|
if (strcmp(name, "nc_init") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "nc_init expects ()");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_NC_INIT, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "nc_shutdown") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "nc_shutdown expects ()");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_NC_SHUTDOWN, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "nc_clear") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "nc_clear expects ()");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_NC_CLEAR, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "nc_draw_text") == 0) {
|
|
(*pos)++; /* '(' */
|
|
/* (y, x, text) -> push y, x, text, then opcode will pop text,x,y */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "nc_draw_text expects (y, x, text)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "nc_draw_text expects (y, x, text)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "nc_draw_text expects (y, x, text)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "nc_draw_text expects (y, x, text)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "nc_draw_text expects (y, x, text)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after nc_draw_text args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_NC_DRAW_TEXT, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "nc_getch") == 0) {
|
|
(*pos)++; /* '(' */
|
|
/* (timeout_ms) */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "nc_getch expects (timeout_ms)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after nc_getch arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_NC_GETCH, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "nc_get_size") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "nc_get_size expects ()");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_NC_GET_SIZE, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "nc_set_style") == 0) {
|
|
(*pos)++; /* '(' */
|
|
/* (style, bg_rgb, fg_rgb) */
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_set_style expects (style, bg_rgb, fg_rgb)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_set_style expects (style, bg_rgb, fg_rgb)"); free(name); return 0; }
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_set_style expects (style, bg_rgb, fg_rgb)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_set_style expects (style, bg_rgb, fg_rgb)"); free(name); return 0; }
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_set_style expects (style, bg_rgb, fg_rgb)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after nc_set_style args"); free(name); return 0; }
|
|
bytecode_add_instruction(bc, OP_NC_SET_STYLE, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "nc_draw_char") == 0) {
|
|
(*pos)++; /* '(' */
|
|
/* (y, x, ch) */
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_char expects (y, x, ch)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_draw_char expects (y, x, ch)"); free(name); return 0; }
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_char expects (y, x, ch)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_draw_char expects (y, x, ch)"); free(name); return 0; }
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_char expects (y, x, ch)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after nc_draw_char args"); free(name); return 0; }
|
|
bytecode_add_instruction(bc, OP_NC_DRAW_CHAR, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "nc_draw_hline") == 0) {
|
|
(*pos)++; /* '(' */
|
|
/* (y, x, len, ch) */
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_hline expects (y, x, len, ch)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_draw_hline expects (y, x, len, ch)"); free(name); return 0; }
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_hline expects (y, x, len, ch)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_draw_hline expects (y, x, len, ch)"); free(name); return 0; }
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_hline expects (y, x, len, ch)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_draw_hline expects (y, x, len, ch)"); free(name); return 0; }
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_hline expects (y, x, len, ch)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after nc_draw_hline args"); free(name); return 0; }
|
|
bytecode_add_instruction(bc, OP_NC_DRAW_HLINE, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "nc_draw_vline") == 0) {
|
|
(*pos)++; /* '(' */
|
|
/* (y, x, len, ch) */
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_vline expects (y, x, len, ch)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_draw_vline expects (y, x, len, ch)"); free(name); return 0; }
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_vline expects (y, x, len, ch)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_draw_vline expects (y, x, len, ch)"); free(name); return 0; }
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_vline expects (y, x, len, ch)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_draw_vline expects (y, x, len, ch)"); free(name); return 0; }
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_vline expects (y, x, len, ch)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after nc_draw_vline args"); free(name); return 0; }
|
|
bytecode_add_instruction(bc, OP_NC_DRAW_VLINE, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "nc_box") == 0) {
|
|
(*pos)++; /* '(' */
|
|
/* (x, y, w, h, style) -- wrapper may reorder */
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_box expects (x, y, w, h, style)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_box expects (x, y, w, h, style)"); free(name); return 0; }
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_box expects (x, y, w, h, style)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_box expects (x, y, w, h, style)"); free(name); return 0; }
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_box expects (x, y, w, h, style)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_box expects (x, y, w, h, style)"); free(name); return 0; }
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_box expects (x, y, w, h, style)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_box expects (x, y, w, h, style)"); free(name); return 0; }
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_box expects (x, y, w, h, style)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after nc_box args"); free(name); return 0; }
|
|
bytecode_add_instruction(bc, OP_NC_BOX, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "nc_fill") == 0) {
|
|
(*pos)++; /* '(' */
|
|
/* (x, y, w, h, ch) */
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_fill expects (x, y, w, h, ch)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_fill expects (x, y, w, h, ch)"); free(name); return 0; }
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_fill expects (x, y, w, h, ch)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_fill expects (x, y, w, h, ch)"); free(name); return 0; }
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_fill expects (x, y, w, h, ch)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_fill expects (x, y, w, h, ch)"); free(name); return 0; }
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_fill expects (x, y, w, h, ch)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_fill expects (x, y, w, h, ch)"); free(name); return 0; }
|
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_fill expects (x, y, w, h, ch)"); free(name); return 0; }
|
|
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after nc_fill args"); free(name); return 0; }
|
|
bytecode_add_instruction(bc, OP_NC_FILL, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "nc_render") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "nc_render expects ()");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_NC_RENDER, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "pcsc_release") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "pcsc_release expects 1 argument (ctx)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after pcsc_release arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_PCSC_RELEASE, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "pcsc_list_readers") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "pcsc_list_readers expects 1 argument (ctx)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after pcsc_list_readers arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_PCSC_LIST_READERS, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "pcsc_connect") == 0) {
|
|
(*pos)++; /* '(' */
|
|
/* ctx, reader */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "pcsc_connect expects (ctx, reader)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "pcsc_connect expects (ctx, reader)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "pcsc_connect expects (ctx, reader)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after pcsc_connect args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_PCSC_CONNECT, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "pcsc_disconnect") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "pcsc_disconnect expects 1 argument (handle)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after pcsc_disconnect arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_PCSC_DISCONNECT, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "pcsc_transmit") == 0) {
|
|
(*pos)++; /* '(' */
|
|
/* handle, bytes */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "pcsc_transmit expects (handle, bytes)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "pcsc_transmit expects (handle, bytes)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "pcsc_transmit expects (handle, bytes)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after pcsc_transmit args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_PCSC_TRANSMIT, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
/* Socket builtins */
|
|
if (strcmp(name, "tcp_listen") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "tcp_listen expects (port, backlog)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "tcp_listen expects (port, backlog)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "tcp_listen expects (port, backlog)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after tcp_listen args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SOCK_TCP_LISTEN, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "tcp_accept") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "tcp_accept expects (listen_fd)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after tcp_accept arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SOCK_TCP_ACCEPT, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "tcp_connect") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "tcp_connect expects (host, port)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "tcp_connect expects (host, port)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "tcp_connect expects (host, port)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after tcp_connect args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SOCK_TCP_CONNECT, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "sock_send") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "sock_send expects (fd, data)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "sock_send expects (fd, data)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "sock_send expects (fd, data)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after sock_send args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SOCK_SEND, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "sock_recv") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "sock_recv expects (fd, maxlen)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "sock_recv expects (fd, maxlen)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "sock_recv expects (fd, maxlen)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after sock_recv args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SOCK_RECV, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "sock_close") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "sock_close expects (fd)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after sock_close arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SOCK_CLOSE, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "unix_listen") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "unix_listen expects (path, backlog)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "unix_listen expects (path, backlog)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "unix_listen expects (path, backlog)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after unix_listen args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SOCK_UNIX_LISTEN, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "unix_connect") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "unix_connect expects (path)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after unix_connect arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SOCK_UNIX_CONNECT, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
/* Async-friendly FD helpers */
|
|
if (strcmp(name, "fd_set_nonblock") == 0) {
|
|
(*pos)++; /* '(' */
|
|
/* Expect (fd, on) -> push fd then on so VM pops on first */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "fd_set_nonblock expects (fd, on)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "fd_set_nonblock expects (fd, on)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "fd_set_nonblock expects (fd, on)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after fd_set_nonblock args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_FD_SET_NONBLOCK, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "fd_poll_read") == 0) {
|
|
(*pos)++; /* '(' */
|
|
/* Expect (fd, timeout_ms) -> push fd then timeout so VM pops timeout first */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "fd_poll_read expects (fd, timeout_ms)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "fd_poll_read expects (fd, timeout_ms)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "fd_poll_read expects (fd, timeout_ms)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after fd_poll_read args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_FD_POLL_READ, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "fd_poll_write") == 0) {
|
|
(*pos)++; /* '(' */
|
|
/* Expect (fd, timeout_ms) */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "fd_poll_write expects (fd, timeout_ms)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "fd_poll_write expects (fd, timeout_ms)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "fd_poll_write expects (fd, timeout_ms)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after fd_poll_write args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_FD_POLL_WRITE, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
/* Serial builtins */
|
|
if (strcmp(name, "serial_open") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "serial_open expects (path, baud)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "serial_open expects (path, baud)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "serial_open expects (path, baud)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after serial_open args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SERIAL_OPEN, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "serial_config") == 0) {
|
|
(*pos)++; /* '(' */
|
|
// fd, data_bits, parity, stop_bits, flow_control
|
|
for (int i = 0; i < 5; ++i) {
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "serial_config expects 5 arguments");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (i < 4) {
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "serial_config expects 5 arguments");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after serial_config args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SERIAL_CONFIG, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "serial_send") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "serial_send expects (fd, data)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "serial_send expects (fd, data)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "serial_send expects (fd, data)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after serial_send args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SERIAL_SEND, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "serial_recv") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "serial_recv expects (fd, maxlen)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "serial_recv expects (fd, maxlen)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "serial_recv expects (fd, maxlen)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after serial_recv args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SERIAL_RECV, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "serial_close") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "serial_close expects (fd)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after serial_close arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SERIAL_CLOSE, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
/* string ops */
|
|
if (strcmp(name, "split") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "split expects string");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
} else {
|
|
parser_fail(*pos, "split expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "split expects separator");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after split args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SPLIT, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "join") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "join expects array");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
} else {
|
|
parser_fail(*pos, "join expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "join expects separator");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after join args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_JOIN, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "substr") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "substr expects string");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
} else {
|
|
parser_fail(*pos, "substr expects 3 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "substr expects start");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
} else {
|
|
parser_fail(*pos, "substr expects 3 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "substr expects len");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after substr args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SUBSTR, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "find") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "find expects haystack");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
} else {
|
|
parser_fail(*pos, "find expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "find expects needle");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after find args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_FIND, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
/* regex ops */
|
|
if (strcmp(name, "regex_match") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "regex_match expects text");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
} else {
|
|
parser_fail(*pos, "regex_match expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "regex_match expects pattern");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after regex_match args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_REGEX_MATCH, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "regex_search") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "regex_search expects text");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
} else {
|
|
parser_fail(*pos, "regex_search expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "regex_search expects pattern");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after regex_search args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_REGEX_SEARCH, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "regex_replace") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "regex_replace expects text");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
} else {
|
|
parser_fail(*pos, "regex_replace expects 3 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "regex_replace expects pattern");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
} else {
|
|
parser_fail(*pos, "regex_replace expects 3 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "regex_replace expects replacement");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after regex_replace args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_REGEX_REPLACE, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
/* array utils */
|
|
if (strcmp(name, "contains") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "contains expects array");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
} else {
|
|
parser_fail(*pos, "contains expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "contains expects value");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after contains args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_CONTAINS, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "indexOf") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "indexOf expects array");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
} else {
|
|
parser_fail(*pos, "indexOf expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "indexOf expects value");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after indexOf args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_INDEX_OF, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "clear") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "clear expects array");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after clear arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_CLEAR, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
/* iteration helpers */
|
|
if (strcmp(name, "enumerate") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "enumerate expects array");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after enumerate arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_ENUMERATE, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "map") == 0) {
|
|
(*pos)++; /* '(' */
|
|
/* arr */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "map expects (array, function)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
/* store arr -> __map_arr */
|
|
char tarr[64];
|
|
snprintf(tarr, sizeof(tarr), "__map_arr_%d", g_temp_counter++);
|
|
int larr = -1, garr = -1;
|
|
if (g_locals) {
|
|
larr = local_add(tarr);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, larr);
|
|
} else {
|
|
garr = sym_index(tarr);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, garr);
|
|
}
|
|
/* func */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "map expects (array, function)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
char tfn[64];
|
|
snprintf(tfn, sizeof(tfn), "__map_fn_%d", g_temp_counter++);
|
|
int lfn = -1, gfn = -1;
|
|
if (g_locals) {
|
|
lfn = local_add(tfn);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, lfn);
|
|
} else {
|
|
gfn = sym_index(tfn);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gfn);
|
|
}
|
|
/* res array */
|
|
bytecode_add_instruction(bc, OP_MAKE_ARRAY, 0);
|
|
char tres[64];
|
|
snprintf(tres, sizeof(tres), "__map_res_%d", g_temp_counter++);
|
|
int lres = -1, gres = -1;
|
|
if (g_locals) {
|
|
lres = local_add(tres);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, lres);
|
|
} else {
|
|
gres = sym_index(tres);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gres);
|
|
}
|
|
/* i=0 */
|
|
int c0 = bytecode_add_constant(bc, make_int(0));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, c0);
|
|
char ti[64];
|
|
snprintf(ti, sizeof(ti), "__map_i_%d", g_temp_counter++);
|
|
int li = -1, gi = -1;
|
|
if (g_locals) {
|
|
li = local_add(ti);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, li);
|
|
} else {
|
|
gi = sym_index(ti);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi);
|
|
}
|
|
/* loop start */
|
|
int loop_start = bc->instr_count;
|
|
/* i < len(arr) */
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, li);
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr);
|
|
}
|
|
bytecode_add_instruction(bc, OP_LEN, 0);
|
|
bytecode_add_instruction(bc, OP_LT, 0);
|
|
int jf = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
/* elem = arr[i] */
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr);
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, li);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr);
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
|
|
}
|
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
|
|
/* call fn(elem) */
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lfn);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gfn);
|
|
}
|
|
/* 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 */
|
|
bytecode_add_instruction(bc, OP_SWAP, 0);
|
|
bytecode_add_instruction(bc, OP_CALL, 1);
|
|
/* Append to result via indexed assignment: res[len(res)] = value */
|
|
/* Store computed value to a temp */
|
|
char tv[64];
|
|
snprintf(tv, sizeof(tv), "__map_v_%d", g_temp_counter++);
|
|
int lv = -1, gv = -1;
|
|
if (g_locals) {
|
|
lv = local_add(tv);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, lv);
|
|
} else {
|
|
gv = sym_index(tv);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gv);
|
|
}
|
|
|
|
/* Push array (for INDEX_SET we need stack: value, index, array; we will build array, index, then value) */
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lres);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gres);
|
|
}
|
|
|
|
/* Compute index = len(res) */
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lres);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gres);
|
|
}
|
|
bytecode_add_instruction(bc, OP_LEN, 0);
|
|
|
|
/* Load value back on top */
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lv);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gv);
|
|
}
|
|
|
|
/* Append via insert: res.insert(index=len(res), value) */
|
|
bytecode_add_instruction(bc, OP_INSERT, 0);
|
|
/* dApache-2.0ard returned new length */
|
|
bytecode_add_instruction(bc, OP_POP, 0);
|
|
|
|
/* i++ */
|
|
int c1 = bytecode_add_constant(bc, make_int(1));
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, li);
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, c1);
|
|
bytecode_add_instruction(bc, OP_ADD, 0);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, li);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, c1);
|
|
bytecode_add_instruction(bc, OP_ADD, 0);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi);
|
|
}
|
|
bytecode_add_instruction(bc, OP_JUMP, loop_start);
|
|
bytecode_set_operand(bc, jf, bc->instr_count);
|
|
/* result value on stack */
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lres);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gres);
|
|
}
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "filter") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "filter expects (array, function)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
char tarr[64];
|
|
snprintf(tarr, sizeof(tarr), "__flt_arr_%d", g_temp_counter++);
|
|
int larr = -1, garr = -1;
|
|
if (g_locals) {
|
|
larr = local_add(tarr);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, larr);
|
|
} else {
|
|
garr = sym_index(tarr);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, garr);
|
|
}
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "filter expects (array, function)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
char tfn[64];
|
|
snprintf(tfn, sizeof(tfn), "__flt_fn_%d", g_temp_counter++);
|
|
int lfn = -1, gfn = -1;
|
|
if (g_locals) {
|
|
lfn = local_add(tfn);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, lfn);
|
|
} else {
|
|
gfn = sym_index(tfn);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gfn);
|
|
}
|
|
bytecode_add_instruction(bc, OP_MAKE_ARRAY, 0);
|
|
char tres[64];
|
|
snprintf(tres, sizeof(tres), "__flt_res_%d", g_temp_counter++);
|
|
int lres = -1, gres = -1;
|
|
if (g_locals) {
|
|
lres = local_add(tres);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, lres);
|
|
} else {
|
|
gres = sym_index(tres);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gres);
|
|
}
|
|
int c0 = bytecode_add_constant(bc, make_int(0));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, c0);
|
|
char ti[64];
|
|
snprintf(ti, sizeof(ti), "__flt_i_%d", g_temp_counter++);
|
|
int li = -1, gi = -1;
|
|
if (g_locals) {
|
|
li = local_add(ti);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, li);
|
|
} else {
|
|
gi = sym_index(ti);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi);
|
|
}
|
|
int loop_start = bc->instr_count;
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, li);
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr);
|
|
}
|
|
bytecode_add_instruction(bc, OP_LEN, 0);
|
|
bytecode_add_instruction(bc, OP_LT, 0);
|
|
int jf = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr);
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, li);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr);
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
|
|
}
|
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lfn);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gfn);
|
|
}
|
|
bytecode_add_instruction(bc, OP_SWAP, 0);
|
|
bytecode_add_instruction(bc, OP_CALL, 1);
|
|
/* if truthy then push elem to res */
|
|
int jskip = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
/* Append element to result: res[len(res)] = elem */
|
|
/* Reload element into a temp */
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr);
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, li);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr);
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
|
|
}
|
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
|
|
|
|
char tvf[64];
|
|
snprintf(tvf, sizeof(tvf), "__flt_v_%d", g_temp_counter++);
|
|
int lvf = -1, gvf = -1;
|
|
if (g_locals) {
|
|
lvf = local_add(tvf);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, lvf);
|
|
} else {
|
|
gvf = sym_index(tvf);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gvf);
|
|
}
|
|
|
|
/* Push array */
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lres);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gres);
|
|
}
|
|
|
|
/* index = len(res) */
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lres);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gres);
|
|
}
|
|
bytecode_add_instruction(bc, OP_LEN, 0);
|
|
|
|
/* value */
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lvf);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gvf);
|
|
}
|
|
|
|
/* Append via insert: res.insert(index=len(res), value) */
|
|
bytecode_add_instruction(bc, OP_INSERT, 0);
|
|
/* dApache-2.0ard returned new length */
|
|
bytecode_add_instruction(bc, OP_POP, 0);
|
|
|
|
int c1 = bytecode_add_constant(bc, make_int(1));
|
|
/* patch skip over append */
|
|
bytecode_set_operand(bc, jskip, bc->instr_count);
|
|
/* i++ */
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, li);
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, c1);
|
|
bytecode_add_instruction(bc, OP_ADD, 0);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, li);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, c1);
|
|
bytecode_add_instruction(bc, OP_ADD, 0);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi);
|
|
}
|
|
bytecode_add_instruction(bc, OP_JUMP, loop_start);
|
|
bytecode_set_operand(bc, jf, bc->instr_count);
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lres);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gres);
|
|
}
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "reduce") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "reduce expects (array, init, function)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
char tarr[64];
|
|
snprintf(tarr, sizeof(tarr), "__red_arr_%d", g_temp_counter++);
|
|
int larr = -1, garr = -1;
|
|
if (g_locals) {
|
|
larr = local_add(tarr);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, larr);
|
|
} else {
|
|
garr = sym_index(tarr);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, garr);
|
|
}
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "reduce expects (array, init, function)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
char tacc[64];
|
|
snprintf(tacc, sizeof(tacc), "__red_acc_%d", g_temp_counter++);
|
|
int lacc = -1, gacc = -1;
|
|
if (g_locals) {
|
|
lacc = local_add(tacc);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, lacc);
|
|
} else {
|
|
gacc = sym_index(tacc);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gacc);
|
|
}
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "reduce expects (array, init, function)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
char tfn[64];
|
|
snprintf(tfn, sizeof(tfn), "__red_fn_%d", g_temp_counter++);
|
|
int lfn = -1, gfn = -1;
|
|
if (g_locals) {
|
|
lfn = local_add(tfn);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, lfn);
|
|
} else {
|
|
gfn = sym_index(tfn);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gfn);
|
|
}
|
|
/* loop */
|
|
int c0 = bytecode_add_constant(bc, make_int(0));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, c0);
|
|
char ti[64];
|
|
snprintf(ti, sizeof(ti), "__red_i_%d", g_temp_counter++);
|
|
int li = -1, gi = -1;
|
|
if (g_locals) {
|
|
li = local_add(ti);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, li);
|
|
} else {
|
|
gi = sym_index(ti);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi);
|
|
}
|
|
int loop_start = bc->instr_count;
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, li);
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr);
|
|
}
|
|
bytecode_add_instruction(bc, OP_LEN, 0);
|
|
bytecode_add_instruction(bc, OP_LT, 0);
|
|
int jf = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
/* elem */
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr);
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, li);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr);
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
|
|
}
|
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
|
|
|
|
/* Store elem to a temp so we can build stack as: fn, acc, elem */
|
|
char telem[64];
|
|
snprintf(telem, sizeof(telem), "__red_elem_%d", g_temp_counter++);
|
|
int lelem = -1, gelem = -1;
|
|
if (g_locals) {
|
|
lelem = local_add(telem);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, lelem);
|
|
} else {
|
|
gelem = sym_index(telem);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gelem);
|
|
}
|
|
|
|
/* push function */
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lfn);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gfn);
|
|
}
|
|
|
|
/* push accumulator (arg1) */
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lacc);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gacc);
|
|
}
|
|
|
|
/* push element (arg2) */
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lelem);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gelem);
|
|
}
|
|
|
|
/* call fn(acc, elem) -> result */
|
|
bytecode_add_instruction(bc, OP_CALL, 2);
|
|
/* store to acc */
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, lacc);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gacc);
|
|
}
|
|
int c1 = bytecode_add_constant(bc, make_int(1));
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, li);
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, c1);
|
|
bytecode_add_instruction(bc, OP_ADD, 0);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, li);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, c1);
|
|
bytecode_add_instruction(bc, OP_ADD, 0);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi);
|
|
}
|
|
bytecode_add_instruction(bc, OP_JUMP, loop_start);
|
|
bytecode_set_operand(bc, jf, bc->instr_count);
|
|
/* result = acc on stack */
|
|
if (g_locals) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lacc);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gacc);
|
|
}
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "zip") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "zip expects first array");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
} else {
|
|
parser_fail(*pos, "zip expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "zip expects second array");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after zip args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_ZIP, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
/* math */
|
|
if (strcmp(name, "min") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "min expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "min expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_MIN, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "max") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "max expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "max expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_MAX, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "fmin") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "fmin expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "fmin expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_FMIN, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "fmax") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "fmax expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "fmax expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_FMAX, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "clamp") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "clamp expects 3 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "clamp expects 3 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "clamp expects 3 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_CLAMP, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "abs") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "abs expects 1 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_ABS, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "floor") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "floor expects 1 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_FLOOR, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "ceil") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "ceil expects 1 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_CEIL, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "trunc") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "trunc expects 1 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_TRUNC, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "round") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "round expects 1 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_ROUND, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "sin") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "sin expects 1 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SIN, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "cos") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "cos expects 1 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_COS, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "tan") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "tan expects 1 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_TAN, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "exp") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "exp expects 1 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_EXP, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "log") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "log expects 1 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_LOG, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "log10") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "log10 expects 1 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_LOG10, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "sqrt") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "sqrt expects 1 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SQRT, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "gcd") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "gcd expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "gcd expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_GCD, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "lcm") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "lcm expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "lcm expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_LCM, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "isqrt") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "isqrt expects 1 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_ISQRT, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "sign") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "sign expects 1 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SIGN, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "pow") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "pow expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "pow expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_POW, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
|
|
if (strcmp(name, "random_seed") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "random_seed expects 1 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_RANDOM_SEED, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "random_int") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "random_int expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "random_int expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_RANDOM_INT, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
|
|
if (strcmp(name, "random_number") == 0) {
|
|
(*pos)++; /* '(' */
|
|
/* expects exactly 1 arg: length */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "random_number expects 1 arg (length)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after random_number arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_RANDOM_NUMBER, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
|
|
/* threading */
|
|
if (strcmp(name, "thread_spawn") == 0) {
|
|
(*pos)++; /* '(' */
|
|
/* thread_spawn(fn [, args]) */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "thread_spawn expects function as first arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
int hasArgs = 0;
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "thread_spawn second arg must be array or value");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
hasArgs = 1;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after thread_spawn args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_THREAD_SPAWN, hasArgs ? 1 : 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "thread_join") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "thread_join expects 1 arg (thread id)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_THREAD_JOIN, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "sleep") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "sleep expects 1 arg (milliseconds)");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SLEEP_MS, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
|
|
/* bitwise ops (32-bit) */
|
|
if (strcmp(name, "band") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "band expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "band expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_BAND, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "bor") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "bor expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "bor expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_BOR, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "bxor") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "bxor expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "bxor expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_BXOR, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "bnot") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "bnot expects 1 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "bnot expects 1 arg");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_BNOT, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "shl") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "shl expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "shl expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SHL, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "shr") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "shr expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "shr expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SHR, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "rol") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "rol expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "rol expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_ROTL, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "ror") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "ror expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "ror expects 2 args");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_ROTR, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
|
|
/* push function value first */
|
|
if (local_idx >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, local_idx);
|
|
} else {
|
|
int gi = sym_index(name);
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
|
|
}
|
|
/* Track namespace alias only for the initial receiver; after any call it's no longer an alias value */
|
|
int __ns_ctx = is_ns_alias(name);
|
|
/* parse arguments */
|
|
(*pos)++; /* '(' */
|
|
int argc = 0;
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] != ')') {
|
|
do {
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected expression as function argument");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
argc++;
|
|
skip_spaces(src, len, pos);
|
|
} while (*pos < len && src[*pos] == ',' && (++(*pos), skip_spaces(src, len, pos), 1));
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after arguments");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
#ifdef FUN_DEBUG
|
|
/* DEBUG: show compiled call (only when FUN_DEBUG/FUN_TRACE enabled at runtime) */
|
|
if (fun_debug_enabled()) {
|
|
printf("compile: CALL %s with %d arg(s)\n", name, argc);
|
|
}
|
|
#endif
|
|
bytecode_add_instruction(bc, OP_CALL, argc);
|
|
/* postfix indexing, slice, and dot access/method calls */
|
|
for (;;) {
|
|
skip_spaces(src, len, pos);
|
|
|
|
/* index/slice */
|
|
if (*pos < len && src[*pos] == '[') {
|
|
(*pos)++;
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected start expression");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] == ':') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
size_t svp = *pos;
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
*pos = svp;
|
|
int ci = bytecode_add_constant(bc, make_int(-1));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
|
|
}
|
|
if (!consume_char(src, len, pos, ']')) {
|
|
parser_fail(*pos, "Expected ']' after slice");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SLICE, 0);
|
|
continue;
|
|
} else {
|
|
if (!consume_char(src, len, pos, ']')) {
|
|
parser_fail(*pos, "Expected ']' after index");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* dot property access and method-call sugar: obj.field or obj.method(...) */
|
|
if (*pos < len && src[*pos] == '.') {
|
|
(*pos)++; /* '.' */
|
|
skip_spaces(src, len, pos);
|
|
char *mname = NULL;
|
|
if (!read_identifier_into(src, len, pos, &mname)) {
|
|
parser_fail(*pos, "Expected identifier after '.'");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
int is_private = (mname && mname[0] == '_');
|
|
int kci = bytecode_add_constant(bc, make_string(mname));
|
|
|
|
/* Peek for immediate call: obj.method( ... ) */
|
|
size_t callp = *pos;
|
|
skip_spaces(src, len, &callp);
|
|
if (callp < len && src[callp] == '(') {
|
|
/* If private method on non-'this' receiver in this context -> error */
|
|
if (is_private) {
|
|
char msg[160];
|
|
snprintf(msg, sizeof(msg), "AccessError: private method '%s' is not accessible here", mname);
|
|
int ci = bytecode_add_constant(bc, make_string(msg));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
|
|
bytecode_add_instruction(bc, OP_PRINT, 0);
|
|
bytecode_add_instruction(bc, OP_HALT, 0);
|
|
free(mname);
|
|
continue;
|
|
}
|
|
|
|
/* After a function call, the receiver is a value (not a namespace alias);
|
|
always treat dot-call as a method with implicit 'this'. */
|
|
int is_ns = 0;
|
|
|
|
/* Method sugar with implicit 'this' */
|
|
bytecode_add_instruction(bc, OP_DUP, 0);
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, kci);
|
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* -> stack: obj, func */
|
|
bytecode_add_instruction(bc, OP_SWAP, 0); /* -> stack: func, obj (this) */
|
|
|
|
/* Consume '(' and parse args */
|
|
*pos = callp + 1;
|
|
int argc = 0;
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] != ')') {
|
|
do {
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected expression as method argument");
|
|
free(mname);
|
|
free(name);
|
|
return 0;
|
|
}
|
|
argc++;
|
|
skip_spaces(src, len, pos);
|
|
} while (*pos < len && src[*pos] == ',' && (++(*pos), skip_spaces(src, len, pos), 1));
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after arguments");
|
|
free(mname);
|
|
free(name);
|
|
return 0;
|
|
}
|
|
|
|
/* Call */
|
|
bytecode_add_instruction(bc, OP_CALL, is_ns ? argc : (argc + 1));
|
|
free(mname);
|
|
continue;
|
|
} else {
|
|
/* Plain property get: obj["field"] */
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, kci);
|
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
|
|
free(mname);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
free(name);
|
|
return 1;
|
|
} else {
|
|
if (local_idx >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, local_idx);
|
|
} else {
|
|
int gi = sym_index(name);
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
|
|
}
|
|
/* track namespace alias only for the initial receiver; after any call it's no longer an alias value */
|
|
int __ns_ctx = is_ns_alias(name);
|
|
/* postfix indexing, slice, and dot access/method calls */
|
|
for (;;) {
|
|
skip_spaces(src, len, pos);
|
|
|
|
/* index/slice */
|
|
if (*pos < len && src[*pos] == '[') {
|
|
(*pos)++;
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected start expression");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] == ':') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
size_t svp = *pos;
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
*pos = svp;
|
|
int ci = bytecode_add_constant(bc, make_int(-1));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
|
|
}
|
|
if (!consume_char(src, len, pos, ']')) {
|
|
parser_fail(*pos, "Expected ']' after slice");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SLICE, 0);
|
|
continue;
|
|
} else {
|
|
if (!consume_char(src, len, pos, ']')) {
|
|
parser_fail(*pos, "Expected ']' after index");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* dot property access and method-call sugar */
|
|
if (*pos < len && src[*pos] == '.') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
char *mname = NULL;
|
|
if (!read_identifier_into(src, len, pos, &mname)) {
|
|
parser_fail(*pos, "Expected identifier after '.'");
|
|
free(name);
|
|
return 0;
|
|
}
|
|
int is_private = (mname && mname[0] == '_');
|
|
int kci = bytecode_add_constant(bc, make_string(mname));
|
|
|
|
/* Peek for call */
|
|
size_t callp = *pos;
|
|
skip_spaces(src, len, &callp);
|
|
if (callp < len && src[callp] == '(') {
|
|
/* If private and receiver is not 'this' -> error */
|
|
if (is_private && !(strcmp(name, "this") == 0)) {
|
|
char msg[160];
|
|
snprintf(msg, sizeof(msg), "AccessError: private method '%s' is not accessible", mname);
|
|
int ci = bytecode_add_constant(bc, make_string(msg));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
|
|
bytecode_add_instruction(bc, OP_PRINT, 0);
|
|
bytecode_add_instruction(bc, OP_HALT, 0);
|
|
free(mname);
|
|
continue;
|
|
}
|
|
|
|
/* Use initial alias context for only the first dot-call; reset after call */
|
|
int is_ns = __ns_ctx;
|
|
|
|
if (!is_ns) {
|
|
/* Method sugar with implicit 'this' */
|
|
bytecode_add_instruction(bc, OP_DUP, 0);
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, kci);
|
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* -> obj, func */
|
|
bytecode_add_instruction(bc, OP_SWAP, 0); /* -> func, obj */
|
|
} else {
|
|
/* Plain property function call */
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, kci);
|
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* -> func */
|
|
}
|
|
|
|
*pos = callp + 1;
|
|
int argc = 0;
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] != ')') {
|
|
do {
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected expression as method argument");
|
|
free(mname);
|
|
free(name);
|
|
return 0;
|
|
}
|
|
argc++;
|
|
skip_spaces(src, len, pos);
|
|
} while (*pos < len && src[*pos] == ',' && (++(*pos), skip_spaces(src, len, pos), 1));
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after arguments");
|
|
free(mname);
|
|
free(name);
|
|
return 0;
|
|
}
|
|
|
|
bytecode_add_instruction(bc, OP_CALL, is_ns ? argc : (argc + 1));
|
|
/* After any call, the receiver is now a value, not a namespace alias */
|
|
__ns_ctx = 0;
|
|
|
|
free(mname);
|
|
continue;
|
|
} else {
|
|
/* plain property get (allowed even for private name) */
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, kci);
|
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
|
|
free(mname);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
free(name);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* unary: '!' unary | '-' unary | primary */
|
|
static int emit_unary(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] == '!') {
|
|
(*pos)++;
|
|
if (!emit_unary(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected expression after '!'");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_NOT, 0);
|
|
return 1;
|
|
}
|
|
if (*pos < len && src[*pos] == '-') {
|
|
(*pos)++;
|
|
/* unary minus -> 0 - expr */
|
|
int ci = bytecode_add_constant(bc, make_int(0));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
|
|
if (!emit_unary(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected expression after unary '-'");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SUB, 0);
|
|
return 1;
|
|
}
|
|
return emit_primary(bc, src, len, pos);
|
|
}
|
|
|
|
/* multiplicative: unary (('*' | '/' | '%') unary)* */
|
|
static int emit_multiplicative(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
|
if (!emit_unary(bc, src, len, pos)) return 0;
|
|
for (;;) {
|
|
skip_spaces(src, len, pos);
|
|
|
|
/* Stop expression at start of inline comment */
|
|
if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '/') {
|
|
break;
|
|
}
|
|
/* Skip block comments inside expressions */
|
|
if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '*') {
|
|
size_t p = *pos + 2;
|
|
while (p + 1 < len && !(src[p] == '*' && src[p + 1] == '/')) {
|
|
p++;
|
|
}
|
|
if (p + 1 < len) p += 2; /* consume closing marker */
|
|
*pos = p;
|
|
continue;
|
|
}
|
|
|
|
if (*pos < len && src[*pos] == '*') {
|
|
(*pos)++;
|
|
if (!emit_unary(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected expression after '*'");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_MUL, 0);
|
|
continue;
|
|
}
|
|
if (*pos < len && src[*pos] == '/') {
|
|
(*pos)++;
|
|
if (!emit_unary(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected expression after '/'");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_DIV, 0);
|
|
continue;
|
|
}
|
|
if (*pos < len && src[*pos] == '%') {
|
|
(*pos)++;
|
|
if (!emit_unary(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected expression after '%'");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_MOD, 0);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* additive: multiplicative (('+' | '-') multiplicative)* */
|
|
static int emit_additive(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
|
if (!emit_multiplicative(bc, src, len, pos)) return 0;
|
|
for (;;) {
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] == '+') {
|
|
(*pos)++;
|
|
if (!emit_multiplicative(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected expression after '+'");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_ADD, 0);
|
|
continue;
|
|
}
|
|
if (*pos < len && src[*pos] == '-') {
|
|
(*pos)++;
|
|
if (!emit_multiplicative(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected expression after '-'");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_SUB, 0);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* relational: additive (('<' | '<=' | '>' | '>=') additive)* */
|
|
static int emit_relational(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
|
if (!emit_additive(bc, src, len, pos)) return 0;
|
|
for (;;) {
|
|
skip_spaces(src, len, pos);
|
|
if (*pos + 1 < len && src[*pos] == '<' && src[*pos + 1] == '=') {
|
|
*pos += 2;
|
|
if (!emit_additive(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected expression after '<='");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_LTE, 0);
|
|
continue;
|
|
}
|
|
if (*pos + 1 < len && src[*pos] == '>' && src[*pos + 1] == '=') {
|
|
*pos += 2;
|
|
if (!emit_additive(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected expression after '>='");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_GTE, 0);
|
|
continue;
|
|
}
|
|
if (*pos < len && src[*pos] == '<') {
|
|
(*pos)++;
|
|
if (!emit_additive(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected expression after '<'");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_LT, 0);
|
|
continue;
|
|
}
|
|
if (*pos < len && src[*pos] == '>') {
|
|
(*pos)++;
|
|
if (!emit_additive(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected expression after '>'");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_GT, 0);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* equality: relational (('==' | '!=') relational)* */
|
|
static int emit_equality(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
|
if (!emit_relational(bc, src, len, pos)) return 0;
|
|
for (;;) {
|
|
skip_spaces(src, len, pos);
|
|
if (*pos + 1 < len && src[*pos] == '=' && src[*pos + 1] == '=') {
|
|
*pos += 2;
|
|
if (!emit_relational(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected expression after '=='");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_EQ, 0);
|
|
continue;
|
|
}
|
|
if (*pos + 1 < len && src[*pos] == '!' && src[*pos + 1] == '=') {
|
|
*pos += 2;
|
|
if (!emit_relational(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected expression after '!='");
|
|
return 0;
|
|
}
|
|
bytecode_add_instruction(bc, OP_NEQ, 0);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* logical AND with short-circuit: equality ( '&&' equality )* */
|
|
static int emit_and_expr(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
|
int jf_idxs[64];
|
|
int jf_count = 0;
|
|
int has_and = 0;
|
|
|
|
/* first operand */
|
|
if (!emit_equality(bc, src, len, pos)) return 0;
|
|
|
|
for (;;) {
|
|
skip_spaces(src, len, pos);
|
|
if (!(*pos + 1 < len && src[*pos] == '&' && src[*pos + 1] == '&')) break;
|
|
*pos += 2;
|
|
has_and = 1;
|
|
|
|
/* if current value is false -> jump to false label (patched later) */
|
|
if (jf_count < (int)(sizeof(jf_idxs) / sizeof(jf_idxs[0]))) {
|
|
jf_idxs[jf_count++] = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
} else {
|
|
parser_fail(*pos, "Too many operands in '&&' chain");
|
|
return 0;
|
|
}
|
|
|
|
/* evaluate next operand */
|
|
if (!emit_equality(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected expression after '&&'");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (has_and) {
|
|
/* final: if last operand is false -> jump false */
|
|
if (jf_count < (int)(sizeof(jf_idxs) / sizeof(jf_idxs[0]))) {
|
|
jf_idxs[jf_count++] = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
} else {
|
|
parser_fail(*pos, "Too many operands in '&&' chain");
|
|
return 0;
|
|
}
|
|
|
|
/* all were truthy -> result true */
|
|
int c1 = bytecode_add_constant(bc, make_bool(1));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, c1);
|
|
int j_end = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
|
|
/* false label: patch all false jumps here, result false */
|
|
int l_false = bc->instr_count;
|
|
for (int i = 0; i < jf_count; ++i) {
|
|
bytecode_set_operand(bc, jf_idxs[i], l_false);
|
|
}
|
|
int c0 = bytecode_add_constant(bc, make_bool(0));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, c0);
|
|
|
|
/* end */
|
|
int l_end = bc->instr_count;
|
|
bytecode_set_operand(bc, j_end, l_end);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* logical OR with short-circuit: and_expr ( '||' and_expr )* */
|
|
static int emit_or_expr(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
|
int true_jumps[64];
|
|
int tj_count = 0;
|
|
int has_or = 0;
|
|
|
|
/* first operand */
|
|
if (!emit_and_expr(bc, src, len, pos)) return 0;
|
|
|
|
for (;;) {
|
|
skip_spaces(src, len, pos);
|
|
if (!(*pos + 1 < len && src[*pos] == '|' && src[*pos + 1] == '|')) break;
|
|
*pos += 2;
|
|
has_or = 1;
|
|
|
|
/* if current value is false -> proceed to next; else -> result true */
|
|
int jf_proceed = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
|
|
/* true path: push true and jump to end */
|
|
int c1 = bytecode_add_constant(bc, make_bool(1));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, c1);
|
|
if (tj_count < (int)(sizeof(true_jumps) / sizeof(true_jumps[0]))) {
|
|
true_jumps[tj_count++] = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
} else {
|
|
parser_fail(*pos, "Too many operands in '||' chain");
|
|
return 0;
|
|
}
|
|
|
|
/* patch to start of next operand */
|
|
bytecode_set_operand(bc, jf_proceed, bc->instr_count);
|
|
|
|
/* evaluate next operand */
|
|
if (!emit_and_expr(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected expression after '||'");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (has_or) {
|
|
/* After evaluating the last operand: test it and produce 1/0 */
|
|
int jf_last = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
|
|
int c1 = bytecode_add_constant(bc, make_int(1));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, c1);
|
|
int j_end_single = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
|
|
int l_false = bc->instr_count;
|
|
bytecode_set_operand(bc, jf_last, l_false);
|
|
int c0 = bytecode_add_constant(bc, make_int(0));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, c0);
|
|
|
|
int l_end = bc->instr_count;
|
|
bytecode_set_operand(bc, j_end_single, l_end);
|
|
for (int i = 0; i < tj_count; ++i) {
|
|
bytecode_set_operand(bc, true_jumps[i], l_end);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* conditional operator (ternary) with right associativity:
|
|
Parses: logical_or ('?' conditional ':' conditional)? */
|
|
static int emit_conditional(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
|
/* parse condition (logical OR precedence or higher) */
|
|
if (!emit_or_expr(bc, src, len, pos)) return 0;
|
|
|
|
for (;;) {
|
|
skip_spaces(src, len, pos);
|
|
if (!(*pos < len && src[*pos] == '?')) break;
|
|
(*pos)++; /* consume '?' */
|
|
|
|
/* If condition is false -> jump to false arm */
|
|
int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
|
|
/* true arm (right-assoc: allow nested ternaries) */
|
|
skip_spaces(src, len, pos);
|
|
if (!emit_conditional(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected expression after '?'");
|
|
return 0;
|
|
}
|
|
|
|
/* After true arm, unconditionally skip false arm */
|
|
int jmp_end = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
|
|
/* false arm label */
|
|
bytecode_set_operand(bc, jmp_false, bc->instr_count);
|
|
|
|
/* require ':' */
|
|
skip_spaces(src, len, pos);
|
|
if (!(*pos < len && src[*pos] == ':')) {
|
|
parser_fail(*pos, "Expected ':' in conditional expression");
|
|
return 0;
|
|
}
|
|
(*pos)++; /* consume ':' */
|
|
|
|
/* false arm (right-assoc) */
|
|
skip_spaces(src, len, pos);
|
|
if (!emit_conditional(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected expression after ':'");
|
|
return 0;
|
|
}
|
|
|
|
/* end label */
|
|
bytecode_set_operand(bc, jmp_end, bc->instr_count);
|
|
/* loop to allow chaining like a ? b : c ? d : e (right-assoc) */
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* top-level expression */
|
|
static int emit_expression(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
|
return emit_conditional(bc, src, len, pos);
|
|
}
|
|
|
|
/*
|
|
* Indentation-aware compiler (2 spaces):
|
|
* - Optional shebang and a single fun <ident>() { ... } wrapper.
|
|
* - Statements supported: print(expr), ident = expr, and if <expr> with an indented block.
|
|
* - Literals: strings, integers, booleans; identifiers are globals.
|
|
* - Ends with HALT.
|
|
*/
|
|
|
|
/* line/indent utilities */
|
|
static void skip_to_eol(const char *src, size_t len, size_t *pos) {
|
|
/* Strict mode: only allow trailing spaces and comments until end-of-line. */
|
|
size_t p = *pos;
|
|
|
|
for (;;) {
|
|
/* skip spaces */
|
|
while (p < len && src[p] == ' ')
|
|
p++;
|
|
|
|
if (p >= len) {
|
|
*pos = p;
|
|
return;
|
|
}
|
|
/* Treat CR, LF, and CRLF as end-of-line */
|
|
if (src[p] == '\r') {
|
|
p++;
|
|
if (p < len && src[p] == '\n') p++;
|
|
*pos = p;
|
|
return;
|
|
}
|
|
if (src[p] == '\n') {
|
|
*pos = p + 1;
|
|
return;
|
|
}
|
|
|
|
/* line or block comments allowed */
|
|
if (p + 1 < len && src[p] == '/' && src[p + 1] == '/') {
|
|
/* consume rest of line up to CR or LF */
|
|
p += 2;
|
|
while (p < len && src[p] != '\n' && src[p] != '\r')
|
|
p++;
|
|
if (p < len && src[p] == '\r') {
|
|
p++;
|
|
if (p < len && src[p] == '\n') p++;
|
|
} else if (p < len && src[p] == '\n') {
|
|
p++;
|
|
}
|
|
*pos = p;
|
|
return;
|
|
}
|
|
|
|
if (p + 1 < len && src[p] == '/' && src[p + 1] == '*') {
|
|
/* consume block comment, then loop again for spaces till EOL */
|
|
p += 2;
|
|
while (p + 1 < len && !(src[p] == '*' && src[p + 1] == '/')) {
|
|
p++;
|
|
}
|
|
if (p + 1 < len) {
|
|
p += 2; /* consume closing */
|
|
continue;
|
|
} else {
|
|
parser_fail(p, "Unterminated block comment at end of file");
|
|
*pos = p;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Any other character here is unexpected trailing garbage */
|
|
parser_fail(p, "Unexpected trailing characters at end of line");
|
|
*pos = p;
|
|
return;
|
|
}
|
|
}
|
|
|
|
static int read_line_start(const char *src, size_t len, size_t *pos, int *out_indent) {
|
|
while (*pos < len) {
|
|
size_t p = *pos;
|
|
int spaces = 0;
|
|
while (p < len && src[p] == ' ') {
|
|
spaces++;
|
|
p++;
|
|
}
|
|
if (p < len && src[p] == '\t') {
|
|
parser_fail(p, "Tabs are forbidden for indentation");
|
|
return 0;
|
|
}
|
|
if (p >= len) {
|
|
*pos = p;
|
|
return 0;
|
|
}
|
|
|
|
/* empty line: handle CR, LF, and CRLF */
|
|
if (src[p] == '\r') {
|
|
p++;
|
|
if (p < len && src[p] == '\n') p++;
|
|
*pos = p;
|
|
continue;
|
|
}
|
|
if (src[p] == '\n') {
|
|
p++;
|
|
*pos = p;
|
|
continue;
|
|
}
|
|
|
|
/* // comment-only line */
|
|
if (p + 1 < len && src[p] == '/' && src[p + 1] == '/') {
|
|
/* skip entire line up to CR/LF */
|
|
p += 2;
|
|
while (p < len && src[p] != '\n' && src[p] != '\r')
|
|
p++;
|
|
if (p < len && src[p] == '\r') {
|
|
p++;
|
|
if (p < len && src[p] == '\n') p++;
|
|
} else if (p < len && src[p] == '\n') {
|
|
p++;
|
|
}
|
|
*pos = p;
|
|
continue;
|
|
}
|
|
|
|
// block comment starting at line (treat as comment-only line)
|
|
if (p + 1 < len && src[p] == '/' && src[p + 1] == '*') {
|
|
p += 2;
|
|
/* advance until we find closing block comment marker */
|
|
while (p + 1 < len && !(src[p] == '*' && src[p + 1] == '/')) {
|
|
p++;
|
|
}
|
|
if (p + 1 < len) p += 2; /* consume closing block comment marker */
|
|
/* consume to end of current line (if any leftover) */
|
|
while (p < len && src[p] != '\n' && src[p] != '\r')
|
|
p++;
|
|
if (p < len && src[p] == '\r') {
|
|
p++;
|
|
if (p < len && src[p] == '\n') p++;
|
|
} else if (p < len && src[p] == '\n') {
|
|
p++;
|
|
}
|
|
*pos = p;
|
|
continue;
|
|
}
|
|
|
|
if (spaces % 2 != 0) {
|
|
parser_fail(p, "Indentation must be multiples of two spaces");
|
|
return 0;
|
|
}
|
|
*out_indent = spaces / 2;
|
|
*pos = p; /* point to first code char */
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* forward decl */
|
|
static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, int current_indent);
|
|
|
|
/* parse and emit a single simple (non-if) statement on the current line */
|
|
static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
|
size_t local_pos = *pos;
|
|
char *name = NULL;
|
|
if (read_identifier_into(src, len, &local_pos, &name)) {
|
|
|
|
/* alias: accept 'sint*' as synonyms for 'int*' */
|
|
if (strcmp(name, "sint8") == 0) {
|
|
free(name);
|
|
name = strdup("int8");
|
|
} else if (strcmp(name, "sint16") == 0) {
|
|
free(name);
|
|
name = strdup("int16");
|
|
} else if (strcmp(name, "sint32") == 0) {
|
|
free(name);
|
|
name = strdup("int32");
|
|
} else if (strcmp(name, "sint64") == 0) {
|
|
free(name);
|
|
name = strdup("int64");
|
|
}
|
|
|
|
/* return statement */
|
|
if (strcmp(name, "return") == 0) {
|
|
free(name);
|
|
skip_spaces(src, len, &local_pos);
|
|
/* optional expression */
|
|
size_t save_pos = local_pos;
|
|
if (emit_expression(bc, src, len, &local_pos)) {
|
|
/* expression result already on stack */
|
|
} else {
|
|
/* no expression: return nil */
|
|
local_pos = save_pos;
|
|
int ci = bytecode_add_constant(bc, make_nil());
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
|
|
}
|
|
bytecode_add_instruction(bc, OP_RETURN, 0);
|
|
*pos = local_pos;
|
|
skip_to_eol(src, len, pos);
|
|
return;
|
|
}
|
|
|
|
/* exit statement: exit [expr]? */
|
|
if (strcmp(name, "exit") == 0) {
|
|
free(name);
|
|
skip_spaces(src, len, &local_pos);
|
|
size_t save_pos = local_pos;
|
|
if (emit_expression(bc, src, len, &local_pos)) {
|
|
/* expression result already on stack */
|
|
} else {
|
|
/* default exit code 0 */
|
|
local_pos = save_pos;
|
|
int ci = bytecode_add_constant(bc, make_int(0));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
|
|
}
|
|
bytecode_add_instruction(bc, OP_EXIT, 0);
|
|
*pos = local_pos;
|
|
skip_to_eol(src, len, pos);
|
|
return;
|
|
}
|
|
|
|
/* break / continue */
|
|
if (strcmp(name, "break") == 0) {
|
|
free(name);
|
|
if (!g_loop_ctx) {
|
|
parser_fail(local_pos, "break used outside of loop");
|
|
return;
|
|
}
|
|
int j = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
if (g_loop_ctx->break_count < (int)(sizeof(g_loop_ctx->break_jumps) / sizeof(g_loop_ctx->break_jumps[0]))) {
|
|
g_loop_ctx->break_jumps[g_loop_ctx->break_count++] = j;
|
|
} else {
|
|
parser_fail(local_pos, "Too many 'break' in one loop");
|
|
return;
|
|
}
|
|
*pos = local_pos;
|
|
skip_to_eol(src, len, pos);
|
|
return;
|
|
}
|
|
if (strcmp(name, "continue") == 0) {
|
|
free(name);
|
|
if (!g_loop_ctx) {
|
|
parser_fail(local_pos, "continue used outside of loop");
|
|
return;
|
|
}
|
|
int j = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
if (g_loop_ctx->cont_count < (int)(sizeof(g_loop_ctx->continue_jumps) / sizeof(g_loop_ctx->continue_jumps[0]))) {
|
|
g_loop_ctx->continue_jumps[g_loop_ctx->cont_count++] = j;
|
|
} else {
|
|
parser_fail(local_pos, "Too many 'continue' in one loop");
|
|
return;
|
|
}
|
|
*pos = local_pos;
|
|
skip_to_eol(src, len, pos);
|
|
return;
|
|
}
|
|
|
|
/* typed declarations:
|
|
number|string|boolean|nil|Class|byte|uint8|uint16|uint32|uint64|int8|int16|int32|int64 <ident> (= expr)?
|
|
Note: 'number' maps to signed 64-bit here. 'byte' is an alias of unsigned 8-bit. 'Class' restricts to class instances.
|
|
*/
|
|
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) {
|
|
int is_number = (strcmp(name, "number") == 0);
|
|
int is_string = (strcmp(name, "string") == 0);
|
|
int is_boolean = (strcmp(name, "boolean") == 0);
|
|
int is_nil = (strcmp(name, "nil") == 0);
|
|
int is_class = (strcmp(name, "class") == 0);
|
|
int is_float = (strcmp(name, "float") == 0);
|
|
int is_array = (strcmp(name, "array") == 0);
|
|
int is_byte = (strcmp(name, "byte") == 0);
|
|
int is_u8 = (strcmp(name, "uint8") == 0) || is_byte;
|
|
int is_u16 = (strcmp(name, "uint16") == 0);
|
|
int is_u32 = (strcmp(name, "uint32") == 0);
|
|
int is_u64 = (strcmp(name, "uint64") == 0);
|
|
int is_s8 = (strcmp(name, "int8") == 0);
|
|
int is_s16 = (strcmp(name, "int16") == 0);
|
|
int is_s32 = (strcmp(name, "int32") == 0);
|
|
int is_s64 = (strcmp(name, "int64") == 0) || is_number; /* number maps to int64 (signed) */
|
|
int decl_bits = is_u8 ? 8 : is_u16 ? 16
|
|
: is_u32 ? 32
|
|
: is_u64 ? 64
|
|
: is_s8 ? 8
|
|
: is_s16 ? 16
|
|
: is_s32 ? 32
|
|
: is_s64 ? 64
|
|
: 0;
|
|
int decl_signed = (is_s8 || is_s16 || is_s32 || is_s64) ? 1 : 0;
|
|
/* store decl bits with sign encoded: negative means signed (number is signed 64-bit) */
|
|
if (decl_signed) decl_bits = -decl_bits;
|
|
|
|
/* declared type metadata: integers use decl_bits; string/boolean/nil/Class/float/array use special markers */
|
|
int decl_meta = decl_bits;
|
|
if (is_string) {
|
|
decl_meta = TYPE_META_STRING;
|
|
} else if (is_boolean) {
|
|
decl_meta = TYPE_META_BOOLEAN;
|
|
} else if (is_nil) {
|
|
decl_meta = TYPE_META_NIL;
|
|
} else if (is_class) {
|
|
decl_meta = TYPE_META_CLASS;
|
|
} else if (is_float) {
|
|
decl_meta = TYPE_META_FLOAT;
|
|
} else if (is_array) {
|
|
decl_meta = TYPE_META_ARRAY;
|
|
}
|
|
|
|
free(name);
|
|
|
|
/* read variable name */
|
|
char *varname = NULL;
|
|
skip_spaces(src, len, &local_pos);
|
|
if (!read_identifier_into(src, len, &local_pos, &varname)) {
|
|
parser_fail(local_pos, "Expected identifier after type declaration");
|
|
return;
|
|
}
|
|
|
|
/* decide local vs global */
|
|
int lidx = -1;
|
|
int gi = -1;
|
|
if (g_locals) {
|
|
int existing = local_find(varname);
|
|
if (existing >= 0)
|
|
lidx = existing;
|
|
else
|
|
lidx = local_add(varname);
|
|
if (lidx >= 0) {
|
|
g_locals->types[lidx] = decl_meta; /* encoding: ±bits for integers; TYPE_META_* for non-integer enforced types; 0 = dynamic */
|
|
}
|
|
} else {
|
|
gi = sym_index(varname);
|
|
if (gi >= 0) {
|
|
G.types[gi] = decl_meta;
|
|
}
|
|
}
|
|
free(varname);
|
|
|
|
skip_spaces(src, len, &local_pos);
|
|
if (local_pos < len && src[local_pos] == '=') {
|
|
local_pos++; /* '=' */
|
|
if (!emit_expression(bc, src, len, &local_pos)) {
|
|
parser_fail(local_pos, "Expected initializer expression after '='");
|
|
return;
|
|
}
|
|
|
|
/* Enforce declared type on initializer */
|
|
if (decl_meta == TYPE_META_STRING) {
|
|
/* expect String */
|
|
bytecode_add_instruction(bc, OP_DUP, 0);
|
|
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
|
int ciExp = bytecode_add_constant(bc, make_string("String"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciExp);
|
|
bytecode_add_instruction(bc, OP_EQ, 0);
|
|
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
/* error block */
|
|
bytecode_set_operand(bc, j_to_error, bc->instr_count);
|
|
{
|
|
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected String"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
|
bytecode_add_instruction(bc, OP_PRINT, 0);
|
|
bytecode_add_instruction(bc, OP_HALT, 0);
|
|
}
|
|
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
|
} else if (decl_meta == TYPE_META_CLASS) {
|
|
/* expect Class instance: Map with "__class" key */
|
|
/* Check typeof(v) == "Map" */
|
|
bytecode_add_instruction(bc, OP_DUP, 0);
|
|
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
|
{
|
|
int ciMap = bytecode_add_constant(bc, make_string("Map"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMap);
|
|
}
|
|
bytecode_add_instruction(bc, OP_EQ, 0);
|
|
int j_err1 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
/* Check v has "__class" */
|
|
bytecode_add_instruction(bc, OP_DUP, 0);
|
|
{
|
|
int kci = bytecode_add_constant(bc, make_string("__class"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, kci);
|
|
}
|
|
bytecode_add_instruction(bc, OP_HAS_KEY, 0);
|
|
int j_err2 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
/* Success path jumps over error block */
|
|
int j_ok = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
/* Error block */
|
|
int err_lbl = bc->instr_count;
|
|
bytecode_set_operand(bc, j_err1, err_lbl);
|
|
bytecode_set_operand(bc, j_err2, err_lbl);
|
|
{
|
|
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Class"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
|
bytecode_add_instruction(bc, OP_PRINT, 0);
|
|
bytecode_add_instruction(bc, OP_HALT, 0);
|
|
}
|
|
/* Continue after checks */
|
|
bytecode_set_operand(bc, j_ok, bc->instr_count);
|
|
} else if (decl_meta == TYPE_META_FLOAT) {
|
|
/* expect Float */
|
|
bytecode_add_instruction(bc, OP_DUP, 0);
|
|
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
|
int ciF = bytecode_add_constant(bc, make_string("Float"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciF);
|
|
bytecode_add_instruction(bc, OP_EQ, 0);
|
|
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
bytecode_set_operand(bc, j_to_error, bc->instr_count);
|
|
{
|
|
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Float"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
|
bytecode_add_instruction(bc, OP_PRINT, 0);
|
|
bytecode_add_instruction(bc, OP_HALT, 0);
|
|
}
|
|
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
|
} else if (decl_meta == TYPE_META_ARRAY) {
|
|
/* expect Array */
|
|
bytecode_add_instruction(bc, OP_DUP, 0);
|
|
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
|
int ciArr = bytecode_add_constant(bc, make_string("Array"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciArr);
|
|
bytecode_add_instruction(bc, OP_EQ, 0);
|
|
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
bytecode_set_operand(bc, j_to_error, bc->instr_count);
|
|
{
|
|
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Array"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
|
bytecode_add_instruction(bc, OP_PRINT, 0);
|
|
bytecode_add_instruction(bc, OP_HALT, 0);
|
|
}
|
|
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
|
} else if (decl_meta == TYPE_META_BOOLEAN) {
|
|
/* accept Boolean literal or Number; if Number, clamp to 0/1 */
|
|
/* check if value is Boolean */
|
|
bytecode_add_instruction(bc, OP_DUP, 0);
|
|
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
|
int ciBool = bytecode_add_constant(bc, make_string("Boolean"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciBool);
|
|
bytecode_add_instruction(bc, OP_EQ, 0);
|
|
int j_not_bool = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
/* it is Boolean -> OK, skip number check */
|
|
int j_done = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
/* not Boolean: check Number */
|
|
bytecode_set_operand(bc, j_not_bool, bc->instr_count);
|
|
bytecode_add_instruction(bc, OP_DUP, 0);
|
|
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
|
int ciNum = bytecode_add_constant(bc, make_string("Number"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciNum);
|
|
bytecode_add_instruction(bc, OP_EQ, 0);
|
|
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
bytecode_set_operand(bc, j_to_error, bc->instr_count);
|
|
{
|
|
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Boolean or Number for boolean"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
|
bytecode_add_instruction(bc, OP_PRINT, 0);
|
|
bytecode_add_instruction(bc, OP_HALT, 0);
|
|
}
|
|
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
|
/* if Number, clamp to 0/1 */
|
|
bytecode_add_instruction(bc, OP_UCLAMP, 1);
|
|
/* common continuation */
|
|
bytecode_set_operand(bc, j_done, bc->instr_count);
|
|
} else if (decl_meta == TYPE_META_NIL) {
|
|
/* expect Nil */
|
|
bytecode_add_instruction(bc, OP_DUP, 0);
|
|
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
|
int ciNil = bytecode_add_constant(bc, make_string("Nil"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciNil);
|
|
bytecode_add_instruction(bc, OP_EQ, 0);
|
|
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
bytecode_set_operand(bc, j_to_error, bc->instr_count);
|
|
{
|
|
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Nil"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
|
bytecode_add_instruction(bc, OP_PRINT, 0);
|
|
bytecode_add_instruction(bc, OP_HALT, 0);
|
|
}
|
|
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
|
} else {
|
|
/* integer widths: expect Number then range-check */
|
|
int abs_bits = decl_bits < 0 ? -decl_bits : decl_bits;
|
|
if (abs_bits > 0) {
|
|
/* typeof == Number */
|
|
bytecode_add_instruction(bc, OP_DUP, 0);
|
|
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
|
int ciNum = bytecode_add_constant(bc, make_string("Number"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciNum);
|
|
bytecode_add_instruction(bc, OP_EQ, 0);
|
|
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
bytecode_set_operand(bc, j_to_error, bc->instr_count);
|
|
{
|
|
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Number"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
|
bytecode_add_instruction(bc, OP_PRINT, 0);
|
|
bytecode_add_instruction(bc, OP_HALT, 0);
|
|
}
|
|
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
|
|
|
if (abs_bits > 0) {
|
|
bytecode_add_instruction(bc, (decl_bits < 0) ? OP_SCLAMP : OP_UCLAMP, abs_bits);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (lidx >= 0) {
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi);
|
|
}
|
|
} else {
|
|
/* default initialize if no '=' given */
|
|
int ci = -1;
|
|
if (is_string) {
|
|
ci = bytecode_add_constant(bc, make_string(""));
|
|
} else if (is_nil) {
|
|
ci = bytecode_add_constant(bc, make_nil());
|
|
} else if (is_class) {
|
|
/* Class-typed variable defaults to Nil until assigned an instance */
|
|
ci = bytecode_add_constant(bc, make_nil());
|
|
} else if (is_boolean) {
|
|
/* booleans default to false */
|
|
ci = bytecode_add_constant(bc, make_bool(0));
|
|
} else if (is_number || (decl_bits != 0)) {
|
|
/* integers default to 0 */
|
|
ci = bytecode_add_constant(bc, make_int(0));
|
|
}
|
|
if (ci >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
|
|
int abs_bits2 = decl_bits < 0 ? -decl_bits : decl_bits;
|
|
if (abs_bits2 > 0) {
|
|
bytecode_add_instruction(bc, (decl_bits < 0) ? OP_SCLAMP : OP_UCLAMP, abs_bits2);
|
|
}
|
|
if (lidx >= 0) {
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi);
|
|
}
|
|
}
|
|
}
|
|
*pos = local_pos;
|
|
skip_to_eol(src, len, pos);
|
|
return;
|
|
}
|
|
|
|
if (strcmp(name, "print") == 0) {
|
|
free(name);
|
|
skip_spaces(src, len, &local_pos);
|
|
(void)consume_char(src, len, &local_pos, '(');
|
|
if (emit_expression(bc, src, len, &local_pos)) {
|
|
(void)consume_char(src, len, &local_pos, ')');
|
|
bytecode_add_instruction(bc, OP_PRINT, 0);
|
|
} else {
|
|
(void)consume_char(src, len, &local_pos, ')');
|
|
}
|
|
*pos = local_pos;
|
|
skip_to_eol(src, len, pos);
|
|
return;
|
|
}
|
|
|
|
if (strcmp(name, "echo") == 0) {
|
|
free(name);
|
|
skip_spaces(src, len, &local_pos);
|
|
(void)consume_char(src, len, &local_pos, '(');
|
|
if (emit_expression(bc, src, len, &local_pos)) {
|
|
(void)consume_char(src, len, &local_pos, ')');
|
|
bytecode_add_instruction(bc, OP_ECHO, 0);
|
|
} else {
|
|
(void)consume_char(src, len, &local_pos, ')');
|
|
}
|
|
*pos = local_pos;
|
|
skip_to_eol(src, len, pos);
|
|
return;
|
|
}
|
|
|
|
/* assignment or simple call */
|
|
int lidx = local_find(name);
|
|
int gi = (lidx < 0) ? sym_index(name) : -1;
|
|
skip_spaces(src, len, &local_pos);
|
|
|
|
/* object field assignment: supports
|
|
- name.field = expr
|
|
- name.field[expr] = expr
|
|
- name.field[expr1][expr2] = expr
|
|
If pattern doesn't match an assignment, fall back to expression stmt (e.g., method call).
|
|
*/
|
|
if (local_pos < len && src[local_pos] == '.') {
|
|
size_t stmt_start = *pos; /* for expression fallback */
|
|
size_t look = local_pos + 1; /* point after '.' */
|
|
skip_spaces(src, len, &look);
|
|
char *fname = NULL;
|
|
if (!read_identifier_into(src, len, &look, &fname)) {
|
|
parser_fail(look, "Expected field name after '.'");
|
|
free(name);
|
|
return;
|
|
}
|
|
skip_spaces(src, len, &look);
|
|
if (look < len && src[look] == '[') {
|
|
/* Handle name.field[...][...] = value */
|
|
/* Load base container and resolve field: base[field] -> inner container on stack */
|
|
if (lidx >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lidx);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
|
|
}
|
|
int fci = bytecode_add_constant(bc, make_string(fname));
|
|
free(fname);
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, fci);
|
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
|
|
|
|
/* Now parse one or more [expr] */
|
|
for (;;) {
|
|
if (!(look < len && src[look] == '[')) break;
|
|
look++; /* consume '[' */
|
|
if (!emit_expression(bc, src, len, &look)) {
|
|
parser_fail(look, "Expected index expression after '['");
|
|
free(name);
|
|
return;
|
|
}
|
|
if (!consume_char(src, len, &look, ']')) {
|
|
parser_fail(look, "Expected ']' after index");
|
|
free(name);
|
|
return;
|
|
}
|
|
skip_spaces(src, len, &look);
|
|
if (look < len && src[look] == '[') {
|
|
/* Need to dereference one level: inner = inner[index] */
|
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
/* Expect '=' to assign into the last container with last index on stack */
|
|
if (look >= len || src[look] != '=') {
|
|
/* Not an assignment: fallback to expression statement */
|
|
free(name);
|
|
size_t expr_pos = stmt_start;
|
|
if (emit_expression(bc, src, len, &expr_pos)) {
|
|
bytecode_add_instruction(bc, OP_POP, 0);
|
|
}
|
|
*pos = expr_pos;
|
|
skip_to_eol(src, len, pos);
|
|
return;
|
|
}
|
|
look++; /* skip '=' */
|
|
if (!emit_expression(bc, src, len, &look)) {
|
|
parser_fail(look, "Expected expression after '='");
|
|
free(name);
|
|
return;
|
|
}
|
|
bytecode_add_instruction(bc, OP_INDEX_SET, 0);
|
|
free(name);
|
|
*pos = look;
|
|
skip_to_eol(src, len, pos);
|
|
return;
|
|
} else if (look < len && src[look] == '=') {
|
|
/* Simple name.field = value
|
|
* Previous implementation pushed (container, key) first and then evaluated value.
|
|
* That relies on CALL preserving underlying stack entries. To be robust,
|
|
* we first evaluate the value into a temporary local, then push (container, key),
|
|
* then load the temporary and perform INDEX_SET.
|
|
*/
|
|
|
|
/* Advance to after '=' and parse value into a temporary local */
|
|
local_pos = look + 1;
|
|
if (!emit_expression(bc, src, len, &local_pos)) {
|
|
parser_fail(local_pos, "Expected expression after '='");
|
|
free(fname);
|
|
free(name);
|
|
return;
|
|
}
|
|
/* store value to a hidden temp local */
|
|
int tmp_local = local_add("__assign_tmp");
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, tmp_local);
|
|
|
|
/* Load container variable */
|
|
if (lidx >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lidx);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
|
|
}
|
|
/* Push key */
|
|
int kci = bytecode_add_constant(bc, make_string(fname));
|
|
free(fname);
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, kci);
|
|
|
|
/* Load value from temp and set */
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, tmp_local);
|
|
bytecode_add_instruction(bc, OP_INDEX_SET, 0);
|
|
|
|
free(name);
|
|
*pos = local_pos;
|
|
skip_to_eol(src, len, pos);
|
|
return;
|
|
} else {
|
|
/* Not an assignment: treat as expression statement (e.g., obj.method(...)) */
|
|
free(fname);
|
|
free(name);
|
|
size_t expr_pos = stmt_start;
|
|
if (emit_expression(bc, src, len, &expr_pos)) {
|
|
bytecode_add_instruction(bc, OP_POP, 0);
|
|
}
|
|
*pos = expr_pos;
|
|
skip_to_eol(src, len, pos);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* array element assignment: name[expr] = expr and nested: name[expr1][expr2] = expr */
|
|
if (local_pos < len && src[local_pos] == '[') {
|
|
/* load array/map variable */
|
|
if (lidx >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lidx);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
|
|
}
|
|
local_pos++; /* '[' */
|
|
if (!emit_expression(bc, src, len, &local_pos)) {
|
|
parser_fail(local_pos, "Expected index expression after '['");
|
|
free(name);
|
|
return;
|
|
}
|
|
if (!consume_char(src, len, &local_pos, ']')) {
|
|
parser_fail(local_pos, "Expected ']' after index");
|
|
free(name);
|
|
return;
|
|
}
|
|
skip_spaces(src, len, &local_pos);
|
|
|
|
/* Nested index: name[expr1][expr2] = value */
|
|
if (local_pos < len && src[local_pos] == '[') {
|
|
/* Reduce base: stack currently has container, index1 -> get inner container */
|
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
|
|
|
|
local_pos++; /* second '[' */
|
|
if (!emit_expression(bc, src, len, &local_pos)) {
|
|
parser_fail(local_pos, "Expected nested index expression after '['");
|
|
free(name);
|
|
return;
|
|
}
|
|
if (!consume_char(src, len, &local_pos, ']')) {
|
|
parser_fail(local_pos, "Expected ']' after nested index");
|
|
free(name);
|
|
return;
|
|
}
|
|
skip_spaces(src, len, &local_pos);
|
|
if (local_pos >= len || src[local_pos] != '=') {
|
|
parser_fail(local_pos, "Expected '=' after nested array index");
|
|
free(name);
|
|
return;
|
|
}
|
|
local_pos++; /* '=' */
|
|
if (!emit_expression(bc, src, len, &local_pos)) {
|
|
parser_fail(local_pos, "Expected expression after '='");
|
|
free(name);
|
|
return;
|
|
}
|
|
/* perform set into inner container */
|
|
bytecode_add_instruction(bc, OP_INDEX_SET, 0);
|
|
free(name);
|
|
*pos = local_pos;
|
|
skip_to_eol(src, len, pos);
|
|
return;
|
|
}
|
|
|
|
/* Single-level: name[expr] = value */
|
|
if (local_pos >= len || src[local_pos] != '=') {
|
|
parser_fail(local_pos, "Expected '=' after array index");
|
|
free(name);
|
|
return;
|
|
}
|
|
local_pos++; /* '=' */
|
|
if (!emit_expression(bc, src, len, &local_pos)) {
|
|
parser_fail(local_pos, "Expected expression after '='");
|
|
free(name);
|
|
return;
|
|
}
|
|
/* perform set */
|
|
bytecode_add_instruction(bc, OP_INDEX_SET, 0);
|
|
free(name);
|
|
*pos = local_pos;
|
|
skip_to_eol(src, len, pos);
|
|
return;
|
|
}
|
|
|
|
free(name);
|
|
if (local_pos < len && src[local_pos] == '=') {
|
|
local_pos++; /* '=' */
|
|
if (emit_expression(bc, src, len, &local_pos)) {
|
|
/* enforce declared type if present (0 = dynamic) */
|
|
int meta = 0;
|
|
if (lidx >= 0 && g_locals) {
|
|
meta = g_locals->types[lidx];
|
|
} else if (gi >= 0) {
|
|
meta = G.types[gi];
|
|
}
|
|
|
|
if (meta == TYPE_META_STRING) {
|
|
/* expect String */
|
|
bytecode_add_instruction(bc, OP_DUP, 0);
|
|
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
|
int ciStr = bytecode_add_constant(bc, make_string("String"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciStr);
|
|
bytecode_add_instruction(bc, OP_EQ, 0);
|
|
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
bytecode_set_operand(bc, j_to_error, bc->instr_count);
|
|
{
|
|
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected String"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
|
bytecode_add_instruction(bc, OP_PRINT, 0);
|
|
bytecode_add_instruction(bc, OP_HALT, 0);
|
|
}
|
|
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
|
} else if (meta == TYPE_META_CLASS) {
|
|
/* expect Class instance: Map with "__class" key */
|
|
/* Check typeof(v) == "Map" */
|
|
bytecode_add_instruction(bc, OP_DUP, 0);
|
|
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
|
{
|
|
int ciMap = bytecode_add_constant(bc, make_string("Map"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMap);
|
|
}
|
|
bytecode_add_instruction(bc, OP_EQ, 0);
|
|
int j_err1 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
/* Check v has "__class" */
|
|
bytecode_add_instruction(bc, OP_DUP, 0);
|
|
{
|
|
int kci = bytecode_add_constant(bc, make_string("__class"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, kci);
|
|
}
|
|
bytecode_add_instruction(bc, OP_HAS_KEY, 0);
|
|
int j_err2 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
/* Success path jumps over error block */
|
|
int j_ok = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
/* Error block */
|
|
int err_lbl = bc->instr_count;
|
|
bytecode_set_operand(bc, j_err1, err_lbl);
|
|
bytecode_set_operand(bc, j_err2, err_lbl);
|
|
{
|
|
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Class"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
|
bytecode_add_instruction(bc, OP_PRINT, 0);
|
|
bytecode_add_instruction(bc, OP_HALT, 0);
|
|
}
|
|
/* Continue after checks */
|
|
bytecode_set_operand(bc, j_ok, bc->instr_count);
|
|
} else if (meta == TYPE_META_FLOAT) {
|
|
/* expect Float */
|
|
bytecode_add_instruction(bc, OP_DUP, 0);
|
|
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
|
int ciF = bytecode_add_constant(bc, make_string("Float"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciF);
|
|
bytecode_add_instruction(bc, OP_EQ, 0);
|
|
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
bytecode_set_operand(bc, j_to_error, bc->instr_count);
|
|
{
|
|
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Float"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
|
bytecode_add_instruction(bc, OP_PRINT, 0);
|
|
bytecode_add_instruction(bc, OP_HALT, 0);
|
|
}
|
|
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
|
} else if (meta == TYPE_META_ARRAY) {
|
|
/* expect Array */
|
|
bytecode_add_instruction(bc, OP_DUP, 0);
|
|
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
|
int ciArr = bytecode_add_constant(bc, make_string("Array"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciArr);
|
|
bytecode_add_instruction(bc, OP_EQ, 0);
|
|
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
bytecode_set_operand(bc, j_to_error, bc->instr_count);
|
|
{
|
|
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Array"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
|
bytecode_add_instruction(bc, OP_PRINT, 0);
|
|
bytecode_add_instruction(bc, OP_HALT, 0);
|
|
}
|
|
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
|
} else if (meta == TYPE_META_BOOLEAN) {
|
|
/* expect Number then clamp to 1 bit (unsigned) */
|
|
bytecode_add_instruction(bc, OP_DUP, 0);
|
|
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
|
int ciNum = bytecode_add_constant(bc, make_string("Number"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciNum);
|
|
bytecode_add_instruction(bc, OP_EQ, 0);
|
|
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
bytecode_set_operand(bc, j_to_error, bc->instr_count);
|
|
{
|
|
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Number for boolean"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
|
bytecode_add_instruction(bc, OP_PRINT, 0);
|
|
bytecode_add_instruction(bc, OP_HALT, 0);
|
|
}
|
|
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
|
bytecode_add_instruction(bc, OP_UCLAMP, 1);
|
|
} else if (meta == TYPE_META_NIL) {
|
|
/* expect Nil */
|
|
bytecode_add_instruction(bc, OP_DUP, 0);
|
|
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
|
int ciNil = bytecode_add_constant(bc, make_string("Nil"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciNil);
|
|
bytecode_add_instruction(bc, OP_EQ, 0);
|
|
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
bytecode_set_operand(bc, j_to_error, bc->instr_count);
|
|
{
|
|
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Nil"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
|
bytecode_add_instruction(bc, OP_PRINT, 0);
|
|
bytecode_add_instruction(bc, OP_HALT, 0);
|
|
}
|
|
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
|
} else if (meta != 0) {
|
|
/* integer widths: expect Number then range-check to declared width */
|
|
int abs_bits = meta < 0 ? -meta : meta;
|
|
/* typeof == Number */
|
|
bytecode_add_instruction(bc, OP_DUP, 0);
|
|
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
|
int ciNum = bytecode_add_constant(bc, make_string("Number"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciNum);
|
|
bytecode_add_instruction(bc, OP_EQ, 0);
|
|
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
bytecode_set_operand(bc, j_to_error, bc->instr_count);
|
|
{
|
|
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Number"));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
|
bytecode_add_instruction(bc, OP_PRINT, 0);
|
|
bytecode_add_instruction(bc, OP_HALT, 0);
|
|
}
|
|
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
|
|
|
if (abs_bits > 0) {
|
|
bytecode_add_instruction(bc, (meta < 0) ? OP_SCLAMP : OP_UCLAMP, abs_bits);
|
|
}
|
|
}
|
|
/* dynamic (meta==0): no enforcement */
|
|
|
|
if (lidx >= 0) {
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi);
|
|
}
|
|
}
|
|
*pos = local_pos;
|
|
skip_to_eol(src, len, pos);
|
|
return;
|
|
} else {
|
|
/* Treat as a generic expression-statement (e.g., method calls like obj.method(...),
|
|
property access chains, or builtin calls behind wrappers). We rewind to the
|
|
start of the statement, emit the full expression, and drop its result. */
|
|
local_pos = *pos;
|
|
if (emit_expression(bc, src, len, &local_pos)) {
|
|
bytecode_add_instruction(bc, OP_POP, 0); /* discard return value of standalone expr */
|
|
*pos = local_pos;
|
|
skip_to_eol(src, len, pos);
|
|
return;
|
|
}
|
|
/* if it wasn't a valid expression either, report a meaningful error */
|
|
parser_fail(local_pos, "Expected assignment '=' or call '(...)' after identifier");
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* fallback: print(expr) without identifier read (unlikely) */
|
|
if (starts_with(src, len, *pos, "print")) {
|
|
*pos += 5;
|
|
skip_spaces(src, len, pos);
|
|
(void)consume_char(src, len, pos, '(');
|
|
if (emit_expression(bc, src, len, pos)) {
|
|
(void)consume_char(src, len, pos, ')');
|
|
bytecode_add_instruction(bc, OP_PRINT, 0);
|
|
} else {
|
|
(void)consume_char(src, len, pos, ')');
|
|
}
|
|
skip_to_eol(src, len, pos);
|
|
return;
|
|
}
|
|
|
|
/* echo(expr): like print but does not add a newline (immediate output) */
|
|
if (starts_with(src, len, *pos, "echo")) {
|
|
*pos += 4;
|
|
skip_spaces(src, len, pos);
|
|
(void)consume_char(src, len, pos, '(');
|
|
if (emit_expression(bc, src, len, pos)) {
|
|
(void)consume_char(src, len, pos, ')');
|
|
bytecode_add_instruction(bc, OP_ECHO, 0);
|
|
} else {
|
|
(void)consume_char(src, len, pos, ')');
|
|
}
|
|
skip_to_eol(src, len, pos);
|
|
return;
|
|
}
|
|
|
|
/* unknown token: report error */
|
|
parser_fail(*pos, "Unknown token at start of statement");
|
|
}
|
|
|
|
/* parse a block with lines at indentation >= current_indent; stop at dedent */
|
|
static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, int current_indent) {
|
|
while (*pos < len) {
|
|
if (g_has_error) return;
|
|
size_t line_start = *pos; /* remember raw line start for dedent backtracking */
|
|
int indent = 0;
|
|
if (!read_line_start(src, len, pos, &indent)) {
|
|
/* EOF or error */
|
|
return;
|
|
}
|
|
if (indent < current_indent) {
|
|
/* dedent -> let caller handle this line */
|
|
*pos = line_start;
|
|
return;
|
|
}
|
|
if (indent > current_indent) {
|
|
/* nested block without a header (tolerate by parsing it and continuing) */
|
|
parse_block(bc, src, len, pos, indent);
|
|
continue;
|
|
}
|
|
|
|
/* at same indent -> parse statement */
|
|
/* Insert a line marker for better runtime error reporting. Use the current
|
|
* position (start of the actual statement) returned by read_line_start(),
|
|
* not the pre-scan position which may point to a comment/blank line. */
|
|
{
|
|
int stmt_line = 1, stmt_col = 1;
|
|
calc_line_col(src, len, *pos, &stmt_line, &stmt_col);
|
|
bytecode_add_instruction(bc, OP_LINE, stmt_line);
|
|
}
|
|
|
|
/* class definition -> factory function */
|
|
if (starts_with(src, len, *pos, "class") && (*pos + 5 < len) && (src[*pos + 5] == ' ' || src[*pos + 5] == '\t')) {
|
|
*pos += 5;
|
|
skip_spaces(src, len, pos);
|
|
/* class name */
|
|
char *cname = NULL;
|
|
if (!read_identifier_into(src, len, pos, &cname)) {
|
|
parser_fail(*pos, "Expected class name after 'class'");
|
|
return;
|
|
}
|
|
int cgi = sym_index(cname);
|
|
|
|
/* optional extends Parent */
|
|
char *parent_name = NULL;
|
|
|
|
/* Optional typed parameter list: class Name(type ident, ...) */
|
|
char *param_names[64];
|
|
int param_kind[64];
|
|
int pcount = 0;
|
|
memset(param_names, 0, sizeof(param_names));
|
|
memset(param_kind, 0, sizeof(param_kind));
|
|
|
|
/* kind: 1=Number (numeric types incl. boolean), 2=String, 3=Nil */
|
|
/* helper macro instead of nested function (C99 compliant) */
|
|
#define MAP_TYPE_KIND(t) ( \
|
|
((t) && strcmp((t), "string") == 0) ? 2 : ((t) && strcmp((t), "nil") == 0) ? 3 \
|
|
: ((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 \
|
|
: 0)
|
|
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] == '(') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] != ')') {
|
|
for (;;) {
|
|
/* read type token */
|
|
char *tname = NULL;
|
|
if (!read_identifier_into(src, len, pos, &tname)) {
|
|
parser_fail(*pos, "Expected type in class parameter list");
|
|
free(cname);
|
|
return;
|
|
}
|
|
/* read param name */
|
|
skip_spaces(src, len, pos);
|
|
char *pname = NULL;
|
|
if (!read_identifier_into(src, len, pos, &pname)) {
|
|
parser_fail(*pos, "Expected parameter name after type");
|
|
free(tname);
|
|
free(cname);
|
|
return;
|
|
}
|
|
if (pcount >= (int)(sizeof(param_names) / sizeof(param_names[0]))) {
|
|
parser_fail(*pos, "Too many class parameters");
|
|
free(tname);
|
|
free(pname);
|
|
free(cname);
|
|
return;
|
|
}
|
|
param_names[pcount] = pname;
|
|
param_kind[pcount] = MAP_TYPE_KIND(tname);
|
|
free(tname);
|
|
pcount++;
|
|
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after class parameter list");
|
|
for (int i = 0; i < pcount; ++i)
|
|
free(param_names[i]);
|
|
free(cname);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* optional 'extends Parent' after parameter list */
|
|
skip_spaces(src, len, pos);
|
|
if (starts_with(src, len, *pos, "extends")) {
|
|
*pos += 7; /* consume 'extends' */
|
|
skip_spaces(src, len, pos);
|
|
if (!read_identifier_into(src, len, pos, &parent_name)) {
|
|
parser_fail(*pos, "Expected parent class name after 'extends'");
|
|
for (int i = 0; i < pcount; ++i)
|
|
free(param_names[i]);
|
|
free(cname);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* end of class header line */
|
|
skip_to_eol(src, len, pos);
|
|
|
|
/* Build factory function: Name(...) -> instance map with fields and methods */
|
|
Bytecode *ctor_bc = bytecode_new();
|
|
/* set debug metadata for class factory */
|
|
if (ctor_bc) {
|
|
if (ctor_bc->name) free((void *)ctor_bc->name);
|
|
ctor_bc->name = strdup(cname);
|
|
if (ctor_bc->source_file) free((void *)ctor_bc->source_file);
|
|
if (g_current_source_path) ctor_bc->source_file = strdup(g_current_source_path);
|
|
}
|
|
/* local env for the factory to allow temp locals */
|
|
LocalEnv ctor_env;
|
|
memset(&ctor_env, 0, sizeof(ctor_env));
|
|
LocalEnv *prev_env = g_locals;
|
|
g_locals = &ctor_env;
|
|
|
|
/* track if _construct is defined in this class */
|
|
int ctor_present = 0;
|
|
|
|
/* Register parameter locals first so args land at 0..pcount-1 */
|
|
for (int i = 0; i < pcount; ++i) {
|
|
local_add(param_names[i]);
|
|
}
|
|
|
|
/* Guard local to detect extra argument at index == pcount */
|
|
int l_extra = local_add("__extra");
|
|
|
|
/* Runtime checks: missing args and type checks */
|
|
for (int i = 0; i < pcount; ++i) {
|
|
/* missing arg: local i must not be Nil */
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, i);
|
|
bytecode_add_instruction(ctor_bc, OP_TYPEOF, 0);
|
|
int ci_nil = bytecode_add_constant(ctor_bc, make_string("Nil"));
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_nil);
|
|
bytecode_add_instruction(ctor_bc, OP_EQ, 0);
|
|
int j_ok_present = bytecode_add_instruction(ctor_bc, OP_JUMP_IF_FALSE, 0);
|
|
/* then -> error */
|
|
{
|
|
char msg[128];
|
|
snprintf(msg, sizeof(msg), "TypeError: missing argument '%s' in %s()", param_names[i], cname);
|
|
int ci_msg = bytecode_add_constant(ctor_bc, make_string(msg));
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_msg);
|
|
bytecode_add_instruction(ctor_bc, OP_PRINT, 0);
|
|
bytecode_add_instruction(ctor_bc, OP_HALT, 0);
|
|
}
|
|
/* patch continue if present */
|
|
bytecode_set_operand(ctor_bc, j_ok_present, ctor_bc->instr_count);
|
|
|
|
/* type check for known kinds */
|
|
int kind = param_kind[i];
|
|
if (kind == 1 || kind == 2 || kind == 3) {
|
|
/* typeof(local i) == Expected ? skip error : go to error */
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, i);
|
|
bytecode_add_instruction(ctor_bc, OP_TYPEOF, 0);
|
|
const char *exp = (kind == 1) ? "Number" : (kind == 2) ? "String"
|
|
: "Nil";
|
|
int ci_exp = bytecode_add_constant(ctor_bc, make_string(exp));
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_exp);
|
|
bytecode_add_instruction(ctor_bc, OP_EQ, 0);
|
|
/* if false -> jump to error */
|
|
int j_to_error = bytecode_add_instruction(ctor_bc, OP_JUMP_IF_FALSE, 0);
|
|
/* on success, skip error block */
|
|
int j_skip_err = bytecode_add_instruction(ctor_bc, OP_JUMP, 0);
|
|
/* error block */
|
|
{
|
|
int err_label = ctor_bc->instr_count;
|
|
bytecode_set_operand(ctor_bc, j_to_error, err_label);
|
|
char msg2[160];
|
|
snprintf(msg2, sizeof(msg2), "TypeError: %s() expects %s for '%s'", cname, exp, param_names[i]);
|
|
int ci_msg2 = bytecode_add_constant(ctor_bc, make_string(msg2));
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_msg2);
|
|
bytecode_add_instruction(ctor_bc, OP_PRINT, 0);
|
|
bytecode_add_instruction(ctor_bc, OP_HALT, 0);
|
|
}
|
|
/* continue label after error block */
|
|
bytecode_set_operand(ctor_bc, j_skip_err, ctor_bc->instr_count);
|
|
}
|
|
}
|
|
|
|
/* Extra args check: guard local must be Nil; if not Nil -> error */
|
|
{
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_extra);
|
|
bytecode_add_instruction(ctor_bc, OP_TYPEOF, 0);
|
|
int ci_nil2 = bytecode_add_constant(ctor_bc, make_string("Nil"));
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_nil2);
|
|
bytecode_add_instruction(ctor_bc, OP_EQ, 0);
|
|
/* if false (not Nil) -> jump to error */
|
|
int j_to_error = bytecode_add_instruction(ctor_bc, OP_JUMP_IF_FALSE, 0);
|
|
/* if true (Nil) -> skip error */
|
|
int j_skip_err = bytecode_add_instruction(ctor_bc, OP_JUMP, 0);
|
|
{
|
|
int err_label = ctor_bc->instr_count;
|
|
bytecode_set_operand(ctor_bc, j_to_error, err_label);
|
|
char msg3[128];
|
|
snprintf(msg3, sizeof(msg3), "TypeError: %s() received too many arguments", cname);
|
|
int ci_msg3 = bytecode_add_constant(ctor_bc, make_string(msg3));
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_msg3);
|
|
bytecode_add_instruction(ctor_bc, OP_PRINT, 0);
|
|
bytecode_add_instruction(ctor_bc, OP_HALT, 0);
|
|
}
|
|
bytecode_set_operand(ctor_bc, j_skip_err, ctor_bc->instr_count);
|
|
}
|
|
|
|
/* instance map: __this = {} (placed after param guard) */
|
|
int l_this = local_add("__this");
|
|
bytecode_add_instruction(ctor_bc, OP_MAKE_MAP, 0);
|
|
bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_this);
|
|
|
|
/* tag instance with its class name: this["__class"] = "<ClassName>" */
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this);
|
|
{
|
|
int kci_cls = bytecode_add_constant(ctor_bc, make_string("__class"));
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, kci_cls);
|
|
int vci_cls = bytecode_add_constant(ctor_bc, make_string(cname));
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, vci_cls);
|
|
}
|
|
bytecode_add_instruction(ctor_bc, OP_INDEX_SET, 0);
|
|
|
|
/* Inheritance: if extends Parent, create Parent(header args...) and merge its keys into this */
|
|
if (parent_name) {
|
|
/* parent_inst = Parent(args...) */
|
|
int parent_gi = sym_index(parent_name);
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_GLOBAL, parent_gi);
|
|
for (int i = 0; i < pcount; ++i) {
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, i);
|
|
}
|
|
bytecode_add_instruction(ctor_bc, OP_CALL, pcount);
|
|
int l_parent = local_add("__parent_inst");
|
|
bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_parent);
|
|
|
|
/* keys = keys(parent_inst) */
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_parent);
|
|
bytecode_add_instruction(ctor_bc, OP_KEYS, 0);
|
|
int l_keys = local_add("__parent_keys");
|
|
bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_keys);
|
|
|
|
/* i = 0 */
|
|
int c0_inh = bytecode_add_constant(ctor_bc, make_int(0));
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, c0_inh);
|
|
int l_i = local_add("__inh_i");
|
|
bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_i);
|
|
|
|
/* loop: while (i < len(keys)) */
|
|
int loop_start = ctor_bc->instr_count;
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_i);
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_keys);
|
|
bytecode_add_instruction(ctor_bc, OP_LEN, 0);
|
|
bytecode_add_instruction(ctor_bc, OP_LT, 0);
|
|
int jmp_false = bytecode_add_instruction(ctor_bc, OP_JUMP_IF_FALSE, 0);
|
|
|
|
/* key = keys[i] */
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_keys);
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_i);
|
|
bytecode_add_instruction(ctor_bc, OP_INDEX_GET, 0);
|
|
int l_k = local_add("__inh_k");
|
|
bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_k);
|
|
|
|
/* if this has key -> skip set */
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this);
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_k);
|
|
bytecode_add_instruction(ctor_bc, OP_HAS_KEY, 0);
|
|
int j_skip_set = bytecode_add_instruction(ctor_bc, OP_JUMP_IF_FALSE, 0);
|
|
/* has key -> nothing to do, jump over set sequence */
|
|
int j_after_maybe_set = bytecode_add_instruction(ctor_bc, OP_JUMP, 0);
|
|
|
|
/* not has key: set this[key] = parent_inst[key] */
|
|
bytecode_set_operand(ctor_bc, j_skip_set, ctor_bc->instr_count);
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this);
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_k);
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_parent);
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_k);
|
|
bytecode_add_instruction(ctor_bc, OP_INDEX_GET, 0);
|
|
bytecode_add_instruction(ctor_bc, OP_INDEX_SET, 0);
|
|
|
|
/* continue after maybe-set */
|
|
bytecode_set_operand(ctor_bc, j_after_maybe_set, ctor_bc->instr_count);
|
|
|
|
/* i++ */
|
|
int c1_inh = bytecode_add_constant(ctor_bc, make_int(1));
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_i);
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, c1_inh);
|
|
bytecode_add_instruction(ctor_bc, OP_ADD, 0);
|
|
bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_i);
|
|
|
|
/* back to loop */
|
|
bytecode_add_instruction(ctor_bc, OP_JUMP, loop_start);
|
|
/* end loop */
|
|
bytecode_set_operand(ctor_bc, jmp_false, ctor_bc->instr_count);
|
|
}
|
|
|
|
/* Parse class body at increased indent */
|
|
int body_indent = 0;
|
|
size_t look_body = *pos;
|
|
if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) {
|
|
/* iterate over class members at body_indent */
|
|
for (;;) {
|
|
size_t member_line_start = *pos;
|
|
int member_indent = 0;
|
|
if (!read_line_start(src, len, pos, &member_indent)) {
|
|
/* EOF */
|
|
break;
|
|
}
|
|
if (member_indent < body_indent) {
|
|
/* end of class body */
|
|
*pos = member_line_start;
|
|
break;
|
|
}
|
|
if (member_indent > body_indent) {
|
|
/* skip nested blocks that are part of previous method parsing */
|
|
parse_block(ctor_bc, src, len, pos, member_indent);
|
|
continue;
|
|
}
|
|
|
|
/* at body_indent: member declaration (field = expr) or method 'fun name(...)' */
|
|
if (starts_with(src, len, *pos, "fun")) {
|
|
/* method definition: fun m(this, ...) ... */
|
|
*pos += 3;
|
|
skip_spaces(src, len, pos);
|
|
char *mname = NULL;
|
|
if (!read_identifier_into(src, len, pos, &mname)) {
|
|
parser_fail(*pos, "Expected method name after 'fun' in class");
|
|
g_locals = prev_env;
|
|
free(cname);
|
|
return;
|
|
}
|
|
int is_ctor_method = (strcmp(mname, "_construct") == 0);
|
|
|
|
skip_spaces(src, len, pos);
|
|
if (!consume_char(src, len, pos, '(')) {
|
|
parser_fail(*pos, "Expected '(' after method name");
|
|
free(mname);
|
|
g_locals = prev_env;
|
|
free(cname);
|
|
return;
|
|
}
|
|
|
|
/* Build method function bytecode */
|
|
Bytecode *m_bc = bytecode_new();
|
|
if (m_bc) {
|
|
if (m_bc->name) free((void *)m_bc->name);
|
|
/* method qualified name: Class.method */
|
|
size_t qlen = strlen(cname) + 1 + strlen(mname) + 1;
|
|
char *q = (char *)malloc(qlen);
|
|
if (q) {
|
|
snprintf(q, qlen, "%s.%s", cname, mname);
|
|
m_bc->name = q;
|
|
}
|
|
if (m_bc->source_file) free((void *)m_bc->source_file);
|
|
if (g_current_source_path) m_bc->source_file = strdup(g_current_source_path);
|
|
}
|
|
LocalEnv m_env;
|
|
memset(&m_env, 0, sizeof(m_env));
|
|
LocalEnv *saved = g_locals;
|
|
g_locals = &m_env;
|
|
|
|
/* Parse params, ensure first is 'this' (insert if missing) */
|
|
int saw_param = 0;
|
|
int param_count = 0;
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] != ')') {
|
|
for (;;) {
|
|
char *pname = NULL;
|
|
if (!read_identifier_into(src, len, pos, &pname)) {
|
|
parser_fail(*pos, "Expected parameter name");
|
|
free(mname);
|
|
g_locals = saved;
|
|
g_locals = prev_env;
|
|
free(cname);
|
|
return;
|
|
}
|
|
if (param_count == 0 && strcmp(pname, "this") != 0) {
|
|
/* Require explicit 'this' as first parameter */
|
|
if (is_ctor_method) {
|
|
parser_fail(*pos, "Constructor '_construct' must declare 'this' as its first parameter");
|
|
} else {
|
|
parser_fail(*pos, "First parameter of a method must be 'this'");
|
|
}
|
|
free(pname);
|
|
free(mname);
|
|
g_locals = saved;
|
|
g_locals = prev_env;
|
|
free(cname);
|
|
return;
|
|
}
|
|
local_add(pname);
|
|
free(pname);
|
|
param_count++;
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
/* no params: enforce (this) */
|
|
if (is_ctor_method) {
|
|
parser_fail(*pos, "Constructor '_construct' must declare 'this' as its first parameter");
|
|
} else {
|
|
parser_fail(*pos, "Method must declare at least 'this' parameter");
|
|
}
|
|
free(mname);
|
|
g_locals = saved;
|
|
g_locals = prev_env;
|
|
free(cname);
|
|
return;
|
|
}
|
|
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after method parameter list");
|
|
free(mname);
|
|
g_locals = saved;
|
|
g_locals = prev_env;
|
|
free(cname);
|
|
return;
|
|
}
|
|
/* end header line */
|
|
skip_to_eol(src, len, pos);
|
|
|
|
/* parse method body at increased indent */
|
|
int m_body_indent = 0;
|
|
size_t look_m = *pos;
|
|
if (read_line_start(src, len, &look_m, &m_body_indent) && m_body_indent > body_indent) {
|
|
parse_block(m_bc, src, len, pos, m_body_indent);
|
|
} else {
|
|
/* empty method body allowed -> return */
|
|
}
|
|
/* ensure return */
|
|
bytecode_add_instruction(m_bc, OP_RETURN, 0);
|
|
|
|
/* restore env to factory */
|
|
g_locals = saved;
|
|
|
|
/* Insert method function into instance: this["mname"] = <function> */
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this);
|
|
int kci = bytecode_add_constant(ctor_bc, make_string(mname));
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, kci);
|
|
int mci = bytecode_add_constant(ctor_bc, make_function(m_bc));
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, mci);
|
|
bytecode_add_instruction(ctor_bc, OP_INDEX_SET, 0);
|
|
|
|
/* mark constructor presence if name matches */
|
|
if (strcmp(mname, "_construct") == 0) {
|
|
ctor_present = 1;
|
|
}
|
|
|
|
free(mname);
|
|
continue;
|
|
}
|
|
|
|
/* field initializer: ident = expr */
|
|
size_t lp = *pos;
|
|
char *fname = NULL;
|
|
if (!read_identifier_into(src, len, &lp, &fname)) {
|
|
parser_fail(*pos, "Expected field or 'fun' in class body");
|
|
g_locals = prev_env;
|
|
free(cname);
|
|
return;
|
|
}
|
|
size_t tmp = lp;
|
|
skip_spaces(src, len, &tmp);
|
|
if (tmp >= len || src[tmp] != '=') {
|
|
free(fname);
|
|
parser_fail(tmp, "Expected '=' in field initializer");
|
|
g_locals = prev_env;
|
|
free(cname);
|
|
return;
|
|
}
|
|
/* commit position and consume '=' */
|
|
*pos = tmp + 1;
|
|
/* emit: this["fname"] = (expr) */
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this);
|
|
int fkey = bytecode_add_constant(ctor_bc, make_string(fname));
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, fkey);
|
|
free(fname);
|
|
if (!emit_expression(ctor_bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected expression in field initializer");
|
|
g_locals = prev_env;
|
|
free(cname);
|
|
return;
|
|
}
|
|
bytecode_add_instruction(ctor_bc, OP_INDEX_SET, 0);
|
|
/* end of line */
|
|
skip_to_eol(src, len, pos);
|
|
}
|
|
} else {
|
|
/* empty class body allowed */
|
|
}
|
|
|
|
/* Override defaults with constructor parameters: this["name"] = local i */
|
|
for (int i = 0; i < pcount; ++i) {
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this);
|
|
int kci = bytecode_add_constant(ctor_bc, make_string(param_names[i]));
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, kci);
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, i);
|
|
bytecode_add_instruction(ctor_bc, OP_INDEX_SET, 0);
|
|
}
|
|
|
|
/* If a constructor exists, invoke: this._construct(this, params...) and drop its return */
|
|
if (ctor_present) {
|
|
/* fetch method: duplicate 'this' so we keep it for the call */
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this);
|
|
bytecode_add_instruction(ctor_bc, OP_DUP, 0); /* -> this, this */
|
|
{
|
|
int kci_ctor = bytecode_add_constant(ctor_bc, make_string("_construct"));
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, kci_ctor);
|
|
}
|
|
bytecode_add_instruction(ctor_bc, OP_INDEX_GET, 0); /* -> this, func */
|
|
bytecode_add_instruction(ctor_bc, OP_SWAP, 0); /* -> func, this */
|
|
|
|
/* push all header parameters as additional args in order */
|
|
for (int i = 0; i < pcount; ++i) {
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, i);
|
|
}
|
|
/* call with implicit 'this' (+1) plus pcount params */
|
|
bytecode_add_instruction(ctor_bc, OP_CALL, pcount + 1);
|
|
/* discard any return value from constructor */
|
|
bytecode_add_instruction(ctor_bc, OP_POP, 0);
|
|
}
|
|
|
|
/* return instance */
|
|
bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this);
|
|
bytecode_add_instruction(ctor_bc, OP_RETURN, 0);
|
|
|
|
/* restore outer locals env */
|
|
g_locals = prev_env;
|
|
|
|
/* bind factory function globally under class name */
|
|
int cci = bytecode_add_constant(bc, make_function(ctor_bc));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, cci);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, cgi);
|
|
/* mark this global as a class for typeof(identifier) */
|
|
G.is_class[cgi] = 1;
|
|
|
|
for (int i = 0; i < pcount; ++i)
|
|
free(param_names[i]);
|
|
if (parent_name) free(parent_name);
|
|
free(cname);
|
|
continue;
|
|
}
|
|
|
|
if (starts_with(src, len, *pos, "fun") && (*pos + 3 < len) && (src[*pos + 3] == ' ' || src[*pos + 3] == '\t')) {
|
|
/* parse header: fun name(arg, ...) */
|
|
*pos += 3;
|
|
skip_spaces(src, len, pos);
|
|
char *fname = NULL;
|
|
if (!read_identifier_into(src, len, pos, &fname)) {
|
|
parser_fail(*pos, "Expected function name after 'fun'");
|
|
return;
|
|
}
|
|
int fgi = sym_index(fname);
|
|
skip_spaces(src, len, pos);
|
|
if (!consume_char(src, len, pos, '(')) {
|
|
parser_fail(*pos, "Expected '(' after function name");
|
|
free(fname);
|
|
return;
|
|
}
|
|
|
|
/* build locals from parameters */
|
|
LocalEnv env = {{0}, 0};
|
|
LocalEnv *prev = g_locals;
|
|
g_locals = &env;
|
|
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] != ')') {
|
|
for (;;) {
|
|
char *pname = NULL;
|
|
if (!read_identifier_into(src, len, pos, &pname)) {
|
|
parser_fail(*pos, "Expected parameter name");
|
|
g_locals = prev;
|
|
free(fname);
|
|
return;
|
|
}
|
|
if (local_find(pname) >= 0) {
|
|
parser_fail(*pos, "Duplicate parameter name '%s'", pname);
|
|
free(pname);
|
|
g_locals = prev;
|
|
free(fname);
|
|
return;
|
|
}
|
|
local_add(pname);
|
|
free(pname);
|
|
skip_spaces(src, len, pos);
|
|
if (*pos < len && src[*pos] == ',') {
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after parameter list");
|
|
g_locals = prev;
|
|
free(fname);
|
|
return;
|
|
}
|
|
/* end header line */
|
|
skip_to_eol(src, len, pos);
|
|
|
|
/* compile body into separate Bytecode */
|
|
Bytecode *fn_bc = bytecode_new();
|
|
if (fn_bc) {
|
|
if (fn_bc->name) free((void *)fn_bc->name);
|
|
fn_bc->name = strdup(fname);
|
|
if (fn_bc->source_file) free((void *)fn_bc->source_file);
|
|
if (g_current_source_path) fn_bc->source_file = strdup(g_current_source_path);
|
|
}
|
|
|
|
/* parse body at increased indent if present */
|
|
int body_indent = 0;
|
|
size_t look_body = *pos;
|
|
if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) {
|
|
/* do not advance pos here; let parse_block consume the line */
|
|
parse_block(fn_bc, src, len, pos, body_indent);
|
|
} else {
|
|
/* empty body: ok */
|
|
}
|
|
/* ensure function returns */
|
|
bytecode_add_instruction(fn_bc, OP_RETURN, 0);
|
|
|
|
#ifdef FUN_DEBUG
|
|
/* DEBUG: dump compiled function bytecode (guarded by runtime env) */
|
|
if (fun_debug_enabled()) {
|
|
printf("=== compiled function %s (%d params) ===\n", fname, env.count);
|
|
bytecode_dump(fn_bc);
|
|
printf("=== end function %s ===\n", fname);
|
|
}
|
|
#endif
|
|
|
|
/* bind function to global: LOAD_CONST <fn> ; STORE_GLOBAL fgi */
|
|
int fci = bytecode_add_constant(bc, make_function(fn_bc));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, fci);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, fgi);
|
|
|
|
g_locals = prev;
|
|
free(fname);
|
|
continue;
|
|
}
|
|
|
|
/* for-sugar:
|
|
* - for <ident> in range(a, b)
|
|
* - for <ident> in <array-expr>
|
|
* - for (<keyIdent>, <valIdent>) in <map-expr>
|
|
*/
|
|
if (starts_with(src, len, *pos, "for")) {
|
|
*pos += 3;
|
|
skip_spaces(src, len, pos);
|
|
|
|
/* Optional tuple '(k, v)' for map iteration */
|
|
int tuple_mode = 0; /* 0 = single var, 1 = (k,v) */
|
|
char *ivar = NULL; /* single variable name OR key variable when tuple */
|
|
char *vvar = NULL; /* value variable when tuple */
|
|
|
|
if (*pos < len && src[*pos] == '(') {
|
|
/* Parse '(k, v)' */
|
|
(*pos)++;
|
|
skip_spaces(src, len, pos);
|
|
if (!read_identifier_into(src, len, pos, &ivar)) {
|
|
parser_fail(*pos, "Expected identifier after '(' in for tuple");
|
|
return;
|
|
}
|
|
skip_spaces(src, len, pos);
|
|
if (!consume_char(src, len, pos, ',')) {
|
|
parser_fail(*pos, "Expected ',' between key and value in for tuple");
|
|
free(ivar);
|
|
return;
|
|
}
|
|
skip_spaces(src, len, pos);
|
|
if (!read_identifier_into(src, len, pos, &vvar)) {
|
|
parser_fail(*pos, "Expected value identifier after ',' in for tuple");
|
|
free(ivar);
|
|
return;
|
|
}
|
|
skip_spaces(src, len, pos);
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' to close for tuple");
|
|
free(ivar);
|
|
free(vvar);
|
|
return;
|
|
}
|
|
tuple_mode = 1;
|
|
} else {
|
|
/* loop variable name */
|
|
if (!read_identifier_into(src, len, pos, &ivar)) {
|
|
parser_fail(*pos, "Expected loop variable after 'for'");
|
|
return;
|
|
}
|
|
}
|
|
|
|
skip_spaces(src, len, pos);
|
|
if (!starts_with(src, len, *pos, "in")) {
|
|
parser_fail(*pos, "Expected 'in' after loop variable");
|
|
free(ivar);
|
|
return;
|
|
}
|
|
*pos += 2;
|
|
skip_spaces(src, len, pos);
|
|
|
|
if (starts_with(src, len, *pos, "range")) {
|
|
/* ===== range(a, b) variant ===== */
|
|
*pos += 5;
|
|
if (!consume_char(src, len, pos, '(')) {
|
|
parser_fail(*pos, "Expected '(' after range");
|
|
free(ivar);
|
|
return;
|
|
}
|
|
|
|
/* Parse start expression */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected start expression in range");
|
|
free(ivar);
|
|
return;
|
|
}
|
|
|
|
/* Determine loop variable storage and store start value */
|
|
int lidx = local_find(ivar);
|
|
int gi = -1;
|
|
if (lidx < 0) {
|
|
if (g_locals)
|
|
lidx = local_add(ivar);
|
|
else
|
|
gi = sym_index(ivar);
|
|
}
|
|
if (lidx >= 0) {
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi);
|
|
}
|
|
|
|
/* comma */
|
|
skip_spaces(src, len, pos);
|
|
if (*pos >= len || src[*pos] != ',') {
|
|
parser_fail(*pos, "Expected ',' between range start and end");
|
|
free(ivar);
|
|
return;
|
|
}
|
|
(*pos)++; /* consume ',' */
|
|
skip_spaces(src, len, pos);
|
|
|
|
/* Parse end expression and store in a temp (local or global) */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected end expression in range");
|
|
free(ivar);
|
|
return;
|
|
}
|
|
|
|
char tmpname[64];
|
|
snprintf(tmpname, sizeof(tmpname), "__for_end_%d", g_temp_counter++);
|
|
|
|
int lend = -1, gend = -1;
|
|
if (g_locals) {
|
|
lend = local_add(tmpname);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, lend);
|
|
} else {
|
|
gend = sym_index(tmpname);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gend);
|
|
}
|
|
|
|
if (!consume_char(src, len, pos, ')')) {
|
|
parser_fail(*pos, "Expected ')' after range arguments");
|
|
free(ivar);
|
|
return;
|
|
}
|
|
|
|
/* end of header line */
|
|
skip_to_eol(src, len, pos);
|
|
|
|
/* emit loop */
|
|
int loop_start = bc->instr_count;
|
|
|
|
/* condition: ivar < end_tmp */
|
|
if (lidx >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lidx);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
|
|
}
|
|
if (lend >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lend);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gend);
|
|
}
|
|
bytecode_add_instruction(bc, OP_LT, 0);
|
|
int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
|
|
/* enter loop context for break/continue */
|
|
LoopCtx ctx = {{0}, 0, {0}, 0, g_loop_ctx};
|
|
g_loop_ctx = &ctx;
|
|
|
|
/* parse body at increased indent (peek) */
|
|
int body_indent = 0;
|
|
size_t look_body = *pos;
|
|
if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) {
|
|
parse_block(bc, src, len, pos, body_indent);
|
|
} else {
|
|
/* empty body ok */
|
|
}
|
|
|
|
/* continue target: start of increment */
|
|
int cont_label = bc->instr_count;
|
|
|
|
/* i = i + 1 */
|
|
int c1 = bytecode_add_constant(bc, make_int(1));
|
|
if (lidx >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lidx);
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, c1);
|
|
bytecode_add_instruction(bc, OP_ADD, 0);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, c1);
|
|
bytecode_add_instruction(bc, OP_ADD, 0);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi);
|
|
}
|
|
|
|
/* back edge and patch */
|
|
bytecode_add_instruction(bc, OP_JUMP, loop_start);
|
|
|
|
/* end label (after loop) */
|
|
int end_label = bc->instr_count;
|
|
bytecode_set_operand(bc, jmp_false, end_label);
|
|
|
|
/* patch continue/break jumps */
|
|
for (int bi = 0; bi < ctx.cont_count; ++bi) {
|
|
bytecode_set_operand(bc, ctx.continue_jumps[bi], cont_label);
|
|
}
|
|
for (int bi = 0; bi < ctx.break_count; ++bi) {
|
|
bytecode_set_operand(bc, ctx.break_jumps[bi], end_label);
|
|
}
|
|
g_loop_ctx = ctx.prev;
|
|
|
|
free(ivar);
|
|
continue;
|
|
} else if (!tuple_mode) {
|
|
/* ===== array iteration: for ivar in <expr> ===== */
|
|
/* Evaluate the iterable once and store in a temp */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected iterable expression after 'in'");
|
|
free(ivar);
|
|
return;
|
|
}
|
|
char arrname[64];
|
|
snprintf(arrname, sizeof(arrname), "__for_arr_%d", g_temp_counter++);
|
|
int larr = -1, garr = -1;
|
|
if (g_locals) {
|
|
larr = local_add(arrname);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, larr);
|
|
} else {
|
|
garr = sym_index(arrname);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, garr);
|
|
}
|
|
|
|
/* Compute length once: len(arr) -> store temp */
|
|
if (larr >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr);
|
|
}
|
|
bytecode_add_instruction(bc, OP_LEN, 0);
|
|
char lenname[64];
|
|
snprintf(lenname, sizeof(lenname), "__for_len_%d", g_temp_counter++);
|
|
int llen = -1, glen = -1;
|
|
if (g_locals) {
|
|
llen = local_add(lenname);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, llen);
|
|
} else {
|
|
glen = sym_index(lenname);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, glen);
|
|
}
|
|
|
|
/* Index temp: i = 0 */
|
|
int c0 = bytecode_add_constant(bc, make_int(0));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, c0);
|
|
char iname[64];
|
|
snprintf(iname, sizeof(iname), "__for_i_%d", g_temp_counter++);
|
|
int li = -1, gi = -1;
|
|
if (g_locals) {
|
|
li = local_add(iname);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, li);
|
|
} else {
|
|
gi = sym_index(iname);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi);
|
|
}
|
|
|
|
/* end of header line */
|
|
skip_to_eol(src, len, pos);
|
|
|
|
/* loop start label */
|
|
int loop_start = bc->instr_count;
|
|
|
|
/* condition: i < len */
|
|
if (li >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, li);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
|
|
}
|
|
if (llen >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, llen);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, glen);
|
|
}
|
|
bytecode_add_instruction(bc, OP_LT, 0);
|
|
int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
|
|
/* element: ivar = arr[i] */
|
|
if (larr >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr);
|
|
}
|
|
if (li >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, li);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
|
|
}
|
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
|
|
|
|
/* assign to ivar (local preferred) */
|
|
int ldst = local_find(ivar);
|
|
int gdst = -1;
|
|
if (ldst < 0) {
|
|
if (g_locals)
|
|
ldst = local_add(ivar);
|
|
else
|
|
gdst = sym_index(ivar);
|
|
}
|
|
if (ldst >= 0) {
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, ldst);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gdst);
|
|
}
|
|
|
|
/* enter loop context for break/continue */
|
|
LoopCtx ctx = {{0}, 0, {0}, 0, g_loop_ctx};
|
|
g_loop_ctx = &ctx;
|
|
|
|
/* parse body at increased indent */
|
|
int body_indent = 0;
|
|
size_t look_body = *pos;
|
|
if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) {
|
|
parse_block(bc, src, len, pos, body_indent);
|
|
} else {
|
|
/* empty body ok */
|
|
}
|
|
|
|
/* continue target: start of increment */
|
|
int cont_label = bc->instr_count;
|
|
|
|
/* i = i + 1 */
|
|
int c1 = bytecode_add_constant(bc, make_int(1));
|
|
if (li >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, li);
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, c1);
|
|
bytecode_add_instruction(bc, OP_ADD, 0);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, li);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, c1);
|
|
bytecode_add_instruction(bc, OP_ADD, 0);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi);
|
|
}
|
|
|
|
/* back edge and patch */
|
|
bytecode_add_instruction(bc, OP_JUMP, loop_start);
|
|
|
|
/* end label (after loop) */
|
|
int end_label = bc->instr_count;
|
|
bytecode_set_operand(bc, jmp_false, end_label);
|
|
|
|
/* patch continue/break jumps */
|
|
for (int bi = 0; bi < ctx.cont_count; ++bi) {
|
|
bytecode_set_operand(bc, ctx.continue_jumps[bi], cont_label);
|
|
}
|
|
for (int bi = 0; bi < ctx.break_count; ++bi) {
|
|
bytecode_set_operand(bc, ctx.break_jumps[bi], end_label);
|
|
}
|
|
g_loop_ctx = ctx.prev;
|
|
|
|
free(ivar);
|
|
continue;
|
|
} else {
|
|
/* ===== map iteration with tuple: for (k, v) in <expr> ===== */
|
|
/* Evaluate the map expr once: store in temp */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
parser_fail(*pos, "Expected map expression after 'in'");
|
|
free(ivar);
|
|
free(vvar);
|
|
return;
|
|
}
|
|
char mapname[64];
|
|
snprintf(mapname, sizeof(mapname), "__for_map_%d", g_temp_counter++);
|
|
int lmap = -1, gmap = -1;
|
|
if (g_locals) {
|
|
lmap = local_add(mapname);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, lmap);
|
|
} else {
|
|
gmap = sym_index(mapname);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gmap);
|
|
}
|
|
|
|
/* keys = keys(map) */
|
|
if (lmap >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lmap);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gmap);
|
|
}
|
|
bytecode_add_instruction(bc, OP_KEYS, 0);
|
|
char keysname[64];
|
|
snprintf(keysname, sizeof(keysname), "__for_keys_%d", g_temp_counter++);
|
|
int lkeys = -1, gkeys = -1;
|
|
if (g_locals) {
|
|
lkeys = local_add(keysname);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, lkeys);
|
|
} else {
|
|
gkeys = sym_index(keysname);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gkeys);
|
|
}
|
|
|
|
/* len(keys) */
|
|
if (lkeys >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lkeys);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gkeys);
|
|
}
|
|
bytecode_add_instruction(bc, OP_LEN, 0);
|
|
char lenname[64];
|
|
snprintf(lenname, sizeof(lenname), "__for_klen_%d", g_temp_counter++);
|
|
int llen = -1, glen = -1;
|
|
if (g_locals) {
|
|
llen = local_add(lenname);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, llen);
|
|
} else {
|
|
glen = sym_index(lenname);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, glen);
|
|
}
|
|
|
|
/* i = 0 */
|
|
int c0m = bytecode_add_constant(bc, make_int(0));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, c0m);
|
|
char iname[64];
|
|
snprintf(iname, sizeof(iname), "__for_ki_%d", g_temp_counter++);
|
|
int li = -1, gi = -1;
|
|
if (g_locals) {
|
|
li = local_add(iname);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, li);
|
|
} else {
|
|
gi = sym_index(iname);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi);
|
|
}
|
|
|
|
/* end of header line */
|
|
skip_to_eol(src, len, pos);
|
|
|
|
/* loop start */
|
|
int loop_start = bc->instr_count;
|
|
/* condition: i < len */
|
|
if (li >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, li);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
|
|
}
|
|
if (llen >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, llen);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, glen);
|
|
}
|
|
bytecode_add_instruction(bc, OP_LT, 0);
|
|
int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
|
|
/* key = keys[i] */
|
|
if (lkeys >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lkeys);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gkeys);
|
|
}
|
|
if (li >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, li);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
|
|
}
|
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
|
|
|
|
/* assign to key variable (ivar) */
|
|
int lk = local_find(ivar);
|
|
int gk = -1;
|
|
if (lk < 0) {
|
|
if (g_locals)
|
|
lk = local_add(ivar);
|
|
else
|
|
gk = sym_index(ivar);
|
|
}
|
|
if (lk >= 0) {
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, lk);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gk);
|
|
}
|
|
|
|
/* value = map[key] */
|
|
if (lmap >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lmap);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gmap);
|
|
}
|
|
/* load key again */
|
|
if (lk >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, lk);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gk);
|
|
}
|
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
|
|
|
|
/* assign to value variable (vvar) */
|
|
int lv = local_find(vvar);
|
|
int gv = -1;
|
|
if (lv < 0) {
|
|
if (g_locals)
|
|
lv = local_add(vvar);
|
|
else
|
|
gv = sym_index(vvar);
|
|
}
|
|
if (lv >= 0) {
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, lv);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gv);
|
|
}
|
|
|
|
/* enter loop context */
|
|
LoopCtx ctx = {{0}, 0, {0}, 0, g_loop_ctx};
|
|
g_loop_ctx = &ctx;
|
|
|
|
/* body */
|
|
int body_indent = 0;
|
|
size_t look_body = *pos;
|
|
if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) {
|
|
parse_block(bc, src, len, pos, body_indent);
|
|
} else {
|
|
/* empty body ok */
|
|
}
|
|
|
|
/* continue target: i = i + 1 */
|
|
int cont_label = bc->instr_count;
|
|
int c1m = bytecode_add_constant(bc, make_int(1));
|
|
if (li >= 0) {
|
|
bytecode_add_instruction(bc, OP_LOAD_LOCAL, li);
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, c1m);
|
|
bytecode_add_instruction(bc, OP_ADD, 0);
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, li);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, c1m);
|
|
bytecode_add_instruction(bc, OP_ADD, 0);
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi);
|
|
}
|
|
|
|
/* back edge */
|
|
bytecode_add_instruction(bc, OP_JUMP, loop_start);
|
|
|
|
/* end label */
|
|
int end_label = bc->instr_count;
|
|
bytecode_set_operand(bc, jmp_false, end_label);
|
|
|
|
/* patch continue/break */
|
|
for (int bi = 0; bi < ctx.cont_count; ++bi) {
|
|
bytecode_set_operand(bc, ctx.continue_jumps[bi], cont_label);
|
|
}
|
|
for (int bi = 0; bi < ctx.break_count; ++bi) {
|
|
bytecode_set_operand(bc, ctx.break_jumps[bi], end_label);
|
|
}
|
|
g_loop_ctx = ctx.prev;
|
|
|
|
free(ivar);
|
|
free(vvar);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (starts_with(src, len, *pos, "if")) {
|
|
int end_jumps[64];
|
|
int end_count = 0;
|
|
|
|
for (;;) {
|
|
/* consume 'if' or 'else if' condition */
|
|
if (starts_with(src, len, *pos, "if")) {
|
|
*pos += 2;
|
|
} else {
|
|
/* for 'else if' we arrive here with *pos already after 'if' */
|
|
}
|
|
|
|
/* require at least one space before condition if present */
|
|
skip_spaces(src, len, pos);
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
/* no condition -> treat as false */
|
|
int ci = bytecode_add_constant(bc, make_int(0));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
|
|
}
|
|
|
|
/* Decide between inline single-statement and indented block on next line */
|
|
size_t ppeek = *pos;
|
|
/* skip spaces after condition */
|
|
while (ppeek < len && src[ppeek] == ' ')
|
|
ppeek++;
|
|
int inline_stmt = 0;
|
|
if (ppeek < len) {
|
|
if (src[ppeek] == '\r' || src[ppeek] == '\n') {
|
|
inline_stmt = 0; /* EOL -> no inline body */
|
|
} else if (ppeek + 1 < len && src[ppeek] == '/' && src[ppeek + 1] == '/') {
|
|
inline_stmt = 0; /* line comment -> no inline body */
|
|
} else if (ppeek + 1 < len && src[ppeek] == '/' && src[ppeek + 1] == '*') {
|
|
inline_stmt = 0; /* block comment at EOL -> no inline body */
|
|
} else {
|
|
inline_stmt = 1; /* there's code after condition on same line */
|
|
}
|
|
}
|
|
|
|
/* conditional jump over this clause's inline/body */
|
|
int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
|
|
if (inline_stmt) {
|
|
/* Compile a single inline statement on the same line:
|
|
if (cond) <statement> */
|
|
*pos = ppeek;
|
|
parse_simple_statement(bc, src, len, pos);
|
|
/* Skip the inline body when condition is false */
|
|
bytecode_set_operand(bc, jmp_false, bc->instr_count);
|
|
/* one-liner form has no else/elseif on the same line; end the chain */
|
|
break;
|
|
}
|
|
|
|
/* No inline statement on this line: consume up to EOL and parse indented block or else-if/else */
|
|
skip_to_eol(src, len, pos);
|
|
|
|
/* parse nested block if next line is indented */
|
|
int next_indent = 0;
|
|
size_t look_next = *pos;
|
|
if (read_line_start(src, len, &look_next, &next_indent)) {
|
|
if (next_indent > current_indent) {
|
|
/* parse body at increased indent (let parse_block consume the line) */
|
|
parse_block(bc, src, len, pos, next_indent);
|
|
} else {
|
|
/* empty body; keep *pos at start of that line */
|
|
}
|
|
} else {
|
|
/* EOF -> empty body */
|
|
}
|
|
|
|
/* after body, unconditionally jump to end of the whole chain */
|
|
int jmp_end = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
if (end_count < (int)(sizeof(end_jumps) / sizeof(end_jumps[0]))) {
|
|
end_jumps[end_count++] = jmp_end;
|
|
} else {
|
|
parser_fail(*pos, "Too many chained else/if clauses");
|
|
return;
|
|
}
|
|
|
|
/* patch false-jump target to start of next clause (or fallthrough) */
|
|
bytecode_set_operand(bc, jmp_false, bc->instr_count);
|
|
|
|
/* look for else or else if at the same indentation */
|
|
size_t look = *pos;
|
|
int look_indent = 0;
|
|
if (!read_line_start(src, len, &look, &look_indent)) {
|
|
/* EOF: break and patch end jumps */
|
|
break;
|
|
}
|
|
if (look_indent != current_indent) {
|
|
/* dedent or deeper indent means no 'else' clause here */
|
|
break;
|
|
}
|
|
if (starts_with(src, len, look, "else")) {
|
|
/* consume 'else' */
|
|
*pos = look + 4;
|
|
skip_spaces(src, len, pos);
|
|
|
|
if (starts_with(src, len, *pos, "if")) {
|
|
/* else if -> consume 'if' token and continue loop to parse condition */
|
|
*pos += 2;
|
|
continue;
|
|
} else {
|
|
/* plain else: parse its block and finish the chain */
|
|
skip_to_eol(src, len, pos);
|
|
int else_indent = 0;
|
|
size_t look_else = *pos;
|
|
if (read_line_start(src, len, &look_else, &else_indent) && else_indent > current_indent) {
|
|
/* let parse_block consume the line */
|
|
parse_block(bc, src, len, pos, else_indent);
|
|
} else {
|
|
/* empty else-body */
|
|
}
|
|
/* end of chain after else */
|
|
break;
|
|
}
|
|
} else {
|
|
/* next line is not an else/else if */
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* patch all end-of-clause jumps to the end of the chain */
|
|
for (int i = 0; i < end_count; ++i) {
|
|
bytecode_set_operand(bc, end_jumps[i], bc->instr_count);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* while loop */
|
|
if (starts_with(src, len, *pos, "while")) {
|
|
*pos += 5;
|
|
skip_spaces(src, len, pos);
|
|
|
|
int loop_start = bc->instr_count;
|
|
|
|
/* condition */
|
|
if (!emit_expression(bc, src, len, pos)) {
|
|
int ci = bytecode_add_constant(bc, make_int(0));
|
|
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
|
|
}
|
|
/* end of condition */
|
|
skip_to_eol(src, len, pos);
|
|
|
|
/* jump over body if false */
|
|
int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
|
|
|
/* enter loop context (continue -> condition) */
|
|
LoopCtx ctx = {{0}, 0, {0}, 0, g_loop_ctx};
|
|
g_loop_ctx = &ctx;
|
|
|
|
/* parse body at increased indent (peek indent without advancing pos) */
|
|
int body_indent = 0;
|
|
size_t look_body = *pos;
|
|
if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) {
|
|
parse_block(bc, src, len, pos, body_indent);
|
|
} else {
|
|
/* empty body allowed */
|
|
}
|
|
|
|
/* patch pending continues to loop_start (re-evaluate condition) */
|
|
for (int bi = 0; bi < ctx.cont_count; ++bi) {
|
|
bytecode_set_operand(bc, ctx.continue_jumps[bi], loop_start);
|
|
}
|
|
|
|
/* back edge to loop start */
|
|
bytecode_add_instruction(bc, OP_JUMP, loop_start);
|
|
|
|
/* end label and patches */
|
|
int end_label = bc->instr_count;
|
|
bytecode_set_operand(bc, jmp_false, end_label);
|
|
for (int bi = 0; bi < ctx.break_count; ++bi) {
|
|
bytecode_set_operand(bc, ctx.break_jumps[bi], end_label);
|
|
}
|
|
g_loop_ctx = ctx.prev;
|
|
continue;
|
|
}
|
|
|
|
/* try/catch/finally */
|
|
if (starts_with(src, len, *pos, "try")) {
|
|
/* consume 'try' */
|
|
*pos += 3;
|
|
/* end of header line */
|
|
skip_to_eol(src, len, pos);
|
|
|
|
/* Install a handler placeholder; will be patched to catch label (or a rethrow stub) */
|
|
int try_push_idx = bytecode_add_instruction(bc, OP_TRY_PUSH, 0);
|
|
|
|
/* parse try body at increased indent (if any) */
|
|
int try_body_indent = 0;
|
|
size_t look_try = *pos;
|
|
if (read_line_start(src, len, &look_try, &try_body_indent) && try_body_indent > current_indent) {
|
|
parse_block(bc, src, len, pos, try_body_indent);
|
|
} else {
|
|
/* empty try body allowed */
|
|
}
|
|
|
|
/* After try body, pop handler for normal (non-exceptional) flow */
|
|
bytecode_add_instruction(bc, OP_TRY_POP, 0);
|
|
|
|
/* on normal completion, jump over catch body */
|
|
int jmp_over_catch_finally = bytecode_add_instruction(bc, OP_JUMP, 0);
|
|
|
|
/* Optional: catch and/or finally clauses at same indentation */
|
|
int seen_catch = 0;
|
|
int seen_finally = 0;
|
|
int catch_label = -1;
|
|
for (;;) {
|
|
size_t look = *pos;
|
|
int look_indent = 0;
|
|
if (!read_line_start(src, len, &look, &look_indent)) break; /* EOF */
|
|
if (look_indent != current_indent) break; /* different indentation -> stop */
|
|
|
|
if (!seen_catch && starts_with(src, len, look, "catch")) {
|
|
/* consume 'catch' */
|
|
*pos = look + 5;
|
|
/* optional variable name */
|
|
skip_spaces(src, len, pos);
|
|
char *ex_name = NULL;
|
|
size_t tmp = *pos;
|
|
int have_name = 0;
|
|
if (read_identifier_into(src, len, &tmp, &ex_name)) {
|
|
*pos = tmp;
|
|
have_name = 1;
|
|
}
|
|
/* end of header line */
|
|
skip_to_eol(src, len, pos);
|
|
|
|
/* Mark catch label and patch try handler target */
|
|
catch_label = bc->instr_count;
|
|
bytecode_set_operand(bc, try_push_idx, catch_label);
|
|
|
|
/* On entering catch, the thrown error is on stack. Bind to name if provided, else pop. */
|
|
if (have_name) {
|
|
int lidx = -1, gi = -1;
|
|
if (g_locals) {
|
|
int existing = local_find(ex_name);
|
|
if (existing >= 0)
|
|
lidx = existing;
|
|
else
|
|
lidx = local_add(ex_name);
|
|
} else {
|
|
gi = sym_index(ex_name);
|
|
}
|
|
if (lidx >= 0)
|
|
bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx);
|
|
else if (gi >= 0)
|
|
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi);
|
|
else
|
|
bytecode_add_instruction(bc, OP_POP, 0);
|
|
} else {
|
|
bytecode_add_instruction(bc, OP_POP, 0);
|
|
}
|
|
if (ex_name) free(ex_name);
|
|
|
|
/* parse catch body at increased indent (if any) */
|
|
int catch_indent = 0;
|
|
size_t look_catch = *pos;
|
|
if (read_line_start(src, len, &look_catch, &catch_indent) && catch_indent > current_indent) {
|
|
parse_block(bc, src, len, pos, catch_indent);
|
|
} else {
|
|
/* empty catch body allowed */
|
|
}
|
|
seen_catch = 1;
|
|
continue;
|
|
}
|
|
|
|
if (!seen_finally && starts_with(src, len, look, "finally")) {
|
|
/* consume 'finally' */
|
|
*pos = look + 7;
|
|
/* end of header line */
|
|
skip_to_eol(src, len, pos);
|
|
|
|
/* parse finally body at increased indent (if any) */
|
|
int finally_indent = 0;
|
|
size_t look_fin = *pos;
|
|
if (read_line_start(src, len, &look_fin, &finally_indent) && finally_indent > current_indent) {
|
|
parse_block(bc, src, len, pos, finally_indent);
|
|
} else {
|
|
/* empty finally body allowed */
|
|
}
|
|
|
|
seen_finally = 1;
|
|
continue;
|
|
}
|
|
|
|
/* no recognized clause at this indentation */
|
|
break;
|
|
}
|
|
|
|
/* If no catch clause was present, make handler rethrow */
|
|
if (!seen_catch) {
|
|
int rethrow_label = bc->instr_count;
|
|
bytecode_set_operand(bc, try_push_idx, rethrow_label);
|
|
/* at handler: immediately rethrow the incoming error */
|
|
bytecode_add_instruction(bc, OP_THROW, 0);
|
|
}
|
|
|
|
/* patch normal-flow jump to here (after catch/finally) */
|
|
bytecode_set_operand(bc, jmp_over_catch_finally, bc->instr_count);
|
|
continue;
|
|
}
|
|
|
|
/* otherwise: simple statement on this line */
|
|
parse_simple_statement(bc, src, len, pos);
|
|
}
|
|
}
|
|
|
|
static Bytecode *compile_minimal(const char *src, size_t len) {
|
|
Bytecode *bc = bytecode_new();
|
|
size_t pos = 0;
|
|
|
|
/* Refresh namespace alias table for this compilation unit */
|
|
ns_aliases_reset();
|
|
ns_aliases_scan(src, len);
|
|
|
|
skip_shebang_if_present(src, len, &pos);
|
|
|
|
/* Allow top-of-file function definitions; do not skip any leading 'fun' line */
|
|
skip_comments(src, len, &pos);
|
|
skip_ws(src, len, &pos);
|
|
|
|
/* parse the top-level block at indent 0 */
|
|
parse_block(bc, src, len, &pos, 0);
|
|
|
|
bytecode_add_instruction(bc, OP_HALT, 0);
|
|
return bc;
|
|
}
|
|
|
|
Bytecode *parse_file_to_bytecode(const char *path) {
|
|
size_t len = 0;
|
|
char *src = read_file_all(path, &len);
|
|
if (!src) {
|
|
fprintf(stderr, "Error: cannot read file: %s\n", path);
|
|
return NULL;
|
|
}
|
|
|
|
/* Preprocess includes before compiling */
|
|
char *prep = preprocess_includes(src);
|
|
const char *compile_src = prep ? prep : src;
|
|
size_t compile_len = strlen(compile_src);
|
|
|
|
/* reset error state */
|
|
g_has_error = 0;
|
|
g_err_pos = 0;
|
|
g_err_msg[0] = '\0';
|
|
g_err_line = 0;
|
|
g_err_col = 0;
|
|
|
|
/* Set current source path for nested bytecodes to inherit */
|
|
const char *prev_source = g_current_source_path;
|
|
g_current_source_path = path;
|
|
Bytecode *bc = compile_minimal(compile_src, compile_len);
|
|
/* assign debug metadata to module bytecode */
|
|
if (bc) {
|
|
if (bc->source_file) free((void *)bc->source_file);
|
|
bc->source_file = path ? strdup(path) : strdup("<input>");
|
|
if (bc->name) free((void *)bc->name);
|
|
/* derive name from basename of path */
|
|
const char *bn = path ? strrchr(path, '/') : NULL;
|
|
const char *base = bn ? bn + 1 : (path ? path : "<input>");
|
|
bc->name = strdup(base);
|
|
}
|
|
/* restore previous */
|
|
g_current_source_path = prev_source;
|
|
|
|
if (g_has_error) {
|
|
int line = 1, col = 1;
|
|
calc_line_col(compile_src, compile_len, g_err_pos, &line, &col);
|
|
g_err_line = line;
|
|
g_err_col = col;
|
|
|
|
/* Try to locate include context marker preceding error */
|
|
const char *marker = "// __include_begin__: ";
|
|
size_t mlen = strlen(marker);
|
|
int inner_line = -1;
|
|
char inc_path[512];
|
|
inc_path[0] = '\0';
|
|
/* scan backward to find last marker line */
|
|
size_t scan = g_err_pos;
|
|
while (scan > 0) {
|
|
/* find start of current line */
|
|
size_t ls = scan;
|
|
while (ls > 0 && compile_src[ls - 1] != '\n')
|
|
ls--;
|
|
/* check if this line starts with marker */
|
|
if (ls + mlen <= compile_len && strncmp(compile_src + ls, marker, mlen) == 0) {
|
|
/* extract path until end-of-line */
|
|
size_t p = ls + mlen;
|
|
size_t pe = p;
|
|
while (pe < compile_len && compile_src[pe] != '\n' && (pe - p) < sizeof(inc_path) - 1)
|
|
pe++;
|
|
memcpy(inc_path, compile_src + p, pe - p);
|
|
inc_path[pe - p] = '\0';
|
|
/* compute inner line as number of newlines from (pe+1) to error position */
|
|
int count = 1;
|
|
size_t q = (pe < compile_len && compile_src[pe] == '\n') ? (pe + 1) : pe;
|
|
while (q < g_err_pos) {
|
|
if (compile_src[q] == '\n') count++;
|
|
q++;
|
|
}
|
|
inner_line = count;
|
|
break;
|
|
}
|
|
/* move to previous line */
|
|
if (ls == 0) break;
|
|
scan = ls - 1;
|
|
}
|
|
|
|
if (inner_line > 0 && inc_path[0] != '\0') {
|
|
/* Adjust for shebang stripping in included file: if it starts with '#!' we
|
|
* removed that entire first line during preprocessing, so add +1 to map
|
|
* back to the physical file line number. */
|
|
{
|
|
FILE *sf = fopen(inc_path, "rb");
|
|
if (sf) {
|
|
int c1 = fgetc(sf);
|
|
int c2 = fgetc(sf);
|
|
if (c1 == '#' && c2 == '!') {
|
|
inner_line += 1;
|
|
}
|
|
fclose(sf);
|
|
}
|
|
}
|
|
fprintf(stderr, "Parse error %s:%d:%d: %s (in %s:%d)\n",
|
|
path ? path : "<input>", line, col, g_err_msg, inc_path, inner_line);
|
|
} else {
|
|
fprintf(stderr, "Parse error %s:%d:%d: %s\n", path ? path : "<input>", line, col, g_err_msg);
|
|
}
|
|
|
|
if (bc) bytecode_free(bc);
|
|
if (prep) free(prep);
|
|
free(src);
|
|
return NULL;
|
|
}
|
|
|
|
if (prep) free(prep);
|
|
free(src);
|
|
return bc;
|
|
}
|
|
|
|
Bytecode *parse_string_to_bytecode(const char *source) {
|
|
if (!source) {
|
|
fprintf(stderr, "Error: null source provided\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* Preprocess includes before compiling */
|
|
char *prep = preprocess_includes(source);
|
|
const char *compile_src = prep ? prep : source;
|
|
size_t len = strlen(compile_src);
|
|
|
|
/* reset error state */
|
|
g_has_error = 0;
|
|
g_err_pos = 0;
|
|
g_err_msg[0] = '\0';
|
|
g_err_line = 0;
|
|
g_err_col = 0;
|
|
|
|
/* Set current source path to <input> for nested bytecodes */
|
|
const char *prev_src = g_current_source_path;
|
|
g_current_source_path = NULL;
|
|
Bytecode *bc = compile_minimal(compile_src, len);
|
|
if (bc) {
|
|
if (bc->source_file) free((void *)bc->source_file);
|
|
bc->source_file = strdup("<input>");
|
|
if (bc->name) free((void *)bc->name);
|
|
bc->name = strdup("<input>");
|
|
}
|
|
g_current_source_path = prev_src;
|
|
|
|
if (g_has_error) {
|
|
int line = 1, col = 1;
|
|
calc_line_col(compile_src, len, g_err_pos, &line, &col);
|
|
g_err_line = line;
|
|
g_err_col = col;
|
|
if (bc) bytecode_free(bc);
|
|
if (prep) free(prep);
|
|
return NULL;
|
|
}
|
|
if (prep) free(prep);
|
|
return bc;
|
|
}
|
|
|
|
int parser_last_error(char *msgBuf, unsigned long msgCap, int *outLine, int *outCol) {
|
|
if (!g_has_error) return 0;
|
|
if (msgBuf && msgCap > 0) {
|
|
snprintf(msgBuf, msgCap, "%s", g_err_msg);
|
|
}
|
|
if (outLine) *outLine = g_err_line;
|
|
if (outCol) *outCol = g_err_col;
|
|
return 1;
|
|
}
|