3776 lines
178 KiB
C
3776 lines
178 KiB
C
/**
|
|
* This file is part of the Fun programming language.
|
|
* https://hanez.org/project/fun/
|
|
*
|
|
* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <stdarg.h>
|
|
|
|
/* ---- parser error state ---- */
|
|
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;
|
|
|
|
/* 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
|
|
|
|
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 */
|
|
int ok = 0;
|
|
size_t save = *pos;
|
|
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;
|
|
}
|
|
*pos = save;
|
|
|
|
/* 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_int(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)++; /* '(' */
|
|
/* Disable compile-time shortcut for typeof(<identifier>); always evaluate at runtime */
|
|
size_t peek = *pos;
|
|
char *vname = NULL;
|
|
int handled = 0;
|
|
if (read_identifier_into(src, len, &peek, &vname)) {
|
|
/* allow spaces before ')' */
|
|
skip_spaces(src, len, &peek);
|
|
if (peek < len && src[peek] == ')') {
|
|
/* Fall back to runtime evaluation path */
|
|
free(vname);
|
|
/* handled remains 0 so we go to general-case below */
|
|
} else {
|
|
/* not a simple identifier-only typeof */
|
|
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 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);
|
|
}
|
|
|
|
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, "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, "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;
|
|
}
|
|
/* 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;
|
|
}
|
|
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;
|
|
}
|
|
/* 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;
|
|
}
|
|
/* 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, "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, "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") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "random expects 1 arg"); free(name); return 0; }
|
|
bytecode_add_instruction(bc, OP_RANDOM_SEED, 0);
|
|
free(name);
|
|
return 1;
|
|
}
|
|
if (strcmp(name, "randomInt") == 0) {
|
|
(*pos)++; /* '(' */
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "randomInt expects 2 args"); free(name); return 0; }
|
|
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "randomInt expects 2 args"); free(name); return 0; }
|
|
bytecode_add_instruction(bc, OP_RANDOM_INT, 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 */
|
|
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_int(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_int(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 1 and jump to end */
|
|
int c1 = bytecode_add_constant(bc, make_int(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;
|
|
}
|
|
|
|
/* 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, "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_tkn = (strcmp(name, "Class") == 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 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_tkn) {
|
|
decl_meta = TYPE_META_CLASS;
|
|
}
|
|
|
|
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_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);
|
|
/* clamp to 0/1 */
|
|
bytecode_add_instruction(bc, OP_UCLAMP, 1);
|
|
} 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 clamp */
|
|
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);
|
|
|
|
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_tkn) {
|
|
/* Class-typed variable defaults to Nil until assigned an instance */
|
|
ci = bytecode_add_constant(bc, make_nil());
|
|
} else if (is_number || is_boolean || (decl_bits != 0)) {
|
|
/* integers/booleans 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;
|
|
}
|
|
|
|
/* 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: name.field = expr (only if '=' follows) */
|
|
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] != '=') {
|
|
/* 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;
|
|
}
|
|
|
|
/* Confirmed assignment: emit container, key, value, then INDEX_SET */
|
|
/* 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);
|
|
|
|
/* Advance local_pos to after '=' and parse value expression */
|
|
local_pos = look + 1; /* skip '=' */
|
|
if (!emit_expression(bc, src, len, &local_pos)) {
|
|
parser_fail(local_pos, "Expected expression after '='");
|
|
free(name);
|
|
return;
|
|
}
|
|
/* perform set: pops value, key, container (in that order) */
|
|
bytecode_add_instruction(bc, OP_INDEX_SET, 0);
|
|
|
|
free(name);
|
|
*pos = local_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_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 clamp 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);
|
|
|
|
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 if (local_pos < len && src[local_pos] == '(') {
|
|
/* call as a statement: compile full call expression and drop result */
|
|
/* rewind to statement start and emit as expression */
|
|
local_pos = *pos;
|
|
if (emit_expression(bc, src, len, &local_pos)) {
|
|
bytecode_add_instruction(bc, OP_POP, 0); /* dApache-2.0ard return value */
|
|
}
|
|
*pos = local_pos;
|
|
skip_to_eol(src, len, pos);
|
|
return;
|
|
} else {
|
|
/* invalid statement: identifier not followed by assignment or call */
|
|
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;
|
|
}
|
|
|
|
/* 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;
|
|
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 */
|
|
{
|
|
int stmt_line = 1, stmt_col = 1;
|
|
calc_line_col(src, len, line_start, &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;
|
|
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();
|
|
/* 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();
|
|
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")) {
|
|
/* 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();
|
|
|
|
/* 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 */
|
|
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>
|
|
*/
|
|
if (starts_with(src, len, *pos, "for")) {
|
|
*pos += 3;
|
|
skip_spaces(src, len, pos);
|
|
|
|
/* loop variable name */
|
|
char *ivar = NULL;
|
|
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 {
|
|
/* ===== 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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
Bytecode *bc = compile_minimal(compile_src, compile_len);
|
|
|
|
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') {
|
|
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;
|
|
|
|
Bytecode *bc = compile_minimal(compile_src, len);
|
|
|
|
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;
|
|
}
|