2025-09-16 02:11:22 +02:00
/**
* This file is part of the Fun programming language .
* https : //hanez.org/project/fun/
*
* Copyright 2025 Johannes Findeisen < you @ hanez . org >
2025-09-30 22:52:12 +02:00
* Licensed under the terms of the Apache - 2.0 license .
* https : //opensource.org/license/apache-2-0
2025-09-16 02:11:22 +02:00
*/
/**
* @ 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
2025-10-01 01:16:52 +02:00
* @ date 2025 - 09 - 16
2025-09-16 02:11:22 +02:00
*/
2025-09-13 23:50:01 +02:00
# include "parser.h"
# include "value.h"
2025-09-14 00:28:08 +02:00
# include "vm.h"
2025-09-13 23:50:01 +02:00
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <ctype.h>
2025-09-14 03:22:22 +02:00
# include <stdarg.h>
/* ---- parser error state ---- */
2025-10-04 17:03:41 +02:00
static const char * g_current_source_path = NULL ; /* for propagating filename into nested bytecodes */
2025-09-14 03:22:22 +02:00
static int g_has_error = 0 ;
static size_t g_err_pos = 0 ;
static char g_err_msg [ 256 ] ;
2025-09-14 22:35:23 +02:00
static int g_err_line = 0 ;
static int g_err_col = 0 ;
2025-09-14 03:22:22 +02:00
2025-09-14 17:06:42 +02:00
/* ---- compiler-generated temporary counter ---- */
static int g_temp_counter = 0 ;
2025-09-28 15:24:09 +02:00
/* Declared type metadata encoding in types[]:
0 = dynamic / untyped ;
positive / negative 8 / 16 / 32 / 64 = integers ( negative means signed ) ;
2025-09-28 23:56:45 +02:00
TYPE_META_STRING / BOOLEAN / NIL mark non - integer enforced types ;
TYPE_META_CLASS marks class instances ( Map with " __class " ) . */
2025-09-28 15:24:09 +02:00
# define TYPE_META_STRING 10001
# define TYPE_META_BOOLEAN 10002
# define TYPE_META_NIL 10003
2025-09-28 23:56:45 +02:00
# define TYPE_META_CLASS 10004
2025-09-28 15:24:09 +02:00
2025-09-14 03:22:22 +02:00
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 ;
}
/* --------------------------- */
2025-09-13 23:50:01 +02:00
2025-09-30 21:03:36 +02:00
/* 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 ;
}
2025-09-15 06:31:32 +02:00
# include "parser_utils.c"
2025-09-14 00:28:08 +02:00
/* very small global symbol table for LOAD_GLOBAL/STORE_GLOBAL */
static struct {
2025-09-16 11:59:48 +02:00
char * names [ MAX_GLOBALS ] ;
2025-09-26 09:13:32 +02:00
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 */
2025-09-14 00:28:08 +02:00
int count ;
2025-09-26 09:13:32 +02:00
} G = { { 0 } , { 0 } , { 0 } , 0 } ;
2025-09-14 00:28:08 +02:00
static int sym_index ( const char * name ) {
for ( int i = 0 ; i < G . count ; + + i ) {
if ( strcmp ( G . names [ i ] , name ) = = 0 ) return i ;
}
2025-09-16 11:59:48 +02:00
if ( G . count > = MAX_GLOBALS ) {
parser_fail ( 0 , " Too many globals (max %d) " , MAX_GLOBALS ) ;
2025-09-14 03:22:22 +02:00
return 0 ;
2025-09-14 00:28:08 +02:00
}
G . names [ G . count ] = strdup ( name ) ;
2025-09-26 09:13:32 +02:00
G . types [ G . count ] = 0 ; /* default: untyped */
G . is_class [ G . count ] = 0 ; /* default: not a class */
2025-09-14 00:28:08 +02:00
return G . count + + ;
}
2025-09-14 15:12:30 +02:00
/* ---- locals environment for functions ---- */
typedef struct {
2025-09-16 11:59:48 +02:00
char * names [ MAX_FRAME_LOCALS ] ;
2025-09-16 12:29:28 +02:00
int types [ MAX_FRAME_LOCALS ] ; /* 0=untyped; else bit width: 8/16/32/64 */
2025-09-14 15:12:30 +02:00
int count ;
} LocalEnv ;
static LocalEnv * g_locals = NULL ;
2025-09-15 03:21:57 +02:00
/* 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 ;
2025-09-14 15:12:30 +02:00
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 ;
2025-09-16 11:59:48 +02:00
if ( g_locals - > count > = MAX_FRAME_LOCALS ) {
parser_fail ( 0 , " Too many local variables/parameters (max %d) " , MAX_FRAME_LOCALS ) ;
2025-09-14 15:12:30 +02:00
return - 1 ;
}
int idx = g_locals - > count + + ;
g_locals - > names [ idx ] = strdup ( name ) ;
return idx ;
}
2025-09-14 03:22:22 +02:00
/* 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 ) {
2025-09-14 00:28:08 +02:00
skip_spaces ( src , len , pos ) ;
2025-09-14 03:22:22 +02:00
/* 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 ;
}
2025-09-15 01:53:41 +02:00
/* postfix indexing or slice */
2025-09-15 01:36:20 +02:00
for ( ; ; ) {
skip_spaces ( src , len , pos ) ;
if ( * pos < len & & src [ * pos ] = = ' [ ' ) {
( * pos ) + + ;
2025-09-15 01:53:41 +02:00
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 ;
}
2025-09-15 01:36:20 +02:00
}
break ;
}
2025-09-14 03:22:22 +02:00
return 1 ;
}
2025-09-14 00:28:08 +02:00
/* 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 ) ;
2025-09-15 01:53:41 +02:00
/* postfix indexing or slice */
2025-09-15 01:36:20 +02:00
for ( ; ; ) {
skip_spaces ( src , len , pos ) ;
if ( * pos < len & & src [ * pos ] = = ' [ ' ) {
( * pos ) + + ;
2025-09-15 01:53:41 +02:00
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 ;
}
2025-09-15 01:36:20 +02:00
}
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 ) ;
2025-09-15 01:53:41 +02:00
/* postfix indexing or slice */
2025-09-15 01:36:20 +02:00
for ( ; ; ) {
skip_spaces ( src , len , pos ) ;
if ( * pos < len & & src [ * pos ] = = ' [ ' ) {
( * pos ) + + ;
2025-09-15 01:53:41 +02:00
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 ;
}
2025-09-15 01:36:20 +02:00
}
break ;
}
2025-09-14 00:28:08 +02:00
return 1 ;
}
2025-09-15 04:41:11 +02:00
/* 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 ;
}
2025-09-14 00:28:08 +02:00
/* 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 ) ;
2025-09-15 01:53:41 +02:00
/* postfix indexing or slice */
2025-09-15 01:36:20 +02:00
for ( ; ; ) {
skip_spaces ( src , len , pos ) ;
if ( * pos < len & & src [ * pos ] = = ' [ ' ) {
( * pos ) + + ;
2025-09-15 01:53:41 +02:00
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 ;
}
2025-09-15 01:36:20 +02:00
}
break ;
}
2025-09-14 00:28:08 +02:00
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 ) {
2025-10-04 17:34:23 +02:00
int ci = bytecode_add_constant ( bc , make_bool ( strcmp ( name , " true " ) = = 0 ? 1 : 0 ) ) ;
2025-09-14 00:28:08 +02:00
free ( name ) ;
bytecode_add_instruction ( bc , OP_LOAD_CONST , ci ) ;
return 1 ;
}
2025-09-14 15:12:30 +02:00
/* 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 ) {
2025-09-15 01:53:41 +02:00
/* 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 ; }
2025-09-16 04:51:49 +02:00
bytecode_add_instruction ( bc , OP_PUSH , 0 ) ;
2025-09-15 01:53:41 +02:00
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 ; }
2025-09-16 04:51:49 +02:00
bytecode_add_instruction ( bc , OP_APOP , 0 ) ;
2025-09-15 01:53:41 +02:00
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 ; }
2025-09-16 04:51:49 +02:00
bytecode_add_instruction ( bc , OP_SET , 0 ) ;
2025-09-15 01:53:41 +02:00
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 ; }
2025-09-16 04:51:49 +02:00
bytecode_add_instruction ( bc , OP_INSERT , 0 ) ;
2025-09-15 01:53:41 +02:00
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 ; }
2025-09-16 04:51:49 +02:00
bytecode_add_instruction ( bc , OP_REMOVE , 0 ) ;
2025-09-15 01:53:41 +02:00
free ( name ) ;
return 1 ;
}
2025-09-15 03:45:26 +02:00
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 ;
}
2025-09-29 03:26:35 +02:00
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 ;
}
2025-09-16 03:45:25 +02:00
if ( strcmp ( name , " typeof " ) = = 0 ) {
( * pos ) + + ; /* '(' */
2025-09-29 03:26:35 +02:00
/* Disable compile-time shortcut for typeof(<identifier>); always evaluate at runtime */
2025-09-16 12:59:44 +02:00
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 ] = = ' ) ' ) {
2025-09-29 03:26:35 +02:00
/* Fall back to runtime evaluation path */
2025-09-16 12:59:44 +02:00
free ( vname ) ;
2025-09-29 03:26:35 +02:00
/* handled remains 0 so we go to general-case below */
2025-09-16 12:59:44 +02:00
} else {
/* not a simple identifier-only typeof */
free ( vname ) ;
}
}
if ( ! handled ) {
2025-09-26 09:13:32 +02:00
/* General case: typeof(expression)
If the value is a Map with " __class " key , return that string ; else return base typeof .
*/
2025-09-16 12:59:44 +02:00
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 ; }
2025-09-26 09:13:32 +02:00
/* [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 ) ;
2025-09-16 12:59:44 +02:00
}
2025-09-16 03:45:25 +02:00
free ( name ) ;
return 1 ;
}
2025-09-15 04:41:11 +02:00
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 ;
}
2025-10-02 00:53:51 +02:00
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 ;
}
2025-10-02 01:50:53 +02:00
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 ) ;
2025-10-04 00:49:05 +02:00
return 1 ;
}
if ( strcmp ( name , " time_now_ms " ) = = 0 ) {
( * pos ) + + ; /* '(' */
if ( ! consume_char ( src , len , pos , ' ) ' ) ) { parser_fail ( * pos , " time_now_ms expects () " ) ; free ( name ) ; return 0 ; }
bytecode_add_instruction ( bc , OP_TIME_NOW_MS , 0 ) ;
free ( name ) ;
return 1 ;
}
if ( strcmp ( name , " clock_mono_ms " ) = = 0 ) {
( * pos ) + + ; /* '(' */
if ( ! consume_char ( src , len , pos , ' ) ' ) ) { parser_fail ( * pos , " clock_mono_ms expects () " ) ; free ( name ) ; return 0 ; }
bytecode_add_instruction ( bc , OP_CLOCK_MONO_MS , 0 ) ;
free ( name ) ;
return 1 ;
}
if ( strcmp ( name , " date_format " ) = = 0 ) {
( * pos ) + + ; /* '(' */
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " date_format expects (ms:int, fmt:string) " ) ; free ( name ) ; return 0 ; }
if ( ! consume_char ( src , len , pos , ' , ' ) ) { parser_fail ( * pos , " date_format expects 2 args " ) ; free ( name ) ; return 0 ; }
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " date_format expects (ms:int, fmt:string) " ) ; free ( name ) ; return 0 ; }
if ( ! consume_char ( src , len , pos , ' ) ' ) ) { parser_fail ( * pos , " Expected ')' after date_format args " ) ; free ( name ) ; return 0 ; }
bytecode_add_instruction ( bc , OP_DATE_FORMAT , 0 ) ;
free ( name ) ;
2025-10-02 01:50:53 +02:00
return 1 ;
}
2025-09-27 10:06:04 +02:00
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 ;
}
2025-10-02 20:08:21 +02:00
/* 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 ;
}
2025-10-04 02:52:30 +02:00
/* Socket builtins */
if ( strcmp ( name , " tcp_listen " ) = = 0 ) {
( * pos ) + + ; /* '(' */
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " tcp_listen expects (port, backlog) " ) ; free ( name ) ; return 0 ; }
if ( ! consume_char ( src , len , pos , ' , ' ) ) { parser_fail ( * pos , " tcp_listen expects (port, backlog) " ) ; free ( name ) ; return 0 ; }
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " tcp_listen expects (port, backlog) " ) ; free ( name ) ; return 0 ; }
if ( ! consume_char ( src , len , pos , ' ) ' ) ) { parser_fail ( * pos , " Expected ')' after tcp_listen args " ) ; free ( name ) ; return 0 ; }
bytecode_add_instruction ( bc , OP_SOCK_TCP_LISTEN , 0 ) ;
free ( name ) ;
return 1 ;
}
if ( strcmp ( name , " tcp_accept " ) = = 0 ) {
( * pos ) + + ; /* '(' */
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " tcp_accept expects (listen_fd) " ) ; free ( name ) ; return 0 ; }
if ( ! consume_char ( src , len , pos , ' ) ' ) ) { parser_fail ( * pos , " Expected ')' after tcp_accept arg " ) ; free ( name ) ; return 0 ; }
bytecode_add_instruction ( bc , OP_SOCK_TCP_ACCEPT , 0 ) ;
free ( name ) ;
return 1 ;
}
if ( strcmp ( name , " tcp_connect " ) = = 0 ) {
( * pos ) + + ; /* '(' */
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " tcp_connect expects (host, port) " ) ; free ( name ) ; return 0 ; }
if ( ! consume_char ( src , len , pos , ' , ' ) ) { parser_fail ( * pos , " tcp_connect expects (host, port) " ) ; free ( name ) ; return 0 ; }
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " tcp_connect expects (host, port) " ) ; free ( name ) ; return 0 ; }
if ( ! consume_char ( src , len , pos , ' ) ' ) ) { parser_fail ( * pos , " Expected ')' after tcp_connect args " ) ; free ( name ) ; return 0 ; }
bytecode_add_instruction ( bc , OP_SOCK_TCP_CONNECT , 0 ) ;
free ( name ) ;
return 1 ;
}
if ( strcmp ( name , " sock_send " ) = = 0 ) {
( * pos ) + + ; /* '(' */
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " sock_send expects (fd, data) " ) ; free ( name ) ; return 0 ; }
if ( ! consume_char ( src , len , pos , ' , ' ) ) { parser_fail ( * pos , " sock_send expects (fd, data) " ) ; free ( name ) ; return 0 ; }
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " sock_send expects (fd, data) " ) ; free ( name ) ; return 0 ; }
if ( ! consume_char ( src , len , pos , ' ) ' ) ) { parser_fail ( * pos , " Expected ')' after sock_send args " ) ; free ( name ) ; return 0 ; }
bytecode_add_instruction ( bc , OP_SOCK_SEND , 0 ) ;
free ( name ) ;
return 1 ;
}
if ( strcmp ( name , " sock_recv " ) = = 0 ) {
( * pos ) + + ; /* '(' */
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " sock_recv expects (fd, maxlen) " ) ; free ( name ) ; return 0 ; }
if ( ! consume_char ( src , len , pos , ' , ' ) ) { parser_fail ( * pos , " sock_recv expects (fd, maxlen) " ) ; free ( name ) ; return 0 ; }
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " sock_recv expects (fd, maxlen) " ) ; free ( name ) ; return 0 ; }
if ( ! consume_char ( src , len , pos , ' ) ' ) ) { parser_fail ( * pos , " Expected ')' after sock_recv args " ) ; free ( name ) ; return 0 ; }
bytecode_add_instruction ( bc , OP_SOCK_RECV , 0 ) ;
free ( name ) ;
return 1 ;
}
if ( strcmp ( name , " sock_close " ) = = 0 ) {
( * pos ) + + ; /* '(' */
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " sock_close expects (fd) " ) ; free ( name ) ; return 0 ; }
if ( ! consume_char ( src , len , pos , ' ) ' ) ) { parser_fail ( * pos , " Expected ')' after sock_close arg " ) ; free ( name ) ; return 0 ; }
bytecode_add_instruction ( bc , OP_SOCK_CLOSE , 0 ) ;
free ( name ) ;
return 1 ;
}
if ( strcmp ( name , " unix_listen " ) = = 0 ) {
( * pos ) + + ; /* '(' */
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " unix_listen expects (path, backlog) " ) ; free ( name ) ; return 0 ; }
if ( ! consume_char ( src , len , pos , ' , ' ) ) { parser_fail ( * pos , " unix_listen expects (path, backlog) " ) ; free ( name ) ; return 0 ; }
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " unix_listen expects (path, backlog) " ) ; free ( name ) ; return 0 ; }
if ( ! consume_char ( src , len , pos , ' ) ' ) ) { parser_fail ( * pos , " Expected ')' after unix_listen args " ) ; free ( name ) ; return 0 ; }
bytecode_add_instruction ( bc , OP_SOCK_UNIX_LISTEN , 0 ) ;
free ( name ) ;
return 1 ;
}
if ( strcmp ( name , " unix_connect " ) = = 0 ) {
( * pos ) + + ; /* '(' */
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " unix_connect expects (path) " ) ; free ( name ) ; return 0 ; }
if ( ! consume_char ( src , len , pos , ' ) ' ) ) { parser_fail ( * pos , " Expected ')' after unix_connect arg " ) ; free ( name ) ; return 0 ; }
bytecode_add_instruction ( bc , OP_SOCK_UNIX_CONNECT , 0 ) ;
free ( name ) ;
return 1 ;
}
2025-09-15 03:59:03 +02:00
/* 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 ;
}
2025-10-04 01:49:22 +02:00
/* regex ops */
if ( strcmp ( name , " regex_match " ) = = 0 ) {
( * pos ) + + ; /* '(' */
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " regex_match expects text " ) ; free ( name ) ; return 0 ; }
if ( * pos < len & & src [ * pos ] = = ' , ' ) { ( * pos ) + + ; skip_spaces ( src , len , pos ) ; } else { parser_fail ( * pos , " regex_match expects 2 args " ) ; free ( name ) ; return 0 ; }
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " regex_match expects pattern " ) ; free ( name ) ; return 0 ; }
if ( ! consume_char ( src , len , pos , ' ) ' ) ) { parser_fail ( * pos , " Expected ')' after regex_match args " ) ; free ( name ) ; return 0 ; }
bytecode_add_instruction ( bc , OP_REGEX_MATCH , 0 ) ;
free ( name ) ;
return 1 ;
}
if ( strcmp ( name , " regex_search " ) = = 0 ) {
( * pos ) + + ; /* '(' */
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " regex_search expects text " ) ; free ( name ) ; return 0 ; }
if ( * pos < len & & src [ * pos ] = = ' , ' ) { ( * pos ) + + ; skip_spaces ( src , len , pos ) ; } else { parser_fail ( * pos , " regex_search expects 2 args " ) ; free ( name ) ; return 0 ; }
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " regex_search expects pattern " ) ; free ( name ) ; return 0 ; }
if ( ! consume_char ( src , len , pos , ' ) ' ) ) { parser_fail ( * pos , " Expected ')' after regex_search args " ) ; free ( name ) ; return 0 ; }
bytecode_add_instruction ( bc , OP_REGEX_SEARCH , 0 ) ;
free ( name ) ;
return 1 ;
}
if ( strcmp ( name , " regex_replace " ) = = 0 ) {
( * pos ) + + ; /* '(' */
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " regex_replace expects text " ) ; free ( name ) ; return 0 ; }
if ( * pos < len & & src [ * pos ] = = ' , ' ) { ( * pos ) + + ; skip_spaces ( src , len , pos ) ; } else { parser_fail ( * pos , " regex_replace expects 3 args " ) ; free ( name ) ; return 0 ; }
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " regex_replace expects pattern " ) ; free ( name ) ; return 0 ; }
if ( * pos < len & & src [ * pos ] = = ' , ' ) { ( * pos ) + + ; skip_spaces ( src , len , pos ) ; } else { parser_fail ( * pos , " regex_replace expects 3 args " ) ; free ( name ) ; return 0 ; }
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " regex_replace expects replacement " ) ; free ( name ) ; return 0 ; }
if ( ! consume_char ( src , len , pos , ' ) ' ) ) { parser_fail ( * pos , " Expected ')' after regex_replace args " ) ; free ( name ) ; return 0 ; }
bytecode_add_instruction ( bc , OP_REGEX_REPLACE , 0 ) ;
free ( name ) ;
return 1 ;
}
2025-09-15 03:59:03 +02:00
/* 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 ;
}
2025-09-15 04:41:11 +02:00
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) */
2025-09-16 04:51:49 +02:00
bytecode_add_instruction ( bc , OP_INSERT , 0 ) ;
2025-09-30 22:52:12 +02:00
/* dApache-2.0ard returned new length */
2025-09-15 04:41:11 +02:00
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) */
2025-09-16 04:51:49 +02:00
bytecode_add_instruction ( bc , OP_INSERT , 0 ) ;
2025-09-30 22:52:12 +02:00
/* dApache-2.0ard returned new length */
2025-09-15 04:41:11 +02:00
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 ;
}
2025-09-15 03:59:03 +02:00
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 ;
}
2025-09-15 01:53:41 +02:00
2025-09-30 22:52:12 +02:00
/* 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 ;
}
2025-09-29 02:36:57 +02:00
/* 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 ;
}
2025-09-14 15:12:30 +02:00
/* 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 ) ;
}
2025-10-01 02:06:26 +02:00
/* 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 ) ;
2025-09-14 15:12:30 +02:00
/* 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 ;
}
2025-09-14 15:48:28 +02:00
# ifdef FUN_DEBUG
2025-09-14 15:12:30 +02:00
/* DEBUG: show compiled call */
printf ( " compile: CALL %s with %d arg(s) \n " , name , argc ) ;
2025-09-14 15:48:28 +02:00
# endif
2025-09-14 15:12:30 +02:00
bytecode_add_instruction ( bc , OP_CALL , argc ) ;
2025-09-15 07:52:03 +02:00
/* postfix indexing, slice, and dot access/method calls */
2025-09-15 01:36:20 +02:00
for ( ; ; ) {
skip_spaces ( src , len , pos ) ;
2025-09-15 07:52:03 +02:00
/* index/slice */
2025-09-15 01:36:20 +02:00
if ( * pos < len & & src [ * pos ] = = ' [ ' ) {
( * pos ) + + ;
2025-09-15 01:53:41 +02:00
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 ;
}
2025-09-15 01:36:20 +02:00
}
2025-09-15 07:52:03 +02:00
2025-09-26 07:53:12 +02:00
/* dot property access and method-call sugar: obj.field or obj.method(...) */
2025-09-15 07:52:03 +02:00
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 ; }
2025-09-26 09:13:32 +02:00
int is_private = ( mname & & mname [ 0 ] = = ' _ ' ) ;
2025-09-15 07:52:03 +02:00
int kci = bytecode_add_constant ( bc , make_string ( mname ) ) ;
2025-09-26 07:53:12 +02:00
/* Peek for immediate call: obj.method( ... ) */
size_t callp = * pos ;
skip_spaces ( src , len , & callp ) ;
if ( callp < len & & src [ callp ] = = ' ( ' ) {
2025-09-26 09:13:32 +02:00
/* 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 ;
}
2025-09-30 21:03:36 +02:00
2025-10-01 02:06:26 +02:00
/* 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 ;
2025-09-30 21:03:36 +02:00
2025-10-01 02:06:26 +02:00
/* 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) */
2025-09-26 07:53:12 +02:00
/* Consume '(' and parse args */
* pos = callp + 1 ;
int argc = 0 ;
skip_spaces ( src , len , pos ) ;
if ( * pos < len & & src [ * pos ] ! = ' ) ' ) {
do {
2025-09-26 09:13:32 +02:00
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " Expected expression as method argument " ) ; free ( mname ) ; free ( name ) ; return 0 ; }
2025-09-26 07:53:12 +02:00
argc + + ;
skip_spaces ( src , len , pos ) ;
} while ( * pos < len & & src [ * pos ] = = ' , ' & & ( + + ( * pos ) , skip_spaces ( src , len , pos ) , 1 ) ) ;
}
2025-09-26 09:13:32 +02:00
if ( ! consume_char ( src , len , pos , ' ) ' ) ) { parser_fail ( * pos , " Expected ')' after arguments " ) ; free ( mname ) ; free ( name ) ; return 0 ; }
2025-09-26 07:53:12 +02:00
2025-09-30 21:03:36 +02:00
/* Call */
bytecode_add_instruction ( bc , OP_CALL , is_ns ? argc : ( argc + 1 ) ) ;
2025-09-26 09:13:32 +02:00
free ( mname ) ;
2025-09-26 07:53:12 +02:00
continue ;
} else {
/* Plain property get: obj["field"] */
bytecode_add_instruction ( bc , OP_LOAD_CONST , kci ) ;
bytecode_add_instruction ( bc , OP_INDEX_GET , 0 ) ;
2025-09-26 09:13:32 +02:00
free ( mname ) ;
2025-09-26 07:53:12 +02:00
continue ;
}
2025-09-15 07:52:03 +02:00
}
2025-09-15 01:36:20 +02:00
break ;
}
2025-09-14 15:12:30 +02:00
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 ) ;
}
2025-10-01 02:06:26 +02:00
/* 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 ) ;
2025-09-15 07:52:03 +02:00
/* postfix indexing, slice, and dot access/method calls */
2025-09-15 01:36:20 +02:00
for ( ; ; ) {
skip_spaces ( src , len , pos ) ;
2025-09-15 07:52:03 +02:00
/* index/slice */
2025-09-15 01:36:20 +02:00
if ( * pos < len & & src [ * pos ] = = ' [ ' ) {
( * pos ) + + ;
2025-09-15 01:53:41 +02:00
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 ;
}
2025-09-15 01:36:20 +02:00
}
2025-09-15 07:52:03 +02:00
2025-09-26 07:53:12 +02:00
/* dot property access and method-call sugar */
2025-09-15 07:52:03 +02:00
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 ; }
2025-09-26 09:13:32 +02:00
int is_private = ( mname & & mname [ 0 ] = = ' _ ' ) ;
2025-09-15 07:52:03 +02:00
int kci = bytecode_add_constant ( bc , make_string ( mname ) ) ;
2025-09-26 07:53:12 +02:00
/* Peek for call */
size_t callp = * pos ;
skip_spaces ( src , len , & callp ) ;
if ( callp < len & & src [ callp ] = = ' ( ' ) {
2025-09-26 09:13:32 +02:00
/* 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 ;
}
2025-10-01 02:06:26 +02:00
/* Use initial alias context for only the first dot-call; reset after call */
int is_ns = __ns_ctx ;
2025-09-30 21:03:36 +02:00
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 */
}
2025-09-26 07:53:12 +02:00
* pos = callp + 1 ;
int argc = 0 ;
skip_spaces ( src , len , pos ) ;
if ( * pos < len & & src [ * pos ] ! = ' ) ' ) {
do {
2025-09-26 09:13:32 +02:00
if ( ! emit_expression ( bc , src , len , pos ) ) { parser_fail ( * pos , " Expected expression as method argument " ) ; free ( mname ) ; free ( name ) ; return 0 ; }
2025-09-26 07:53:12 +02:00
argc + + ;
skip_spaces ( src , len , pos ) ;
} while ( * pos < len & & src [ * pos ] = = ' , ' & & ( + + ( * pos ) , skip_spaces ( src , len , pos ) , 1 ) ) ;
}
2025-09-26 09:13:32 +02:00
if ( ! consume_char ( src , len , pos , ' ) ' ) ) { parser_fail ( * pos , " Expected ')' after arguments " ) ; free ( mname ) ; free ( name ) ; return 0 ; }
2025-09-26 07:53:12 +02:00
2025-09-30 21:03:36 +02:00
bytecode_add_instruction ( bc , OP_CALL , is_ns ? argc : ( argc + 1 ) ) ;
2025-10-01 02:06:26 +02:00
/* After any call, the receiver is now a value, not a namespace alias */
__ns_ctx = 0 ;
2025-09-26 09:13:32 +02:00
free ( mname ) ;
2025-09-26 07:53:12 +02:00
continue ;
} else {
2025-09-26 09:13:32 +02:00
/* plain property get (allowed even for private name) */
2025-09-26 07:53:12 +02:00
bytecode_add_instruction ( bc , OP_LOAD_CONST , kci ) ;
bytecode_add_instruction ( bc , OP_INDEX_GET , 0 ) ;
2025-09-26 09:13:32 +02:00
free ( mname ) ;
2025-09-26 07:53:12 +02:00
continue ;
}
2025-09-15 07:52:03 +02:00
}
2025-09-15 01:36:20 +02:00
break ;
}
2025-09-14 15:12:30 +02:00
free ( name ) ;
return 1 ;
}
2025-09-14 00:28:08 +02:00
}
return 0 ;
}
2025-09-14 03:22:22 +02:00
/* 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 ) ;
2025-09-14 15:40:09 +02:00
/* 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 ;
}
2025-09-14 03:22:22 +02:00
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 ;
}
2025-09-14 21:10:52 +02:00
/* logical AND with short-circuit: equality ( '&&' equality )* */
2025-09-14 03:22:22 +02:00
static int emit_and_expr ( Bytecode * bc , const char * src , size_t len , size_t * pos ) {
2025-09-14 21:10:52 +02:00
int jf_idxs [ 64 ] ;
int jf_count = 0 ;
int has_and = 0 ;
/* first operand */
2025-09-14 03:22:22 +02:00
if ( ! emit_equality ( bc , src , len , pos ) ) return 0 ;
2025-09-14 21:10:52 +02:00
2025-09-14 03:22:22 +02:00
for ( ; ; ) {
skip_spaces ( src , len , pos ) ;
2025-09-14 21:10:52 +02:00
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 ;
2025-09-14 03:22:22 +02:00
}
}
2025-09-14 21:10:52 +02:00
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 */
2025-10-04 17:34:23 +02:00
int c1 = bytecode_add_constant ( bc , make_bool ( 1 ) ) ;
2025-09-14 21:10:52 +02:00
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 ) ;
}
2025-10-04 17:34:23 +02:00
int c0 = bytecode_add_constant ( bc , make_bool ( 0 ) ) ;
2025-09-14 21:10:52 +02:00
bytecode_add_instruction ( bc , OP_LOAD_CONST , c0 ) ;
/* end */
int l_end = bc - > instr_count ;
bytecode_set_operand ( bc , j_end , l_end ) ;
}
2025-09-14 03:22:22 +02:00
return 1 ;
}
2025-09-14 21:10:52 +02:00
/* logical OR with short-circuit: and_expr ( '||' and_expr )* */
2025-09-14 03:22:22 +02:00
static int emit_or_expr ( Bytecode * bc , const char * src , size_t len , size_t * pos ) {
2025-09-14 21:10:52 +02:00
int true_jumps [ 64 ] ;
int tj_count = 0 ;
int has_or = 0 ;
/* first operand */
2025-09-14 03:22:22 +02:00
if ( ! emit_and_expr ( bc , src , len , pos ) ) return 0 ;
2025-09-14 21:10:52 +02:00
2025-09-14 03:22:22 +02:00
for ( ; ; ) {
skip_spaces ( src , len , pos ) ;
2025-09-14 21:10:52 +02:00
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 ) ;
2025-10-04 17:34:23 +02:00
/* true path: push true and jump to end */
int c1 = bytecode_add_constant ( bc , make_bool ( 1 ) ) ;
2025-09-14 21:10:52 +02:00
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 ;
2025-09-14 03:22:22 +02:00
}
}
2025-09-14 21:10:52 +02:00
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 ) ;
}
}
2025-09-14 03:22:22 +02:00
return 1 ;
}
2025-10-01 23:05:30 +02:00
/* 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 ;
}
2025-09-14 03:22:22 +02:00
/* top-level expression */
static int emit_expression ( Bytecode * bc , const char * src , size_t len , size_t * pos ) {
2025-10-01 23:05:30 +02:00
return emit_conditional ( bc , src , len , pos ) ;
2025-09-14 03:22:22 +02:00
}
2025-09-13 23:50:01 +02:00
/*
2025-09-14 03:22:22 +02:00
* 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 .
2025-09-13 23:50:01 +02:00
* - Ends with HALT .
*/
2025-09-14 03:22:22 +02:00
/* line/indent utilities */
static void skip_to_eol ( const char * src , size_t len , size_t * pos ) {
2025-09-16 07:09:31 +02:00
/* 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 ; }
2025-09-27 23:02:13 +02:00
/* 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 ' ) {
2025-09-16 07:09:31 +02:00
* pos = p + 1 ;
return ;
}
/* line or block comments allowed */
if ( p + 1 < len & & src [ p ] = = ' / ' & & src [ p + 1 ] = = ' / ' ) {
2025-09-27 23:02:13 +02:00
/* 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 + + ;
}
2025-09-16 07:09:31 +02:00
* 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 ) {
2025-09-27 23:02:13 +02:00
p + = 2 ; /* consume closing */
2025-09-16 07:09:31 +02:00
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 ;
}
2025-09-14 03:22:22 +02:00
}
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 ; }
2025-09-27 23:02:13 +02:00
/* 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 ; }
2025-09-14 03:22:22 +02:00
2025-09-14 15:12:30 +02:00
/* // comment-only line */
2025-09-14 03:22:22 +02:00
if ( p + 1 < len & & src [ p ] = = ' / ' & & src [ p + 1 ] = = ' / ' ) {
2025-09-27 23:02:13 +02:00
/* 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 + + ; }
2025-09-14 03:22:22 +02:00
* pos = p ;
continue ;
}
2025-09-14 15:12:30 +02:00
// 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) */
2025-09-27 23:02:13 +02:00
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 + + ; }
2025-09-14 15:12:30 +02:00
* pos = p ;
continue ;
}
2025-09-14 03:22:22 +02:00
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 ) ) {
2025-09-16 12:59:44 +02:00
/* 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 " ) ; }
2025-09-14 15:12:30 +02:00
/* 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 ;
2025-10-04 13:02:29 +02:00
skip_to_eol ( src , len , pos ) ;
return ;
}
/* exit statement: exit [expr]? */
if ( strcmp ( name , " exit " ) = = 0 ) {
free ( name ) ;
skip_spaces ( src , len , & local_pos ) ;
size_t save_pos = local_pos ;
if ( emit_expression ( bc , src , len , & local_pos ) ) {
/* expression result already on stack */
} else {
/* default exit code 0 */
local_pos = save_pos ;
int ci = bytecode_add_constant ( bc , make_int ( 0 ) ) ;
bytecode_add_instruction ( bc , OP_LOAD_CONST , ci ) ;
}
bytecode_add_instruction ( bc , OP_EXIT , 0 ) ;
* pos = local_pos ;
2025-09-14 15:12:30 +02:00
skip_to_eol ( src , len , pos ) ;
return ;
}
2025-09-15 03:21:57 +02:00
/* 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 ;
}
2025-09-16 12:38:47 +02:00
/* typed declarations:
2025-09-28 23:56:45 +02:00
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 .
2025-09-16 12:38:47 +02:00
*/
2025-09-16 12:29:28 +02:00
if ( strcmp ( name , " number " ) = = 0 | | strcmp ( name , " string " ) = = 0 | | strcmp ( name , " boolean " ) = = 0 | | strcmp ( name , " nil " ) = = 0
2025-09-28 23:56:45 +02:00
| | strcmp ( name , " Class " ) = = 0
2025-09-28 23:10:49 +02:00
| | strcmp ( name , " byte " ) = = 0
2025-09-16 12:38:47 +02:00
| | 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 ) {
2025-09-16 07:09:31 +02:00
int is_number = ( strcmp ( name , " number " ) = = 0 ) ;
int is_string = ( strcmp ( name , " string " ) = = 0 ) ;
2025-09-14 03:22:22 +02:00
int is_boolean = ( strcmp ( name , " boolean " ) = = 0 ) ;
2025-09-16 07:09:31 +02:00
int is_nil = ( strcmp ( name , " nil " ) = = 0 ) ;
2025-09-28 23:56:45 +02:00
int is_class_tkn = ( strcmp ( name , " Class " ) = = 0 ) ;
2025-09-28 23:10:49 +02:00
int is_byte = ( strcmp ( name , " byte " ) = = 0 ) ;
int is_u8 = ( strcmp ( name , " uint8 " ) = = 0 ) | | is_byte ;
2025-09-16 12:29:28 +02:00
int is_u16 = ( strcmp ( name , " uint16 " ) = = 0 ) ;
int is_u32 = ( strcmp ( name , " uint32 " ) = = 0 ) ;
2025-09-17 10:03:40 +02:00
int is_u64 = ( strcmp ( name , " uint64 " ) = = 0 ) ;
2025-09-16 12:38:47 +02:00
int is_s8 = ( strcmp ( name , " int8 " ) = = 0 ) ;
int is_s16 = ( strcmp ( name , " int16 " ) = = 0 ) ;
int is_s32 = ( strcmp ( name , " int32 " ) = = 0 ) ;
2025-09-17 10:03:40 +02:00
int is_s64 = ( strcmp ( name , " int64 " ) = = 0 ) | | is_number ; /* number maps to int64 (signed) */
2025-09-16 12:38:47 +02:00
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 ;
2025-09-17 10:03:40 +02:00
/* store decl bits with sign encoded: negative means signed (number is signed 64-bit) */
2025-09-16 12:38:47 +02:00
if ( decl_signed ) decl_bits = - decl_bits ;
2025-09-28 15:24:09 +02:00
2025-09-28 23:56:45 +02:00
/* declared type metadata: integers use decl_bits; string/boolean/nil/Class use special markers */
2025-09-28 15:24:09 +02:00
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 ;
2025-09-28 23:56:45 +02:00
} else if ( is_class_tkn ) {
decl_meta = TYPE_META_CLASS ;
2025-09-28 15:24:09 +02:00
}
2025-09-14 03:22:22 +02:00
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 ;
}
2025-09-14 15:12:30 +02:00
/* 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 ) ;
2025-09-16 12:29:28 +02:00
if ( lidx > = 0 ) {
2025-09-28 15:24:09 +02:00
g_locals - > types [ lidx ] = decl_meta ; /* encoding: ±bits for integers; TYPE_META_* for non-integer enforced types; 0 = dynamic */
2025-09-16 12:29:28 +02:00
}
2025-09-14 15:12:30 +02:00
} else {
gi = sym_index ( varname ) ;
2025-09-16 12:29:28 +02:00
if ( gi > = 0 ) {
2025-09-28 15:24:09 +02:00
G . types [ gi ] = decl_meta ;
2025-09-16 12:29:28 +02:00
}
2025-09-14 15:12:30 +02:00
}
2025-09-14 03:22:22 +02:00
free ( varname ) ;
skip_spaces ( src , len , & local_pos ) ;
if ( local_pos < len & & src [ local_pos ] = = ' = ' ) {
local_pos + + ; /* '=' */
2025-09-14 15:12:30 +02:00
if ( ! emit_expression ( bc , src , len , & local_pos ) ) {
2025-09-14 03:22:22 +02:00
parser_fail ( local_pos , " Expected initializer expression after '=' " ) ;
return ;
}
2025-09-28 15:24:09 +02:00
/* 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 ) ;
2025-09-28 23:56:45 +02:00
} 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 ) ;
2025-09-28 15:24:09 +02:00
} else if ( decl_meta = = TYPE_META_BOOLEAN ) {
2025-10-04 17:34:23 +02:00
/* accept Boolean literal or Number; if Number, clamp to 0/1 */
/* check if value is Boolean */
bytecode_add_instruction ( bc , OP_DUP , 0 ) ;
bytecode_add_instruction ( bc , OP_TYPEOF , 0 ) ;
int ciBool = bytecode_add_constant ( bc , make_string ( " Boolean " ) ) ;
bytecode_add_instruction ( bc , OP_LOAD_CONST , ciBool ) ;
bytecode_add_instruction ( bc , OP_EQ , 0 ) ;
int j_not_bool = bytecode_add_instruction ( bc , OP_JUMP_IF_FALSE , 0 ) ;
/* it is Boolean -> OK, skip number check */
int j_done = bytecode_add_instruction ( bc , OP_JUMP , 0 ) ;
/* not Boolean: check Number */
bytecode_set_operand ( bc , j_not_bool , bc - > instr_count ) ;
2025-09-28 15:24:09 +02:00
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 ) ;
{
2025-10-04 17:34:23 +02:00
int ciMsg = bytecode_add_constant ( bc , make_string ( " TypeError: expected Boolean or Number for boolean " ) ) ;
2025-09-28 15:24:09 +02:00
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 ) ;
2025-10-04 17:34:23 +02:00
/* if Number, clamp to 0/1 */
2025-09-28 15:24:09 +02:00
bytecode_add_instruction ( bc , OP_UCLAMP , 1 ) ;
2025-10-04 17:34:23 +02:00
/* common continuation */
bytecode_set_operand ( bc , j_done , bc - > instr_count ) ;
2025-09-28 15:24:09 +02:00
} 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 ) ;
}
2025-09-16 12:29:28 +02:00
}
2025-09-28 15:24:09 +02:00
2025-09-14 15:12:30 +02:00
if ( lidx > = 0 ) {
bytecode_add_instruction ( bc , OP_STORE_LOCAL , lidx ) ;
} else {
bytecode_add_instruction ( bc , OP_STORE_GLOBAL , gi ) ;
}
2025-09-14 03:22:22 +02:00
} else {
/* default initialize if no '=' given */
int ci = - 1 ;
2025-09-16 12:29:28 +02:00
if ( is_string ) {
2025-09-14 03:22:22 +02:00
ci = bytecode_add_constant ( bc , make_string ( " " ) ) ;
2025-09-16 07:09:31 +02:00
} else if ( is_nil ) {
ci = bytecode_add_constant ( bc , make_nil ( ) ) ;
2025-09-28 23:56:45 +02:00
} else if ( is_class_tkn ) {
/* Class-typed variable defaults to Nil until assigned an instance */
ci = bytecode_add_constant ( bc , make_nil ( ) ) ;
2025-10-04 17:34:23 +02:00
} else if ( is_boolean ) {
/* booleans default to false */
ci = bytecode_add_constant ( bc , make_bool ( 0 ) ) ;
} else if ( is_number | | ( decl_bits ! = 0 ) ) {
/* integers default to 0 */
2025-09-16 12:29:28 +02:00
ci = bytecode_add_constant ( bc , make_int ( 0 ) ) ;
2025-09-14 03:22:22 +02:00
}
if ( ci > = 0 ) {
bytecode_add_instruction ( bc , OP_LOAD_CONST , ci ) ;
2025-09-16 12:38:47 +02:00
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 ) ;
2025-09-16 12:29:28 +02:00
}
2025-09-14 15:12:30 +02:00
if ( lidx > = 0 ) {
bytecode_add_instruction ( bc , OP_STORE_LOCAL , lidx ) ;
} else {
bytecode_add_instruction ( bc , OP_STORE_GLOBAL , gi ) ;
}
2025-09-14 03:22:22 +02:00
}
}
* 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 */
2025-09-14 15:40:09 +02:00
int lidx = local_find ( name ) ;
int gi = ( lidx < 0 ) ? sym_index ( name ) : - 1 ;
2025-09-14 03:22:22 +02:00
skip_spaces ( src , len , & local_pos ) ;
2025-09-15 01:36:20 +02:00
2025-09-15 07:52:03 +02:00
/* 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 ;
}
2025-09-15 08:39:41 +02:00
/* array element assignment: name[expr] = expr and nested: name[expr1][expr2] = expr */
2025-09-15 01:36:20 +02:00
if ( local_pos < len & & src [ local_pos ] = = ' [ ' ) {
2025-09-15 08:39:41 +02:00
/* load array/map variable */
2025-09-15 01:36:20 +02:00
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 ) ;
2025-09-15 08:39:41 +02:00
/* 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 */
2025-09-15 01:36:20 +02:00
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 ) ;
2025-09-14 03:22:22 +02:00
if ( local_pos < len & & src [ local_pos ] = = ' = ' ) {
local_pos + + ; /* '=' */
if ( emit_expression ( bc , src , len , & local_pos ) ) {
2025-09-28 15:24:09 +02:00
/* enforce declared type if present (0 = dynamic) */
int meta = 0 ;
2025-09-16 12:29:28 +02:00
if ( lidx > = 0 & & g_locals ) {
2025-09-28 15:24:09 +02:00
meta = g_locals - > types [ lidx ] ;
2025-09-16 12:29:28 +02:00
} else if ( gi > = 0 ) {
2025-09-28 15:24:09 +02:00
meta = G . types [ gi ] ;
2025-09-16 12:29:28 +02:00
}
2025-09-28 15:24:09 +02:00
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 ) ;
2025-09-28 23:56:45 +02:00
} 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 ) ;
2025-09-28 15:24:09 +02:00
} 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 ) ;
2025-09-16 12:29:28 +02:00
}
2025-09-28 15:24:09 +02:00
/* dynamic (meta==0): no enforcement */
2025-09-14 15:40:09 +02:00
if ( lidx > = 0 ) {
bytecode_add_instruction ( bc , OP_STORE_LOCAL , lidx ) ;
} else {
bytecode_add_instruction ( bc , OP_STORE_GLOBAL , gi ) ;
}
2025-09-14 03:22:22 +02:00
}
* pos = local_pos ;
skip_to_eol ( src , len , pos ) ;
return ;
} else if ( local_pos < len & & src [ local_pos ] = = ' ( ' ) {
2025-09-14 15:12:30 +02:00
/* 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 ) ) {
2025-09-30 22:52:12 +02:00
bytecode_add_instruction ( bc , OP_POP , 0 ) ; /* dApache-2.0ard return value */
2025-09-14 15:12:30 +02:00
}
2025-09-14 03:22:22 +02:00
* pos = local_pos ;
skip_to_eol ( src , len , pos ) ;
return ;
} else {
2025-09-16 07:09:31 +02:00
/* invalid statement: identifier not followed by assignment or call */
parser_fail ( local_pos , " Expected assignment '=' or call '(...)' after identifier " ) ;
2025-09-14 03:22:22 +02:00
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 */
2025-09-16 11:23:42 +02:00
/* 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 ) ;
}
2025-09-26 07:39:13 +02:00
/* 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 ) ;
2025-10-01 01:16:52 +02:00
/* optional extends Parent */
char * parent_name = NULL ;
2025-09-26 08:06:30 +02:00
/* 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 */
2025-09-26 09:20:12 +02:00
/* 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 : \
2025-09-28 23:10:49 +02:00
( ( 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 : \
2025-09-26 09:20:12 +02:00
0 )
2025-09-26 08:06:30 +02:00
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 ;
2025-09-26 09:20:12 +02:00
param_kind [ pcount ] = MAP_TYPE_KIND ( tname ) ;
2025-09-26 08:06:30 +02:00
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 ;
}
}
2025-10-01 01:16:52 +02:00
/* 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 ;
}
}
2025-09-26 07:39:13 +02:00
/* end of class header line */
skip_to_eol ( src , len , pos ) ;
2025-09-26 08:06:30 +02:00
/* Build factory function: Name(...) -> instance map with fields and methods */
2025-09-26 07:39:13 +02:00
Bytecode * ctor_bc = bytecode_new ( ) ;
2025-10-04 17:03:41 +02:00
/* set debug metadata for class factory */
if ( ctor_bc ) {
if ( ctor_bc - > name ) free ( ( void * ) ctor_bc - > name ) ;
ctor_bc - > name = strdup ( cname ) ;
if ( ctor_bc - > source_file ) free ( ( void * ) ctor_bc - > source_file ) ;
if ( g_current_source_path ) ctor_bc - > source_file = strdup ( g_current_source_path ) ;
}
2025-09-26 07:39:13 +02:00
/* 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 ;
2025-10-01 01:16:52 +02:00
/* track if _construct is defined in this class */
int ctor_present = 0 ;
2025-09-26 08:06:30 +02:00
/* 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) */
2025-09-26 07:39:13 +02:00
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 ) ;
2025-09-26 09:13:32 +02:00
/* 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 ) ;
2025-10-01 01:16:52 +02:00
/* 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 ) ;
}
2025-09-26 07:39:13 +02:00
/* 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 ;
}
2025-10-01 01:16:52 +02:00
int is_ctor_method = ( strcmp ( mname , " _construct " ) = = 0 ) ;
2025-09-26 07:39:13 +02:00
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 ( ) ;
2025-10-04 17:03:41 +02:00
if ( m_bc ) {
if ( m_bc - > name ) free ( ( void * ) m_bc - > name ) ;
/* method qualified name: Class.method */
size_t qlen = strlen ( cname ) + 1 + strlen ( mname ) + 1 ;
char * q = ( char * ) malloc ( qlen ) ;
if ( q ) {
snprintf ( q , qlen , " %s.%s " , cname , mname ) ;
m_bc - > name = q ;
}
if ( m_bc - > source_file ) free ( ( void * ) m_bc - > source_file ) ;
if ( g_current_source_path ) m_bc - > source_file = strdup ( g_current_source_path ) ;
}
2025-09-26 07:39:13 +02:00
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 */
2025-10-01 01:16:52 +02:00
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' " ) ;
}
2025-09-26 07:39:13 +02:00
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) */
2025-10-01 01:16:52 +02:00
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 " ) ;
}
2025-09-26 07:39:13 +02:00
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 ) ;
2025-10-01 01:16:52 +02:00
/* mark constructor presence if name matches */
if ( strcmp ( mname , " _construct " ) = = 0 ) {
ctor_present = 1 ;
}
2025-09-26 07:39:13 +02:00
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 */
}
2025-09-26 08:06:30 +02:00
/* 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 ) ;
}
2025-10-01 01:16:52 +02:00
/* 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 ) ;
}
2025-09-26 07:39:13 +02:00
/* 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 ) ;
2025-09-26 09:13:32 +02:00
/* mark this global as a class for typeof(identifier) */
G . is_class [ cgi ] = 1 ;
2025-09-26 07:39:13 +02:00
2025-09-26 08:06:30 +02:00
for ( int i = 0 ; i < pcount ; + + i ) free ( param_names [ i ] ) ;
2025-10-01 01:16:52 +02:00
if ( parent_name ) free ( parent_name ) ;
2025-09-26 07:39:13 +02:00
free ( cname ) ;
continue ;
}
2025-09-14 15:12:30 +02:00
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 ( ) ;
2025-10-04 17:03:41 +02:00
if ( fn_bc ) {
if ( fn_bc - > name ) free ( ( void * ) fn_bc - > name ) ;
fn_bc - > name = strdup ( fname ) ;
if ( fn_bc - > source_file ) free ( ( void * ) fn_bc - > source_file ) ;
if ( g_current_source_path ) fn_bc - > source_file = strdup ( g_current_source_path ) ;
}
2025-09-14 15:12:30 +02:00
/* 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 ) ;
2025-09-14 15:48:28 +02:00
# ifdef FUN_DEBUG
2025-09-14 15:12:30 +02:00
/* 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 ) ;
2025-09-14 15:48:28 +02:00
# endif
2025-09-14 15:12:30 +02:00
/* 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 ;
}
2025-09-15 03:03:00 +02:00
/* for-sugar:
* - for < ident > in range ( a , b )
* - for < ident > in < array - expr >
*/
2025-09-14 17:06:42 +02:00
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 ) ;
2025-09-15 03:03:00 +02:00
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 ) ;
2025-09-15 03:21:57 +02:00
/* enter loop context for break/continue */
LoopCtx ctx = { { 0 } , 0 , { 0 } , 0 , g_loop_ctx } ;
g_loop_ctx = & ctx ;
2025-09-15 03:03:00 +02:00
/* 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 */
}
2025-09-15 03:21:57 +02:00
/* continue target: start of increment */
int cont_label = bc - > instr_count ;
2025-09-15 03:03:00 +02:00
/* 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 ) ;
2025-09-15 03:21:57 +02:00
/* 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 ;
2025-09-15 03:03:00 +02:00
2025-09-14 17:06:42 +02:00
free ( ivar ) ;
2025-09-15 03:03:00 +02:00
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 ) ;
}
2025-09-15 03:21:57 +02:00
/* enter loop context for break/continue */
LoopCtx ctx = { { 0 } , 0 , { 0 } , 0 , g_loop_ctx } ;
g_loop_ctx = & ctx ;
2025-09-15 03:03:00 +02:00
/* 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 */
}
2025-09-15 03:21:57 +02:00
/* continue target: start of increment */
int cont_label = bc - > instr_count ;
2025-09-15 03:03:00 +02:00
/* 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 ) ;
2025-09-15 03:21:57 +02:00
/* 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 ;
2025-09-15 03:03:00 +02:00
2025-09-14 17:06:42 +02:00
free ( ivar ) ;
2025-09-15 03:03:00 +02:00
continue ;
2025-09-14 17:06:42 +02:00
}
}
2025-09-14 03:22:22 +02:00
if ( starts_with ( src , len , * pos , " if " ) ) {
2025-09-14 03:42:11 +02:00
int end_jumps [ 64 ] ;
int end_count = 0 ;
2025-09-14 03:22:22 +02:00
2025-09-14 03:42:11 +02:00
for ( ; ; ) {
/* consume 'if' or 'else if' condition */
if ( starts_with ( src , len , * pos , " if " ) ) {
* pos + = 2 ;
2025-09-14 03:22:22 +02:00
} else {
2025-09-14 03:42:11 +02:00
/* 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 ) ;
}
2025-10-01 23:05:30 +02:00
/* 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 */
2025-09-14 03:42:11 +02:00
int jmp_false = bytecode_add_instruction ( bc , OP_JUMP_IF_FALSE , 0 ) ;
2025-10-01 23:05:30 +02:00
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 ) ;
2025-09-14 03:42:11 +02:00
/* parse nested block if next line is indented */
int next_indent = 0 ;
2025-09-14 15:12:30 +02:00
size_t look_next = * pos ;
if ( read_line_start ( src , len , & look_next , & next_indent ) ) {
2025-09-14 03:42:11 +02:00
if ( next_indent > current_indent ) {
2025-09-14 15:12:30 +02:00
/* parse body at increased indent (let parse_block consume the line) */
2025-09-14 03:42:11 +02:00
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 ;
2025-09-14 15:12:30 +02:00
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 */
2025-09-14 03:42:11 +02:00
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 ;
2025-09-14 03:22:22 +02:00
}
}
2025-09-14 03:42:11 +02:00
/* 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 ) ;
}
2025-09-14 03:22:22 +02:00
continue ;
}
2025-09-14 15:40:09 +02:00
/* 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 ) ;
2025-09-15 03:21:57 +02:00
/* enter loop context (continue -> condition) */
LoopCtx ctx = { { 0 } , 0 , { 0 } , 0 , g_loop_ctx } ;
g_loop_ctx = & ctx ;
2025-09-14 15:40:09 +02:00
/* 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 */
}
2025-09-15 03:21:57 +02:00
/* 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 ) ;
}
2025-09-14 15:40:09 +02:00
/* back edge to loop start */
bytecode_add_instruction ( bc , OP_JUMP , loop_start ) ;
2025-09-15 03:21:57 +02:00
/* 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 ;
2025-09-14 15:40:09 +02:00
continue ;
}
2025-10-03 23:25:55 +02:00
/* try/catch/finally (syntax support; runtime exceptions not yet implemented) */
if ( starts_with ( src , len , * pos , " try " ) ) {
/* consume 'try' */
* pos + = 3 ;
/* end of header line */
skip_to_eol ( src , len , pos ) ;
/* parse try body at increased indent (if any) */
int try_body_indent = 0 ;
size_t look_try = * pos ;
if ( read_line_start ( src , len , & look_try , & try_body_indent ) & & try_body_indent > current_indent ) {
parse_block ( bc , src , len , pos , try_body_indent ) ;
} else {
/* empty try body allowed */
}
/* Optional: catch and/or finally clauses at same indentation */
int seen_catch = 0 ;
int seen_finally = 0 ;
for ( ; ; ) {
size_t look = * pos ;
int look_indent = 0 ;
if ( ! read_line_start ( src , len , & look , & look_indent ) ) break ; /* EOF */
if ( look_indent ! = current_indent ) break ; /* different indentation -> stop */
if ( ! seen_catch & & starts_with ( src , len , look , " catch " ) ) {
/* consume 'catch' */
* pos = look + 5 ;
/* optional variable name */
skip_spaces ( src , len , pos ) ;
char * ex_name = NULL ;
size_t tmp = * pos ;
if ( read_identifier_into ( src , len , & tmp , & ex_name ) ) {
* pos = tmp ;
free ( ex_name ) ;
}
/* end of header line */
skip_to_eol ( src , len , pos ) ;
/* We currently don't have runtime exceptions: emit an unconditional jump over the catch body (so it's parsed but never executed) */
int j_over = bytecode_add_instruction ( bc , OP_JUMP , 0 ) ;
/* parse catch body at increased indent (if any) */
int catch_indent = 0 ;
size_t look_catch = * pos ;
if ( read_line_start ( src , len , & look_catch , & catch_indent ) & & catch_indent > current_indent ) {
parse_block ( bc , src , len , pos , catch_indent ) ;
} else {
/* empty catch body allowed */
}
/* patch jump to here (after catch body) */
bytecode_set_operand ( bc , j_over , bc - > instr_count ) ;
seen_catch = 1 ;
continue ;
}
if ( ! seen_finally & & starts_with ( src , len , look , " finally " ) ) {
/* consume 'finally' */
* pos = look + 7 ;
/* end of header line */
skip_to_eol ( src , len , pos ) ;
/* parse finally body at increased indent (if any) */
int finally_indent = 0 ;
size_t look_fin = * pos ;
if ( read_line_start ( src , len , & look_fin , & finally_indent ) & & finally_indent > current_indent ) {
parse_block ( bc , src , len , pos , finally_indent ) ;
} else {
/* empty finally body allowed */
}
seen_finally = 1 ;
continue ;
}
/* no recognized clause at this indentation */
break ;
}
continue ;
}
2025-09-14 03:22:22 +02:00
/* otherwise: simple statement on this line */
parse_simple_statement ( bc , src , len , pos ) ;
}
}
2025-09-13 23:50:01 +02:00
static Bytecode * compile_minimal ( const char * src , size_t len ) {
Bytecode * bc = bytecode_new ( ) ;
size_t pos = 0 ;
2025-09-30 21:03:36 +02:00
/* Refresh namespace alias table for this compilation unit */
ns_aliases_reset ( ) ;
ns_aliases_scan ( src , len ) ;
2025-09-13 23:50:01 +02:00
skip_shebang_if_present ( src , len , & pos ) ;
2025-09-16 03:49:28 +02:00
/* Allow top-of-file function definitions; do not skip any leading 'fun' line */
2025-09-13 23:50:01 +02:00
skip_comments ( src , len , & pos ) ;
skip_ws ( src , len , & pos ) ;
2025-09-14 03:22:22 +02:00
/* parse the top-level block at indent 0 */
parse_block ( bc , src , len , & pos , 0 ) ;
2025-09-13 23:50:01 +02:00
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 ;
}
2025-09-27 17:39:03 +02:00
/* Preprocess includes before compiling */
char * prep = preprocess_includes ( src ) ;
const char * compile_src = prep ? prep : src ;
size_t compile_len = strlen ( compile_src ) ;
2025-09-14 03:22:22 +02:00
/* reset error state */
g_has_error = 0 ;
g_err_pos = 0 ;
g_err_msg [ 0 ] = ' \0 ' ;
2025-09-14 22:35:23 +02:00
g_err_line = 0 ;
g_err_col = 0 ;
2025-09-14 03:22:22 +02:00
2025-10-04 17:03:41 +02:00
/* Set current source path for nested bytecodes to inherit */
const char * prev_source = g_current_source_path ;
g_current_source_path = path ;
Bytecode * bc = compile_minimal ( compile_src , compile_len ) ;
/* assign debug metadata to module bytecode */
if ( bc ) {
if ( bc - > source_file ) free ( ( void * ) bc - > source_file ) ;
bc - > source_file = path ? strdup ( path ) : strdup ( " <input> " ) ;
if ( bc - > name ) free ( ( void * ) bc - > name ) ;
/* derive name from basename of path */
const char * bn = path ? strrchr ( path , ' / ' ) : NULL ;
const char * base = bn ? bn + 1 : ( path ? path : " <input> " ) ;
bc - > name = strdup ( base ) ;
}
/* restore previous */
g_current_source_path = prev_source ;
2025-09-14 03:22:22 +02:00
if ( g_has_error ) {
int line = 1 , col = 1 ;
2025-09-27 17:39:03 +02:00
calc_line_col ( compile_src , compile_len , g_err_pos , & line , & col ) ;
2025-09-14 22:35:23 +02:00
g_err_line = line ;
g_err_col = col ;
2025-09-29 02:36:57 +02:00
/* 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 ) ;
}
2025-09-14 03:22:22 +02:00
if ( bc ) bytecode_free ( bc ) ;
2025-09-27 17:39:03 +02:00
if ( prep ) free ( prep ) ;
2025-09-14 03:22:22 +02:00
free ( src ) ;
return NULL ;
}
2025-09-27 17:39:03 +02:00
if ( prep ) free ( prep ) ;
2025-09-13 23:50:01 +02:00
free ( src ) ;
return bc ;
2025-09-14 18:42:23 +02:00
}
Bytecode * parse_string_to_bytecode ( const char * source ) {
if ( ! source ) {
fprintf ( stderr , " Error: null source provided \n " ) ;
return NULL ;
}
2025-09-27 17:39:03 +02:00
/* Preprocess includes before compiling */
char * prep = preprocess_includes ( source ) ;
const char * compile_src = prep ? prep : source ;
size_t len = strlen ( compile_src ) ;
2025-09-14 18:42:23 +02:00
/* reset error state */
g_has_error = 0 ;
g_err_pos = 0 ;
g_err_msg [ 0 ] = ' \0 ' ;
2025-09-14 22:35:23 +02:00
g_err_line = 0 ;
g_err_col = 0 ;
2025-09-14 18:42:23 +02:00
2025-10-04 17:03:41 +02:00
/* Set current source path to <input> for nested bytecodes */
const char * prev_src = g_current_source_path ;
g_current_source_path = NULL ;
2025-09-27 17:39:03 +02:00
Bytecode * bc = compile_minimal ( compile_src , len ) ;
2025-10-04 17:03:41 +02:00
if ( bc ) {
if ( bc - > source_file ) free ( ( void * ) bc - > source_file ) ;
bc - > source_file = strdup ( " <input> " ) ;
if ( bc - > name ) free ( ( void * ) bc - > name ) ;
bc - > name = strdup ( " <input> " ) ;
}
g_current_source_path = prev_src ;
2025-09-14 18:42:23 +02:00
if ( g_has_error ) {
int line = 1 , col = 1 ;
2025-09-27 17:39:03 +02:00
calc_line_col ( compile_src , len , g_err_pos , & line , & col ) ;
2025-09-14 22:35:23 +02:00
g_err_line = line ;
g_err_col = col ;
2025-09-14 18:42:23 +02:00
if ( bc ) bytecode_free ( bc ) ;
2025-09-27 17:39:03 +02:00
if ( prep ) free ( prep ) ;
2025-09-14 18:42:23 +02:00
return NULL ;
}
2025-09-27 17:39:03 +02:00
if ( prep ) free ( prep ) ;
2025-09-14 18:42:23 +02:00
return bc ;
2025-09-14 22:35:23 +02:00
}
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 ;
2025-09-28 23:56:45 +02:00
}