Added tons of AI generated docs and a lot of cleanups. No code changes. (0.41.5)
This commit is contained in:
parent
24c54fba41
commit
4e69a3ce63
151 changed files with 3897 additions and 427 deletions
|
|
@ -9,8 +9,20 @@
|
||||||
|
|
||||||
#include "value.h"
|
#include "value.h"
|
||||||
|
|
||||||
/* array utilities */
|
/**
|
||||||
|
* @file array_utils.c
|
||||||
|
* @brief Utility functions for operating on Value arrays.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if an array Value contains a given element.
|
||||||
|
*
|
||||||
|
* Performs linear search using value_equals() on copied elements.
|
||||||
|
*
|
||||||
|
* @param v Array Value to inspect (may be NULL).
|
||||||
|
* @param needle Value to search for.
|
||||||
|
* @return 1 if found, 0 otherwise or if v is not an array.
|
||||||
|
*/
|
||||||
int array_contains(const Value *v, const Value *needle) {
|
int array_contains(const Value *v, const Value *needle) {
|
||||||
if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
|
if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
|
||||||
int n = array_length(v);
|
int n = array_length(v);
|
||||||
|
|
@ -25,6 +37,13 @@ int array_contains(const Value *v, const Value *needle) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Find the index of the first occurrence of an element in an array.
|
||||||
|
*
|
||||||
|
* @param v Array Value to search (may be NULL).
|
||||||
|
* @param needle Value to look for.
|
||||||
|
* @return Zero-based index when found; -1 if not found or if v is not an array.
|
||||||
|
*/
|
||||||
int array_index_of(const Value *v, const Value *needle) {
|
int array_index_of(const Value *v, const Value *needle) {
|
||||||
if (!v || v->type != VAL_ARRAY || !v->arr) return -1;
|
if (!v || v->type != VAL_ARRAY || !v->arr) return -1;
|
||||||
int n = array_length(v);
|
int n = array_length(v);
|
||||||
|
|
@ -39,6 +58,13 @@ int array_index_of(const Value *v, const Value *needle) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove all elements from an array Value, freeing their contents.
|
||||||
|
*
|
||||||
|
* Uses array_pop() repeatedly to clear the array.
|
||||||
|
*
|
||||||
|
* @param v Array Value to clear (in place). No-op if not an array.
|
||||||
|
*/
|
||||||
void array_clear(Value *v) {
|
void array_clear(Value *v) {
|
||||||
if (!v || v->type != VAL_ARRAY || !v->arr) return;
|
if (!v || v->type != VAL_ARRAY || !v->arr) return;
|
||||||
int n = array_length(v);
|
int n = array_length(v);
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file bytecode.c
|
||||||
|
* @brief Bytecode container utilities: creation, mutation, dump helpers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Allocate and initialize an empty Bytecode object.
|
||||||
|
*
|
||||||
|
* Initializes instruction and constant arrays to empty and clears metadata.
|
||||||
|
* Caller owns the returned pointer and must free it with bytecode_free().
|
||||||
|
*
|
||||||
|
* @return Newly allocated Bytecode*, or NULL on allocation failure.
|
||||||
|
*/
|
||||||
Bytecode *bytecode_new(void) {
|
Bytecode *bytecode_new(void) {
|
||||||
Bytecode *bc = (Bytecode *)malloc(sizeof(Bytecode));
|
Bytecode *bc = (Bytecode *)malloc(sizeof(Bytecode));
|
||||||
bc->instructions = NULL;
|
bc->instructions = NULL;
|
||||||
|
|
@ -22,12 +35,30 @@ Bytecode *bytecode_new(void) {
|
||||||
return bc;
|
return bc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Append a constant to a Bytecode's constant table.
|
||||||
|
*
|
||||||
|
* The value is deep-copied into the table; the caller retains ownership of v
|
||||||
|
* and may free it independently.
|
||||||
|
*
|
||||||
|
* @param bc Target bytecode (must not be NULL).
|
||||||
|
* @param v Value to store (copied).
|
||||||
|
* @return The index of the stored constant (zero-based).
|
||||||
|
*/
|
||||||
int bytecode_add_constant(Bytecode *bc, Value v) {
|
int bytecode_add_constant(Bytecode *bc, Value v) {
|
||||||
bc->constants = (Value *)realloc(bc->constants, sizeof(Value) * (bc->const_count + 1));
|
bc->constants = (Value *)realloc(bc->constants, sizeof(Value) * (bc->const_count + 1));
|
||||||
bc->constants[bc->const_count] = copy_value(&v);
|
bc->constants[bc->const_count] = copy_value(&v);
|
||||||
return bc->const_count++;
|
return bc->const_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Append a single instruction to the instruction stream.
|
||||||
|
*
|
||||||
|
* @param bc Target bytecode (must not be NULL).
|
||||||
|
* @param op Opcode to emit.
|
||||||
|
* @param operand Operand value (semantics depend on opcode).
|
||||||
|
* @return The index of the emitted instruction (zero-based).
|
||||||
|
*/
|
||||||
int bytecode_add_instruction(Bytecode *bc, OpCode op, int32_t operand) {
|
int bytecode_add_instruction(Bytecode *bc, OpCode op, int32_t operand) {
|
||||||
bc->instructions = (Instruction *)realloc(bc->instructions, sizeof(Instruction) * (bc->instr_count + 1));
|
bc->instructions = (Instruction *)realloc(bc->instructions, sizeof(Instruction) * (bc->instr_count + 1));
|
||||||
bc->instructions[bc->instr_count].op = op;
|
bc->instructions[bc->instr_count].op = op;
|
||||||
|
|
@ -35,12 +66,29 @@ int bytecode_add_instruction(Bytecode *bc, OpCode op, int32_t operand) {
|
||||||
return bc->instr_count++;
|
return bc->instr_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Patch the operand of a previously emitted instruction.
|
||||||
|
*
|
||||||
|
* Silently ignores out-of-bounds indices.
|
||||||
|
*
|
||||||
|
* @param bc Target bytecode.
|
||||||
|
* @param idx Instruction index to patch.
|
||||||
|
* @param operand New operand value.
|
||||||
|
*/
|
||||||
void bytecode_set_operand(Bytecode *bc, int idx, int32_t operand) {
|
void bytecode_set_operand(Bytecode *bc, int idx, int32_t operand) {
|
||||||
if (idx >= 0 && idx < bc->instr_count) {
|
if (idx >= 0 && idx < bc->instr_count) {
|
||||||
bc->instructions[idx].operand = operand;
|
bc->instructions[idx].operand = operand;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Free a Bytecode and all memory it owns.
|
||||||
|
*
|
||||||
|
* Frees constants (deep), instruction array, and metadata strings.
|
||||||
|
* Accepts NULL and is then a no-op.
|
||||||
|
*
|
||||||
|
* @param bc Bytecode to free (may be NULL).
|
||||||
|
*/
|
||||||
void bytecode_free(Bytecode *bc) {
|
void bytecode_free(Bytecode *bc) {
|
||||||
if (!bc) return;
|
if (!bc) return;
|
||||||
for (int i = 0; i < bc->const_count; ++i) {
|
for (int i = 0; i < bc->const_count; ++i) {
|
||||||
|
|
@ -53,6 +101,12 @@ void bytecode_free(Bytecode *bc) {
|
||||||
free(bc);
|
free(bc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Convert an opcode enum to a short mnemonic string.
|
||||||
|
*
|
||||||
|
* @param op Opcode value.
|
||||||
|
* @return Read-only C string with the mnemonic, or "???" if unknown.
|
||||||
|
*/
|
||||||
static const char *opcode_name(OpCode op) {
|
static const char *opcode_name(OpCode op) {
|
||||||
switch (op) {
|
switch (op) {
|
||||||
case OP_NOP:
|
case OP_NOP:
|
||||||
|
|
@ -358,6 +412,14 @@ static const char *opcode_name(OpCode op) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Print a human-readable dump of constants and instructions to stdout.
|
||||||
|
*
|
||||||
|
* Intended for debugging and tests. Formats constants with print_value()
|
||||||
|
* and shows each instruction index, mnemonic and operand.
|
||||||
|
*
|
||||||
|
* @param bc Bytecode to dump (prints "<null bytecode>" if NULL).
|
||||||
|
*/
|
||||||
void bytecode_dump(const Bytecode *bc) {
|
void bytecode_dump(const Bytecode *bc) {
|
||||||
if (!bc) {
|
if (!bc) {
|
||||||
printf("<null bytecode>\n");
|
printf("<null bytecode>\n");
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,31 @@
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file bytecode.h
|
||||||
|
* @brief Definitions for the Fun VM bytecode: opcodes, instruction format, and bytecode container API.
|
||||||
|
*
|
||||||
|
* This header declares the VM's operation codes (@ref OpCode), the compact
|
||||||
|
* instruction representation (@ref Instruction), and the owning bytecode
|
||||||
|
* container (@ref Bytecode) together with minimal constructor/manipulation
|
||||||
|
* helpers. The concrete execution semantics for each opcode are implemented
|
||||||
|
* in the VM (see vm.c and vm/* handlers).
|
||||||
|
*/
|
||||||
#ifndef FUN_BYTECODE_H
|
#ifndef FUN_BYTECODE_H
|
||||||
#define FUN_BYTECODE_H
|
#define FUN_BYTECODE_H
|
||||||
|
|
||||||
#include "value.h"
|
#include "value.h"
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
// VM opcodes
|
/**
|
||||||
|
* @brief VM operation codes executed by the Fun virtual machine.
|
||||||
|
*
|
||||||
|
* Unless stated otherwise, opcodes operate on the VM stack. Comments on each
|
||||||
|
* opcode describe stack effects using a left-to-right pop order and the value
|
||||||
|
* pushed as a result. For example, "pops b, a; pushes a+b" means the
|
||||||
|
* instruction will pop first b then a from the stack and finally push the
|
||||||
|
* result of a+b.
|
||||||
|
*/
|
||||||
typedef enum {
|
typedef enum {
|
||||||
OP_NOP,
|
OP_NOP,
|
||||||
OP_LOAD_CONST, // operand = constant index
|
OP_LOAD_CONST, // operand = constant index
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,47 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file curl.c
|
||||||
|
* @brief libcurl helpers and buffers used by HTTP-related VM opcodes.
|
||||||
*
|
*
|
||||||
* Added: 2025-12-11 (2025-12-11 migrated from src/vm/libsql/common.c)
|
* Declares small buffer helpers and libcurl write callbacks that support
|
||||||
|
* network-related opcodes when FUN_WITH_CURL is enabled.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Ensure libcurl headers and helpers are defined at file scope (not inside vm_run) */
|
/* Ensure libcurl headers and helpers are defined at file scope (not inside vm_run) */
|
||||||
#ifdef FUN_WITH_CURL
|
#ifdef FUN_WITH_CURL
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Simple growable buffer for libcurl write callbacks.
|
||||||
|
*
|
||||||
|
* The buffer grows via realloc as more data arrives. The content is kept
|
||||||
|
* NUL-terminated for convenience.
|
||||||
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char *d;
|
char *d; /**< Data pointer (NUL-terminated). */
|
||||||
size_t n;
|
size_t n; /**< Number of bytes stored (excluding NUL). */
|
||||||
} FunCurlBuf;
|
} FunCurlBuf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief libcurl write callback that appends data to a FunCurlBuf.
|
||||||
|
*
|
||||||
|
* Reallocates the destination buffer as needed and keeps it NUL-terminated.
|
||||||
|
*
|
||||||
|
* @param ptr Pointer to incoming data block provided by libcurl.
|
||||||
|
* @param sz Size of each data element.
|
||||||
|
* @param nm Number of elements in this block.
|
||||||
|
* @param ud User data; must be a FunCurlBuf*.
|
||||||
|
* @return Number of bytes actually handled (sz*nm) on success, 0 on failure
|
||||||
|
* to signal an error to libcurl.
|
||||||
|
*/
|
||||||
static size_t fun_curl_write_cb(void *ptr, size_t sz, size_t nm, void *ud) {
|
static size_t fun_curl_write_cb(void *ptr, size_t sz, size_t nm, void *ud) {
|
||||||
size_t add = sz * nm;
|
size_t add = sz * nm;
|
||||||
FunCurlBuf *b = (FunCurlBuf *)ud;
|
FunCurlBuf *b = (FunCurlBuf *)ud;
|
||||||
|
|
@ -27,6 +53,16 @@ static size_t fun_curl_write_cb(void *ptr, size_t sz, size_t nm, void *ud) {
|
||||||
b->d[b->n] = '\0';
|
b->d[b->n] = '\0';
|
||||||
return add;
|
return add;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief libcurl write callback that writes directly to a FILE*.
|
||||||
|
*
|
||||||
|
* @param ptr Pointer to incoming data.
|
||||||
|
* @param sz Size of each element.
|
||||||
|
* @param nm Number of elements.
|
||||||
|
* @param ud User data; must be a FILE* opened for writing in binary mode.
|
||||||
|
* @return Number of elements written (as returned by fwrite).
|
||||||
|
*/
|
||||||
static size_t fun_curl_file_write_cb(void *ptr, size_t sz, size_t nm, void *ud) {
|
static size_t fun_curl_file_write_cb(void *ptr, size_t sz, size_t nm, void *ud) {
|
||||||
FILE *f = (FILE *)ud;
|
FILE *f = (FILE *)ud;
|
||||||
return fwrite(ptr, sz, nm, f);
|
return fwrite(ptr, sz, nm, f);
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,18 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file ini.c
|
||||||
|
* @brief INI parsing helpers and VM opcode support via iniparser.
|
||||||
*
|
*
|
||||||
* Added: 2025-12-11 (2025-12-11 migrated from src/vm.c)
|
* Provides includes and declarations required for INI-related opcodes when
|
||||||
|
* FUN_WITH_INI is enabled at build time.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifdef FUN_WITH_INI
|
#ifdef FUN_WITH_INI
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,19 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file json.c
|
||||||
|
* @brief JSON extension helpers and VM opcode cases (conditional build).
|
||||||
*
|
*
|
||||||
* Added: 2025-12-11 (2025-12-11 migrated from src/vm.c)
|
* Provides conversions between json-c objects and the Fun Value type and
|
||||||
|
* supplies VM opcode case implementations when FUN_WITH_JSON is enabled.
|
||||||
|
* This unit may be included or compiled conditionally based on build flags.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* json-c helpers and VM opcode cases (included from vm.c) */
|
/* json-c helpers and VM opcode cases (included from vm.c) */
|
||||||
|
|
@ -19,6 +26,23 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
/* --- Conversion helpers between json-c and Fun Value --- */
|
/* --- Conversion helpers between json-c and Fun Value --- */
|
||||||
|
/**
|
||||||
|
* @brief Convert a json-c object into a Fun Value.
|
||||||
|
*
|
||||||
|
* Maps json-c primitive and compound types to the closest Fun Value
|
||||||
|
* representation.
|
||||||
|
* - null -> Nil
|
||||||
|
* - boolean -> Bool
|
||||||
|
* - number (int/double) -> Int/Float
|
||||||
|
* - string -> String
|
||||||
|
* - array -> Array (recursively converted)
|
||||||
|
* - object -> Map<string,any> (values recursively converted)
|
||||||
|
*
|
||||||
|
* @param j Pointer to a json_object; may be NULL.
|
||||||
|
* @return A Value representing the converted JSON data. Ownership of the
|
||||||
|
* returned Value belongs to the caller and must be freed with
|
||||||
|
* free_value() when no longer needed.
|
||||||
|
*/
|
||||||
static Value json_to_fun(json_object *j) {
|
static Value json_to_fun(json_object *j) {
|
||||||
if (!j) return make_nil();
|
if (!j) return make_nil();
|
||||||
enum json_type t = json_object_get_type(j);
|
enum json_type t = json_object_get_type(j);
|
||||||
|
|
@ -62,6 +86,16 @@ static Value json_to_fun(json_object *j) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Convert a Fun Value into a json-c object.
|
||||||
|
*
|
||||||
|
* Produces a newly-allocated json_object tree representing the supplied
|
||||||
|
* Value. Unsupported Fun types are stringified using a placeholder.
|
||||||
|
*
|
||||||
|
* @param v Pointer to the source Value. Must not be NULL.
|
||||||
|
* @return Newly created json_object* on success. The caller owns the
|
||||||
|
* returned object and must release it with json_object_put().
|
||||||
|
*/
|
||||||
static json_object *fun_to_json(const Value *v) {
|
static json_object *fun_to_json(const Value *v) {
|
||||||
switch (v->type) {
|
switch (v->type) {
|
||||||
case VAL_NIL:
|
case VAL_NIL:
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,20 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file openssl.c
|
||||||
|
* @brief OpenSSL-based hashing helpers used by crypto-related opcodes.
|
||||||
*
|
*
|
||||||
* Added: 2026-02-19
|
* Provides thin wrappers around OpenSSL EVP routines to compute common
|
||||||
|
* digests (MD5, RIPEMD-160, SHA-256, SHA-512). When FUN_WITH_OPENSSL is
|
||||||
|
* disabled, these helpers fall back to returning empty strings to keep the
|
||||||
|
* VM behavior consistent.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -23,10 +31,16 @@ int EVP_MD_get_size(const EVP_MD *md);
|
||||||
#endif
|
#endif
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
/* Compute MD5 hex of input buffer; returns malloc'ed C string (lowercase hex).
|
/**
|
||||||
* Caller must free(). Returns NULL on failure. When OpenSSL is disabled,
|
* @brief Compute MD5 digest and return it as a lowercase hex string.
|
||||||
* returns an allocated empty string ("") to keep behavior consistent with
|
*
|
||||||
* other optional extensions. */
|
* @param data Pointer to input bytes (may be NULL if len==0).
|
||||||
|
* @param len Number of input bytes.
|
||||||
|
* @return Newly-allocated NUL-terminated hex string on success; NULL on
|
||||||
|
* failure. When FUN_WITH_OPENSSL is disabled, returns an allocated
|
||||||
|
* empty string to keep behavior consistent.
|
||||||
|
* @note The caller owns the returned buffer and must free() it.
|
||||||
|
*/
|
||||||
static char *fun_openssl_md5_hex(const unsigned char *data, size_t len) {
|
static char *fun_openssl_md5_hex(const unsigned char *data, size_t len) {
|
||||||
static const char hexdig[] = "0123456789abcdef";
|
static const char hexdig[] = "0123456789abcdef";
|
||||||
if (!data && len != 0) return NULL;
|
if (!data && len != 0) return NULL;
|
||||||
|
|
@ -68,8 +82,15 @@ static char *fun_openssl_md5_hex(const unsigned char *data, size_t len) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Compute SHA-256 hex of input buffer; returns malloc'ed C string (lowercase hex).
|
/**
|
||||||
* Fallback when OpenSSL disabled: empty string. */
|
* @brief Compute SHA-256 digest and return it as a lowercase hex string.
|
||||||
|
*
|
||||||
|
* @param data Pointer to input bytes (may be NULL if len==0).
|
||||||
|
* @param len Number of input bytes.
|
||||||
|
* @return Newly-allocated hex string on success; NULL on failure. When
|
||||||
|
* FUN_WITH_OPENSSL is disabled, returns an allocated empty string.
|
||||||
|
* @note The caller must free() the returned buffer.
|
||||||
|
*/
|
||||||
static char *fun_openssl_sha256_hex(const unsigned char *data, size_t len) {
|
static char *fun_openssl_sha256_hex(const unsigned char *data, size_t len) {
|
||||||
static const char hexdig[] = "0123456789abcdef";
|
static const char hexdig[] = "0123456789abcdef";
|
||||||
if (!data && len != 0) return NULL;
|
if (!data && len != 0) return NULL;
|
||||||
|
|
@ -110,8 +131,15 @@ static char *fun_openssl_sha256_hex(const unsigned char *data, size_t len) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Compute SHA-512 hex of input buffer; returns malloc'ed C string (lowercase hex).
|
/**
|
||||||
* Fallback when OpenSSL disabled: empty string. */
|
* @brief Compute SHA-512 digest and return it as a lowercase hex string.
|
||||||
|
*
|
||||||
|
* @param data Pointer to input bytes (may be NULL if len==0).
|
||||||
|
* @param len Number of input bytes.
|
||||||
|
* @return Newly-allocated hex string on success; NULL on failure. When
|
||||||
|
* FUN_WITH_OPENSSL is disabled, returns an allocated empty string.
|
||||||
|
* @note The caller must free() the returned buffer.
|
||||||
|
*/
|
||||||
static char *fun_openssl_sha512_hex(const unsigned char *data, size_t len) {
|
static char *fun_openssl_sha512_hex(const unsigned char *data, size_t len) {
|
||||||
static const char hexdig[] = "0123456789abcdef";
|
static const char hexdig[] = "0123456789abcdef";
|
||||||
if (!data && len != 0) return NULL;
|
if (!data && len != 0) return NULL;
|
||||||
|
|
@ -152,10 +180,19 @@ static char *fun_openssl_sha512_hex(const unsigned char *data, size_t len) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Compute RIPEMD-160 hex of input buffer; returns malloc'ed C string (lowercase hex).
|
/**
|
||||||
* Note: On OpenSSL 3.x, RIPEMD160 may require the legacy provider; if unavailable,
|
* @brief Compute RIPEMD-160 digest and return it as a lowercase hex string.
|
||||||
* EVP_ripemd160() can return NULL. In that case we return NULL and the VM opcode
|
*
|
||||||
* will fall back to empty string behavior. When OpenSSL is disabled, return empty string. */
|
* On some OpenSSL builds (e.g., 3.x without legacy provider), RIPEMD-160 may
|
||||||
|
* be unavailable and EVP_ripemd160() can return NULL.
|
||||||
|
*
|
||||||
|
* @param data Pointer to input bytes (may be NULL if len==0).
|
||||||
|
* @param len Number of input bytes.
|
||||||
|
* @return Newly-allocated hex string on success; NULL if the digest is
|
||||||
|
* unavailable or another error occurs. When FUN_WITH_OPENSSL is
|
||||||
|
* disabled, returns an allocated empty string.
|
||||||
|
* @note The caller must free() the returned buffer.
|
||||||
|
*/
|
||||||
static char *fun_openssl_ripemd160_hex(const unsigned char *data, size_t len) {
|
static char *fun_openssl_ripemd160_hex(const unsigned char *data, size_t len) {
|
||||||
static const char hexdig[] = "0123456789abcdef";
|
static const char hexdig[] = "0123456789abcdef";
|
||||||
if (!data && len != 0) return NULL;
|
if (!data && len != 0) return NULL;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,18 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file pcre2.c
|
||||||
|
* @brief PCRE2 configuration header and includes for regex-related opcodes.
|
||||||
*
|
*
|
||||||
* Added: 2025-12-11 (2025-12-11 migrated from src/vm/libsql/common.c)
|
* Ensures PCRE2 code unit width is defined consistently prior to including
|
||||||
|
* <pcre2.h> when FUN_WITH_PCRE2 is enabled.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Ensure PCRE2 is configured consistently across the whole translation unit.
|
/* Ensure PCRE2 is configured consistently across the whole translation unit.
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,20 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2025-12-11 (2025-12-11 migrated from src/vm.c)
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/**
|
||||||
PCSC helpers: registries and helper functions.
|
* @file pcsc.c
|
||||||
Included the file scope from vm.c.
|
* @brief PC/SC smartcard helper registries and lookup utilities.
|
||||||
*/
|
*
|
||||||
|
* Provides small fixed-size registries for PC/SC contexts and card handles,
|
||||||
|
* plus allocation and lookup helpers used by VM opcodes when FUN_WITH_PCSC
|
||||||
|
* is enabled.
|
||||||
|
*/
|
||||||
|
|
||||||
#ifdef FUN_WITH_PCSC
|
#ifdef FUN_WITH_PCSC
|
||||||
#if defined(__has_include)
|
#if defined(__has_include)
|
||||||
|
|
@ -44,6 +46,11 @@ typedef struct {
|
||||||
static pcsc_ctx_entry g_pcsc_ctx[8];
|
static pcsc_ctx_entry g_pcsc_ctx[8];
|
||||||
static pcsc_card_entry g_pcsc_card[32];
|
static pcsc_card_entry g_pcsc_card[32];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Allocate a free context slot in the PC/SC registry.
|
||||||
|
*
|
||||||
|
* @return A 1-based slot id on success, or 0 if no free slot is available.
|
||||||
|
*/
|
||||||
static int pcsc_alloc_ctx_slot(void) {
|
static int pcsc_alloc_ctx_slot(void) {
|
||||||
for (int i = 0; i < (int)(sizeof(g_pcsc_ctx) / sizeof(g_pcsc_ctx[0])); ++i) {
|
for (int i = 0; i < (int)(sizeof(g_pcsc_ctx) / sizeof(g_pcsc_ctx[0])); ++i) {
|
||||||
if (!g_pcsc_ctx[i].in_use) {
|
if (!g_pcsc_ctx[i].in_use) {
|
||||||
|
|
@ -55,6 +62,11 @@ static int pcsc_alloc_ctx_slot(void) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Allocate a free card slot in the PC/SC registry.
|
||||||
|
*
|
||||||
|
* @return A 1-based slot id on success, or 0 if no free slot is available.
|
||||||
|
*/
|
||||||
static int pcsc_alloc_card_slot(void) {
|
static int pcsc_alloc_card_slot(void) {
|
||||||
for (int i = 0; i < (int)(sizeof(g_pcsc_card) / sizeof(g_pcsc_card[0])); ++i) {
|
for (int i = 0; i < (int)(sizeof(g_pcsc_card) / sizeof(g_pcsc_card[0])); ++i) {
|
||||||
if (!g_pcsc_card[i].in_use) {
|
if (!g_pcsc_card[i].in_use) {
|
||||||
|
|
@ -67,6 +79,12 @@ static int pcsc_alloc_card_slot(void) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lookup a context slot by id.
|
||||||
|
*
|
||||||
|
* @param id 1-based context id previously returned by pcsc_alloc_ctx_slot().
|
||||||
|
* @return Pointer to the registry entry if valid and in use; NULL otherwise.
|
||||||
|
*/
|
||||||
static pcsc_ctx_entry *pcsc_get_ctx(int id) {
|
static pcsc_ctx_entry *pcsc_get_ctx(int id) {
|
||||||
if (id <= 0) return NULL;
|
if (id <= 0) return NULL;
|
||||||
int idx = id - 1;
|
int idx = id - 1;
|
||||||
|
|
@ -75,6 +93,12 @@ static pcsc_ctx_entry *pcsc_get_ctx(int id) {
|
||||||
return &g_pcsc_ctx[idx];
|
return &g_pcsc_ctx[idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lookup a card slot by id.
|
||||||
|
*
|
||||||
|
* @param id 1-based card id previously returned by pcsc_alloc_card_slot().
|
||||||
|
* @return Pointer to the registry entry if valid and in use; NULL otherwise.
|
||||||
|
*/
|
||||||
static pcsc_card_entry *pcsc_get_card(int id) {
|
static pcsc_card_entry *pcsc_get_card(int id) {
|
||||||
if (id <= 0) return NULL;
|
if (id <= 0) return NULL;
|
||||||
int idx = id - 1;
|
int idx = id - 1;
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,56 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2025-12-11 (2025-12-11 migrated from src/vm/sqlite/common.c)
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SQLite handle registry and helpers
|
* @file sqlite.c
|
||||||
|
* @brief SQLite handle registry and helper utilities for the Fun VM extension.
|
||||||
|
*
|
||||||
|
* This module provides a very small registry for SQLite database handles when
|
||||||
|
* the project is compiled with FUN_WITH_SQLITE. It assigns small integer
|
||||||
|
* identifiers to opened sqlite3 connections and allows retrieval or removal of
|
||||||
|
* those entries. The registry itself does not open or close SQLite databases —
|
||||||
|
* it only stores pointers provided by the caller.
|
||||||
|
*
|
||||||
|
* Thread-safety: This registry is not thread-safe. Callers must ensure
|
||||||
|
* external synchronization if used from multiple threads.
|
||||||
*/
|
*/
|
||||||
#ifdef FUN_WITH_SQLITE
|
#ifdef FUN_WITH_SQLITE
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Node in a singly-linked list of registered SQLite handles.
|
||||||
|
*/
|
||||||
typedef struct SqlHandle {
|
typedef struct SqlHandle {
|
||||||
int id;
|
int id;
|
||||||
sqlite3 *db;
|
sqlite3 *db;
|
||||||
struct SqlHandle *next;
|
struct SqlHandle *next;
|
||||||
} SqlHandle;
|
} SqlHandle;
|
||||||
|
|
||||||
|
/** Global head of the SQLite handle list. */
|
||||||
static SqlHandle *g_sql_handles = NULL;
|
static SqlHandle *g_sql_handles = NULL;
|
||||||
|
/** Next positive identifier to assign to a newly added handle. */
|
||||||
static int g_sql_next_id = 1;
|
static int g_sql_next_id = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add a sqlite3 handle to the registry.
|
||||||
|
*
|
||||||
|
* The function allocates a new list node, assigns a fresh positive id, and
|
||||||
|
* prepends it to the internal registry list.
|
||||||
|
*
|
||||||
|
* @param db Valid pointer to an opened sqlite3 connection.
|
||||||
|
* @return Pointer to the newly created SqlHandle entry on success; NULL on
|
||||||
|
* allocation failure. The returned pointer remains owned by the
|
||||||
|
* registry; do not free it directly.
|
||||||
|
* @note This function does not take ownership of the sqlite3 connection in the
|
||||||
|
* sense of closing it; removal from the registry will not call
|
||||||
|
* sqlite3_close().
|
||||||
|
*/
|
||||||
static SqlHandle *sql_reg_add(sqlite3 *db) {
|
static SqlHandle *sql_reg_add(sqlite3 *db) {
|
||||||
SqlHandle *h = (SqlHandle *)calloc(1, sizeof(SqlHandle));
|
SqlHandle *h = (SqlHandle *)calloc(1, sizeof(SqlHandle));
|
||||||
if (!h) return NULL;
|
if (!h) return NULL;
|
||||||
|
|
@ -34,12 +61,27 @@ static SqlHandle *sql_reg_add(sqlite3 *db) {
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Look up a registered SQLite handle by id.
|
||||||
|
*
|
||||||
|
* @param id Positive identifier previously returned by sql_reg_add().
|
||||||
|
* @return Pointer to the SqlHandle entry if found; NULL otherwise.
|
||||||
|
*/
|
||||||
static SqlHandle *sql_reg_get(int id) {
|
static SqlHandle *sql_reg_get(int id) {
|
||||||
for (SqlHandle *p = g_sql_handles; p; p = p->next)
|
for (SqlHandle *p = g_sql_handles; p; p = p->next)
|
||||||
if (p->id == id) return p;
|
if (p->id == id) return p;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove a SQLite handle entry from the registry.
|
||||||
|
*
|
||||||
|
* Deletes the list node associated with the given id.
|
||||||
|
*
|
||||||
|
* @param id Positive identifier of the entry to remove.
|
||||||
|
* @note This function does not close the underlying sqlite3 connection; the
|
||||||
|
* caller is responsible for calling sqlite3_close() if appropriate.
|
||||||
|
*/
|
||||||
static void sql_reg_del(int id) {
|
static void sql_reg_del(int id) {
|
||||||
SqlHandle **pp = &g_sql_handles;
|
SqlHandle **pp = &g_sql_handles;
|
||||||
while (*pp) {
|
while (*pp) {
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,48 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2025-12-11 (2025-12-11 migrated from src/vm/libsql/common.c)
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file xml2.c
|
||||||
|
* @brief Lightweight libxml2 handle registry for Fun VM extension helpers.
|
||||||
|
*
|
||||||
|
* This module provides small fixed-size registries for libxml2 objects when
|
||||||
|
* compiled with FUN_WITH_XML2. Documents and nodes can be associated with
|
||||||
|
* small integer handles to make them easier to pass around inside the
|
||||||
|
* interpreter and its C API boundaries. The document registry owns the
|
||||||
|
* underlying xmlDoc, while the node registry does not own the xmlNode — nodes
|
||||||
|
* are freed when their owning document is freed.
|
||||||
|
*
|
||||||
|
* Limits: The registries are fixed-size (docs: 64, nodes: 256). Handle value 0
|
||||||
|
* is reserved and indicates failure/invalid.
|
||||||
|
*
|
||||||
|
* Thread-safety: Not thread-safe. Coordinate access externally if needed.
|
||||||
|
*/
|
||||||
#ifdef FUN_WITH_XML2
|
#ifdef FUN_WITH_XML2
|
||||||
#include <libxml/parser.h>
|
#include <libxml/parser.h>
|
||||||
#include <libxml/tree.h>
|
#include <libxml/tree.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Slot describing a registered XML document.
|
||||||
|
*
|
||||||
|
* The registry owns the xmlDocPtr and will free it when the handle is
|
||||||
|
* released via xml_doc_free_handle().
|
||||||
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
xmlDocPtr doc;
|
xmlDocPtr doc;
|
||||||
int in_use;
|
int in_use;
|
||||||
} XmlDocSlot;
|
} XmlDocSlot;
|
||||||
|
/**
|
||||||
|
* @brief Slot describing a registered XML node.
|
||||||
|
*
|
||||||
|
* The registry does not own the node; it is managed by its document. Releasing
|
||||||
|
* a node handle does not free the node memory.
|
||||||
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
xmlNodePtr node;
|
xmlNodePtr node;
|
||||||
int in_use;
|
int in_use;
|
||||||
|
|
@ -25,6 +51,14 @@ typedef struct {
|
||||||
static XmlDocSlot g_xml_docs[64];
|
static XmlDocSlot g_xml_docs[64];
|
||||||
static XmlNodeSlot g_xml_nodes[256];
|
static XmlNodeSlot g_xml_nodes[256];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Allocate a document handle for the given xmlDoc pointer.
|
||||||
|
*
|
||||||
|
* @param d Valid xmlDocPtr to register. Ownership is transferred to the
|
||||||
|
* registry, which will xmlFreeDoc() it when the handle is freed.
|
||||||
|
* @return Positive handle in the range [1, 63] on success; 0 if no slot is
|
||||||
|
* available.
|
||||||
|
*/
|
||||||
static int xml_doc_alloc(xmlDocPtr d) {
|
static int xml_doc_alloc(xmlDocPtr d) {
|
||||||
for (int i = 1; i < (int)(sizeof(g_xml_docs) / sizeof(g_xml_docs[0])); ++i) {
|
for (int i = 1; i < (int)(sizeof(g_xml_docs) / sizeof(g_xml_docs[0])); ++i) {
|
||||||
if (!g_xml_docs[i].in_use) {
|
if (!g_xml_docs[i].in_use) {
|
||||||
|
|
@ -35,10 +69,22 @@ static int xml_doc_alloc(xmlDocPtr d) {
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @brief Retrieve a registered xmlDoc by handle.
|
||||||
|
*
|
||||||
|
* @param h Handle previously returned by xml_doc_alloc().
|
||||||
|
* @return xmlDocPtr if the handle is valid and in use; NULL otherwise.
|
||||||
|
*/
|
||||||
static xmlDocPtr xml_doc_get(int h) {
|
static xmlDocPtr xml_doc_get(int h) {
|
||||||
if (h > 0 && h < (int)(sizeof(g_xml_docs) / sizeof(g_xml_docs[0])) && g_xml_docs[h].in_use) return g_xml_docs[h].doc;
|
if (h > 0 && h < (int)(sizeof(g_xml_docs) / sizeof(g_xml_docs[0])) && g_xml_docs[h].in_use) return g_xml_docs[h].doc;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @brief Free a document handle and the underlying xmlDoc.
|
||||||
|
*
|
||||||
|
* @param h Handle to release.
|
||||||
|
* @return 1 if the handle was valid and has been released; 0 otherwise.
|
||||||
|
*/
|
||||||
static int xml_doc_free_handle(int h) {
|
static int xml_doc_free_handle(int h) {
|
||||||
if (h <= 0 || h >= (int)(sizeof(g_xml_docs) / sizeof(g_xml_docs[0])) || !g_xml_docs[h].in_use) return 0;
|
if (h <= 0 || h >= (int)(sizeof(g_xml_docs) / sizeof(g_xml_docs[0])) || !g_xml_docs[h].in_use) return 0;
|
||||||
if (g_xml_docs[h].doc) xmlFreeDoc(g_xml_docs[h].doc);
|
if (g_xml_docs[h].doc) xmlFreeDoc(g_xml_docs[h].doc);
|
||||||
|
|
@ -47,6 +93,14 @@ static int xml_doc_free_handle(int h) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Allocate a node handle for the given xmlNode pointer.
|
||||||
|
*
|
||||||
|
* @param n Valid xmlNodePtr to register. Ownership is NOT transferred;
|
||||||
|
* nodes are managed by their owning document.
|
||||||
|
* @return Positive handle in the range [1, 255] on success; 0 if no slot is
|
||||||
|
* available.
|
||||||
|
*/
|
||||||
static int xml_node_alloc(xmlNodePtr n) {
|
static int xml_node_alloc(xmlNodePtr n) {
|
||||||
for (int i = 1; i < (int)(sizeof(g_xml_nodes) / sizeof(g_xml_nodes[0])); ++i) {
|
for (int i = 1; i < (int)(sizeof(g_xml_nodes) / sizeof(g_xml_nodes[0])); ++i) {
|
||||||
if (!g_xml_nodes[i].in_use) {
|
if (!g_xml_nodes[i].in_use) {
|
||||||
|
|
@ -57,10 +111,25 @@ static int xml_node_alloc(xmlNodePtr n) {
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @brief Retrieve a registered xmlNode by handle.
|
||||||
|
*
|
||||||
|
* @param h Handle previously returned by xml_node_alloc().
|
||||||
|
* @return xmlNodePtr if the handle is valid and in use; NULL otherwise.
|
||||||
|
*/
|
||||||
static xmlNodePtr xml_node_get(int h) {
|
static xmlNodePtr xml_node_get(int h) {
|
||||||
if (h > 0 && h < (int)(sizeof(g_xml_nodes) / sizeof(g_xml_nodes[0])) && g_xml_nodes[h].in_use) return g_xml_nodes[h].node;
|
if (h > 0 && h < (int)(sizeof(g_xml_nodes) / sizeof(g_xml_nodes[0])) && g_xml_nodes[h].in_use) return g_xml_nodes[h].node;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @brief Free a node handle without freeing the underlying node.
|
||||||
|
*
|
||||||
|
* Nodes are owned by their document; releasing the document invalidates any
|
||||||
|
* associated node handles.
|
||||||
|
*
|
||||||
|
* @param h Handle to release.
|
||||||
|
* @return 1 if the handle was valid and has been released; 0 otherwise.
|
||||||
|
*/
|
||||||
static int xml_node_free_handle(int h) {
|
static int xml_node_free_handle(int h) {
|
||||||
if (h <= 0 || h >= (int)(sizeof(g_xml_nodes) / sizeof(g_xml_nodes[0])) || !g_xml_nodes[h].in_use) return 0;
|
if (h <= 0 || h >= (int)(sizeof(g_xml_nodes) / sizeof(g_xml_nodes[0])) || !g_xml_nodes[h].in_use) return 0;
|
||||||
/* nodes are owned by their document; do not free here */
|
/* nodes are owned by their document; do not free here */
|
||||||
|
|
|
||||||
26
src/fun.c
26
src/fun.c
|
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
|
|
@ -7,7 +7,10 @@
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/**
|
||||||
|
* @file fun.c
|
||||||
|
* @brief Command-line interface and entry point for the Fun interpreter.
|
||||||
|
*
|
||||||
* Main entry point for the Fun language interpreter.
|
* Main entry point for the Fun language interpreter.
|
||||||
* Builds a CLI that runs a script file if provided; otherwise starts the REPL
|
* Builds a CLI that runs a script file if provided; otherwise starts the REPL
|
||||||
* when compiled with FUN_WITH_REPL enabled.
|
* when compiled with FUN_WITH_REPL enabled.
|
||||||
|
|
@ -29,6 +32,14 @@
|
||||||
#define FUN_VERSION "0.0.0-dev"
|
#define FUN_VERSION "0.0.0-dev"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Print command-line usage instructions to stdout.
|
||||||
|
*
|
||||||
|
* The usage varies depending on whether the binary was built with
|
||||||
|
* FUN_WITH_REPL enabled.
|
||||||
|
*
|
||||||
|
* @param prog Program name/path used in usage lines. May be NULL.
|
||||||
|
*/
|
||||||
static void print_usage(const char *prog) {
|
static void print_usage(const char *prog) {
|
||||||
printf("Fun %s\n", FUN_VERSION);
|
printf("Fun %s\n", FUN_VERSION);
|
||||||
printf("Usage:\n");
|
printf("Usage:\n");
|
||||||
|
|
@ -51,6 +62,17 @@ static void print_usage(const char *prog) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Program entry point for the Fun interpreter.
|
||||||
|
*
|
||||||
|
* Parses CLI options, compiles and runs a script file if provided, or launches
|
||||||
|
* the REPL when enabled and no script is given. Exposes script arguments to
|
||||||
|
* the program via FUN_ARGC/FUN_ARGV_i/FUN_ARGS environment variables.
|
||||||
|
*
|
||||||
|
* @param argc Argument count.
|
||||||
|
* @param argv Argument vector; argv[0] is used to expose FUN_EXECUTABLE.
|
||||||
|
* @return Process exit code, typically taken from the VM after execution.
|
||||||
|
*/
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
/* Set FUN_EXECUTABLE environment variable to the path of this binary */
|
/* Set FUN_EXECUTABLE environment variable to the path of this binary */
|
||||||
setenv("FUN_EXECUTABLE", argv[0], 1);
|
setenv("FUN_EXECUTABLE", argv[0], 1);
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,23 @@
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file fun_test.c
|
||||||
|
* @brief Minimal bytecode-level test harness for core VM operations.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "bytecode.h"
|
#include "bytecode.h"
|
||||||
#include "value.h"
|
#include "value.h"
|
||||||
#include "vm.h"
|
#include "vm.h"
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Assert that a Value is an integer equal to the expected number.
|
||||||
|
*
|
||||||
|
* Prints a diagnostic and returns 1 from main on failure; used in quick
|
||||||
|
* sanity checks when extending this test harness.
|
||||||
|
*/
|
||||||
#define ASSERT_EQ(val, expected) \
|
#define ASSERT_EQ(val, expected) \
|
||||||
if ((val).type != VAL_INT || (val).i != (expected)) { \
|
if ((val).type != VAL_INT || (val).i != (expected)) { \
|
||||||
fprintf(stderr, "Assertion failed: expected %lld, got ", (long long)(expected)); \
|
fprintf(stderr, "Assertion failed: expected %lld, got ", (long long)(expected)); \
|
||||||
|
|
@ -21,6 +32,15 @@
|
||||||
return 1; \
|
return 1; \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Build and execute a demo bytecode program and print results.
|
||||||
|
*
|
||||||
|
* Exercises arithmetic, comparisons, logic, stack ops, rounding and
|
||||||
|
* transcendental functions, integer math helpers, and fmin/fmax semantics.
|
||||||
|
* Intended for manual inspection rather than strict unit testing.
|
||||||
|
*
|
||||||
|
* @return 0 on successful execution.
|
||||||
|
*/
|
||||||
int main(void) {
|
int main(void) {
|
||||||
VM vm;
|
VM vm;
|
||||||
vm_init(&vm);
|
vm_init(&vm);
|
||||||
|
|
|
||||||
68
src/funstx.c
68
src/funstx.c
|
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
|
|
@ -7,6 +7,11 @@
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file funstx.c
|
||||||
|
* @brief Syntax checker and auto-fixer tool for Fun source files.
|
||||||
|
*/
|
||||||
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
@ -15,11 +20,24 @@
|
||||||
#include "bytecode.h"
|
#include "bytecode.h"
|
||||||
#include "parser.h"
|
#include "parser.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Print command-line usage for the funstx tool to stderr.
|
||||||
|
* @param prog Program name (argv[0]). May be NULL.
|
||||||
|
*/
|
||||||
static void usage(const char *prog) {
|
static void usage(const char *prog) {
|
||||||
fprintf(stderr, "Usage: %s [--fix] [--quiet] <file1.fun> [file2.fun ...]\n", prog);
|
fprintf(stderr, "Usage: %s [--fix] [--quiet] <file1.fun> [file2.fun ...]\n", prog);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Read entire file into a malloc'd buffer terminated with '\0'. Returns 1 on success. */
|
/**
|
||||||
|
* @brief Read entire file into a newly allocated buffer.
|
||||||
|
*
|
||||||
|
* The returned buffer is NUL-terminated for convenience. Caller must free it.
|
||||||
|
*
|
||||||
|
* @param path Path to file to read.
|
||||||
|
* @param out_buf Output pointer to receive malloc'd buffer.
|
||||||
|
* @param out_len Output length of the file (without the terminating NUL).
|
||||||
|
* @return 1 on success, 0 on failure.
|
||||||
|
*/
|
||||||
static int read_all(const char *path, char **out_buf, size_t *out_len) {
|
static int read_all(const char *path, char **out_buf, size_t *out_len) {
|
||||||
*out_buf = NULL;
|
*out_buf = NULL;
|
||||||
*out_len = 0;
|
*out_len = 0;
|
||||||
|
|
@ -51,7 +69,14 @@ static int read_all(const char *path, char **out_buf, size_t *out_len) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Write buffer to file atomically-ish (overwrite). */
|
/**
|
||||||
|
* @brief Overwrite a file with the provided buffer.
|
||||||
|
*
|
||||||
|
* @param path Target file path.
|
||||||
|
* @param buf Buffer to write.
|
||||||
|
* @param len Number of bytes to write.
|
||||||
|
* @return 1 on success, 0 otherwise.
|
||||||
|
*/
|
||||||
static int write_all(const char *path, const char *buf, size_t len) {
|
static int write_all(const char *path, const char *buf, size_t len) {
|
||||||
FILE *f = fopen(path, "wb");
|
FILE *f = fopen(path, "wb");
|
||||||
if (!f) return 0;
|
if (!f) return 0;
|
||||||
|
|
@ -61,14 +86,32 @@ static int write_all(const char *path, const char *buf, size_t len) {
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check if c is a word constituent (identifier char) */
|
/**
|
||||||
|
* @brief Determine whether a character is an identifier constituent.
|
||||||
|
* @param c Character code (unsigned char promoted to int).
|
||||||
|
* @return Non-zero if c is '_' or an alphanumeric character.
|
||||||
|
*/
|
||||||
static int is_word(int c) {
|
static int is_word(int c) {
|
||||||
return (c == '_' || isalnum(c));
|
return (c == '_' || isalnum(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Apply auto-fixes to the given source text. Returns newly malloc'd buffer and new length.
|
/**
|
||||||
* Idempotent, focuses on parser-related constraints: 2-space indents, no tab indents,
|
* @brief Apply conservative style/syntax auto-fixes to a Fun source buffer.
|
||||||
* CRLF->LF, trim trailing spaces, ensure final newline, normalize 'sint*' to 'int*'.
|
*
|
||||||
|
* Fixes include:
|
||||||
|
* - Normalize indentation to 2 spaces (tabs become 2 spaces per tab)
|
||||||
|
* - Convert CRLF/CR line endings to LF
|
||||||
|
* - Trim trailing spaces on each line
|
||||||
|
* - Ensure the file ends with a single LF
|
||||||
|
* - Normalize identifiers 'sint8/16/32/64' to 'int8/16/32/64' at word boundaries
|
||||||
|
*
|
||||||
|
* Returns a newly allocated buffer containing the fixed content and writes the
|
||||||
|
* resulting length to out_len. Caller must free the returned buffer.
|
||||||
|
*
|
||||||
|
* @param src Input buffer.
|
||||||
|
* @param len Input length.
|
||||||
|
* @param out_len Output length of fixed buffer.
|
||||||
|
* @return Newly malloc'd fixed buffer on success, or NULL on OOM.
|
||||||
*/
|
*/
|
||||||
static char *apply_fixes(const char *src, size_t len, size_t *out_len) {
|
static char *apply_fixes(const char *src, size_t len, size_t *out_len) {
|
||||||
/* First pass: normalize line endings to LF and compute an upper bound size */
|
/* First pass: normalize line endings to LF and compute an upper bound size */
|
||||||
|
|
@ -242,6 +285,17 @@ static char *apply_fixes(const char *src, size_t len, size_t *out_len) {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Entry point for funstx.
|
||||||
|
*
|
||||||
|
* Parses flags, optionally applies in-place fixes (--fix), and validates each
|
||||||
|
* provided Fun source file by attempting to parse it. With --quiet, only
|
||||||
|
* errors are printed; otherwise, prints "OK" for valid files.
|
||||||
|
*
|
||||||
|
* @param argc Argument count.
|
||||||
|
* @param argv Argument vector.
|
||||||
|
* @return 0 if all files are valid (and fixes succeeded if requested), non-zero otherwise.
|
||||||
|
*/
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
int do_fix = 0;
|
int do_fix = 0;
|
||||||
int quiet = 0;
|
int quiet = 0;
|
||||||
|
|
|
||||||
32
src/iter.c
32
src/iter.c
|
|
@ -7,10 +7,26 @@
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file iter.c
|
||||||
|
* @brief Iterator-style helpers exposed as built-ins (enumerate, zip).
|
||||||
|
*/
|
||||||
|
|
||||||
#include "value.h"
|
#include "value.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
/* enumerate(arr) -> [[0, v0], [1, v1], ...] */
|
/**
|
||||||
|
* @brief Build an array of [index, value] pairs from an input array.
|
||||||
|
*
|
||||||
|
* For an input array [v0, v1, ...], returns [[0, v0], [1, v1], ...].
|
||||||
|
* Non-array inputs are treated as empty arrays by array_length/array_get_copy.
|
||||||
|
*
|
||||||
|
* Ownership: The returned Value owns its internal array; caller must free it
|
||||||
|
* with free_value().
|
||||||
|
*
|
||||||
|
* @param arr Input array Value.
|
||||||
|
* @return An array Value of length equal to the input's length.
|
||||||
|
*/
|
||||||
Value bi_enumerate(const Value *arr) {
|
Value bi_enumerate(const Value *arr) {
|
||||||
int n = array_length(arr);
|
int n = array_length(arr);
|
||||||
if (n <= 0) return make_array_from_values(NULL, 0);
|
if (n <= 0) return make_array_from_values(NULL, 0);
|
||||||
|
|
@ -34,7 +50,19 @@ Value bi_enumerate(const Value *arr) {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* zip(a, b) -> [[a0,b0], [a1,b1], ...] up to min(len(a),len(b)) */
|
/**
|
||||||
|
* @brief Zip two arrays into an array of pairs up to the shorter length.
|
||||||
|
*
|
||||||
|
* For inputs a=[a0,a1,...], b=[b0,b1,...], returns [[a0,b0],[a1,b1], ...]
|
||||||
|
* with length min(len(a), len(b)).
|
||||||
|
*
|
||||||
|
* Ownership: The returned Value owns its internal array; caller must free it
|
||||||
|
* with free_value().
|
||||||
|
*
|
||||||
|
* @param a First input array Value.
|
||||||
|
* @param b Second input array Value.
|
||||||
|
* @return Array of pairs as a Value.
|
||||||
|
*/
|
||||||
Value bi_zip(const Value *a, const Value *b) {
|
Value bi_zip(const Value *a, const Value *b) {
|
||||||
int na = array_length(a);
|
int na = array_length(a);
|
||||||
int nb = array_length(b);
|
int nb = array_length(b);
|
||||||
|
|
|
||||||
60
src/map.c
60
src/map.c
|
|
@ -7,6 +7,11 @@
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file map.c
|
||||||
|
* @brief Simple string-keyed map implementation backing VAL_MAP Values.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "value.h"
|
#include "value.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
@ -20,6 +25,13 @@ typedef struct Map {
|
||||||
Value *vals; /* each value owned here */
|
Value *vals; /* each value owned here */
|
||||||
} Map;
|
} Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a new empty map Value.
|
||||||
|
*
|
||||||
|
* Allocates an internal Map structure with refcount=1 and zero capacity.
|
||||||
|
*
|
||||||
|
* @return A Value of type VAL_MAP on success, or VAL_NIL on allocation failure.
|
||||||
|
*/
|
||||||
Value make_map_empty(void) {
|
Value make_map_empty(void) {
|
||||||
Map *m = (Map *)malloc(sizeof(Map));
|
Map *m = (Map *)malloc(sizeof(Map));
|
||||||
if (!m) return make_nil();
|
if (!m) return make_nil();
|
||||||
|
|
@ -34,6 +46,12 @@ Value make_map_empty(void) {
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Ensure the map has capacity for at least need elements.
|
||||||
|
* @param m Internal map pointer (must not be NULL).
|
||||||
|
* @param need Required capacity.
|
||||||
|
* @return 1 on success, 0 on allocation failure.
|
||||||
|
*/
|
||||||
static int map_ensure_cap(Map *m, int need) {
|
static int map_ensure_cap(Map *m, int need) {
|
||||||
if (m->cap >= need) return 1;
|
if (m->cap >= need) return 1;
|
||||||
int ncap = m->cap == 0 ? 4 : m->cap * 2;
|
int ncap = m->cap == 0 ? 4 : m->cap * 2;
|
||||||
|
|
@ -48,6 +66,16 @@ static int map_ensure_cap(Map *m, int need) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Insert or replace a key in the map.
|
||||||
|
*
|
||||||
|
* On success, ownership of v transfers into the map. On failure, v is freed.
|
||||||
|
*
|
||||||
|
* @param vm Target Value of type VAL_MAP.
|
||||||
|
* @param key NUL-terminated key string (copied into the map).
|
||||||
|
* @param v Value to store; consumed on success.
|
||||||
|
* @return 1 on success, 0 on error (type mismatch, OOM, or NULL params).
|
||||||
|
*/
|
||||||
int map_set(Value *vm, const char *key, Value v) {
|
int map_set(Value *vm, const char *key, Value v) {
|
||||||
if (!vm || vm->type != VAL_MAP || !vm->map || !key) {
|
if (!vm || vm->type != VAL_MAP || !vm->map || !key) {
|
||||||
free_value(v);
|
free_value(v);
|
||||||
|
|
@ -71,6 +99,16 @@ int map_set(Value *vm, const char *key, Value v) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Look up a key and copy the stored value into out.
|
||||||
|
*
|
||||||
|
* The returned value is a deep copy; caller owns it and must free it.
|
||||||
|
*
|
||||||
|
* @param vm Source map Value (VAL_MAP).
|
||||||
|
* @param key Key to search for.
|
||||||
|
* @param out Output pointer to receive a copy; may be NULL to only test presence.
|
||||||
|
* @return 1 if found (and out filled if non-NULL), 0 otherwise.
|
||||||
|
*/
|
||||||
int map_get_copy(const Value *vm, const char *key, Value *out) {
|
int map_get_copy(const Value *vm, const char *key, Value *out) {
|
||||||
if (!vm || vm->type != VAL_MAP || !vm->map || !key) return 0;
|
if (!vm || vm->type != VAL_MAP || !vm->map || !key) return 0;
|
||||||
Map *m = (Map *)vm->map;
|
Map *m = (Map *)vm->map;
|
||||||
|
|
@ -83,6 +121,12 @@ int map_get_copy(const Value *vm, const char *key, Value *out) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check whether the map contains the specified key.
|
||||||
|
* @param vm Map Value (VAL_MAP).
|
||||||
|
* @param key Key to search for.
|
||||||
|
* @return 1 if present, 0 if absent or on invalid input.
|
||||||
|
*/
|
||||||
int map_has(const Value *vm, const char *key) {
|
int map_has(const Value *vm, const char *key) {
|
||||||
if (!vm || vm->type != VAL_MAP || !vm->map || !key) return 0;
|
if (!vm || vm->type != VAL_MAP || !vm->map || !key) return 0;
|
||||||
Map *m = (Map *)vm->map;
|
Map *m = (Map *)vm->map;
|
||||||
|
|
@ -92,6 +136,14 @@ int map_has(const Value *vm, const char *key) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return all map keys as an array of strings.
|
||||||
|
*
|
||||||
|
* Ownership: Caller must free the returned Value with free_value().
|
||||||
|
*
|
||||||
|
* @param vm Map Value (VAL_MAP).
|
||||||
|
* @return Array Value of keys; empty array if vm is not a map or is empty.
|
||||||
|
*/
|
||||||
Value map_keys_array(const Value *vm) {
|
Value map_keys_array(const Value *vm) {
|
||||||
if (!vm || vm->type != VAL_MAP || !vm->map) return make_array_from_values(NULL, 0);
|
if (!vm || vm->type != VAL_MAP || !vm->map) return make_array_from_values(NULL, 0);
|
||||||
Map *m = (Map *)vm->map;
|
Map *m = (Map *)vm->map;
|
||||||
|
|
@ -108,6 +160,14 @@ Value map_keys_array(const Value *vm) {
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return all map values as an array (deep-copied).
|
||||||
|
*
|
||||||
|
* Ownership: Caller must free the returned Value with free_value().
|
||||||
|
*
|
||||||
|
* @param vm Map Value (VAL_MAP).
|
||||||
|
* @return Array Value of values; empty array if vm is not a map or is empty.
|
||||||
|
*/
|
||||||
Value map_values_array(const Value *vm) {
|
Value map_values_array(const Value *vm) {
|
||||||
if (!vm || vm->type != VAL_MAP || !vm->map) return make_array_from_values(NULL, 0);
|
if (!vm || vm->type != VAL_MAP || !vm->map) return make_array_from_values(NULL, 0);
|
||||||
Map *m = (Map *)vm->map;
|
Map *m = (Map *)vm->map;
|
||||||
|
|
|
||||||
326
src/parser.c
326
src/parser.c
|
|
@ -72,6 +72,15 @@ static int g_err_col = 0;
|
||||||
static int g_temp_counter = 0;
|
static int g_temp_counter = 0;
|
||||||
|
|
||||||
/* ---- runtime debug control (for suppressing noisy stdout in production/CGI) ---- */
|
/* ---- runtime debug control (for suppressing noisy stdout in production/CGI) ---- */
|
||||||
|
/**
|
||||||
|
* @brief Interpret an environment variable as a boolean flag.
|
||||||
|
*
|
||||||
|
* Recognizes common truthy forms: 1, true/TRUE, yes/YES, on/ON.
|
||||||
|
* Any other value (including unset) is considered false.
|
||||||
|
*
|
||||||
|
* @param name Environment variable name (must not be NULL).
|
||||||
|
* @return 1 if the variable is set to a recognized truthy value, 0 otherwise.
|
||||||
|
*/
|
||||||
static int env_truthy(const char *name) {
|
static int env_truthy(const char *name) {
|
||||||
const char *v = getenv(name);
|
const char *v = getenv(name);
|
||||||
if (!v) return 0;
|
if (!v) return 0;
|
||||||
|
|
@ -85,6 +94,14 @@ static int env_truthy(const char *name) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Determine whether parser/compiler debug tracing is enabled.
|
||||||
|
*
|
||||||
|
* Debugging can be enabled by setting either FUN_TRACE or FUN_DEBUG to a
|
||||||
|
* truthy environment value (see env_truthy).
|
||||||
|
*
|
||||||
|
* @return 1 if debug is enabled, 0 otherwise.
|
||||||
|
*/
|
||||||
static int fun_debug_enabled(void) {
|
static int fun_debug_enabled(void) {
|
||||||
/* Allow enabling debug dumps at runtime via environment.
|
/* Allow enabling debug dumps at runtime via environment.
|
||||||
* Default is OFF to avoid contaminating stdout (e.g., CGI responses).
|
* Default is OFF to avoid contaminating stdout (e.g., CGI responses).
|
||||||
|
|
@ -99,13 +116,29 @@ static int fun_debug_enabled(void) {
|
||||||
positive/negative 8/16/32/64 = integers (negative means signed);
|
positive/negative 8/16/32/64 = integers (negative means signed);
|
||||||
TYPE_META_STRING/BOOLEAN/NIL mark non-integer enforced types;
|
TYPE_META_STRING/BOOLEAN/NIL mark non-integer enforced types;
|
||||||
TYPE_META_CLASS marks class instances (Map with "__class"). */
|
TYPE_META_CLASS marks class instances (Map with "__class"). */
|
||||||
|
/** @brief Type metadata tag used for string enforcement in declared types. */
|
||||||
#define TYPE_META_STRING 10001
|
#define TYPE_META_STRING 10001
|
||||||
|
/** @brief Type metadata tag used for boolean enforcement in declared types. */
|
||||||
#define TYPE_META_BOOLEAN 10002
|
#define TYPE_META_BOOLEAN 10002
|
||||||
|
/** @brief Type metadata tag indicating explicit nil type. */
|
||||||
#define TYPE_META_NIL 10003
|
#define TYPE_META_NIL 10003
|
||||||
|
/** @brief Type metadata tag marking class/instance values. */
|
||||||
#define TYPE_META_CLASS 10004
|
#define TYPE_META_CLASS 10004
|
||||||
|
/** @brief Type metadata tag marking floating point numbers. */
|
||||||
#define TYPE_META_FLOAT 10005
|
#define TYPE_META_FLOAT 10005
|
||||||
|
/** @brief Type metadata tag marking array values. */
|
||||||
#define TYPE_META_ARRAY 10006
|
#define TYPE_META_ARRAY 10006
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Record a parser/compiler error at a given source position.
|
||||||
|
*
|
||||||
|
* Formats an error message into the parser's global error buffer and stores
|
||||||
|
* the offending byte position. Line and column are derived later on demand.
|
||||||
|
*
|
||||||
|
* @param pos Byte offset in the current (preprocessed) source.
|
||||||
|
* @param fmt printf-style format string for the message.
|
||||||
|
* @param ... Arguments matching fmt.
|
||||||
|
*/
|
||||||
static void parser_fail(size_t pos, const char *fmt, ...) {
|
static void parser_fail(size_t pos, const char *fmt, ...) {
|
||||||
g_has_error = 1;
|
g_has_error = 1;
|
||||||
g_err_pos = pos;
|
g_err_pos = pos;
|
||||||
|
|
@ -115,6 +148,18 @@ static void parser_fail(size_t pos, const char *fmt, ...) {
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compute one-based line and column from a byte position.
|
||||||
|
*
|
||||||
|
* Counts newlines up to the smaller of pos and len to derive a human-friendly
|
||||||
|
* (line, column) pair. Columns are one-based and reset after each newline.
|
||||||
|
*
|
||||||
|
* @param src Source buffer.
|
||||||
|
* @param len Source length in bytes.
|
||||||
|
* @param pos Target byte position within the source.
|
||||||
|
* @param out_line Optional out parameter for the computed line.
|
||||||
|
* @param out_col Optional out parameter for the computed column.
|
||||||
|
*/
|
||||||
static void calc_line_col(const char *src, size_t len, size_t pos, int *out_line, int *out_col) {
|
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;
|
int line = 1, col = 1;
|
||||||
size_t limit = pos < len ? pos : len;
|
size_t limit = pos < len ? pos : len;
|
||||||
|
|
@ -138,6 +183,9 @@ static void calc_line_col(const char *src, size_t len, size_t pos, int *out_line
|
||||||
static char *g_ns_aliases[64];
|
static char *g_ns_aliases[64];
|
||||||
static int g_ns_alias_count = 0;
|
static int g_ns_alias_count = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset and free all tracked namespace aliases.
|
||||||
|
*/
|
||||||
static void ns_aliases_reset(void) {
|
static void ns_aliases_reset(void) {
|
||||||
for (int i = 0; i < g_ns_alias_count; ++i) {
|
for (int i = 0; i < g_ns_alias_count; ++i) {
|
||||||
free(g_ns_aliases[i]);
|
free(g_ns_aliases[i]);
|
||||||
|
|
@ -146,6 +194,16 @@ static void ns_aliases_reset(void) {
|
||||||
g_ns_alias_count = 0;
|
g_ns_alias_count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Scan preprocessed source for namespace alias markers.
|
||||||
|
*
|
||||||
|
* Looks for lines starting with "// __ns_alias__: <name>" and stores <name>
|
||||||
|
* so that member-style calls on that identifier are parsed as plain calls
|
||||||
|
* (no implicit receiver).
|
||||||
|
*
|
||||||
|
* @param src Preprocessed source buffer.
|
||||||
|
* @param len Length of src in bytes.
|
||||||
|
*/
|
||||||
static void ns_aliases_scan(const char *src, size_t len) {
|
static void ns_aliases_scan(const char *src, size_t len) {
|
||||||
const char *marker = "// __ns_alias__: ";
|
const char *marker = "// __ns_alias__: ";
|
||||||
size_t mlen = strlen(marker);
|
size_t mlen = strlen(marker);
|
||||||
|
|
@ -184,6 +242,12 @@ static void ns_aliases_scan(const char *src, size_t len) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check whether an identifier is a registered namespace alias.
|
||||||
|
*
|
||||||
|
* @param name Identifier to test.
|
||||||
|
* @return 1 if name is a known alias, 0 otherwise.
|
||||||
|
*/
|
||||||
static int is_ns_alias(const char *name) {
|
static int is_ns_alias(const char *name) {
|
||||||
if (!name) return 0;
|
if (!name) return 0;
|
||||||
for (int i = 0; i < g_ns_alias_count; ++i) {
|
for (int i = 0; i < g_ns_alias_count; ++i) {
|
||||||
|
|
@ -202,6 +266,12 @@ static struct {
|
||||||
int count;
|
int count;
|
||||||
} G = {{0}, {0}, {0}, 0};
|
} G = {{0}, {0}, {0}, 0};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Find a global symbol index by name.
|
||||||
|
*
|
||||||
|
* @param name Symbol name.
|
||||||
|
* @return Index in the global table, or -1 if not found.
|
||||||
|
*/
|
||||||
static int sym_find(const char *name) {
|
static int sym_find(const char *name) {
|
||||||
for (int i = 0; i < G.count; ++i) {
|
for (int i = 0; i < G.count; ++i) {
|
||||||
if (strcmp(G.names[i], name) == 0) return i;
|
if (strcmp(G.names[i], name) == 0) return i;
|
||||||
|
|
@ -209,6 +279,16 @@ static int sym_find(const char *name) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get or create a global symbol index for a name.
|
||||||
|
*
|
||||||
|
* Ensures the symbol exists in the global table, creating a new entry with
|
||||||
|
* default metadata when absent.
|
||||||
|
*
|
||||||
|
* @param name Symbol name.
|
||||||
|
* @return Index of the symbol (>=0). Returns 0 after reporting an error if
|
||||||
|
* the table is full.
|
||||||
|
*/
|
||||||
static int sym_index(const char *name) {
|
static int sym_index(const char *name) {
|
||||||
int existing = sym_find(name);
|
int existing = sym_find(name);
|
||||||
if (existing >= 0) return existing;
|
if (existing >= 0) return existing;
|
||||||
|
|
@ -235,6 +315,14 @@ static LocalEnv *g_locals = NULL;
|
||||||
static LocalEnv *g_func_env_stack[64];
|
static LocalEnv *g_func_env_stack[64];
|
||||||
static int g_func_env_depth = 0; /* number of valid outer env entries */
|
static int g_func_env_depth = 0; /* number of valid outer env entries */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check whether a local name exists in any outer function environment.
|
||||||
|
*
|
||||||
|
* Used to enforce no-capture semantics for nested functions.
|
||||||
|
*
|
||||||
|
* @param name Local identifier to search for.
|
||||||
|
* @return 1 if present in any outer environment, 0 otherwise.
|
||||||
|
*/
|
||||||
static int name_in_outer_envs(const char *name) {
|
static int name_in_outer_envs(const char *name) {
|
||||||
if (g_func_env_depth <= 0) return 0;
|
if (g_func_env_depth <= 0) return 0;
|
||||||
for (int d = g_func_env_depth - 1; d >= 0; --d) {
|
for (int d = g_func_env_depth - 1; d >= 0; --d) {
|
||||||
|
|
@ -258,6 +346,12 @@ typedef struct LoopCtx {
|
||||||
|
|
||||||
static LoopCtx *g_loop_ctx = NULL;
|
static LoopCtx *g_loop_ctx = NULL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Find the index of a local variable in the current function.
|
||||||
|
*
|
||||||
|
* @param name Local identifier to look up.
|
||||||
|
* @return Zero-based local index, or -1 if not found or outside a function.
|
||||||
|
*/
|
||||||
static int local_find(const char *name) {
|
static int local_find(const char *name) {
|
||||||
if (!g_locals) return -1;
|
if (!g_locals) return -1;
|
||||||
for (int i = 0; i < g_locals->count; ++i) {
|
for (int i = 0; i < g_locals->count; ++i) {
|
||||||
|
|
@ -266,6 +360,12 @@ static int local_find(const char *name) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add a new local variable to the current function environment.
|
||||||
|
*
|
||||||
|
* @param name Local identifier to define.
|
||||||
|
* @return The assigned local index, or -1 if no function env exists or limit exceeded.
|
||||||
|
*/
|
||||||
static int local_add(const char *name) {
|
static int local_add(const char *name) {
|
||||||
if (!g_locals) return -1;
|
if (!g_locals) return -1;
|
||||||
if (g_locals->count >= MAX_FRAME_LOCALS) {
|
if (g_locals->count >= MAX_FRAME_LOCALS) {
|
||||||
|
|
@ -281,6 +381,18 @@ static int local_add(const char *name) {
|
||||||
static int emit_expression(Bytecode *bc, const char *src, size_t len, size_t *pos);
|
static int emit_expression(Bytecode *bc, const char *src, size_t len, size_t *pos);
|
||||||
|
|
||||||
/* primary: (expr) | string | number | true/false | identifier */
|
/* primary: (expr) | string | number | true/false | identifier */
|
||||||
|
/**
|
||||||
|
* @brief Parse and emit bytecode for primary expressions.
|
||||||
|
*
|
||||||
|
* Handles literals, identifiers, parenthesized expressions, function literals,
|
||||||
|
* array/map literals, indexing, calls, member access, and related constructs.
|
||||||
|
*
|
||||||
|
* @param bc Target bytecode under construction.
|
||||||
|
* @param src Source buffer.
|
||||||
|
* @param len Source length in bytes.
|
||||||
|
* @param pos In/out byte position pointer; advanced past the parsed primary.
|
||||||
|
* @return 1 on success, 0 on parse error.
|
||||||
|
*/
|
||||||
static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
||||||
skip_spaces(src, len, pos);
|
skip_spaces(src, len, pos);
|
||||||
|
|
||||||
|
|
@ -4301,7 +4413,18 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* unary: '!' unary | '-' unary | primary */
|
/**
|
||||||
|
* @brief Parse and emit unary expressions.
|
||||||
|
*
|
||||||
|
* Supports logical not (!) and unary minus (-) with right associativity,
|
||||||
|
* falling back to primary expressions.
|
||||||
|
*
|
||||||
|
* @param bc Target bytecode.
|
||||||
|
* @param src Source buffer.
|
||||||
|
* @param len Source length.
|
||||||
|
* @param pos In/out byte position pointer.
|
||||||
|
* @return 1 on success, 0 on error.
|
||||||
|
*/
|
||||||
static int emit_unary(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
static int emit_unary(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
||||||
skip_spaces(src, len, pos);
|
skip_spaces(src, len, pos);
|
||||||
if (*pos < len && src[*pos] == '!') {
|
if (*pos < len && src[*pos] == '!') {
|
||||||
|
|
@ -4328,7 +4451,17 @@ static int emit_unary(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
||||||
return emit_primary(bc, src, len, pos);
|
return emit_primary(bc, src, len, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* multiplicative: unary (('*' | '/' | '%') unary)* */
|
/**
|
||||||
|
* @brief Parse and emit multiplicative expressions.
|
||||||
|
*
|
||||||
|
* Grammar: unary (('*' | '/' | '%') unary)*
|
||||||
|
*
|
||||||
|
* @param bc Target bytecode.
|
||||||
|
* @param src Source buffer.
|
||||||
|
* @param len Source length.
|
||||||
|
* @param pos In/out byte position pointer.
|
||||||
|
* @return 1 on success, 0 on error.
|
||||||
|
*/
|
||||||
static int emit_multiplicative(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
static int emit_multiplicative(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
||||||
if (!emit_unary(bc, src, len, pos)) return 0;
|
if (!emit_unary(bc, src, len, pos)) return 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
|
@ -4381,7 +4514,17 @@ static int emit_multiplicative(Bytecode *bc, const char *src, size_t len, size_t
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* additive: multiplicative (('+' | '-') multiplicative)* */
|
/**
|
||||||
|
* @brief Parse and emit additive expressions.
|
||||||
|
*
|
||||||
|
* Grammar: multiplicative (('+' | '-') multiplicative)*
|
||||||
|
*
|
||||||
|
* @param bc Target bytecode.
|
||||||
|
* @param src Source buffer.
|
||||||
|
* @param len Source length.
|
||||||
|
* @param pos In/out byte position pointer.
|
||||||
|
* @return 1 on success, 0 on error.
|
||||||
|
*/
|
||||||
static int emit_additive(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
static int emit_additive(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
||||||
if (!emit_multiplicative(bc, src, len, pos)) return 0;
|
if (!emit_multiplicative(bc, src, len, pos)) return 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
|
@ -4409,7 +4552,17 @@ static int emit_additive(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* relational: additive (('<' | '<=' | '>' | '>=') additive)* */
|
/**
|
||||||
|
* @brief Parse and emit relational expressions.
|
||||||
|
*
|
||||||
|
* Grammar: additive (('<' | '<=' | '>' | '>=') additive)*
|
||||||
|
*
|
||||||
|
* @param bc Target bytecode.
|
||||||
|
* @param src Source buffer.
|
||||||
|
* @param len Source length.
|
||||||
|
* @param pos In/out byte position pointer.
|
||||||
|
* @return 1 on success, 0 on error.
|
||||||
|
*/
|
||||||
static int emit_relational(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
static int emit_relational(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
||||||
if (!emit_additive(bc, src, len, pos)) return 0;
|
if (!emit_additive(bc, src, len, pos)) return 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
|
@ -4455,7 +4608,17 @@ static int emit_relational(Bytecode *bc, const char *src, size_t len, size_t *po
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* equality: relational (('==' | '!=') relational)* */
|
/**
|
||||||
|
* @brief Parse and emit equality/inequality expressions.
|
||||||
|
*
|
||||||
|
* Grammar: relational (('==' | '!=') relational)*
|
||||||
|
*
|
||||||
|
* @param bc Target bytecode.
|
||||||
|
* @param src Source buffer.
|
||||||
|
* @param len Source length.
|
||||||
|
* @param pos In/out byte position pointer.
|
||||||
|
* @return 1 on success, 0 on error.
|
||||||
|
*/
|
||||||
static int emit_equality(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
static int emit_equality(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
||||||
if (!emit_relational(bc, src, len, pos)) return 0;
|
if (!emit_relational(bc, src, len, pos)) return 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
|
@ -4483,7 +4646,18 @@ static int emit_equality(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* logical AND with short-circuit: equality ( '&&' equality )* */
|
/**
|
||||||
|
* @brief Parse and emit logical AND (&&) with short-circuiting.
|
||||||
|
*
|
||||||
|
* Evaluates left-to-right, jumping around subsequent operands when a false
|
||||||
|
* operand is encountered.
|
||||||
|
*
|
||||||
|
* @param bc Target bytecode.
|
||||||
|
* @param src Source buffer.
|
||||||
|
* @param len Source length.
|
||||||
|
* @param pos In/out byte position pointer.
|
||||||
|
* @return 1 on success, 0 on error.
|
||||||
|
*/
|
||||||
static int emit_and_expr(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
static int emit_and_expr(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
||||||
int jf_idxs[64];
|
int jf_idxs[64];
|
||||||
int jf_count = 0;
|
int jf_count = 0;
|
||||||
|
|
@ -4543,7 +4717,18 @@ static int emit_and_expr(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* logical OR with short-circuit: and_expr ( '||' and_expr )* */
|
/**
|
||||||
|
* @brief Parse and emit logical OR (||) with short-circuiting.
|
||||||
|
*
|
||||||
|
* Evaluates left-to-right; when a true operand is encountered, remaining
|
||||||
|
* operands are skipped and the expression yields true.
|
||||||
|
*
|
||||||
|
* @param bc Target bytecode.
|
||||||
|
* @param src Source buffer.
|
||||||
|
* @param len Source length.
|
||||||
|
* @param pos In/out byte position pointer.
|
||||||
|
* @return 1 on success, 0 on error.
|
||||||
|
*/
|
||||||
static int emit_or_expr(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
static int emit_or_expr(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
||||||
int true_jumps[64];
|
int true_jumps[64];
|
||||||
int tj_count = 0;
|
int tj_count = 0;
|
||||||
|
|
@ -4604,8 +4789,17 @@ static int emit_or_expr(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* conditional operator (ternary) with right associativity:
|
/**
|
||||||
Parses: logical_or ('?' conditional ':' conditional)? */
|
* @brief Parse and emit the ternary conditional operator.
|
||||||
|
*
|
||||||
|
* Grammar (right-associative): logical_or ('?' conditional ':' conditional)?
|
||||||
|
*
|
||||||
|
* @param bc Target bytecode.
|
||||||
|
* @param src Source buffer.
|
||||||
|
* @param len Source length.
|
||||||
|
* @param pos In/out byte position pointer.
|
||||||
|
* @return 1 on success, 0 on error.
|
||||||
|
*/
|
||||||
static int emit_conditional(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
static int emit_conditional(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
||||||
/* parse condition (logical OR precedence or higher) */
|
/* parse condition (logical OR precedence or higher) */
|
||||||
if (!emit_or_expr(bc, src, len, pos)) return 0;
|
if (!emit_or_expr(bc, src, len, pos)) return 0;
|
||||||
|
|
@ -4654,6 +4848,19 @@ static int emit_conditional(Bytecode *bc, const char *src, size_t len, size_t *p
|
||||||
}
|
}
|
||||||
|
|
||||||
/* top-level expression */
|
/* top-level expression */
|
||||||
|
/**
|
||||||
|
* @brief Parse and emit a full expression using precedence climbing.
|
||||||
|
*
|
||||||
|
* Delegates to emit_conditional which handles the highest-level precedence
|
||||||
|
* (ternary). This serves as the common entry for expression parsing at
|
||||||
|
* various grammar positions.
|
||||||
|
*
|
||||||
|
* @param bc Target bytecode.
|
||||||
|
* @param src Source buffer.
|
||||||
|
* @param len Source length in bytes.
|
||||||
|
* @param pos In/out byte position pointer.
|
||||||
|
* @return 1 on success, 0 on parse error.
|
||||||
|
*/
|
||||||
static int emit_expression(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
static int emit_expression(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
||||||
return emit_conditional(bc, src, len, pos);
|
return emit_conditional(bc, src, len, pos);
|
||||||
}
|
}
|
||||||
|
|
@ -4667,6 +4874,16 @@ static int emit_expression(Bytecode *bc, const char *src, size_t len, size_t *po
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* line/indent utilities */
|
/* line/indent utilities */
|
||||||
|
/**
|
||||||
|
* @brief Advance position to the end of the current line, validating tail.
|
||||||
|
*
|
||||||
|
* Skips spaces and inline/block comments until CR/LF/CRLF or end-of-input.
|
||||||
|
* Reports an error if trailing non-space/comment characters are found.
|
||||||
|
*
|
||||||
|
* @param src Source buffer.
|
||||||
|
* @param len Source length.
|
||||||
|
* @param pos In/out byte position pointer; set to first char of next line.
|
||||||
|
*/
|
||||||
static void skip_to_eol(const char *src, size_t len, size_t *pos) {
|
static void skip_to_eol(const char *src, size_t len, size_t *pos) {
|
||||||
/* Strict mode: only allow trailing spaces and comments until end-of-line. */
|
/* Strict mode: only allow trailing spaces and comments until end-of-line. */
|
||||||
size_t p = *pos;
|
size_t p = *pos;
|
||||||
|
|
@ -4731,6 +4948,30 @@ static void skip_to_eol(const char *src, size_t len, size_t *pos) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read the start of a logical line and compute indentation.
|
||||||
|
*
|
||||||
|
* Consumes blank lines and comment-only lines. Tabs are forbidden for
|
||||||
|
* indentation; only spaces are allowed, counted in units of 2.
|
||||||
|
*
|
||||||
|
* @param src Source buffer.
|
||||||
|
* @param len Source length.
|
||||||
|
* @param pos In/out byte position pointer; advanced to first non-space.
|
||||||
|
* @param out_indent Receives the number of leading spaces on the line.
|
||||||
|
* @return 1 if a non-empty logical line was found, 0 if end reached.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @brief Read the start of a logical line and compute indentation.
|
||||||
|
*
|
||||||
|
* Consumes blank lines and comment-only lines. Tabs are forbidden for
|
||||||
|
* indentation; only spaces are allowed, counted in units of 2.
|
||||||
|
*
|
||||||
|
* @param src Source buffer.
|
||||||
|
* @param len Source length.
|
||||||
|
* @param pos In/out byte position pointer; advanced to first non-space.
|
||||||
|
* @param out_indent Receives the number of leading spaces on the line.
|
||||||
|
* @return 1 if a non-empty logical line was found, 0 if end reached.
|
||||||
|
*/
|
||||||
static int read_line_start(const char *src, size_t len, size_t *pos, int *out_indent) {
|
static int read_line_start(const char *src, size_t len, size_t *pos, int *out_indent) {
|
||||||
while (*pos < len) {
|
while (*pos < len) {
|
||||||
size_t p = *pos;
|
size_t p = *pos;
|
||||||
|
|
@ -4813,6 +5054,17 @@ static int read_line_start(const char *src, size_t len, size_t *pos, int *out_in
|
||||||
static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, int current_indent);
|
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 */
|
/* parse and emit a single simple (non-if) statement on the current line */
|
||||||
|
/**
|
||||||
|
* @brief Parse a single statement on the current line and emit bytecode.
|
||||||
|
*
|
||||||
|
* Handles assignments, function calls, control flow starters, print/debug
|
||||||
|
* statements and other simple constructs that fit on one logical line.
|
||||||
|
*
|
||||||
|
* @param bc Target bytecode.
|
||||||
|
* @param src Source buffer.
|
||||||
|
* @param len Source length.
|
||||||
|
* @param pos In/out byte position pointer at start of the statement line.
|
||||||
|
*/
|
||||||
static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, size_t *pos) {
|
||||||
size_t local_pos = *pos;
|
size_t local_pos = *pos;
|
||||||
char *name = NULL;
|
char *name = NULL;
|
||||||
|
|
@ -5665,6 +5917,19 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
|
||||||
}
|
}
|
||||||
|
|
||||||
/* parse a block with lines at indentation >= current_indent; stop at dedent */
|
/* parse a block with lines at indentation >= current_indent; stop at dedent */
|
||||||
|
/**
|
||||||
|
* @brief Parse a block of statements at a given indentation level.
|
||||||
|
*
|
||||||
|
* Continues parsing lines while their indentation is >= current_indent. On
|
||||||
|
* dedent, returns control to the caller so upstream constructs (e.g., if/while)
|
||||||
|
* can close. Inserts OP_LINE markers to improve runtime diagnostics.
|
||||||
|
*
|
||||||
|
* @param bc Target bytecode.
|
||||||
|
* @param src Source buffer.
|
||||||
|
* @param len Source length.
|
||||||
|
* @param pos In/out byte position pointer.
|
||||||
|
* @param current_indent Indentation level (spaces) of the enclosing block.
|
||||||
|
*/
|
||||||
static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, int current_indent) {
|
static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, int current_indent) {
|
||||||
while (*pos < len) {
|
while (*pos < len) {
|
||||||
if (g_has_error) return;
|
if (g_has_error) return;
|
||||||
|
|
@ -7219,6 +7484,17 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compile a full source buffer into bytecode.
|
||||||
|
*
|
||||||
|
* Preprocesses namespace aliases, handles shebangs and top-level constructs,
|
||||||
|
* parses the indentation-aware block and appends a final HALT instruction.
|
||||||
|
*
|
||||||
|
* @param src Source buffer to compile (preprocessed when used via file API).
|
||||||
|
* @param len Length of the source buffer in bytes.
|
||||||
|
* @return Newly allocated Bytecode on success; never NULL here (errors are
|
||||||
|
* recorded globally and may lead to incomplete bytecode).
|
||||||
|
*/
|
||||||
static Bytecode *compile_minimal(const char *src, size_t len) {
|
static Bytecode *compile_minimal(const char *src, size_t len) {
|
||||||
Bytecode *bc = bytecode_new();
|
Bytecode *bc = bytecode_new();
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
|
|
@ -7240,6 +7516,16 @@ static Bytecode *compile_minimal(const char *src, size_t len) {
|
||||||
return bc;
|
return bc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parse a .fun source file and return compiled bytecode.
|
||||||
|
*
|
||||||
|
* Reads the file, preprocesses includes (tracking original file paths),
|
||||||
|
* resets the global error state, and compiles to Bytecode while attaching
|
||||||
|
* source metadata (name, source_file).
|
||||||
|
*
|
||||||
|
* @param path Filesystem path to the source file.
|
||||||
|
* @return Bytecode pointer on success; NULL on I/O or parse error.
|
||||||
|
*/
|
||||||
Bytecode *parse_file_to_bytecode(const char *path) {
|
Bytecode *parse_file_to_bytecode(const char *path) {
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
char *src = read_file_all(path, &len);
|
char *src = read_file_all(path, &len);
|
||||||
|
|
@ -7416,6 +7702,15 @@ Bytecode *parse_file_to_bytecode(const char *path) {
|
||||||
return bc;
|
return bc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parse a source string and return compiled bytecode.
|
||||||
|
*
|
||||||
|
* Suitable for REPL or tests. Performs include preprocessing with no base
|
||||||
|
* path, compiles, and attaches generic source metadata.
|
||||||
|
*
|
||||||
|
* @param source NUL-terminated source code string.
|
||||||
|
* @return Bytecode pointer on success; NULL on parse error.
|
||||||
|
*/
|
||||||
Bytecode *parse_string_to_bytecode(const char *source) {
|
Bytecode *parse_string_to_bytecode(const char *source) {
|
||||||
if (!source) {
|
if (!source) {
|
||||||
fprintf(stderr, "Error: null source provided\n");
|
fprintf(stderr, "Error: null source provided\n");
|
||||||
|
|
@ -7459,6 +7754,19 @@ Bytecode *parse_string_to_bytecode(const char *source) {
|
||||||
return bc;
|
return bc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retrieve the last parser/compiler error information, if any.
|
||||||
|
*
|
||||||
|
* Copies the error message into msgBuf (truncated to msgCap-1), and returns
|
||||||
|
* the one-based line and column where available. If no error is pending,
|
||||||
|
* returns 0 and leaves outputs unchanged.
|
||||||
|
*
|
||||||
|
* @param msgBuf Destination buffer for the error message (may be NULL).
|
||||||
|
* @param msgCap Capacity of msgBuf in bytes.
|
||||||
|
* @param outLine Optional out param for one-based line number.
|
||||||
|
* @param outCol Optional out param for one-based column number.
|
||||||
|
* @return 1 if an error was available and copied, 0 otherwise.
|
||||||
|
*/
|
||||||
int parser_last_error(char *msgBuf, unsigned long msgCap, int *outLine, int *outCol) {
|
int parser_last_error(char *msgBuf, unsigned long msgCap, int *outLine, int *outCol) {
|
||||||
if (!g_has_error) return 0;
|
if (!g_has_error) return 0;
|
||||||
if (msgBuf && msgCap > 0) {
|
if (msgBuf && msgCap > 0) {
|
||||||
|
|
|
||||||
39
src/parser.h
39
src/parser.h
|
|
@ -8,14 +8,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a .fun source file and compile it into entry bytecode.
|
* @file parser.h
|
||||||
* Minimal support:
|
* @brief Public API for parsing Fun source into bytecode.
|
||||||
* - Optional shebang on the first line.
|
|
||||||
* - Optional single function wrapper: fun <ident>() { ... }
|
|
||||||
* - print("...") statements inside function or at top-level.
|
|
||||||
* Produces bytecode that executes all dApache-2.0overed print statements and halts.
|
|
||||||
*
|
|
||||||
* Returns: newly allocated Bytecode*, or NULL on error.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef FUN_PARSER_H
|
#ifndef FUN_PARSER_H
|
||||||
|
|
@ -23,12 +17,37 @@
|
||||||
|
|
||||||
#include "bytecode.h"
|
#include "bytecode.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parse a .fun source file and compile it into a bytecode chunk.
|
||||||
|
*
|
||||||
|
* The parser accepts an optional shebang on the first line and supports a
|
||||||
|
* minimal top-level or single-function program model. The returned bytecode
|
||||||
|
* will execute discovered statements (e.g., print) and then halt.
|
||||||
|
*
|
||||||
|
* @param path Filesystem path to the .fun source file. Must be a
|
||||||
|
* null-terminated UTF-8 string.
|
||||||
|
* @return Newly allocated Bytecode instance on success, or NULL on error.
|
||||||
|
*/
|
||||||
Bytecode *parse_file_to_bytecode(const char *path);
|
Bytecode *parse_file_to_bytecode(const char *path);
|
||||||
|
|
||||||
/* Parse source provided as a single string buffer (for REPL, tests, etc.). */
|
/**
|
||||||
|
* @brief Parse source from a provided string buffer (REPL/tests helper).
|
||||||
|
*
|
||||||
|
* @param source Null-terminated Fun program text.
|
||||||
|
* @return Newly allocated Bytecode instance on success, or NULL on error.
|
||||||
|
*/
|
||||||
Bytecode *parse_string_to_bytecode(const char *source);
|
Bytecode *parse_string_to_bytecode(const char *source);
|
||||||
|
|
||||||
/* Query the last parser error (1 if present, 0 if none). */
|
/**
|
||||||
|
* @brief Retrieve information about the last parser error, if any.
|
||||||
|
*
|
||||||
|
* @param msgBuf Output buffer to receive a human-readable error message.
|
||||||
|
* @param msgCap Capacity of msgBuf in bytes.
|
||||||
|
* @param outLine Optional output: 1-based line number where the error occurred.
|
||||||
|
* @param outCol Optional output: 1-based column number where the error occurred.
|
||||||
|
* @return 1 if an error was present and fields were populated, 0 if there is
|
||||||
|
* no recorded error.
|
||||||
|
*/
|
||||||
int parser_last_error(char *msgBuf, unsigned long msgCap, int *outLine, int *outCol);
|
int parser_last_error(char *msgBuf, unsigned long msgCap, int *outLine, int *outCol);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,29 @@
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file parser_utils.c
|
||||||
|
* @brief Low-level parsing helpers and include preprocessor for the Fun parser.
|
||||||
|
*
|
||||||
|
* This module provides character scanners, token helpers, simple string/number
|
||||||
|
* literal readers, a lightweight include preprocessor that can expand
|
||||||
|
* `include` directives, and mapping utilities to translate expanded line
|
||||||
|
* numbers back to original files for diagnostics.
|
||||||
|
*/
|
||||||
#include "parser.h"
|
#include "parser.h"
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read entire file into a newly allocated buffer.
|
||||||
|
*
|
||||||
|
* @param path Path to file.
|
||||||
|
* @param out_len Optional; receives number of bytes read (not including NUL).
|
||||||
|
* @return Newly allocated NUL-terminated buffer on success, or NULL on error.
|
||||||
|
* Caller must free() the returned buffer.
|
||||||
|
*/
|
||||||
static char *read_file_all(const char *path, size_t *out_len) {
|
static char *read_file_all(const char *path, size_t *out_len) {
|
||||||
FILE *f = fopen(path, "rb");
|
FILE *f = fopen(path, "rb");
|
||||||
if (!f) return NULL;
|
if (!f) return NULL;
|
||||||
|
|
@ -38,6 +55,12 @@ static char *read_file_all(const char *path, size_t *out_len) {
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Skip spaces, tabs, carriage returns and newlines.
|
||||||
|
* @param src Source buffer.
|
||||||
|
* @param len Buffer length.
|
||||||
|
* @param pos In/out byte offset; advanced past whitespace.
|
||||||
|
*/
|
||||||
static void skip_ws(const char *src, size_t len, size_t *pos) {
|
static void skip_ws(const char *src, size_t len, size_t *pos) {
|
||||||
while (*pos < len) {
|
while (*pos < len) {
|
||||||
char c = src[*pos];
|
char c = src[*pos];
|
||||||
|
|
@ -49,12 +72,19 @@ static void skip_ws(const char *src, size_t len, size_t *pos) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Advance @p pos to the next line, consuming the trailing '\n' if present.
|
||||||
|
*/
|
||||||
static void skip_line(const char *src, size_t len, size_t *pos) {
|
static void skip_line(const char *src, size_t len, size_t *pos) {
|
||||||
while (*pos < len && src[*pos] != '\n')
|
while (*pos < len && src[*pos] != '\n')
|
||||||
(*pos)++;
|
(*pos)++;
|
||||||
if (*pos < len && src[*pos] == '\n') (*pos)++;
|
if (*pos < len && src[*pos] == '\n') (*pos)++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Skip whitespace, then line (//) and block (/* ... */) comments.
|
||||||
|
* Continues until the next non-comment, non-whitespace character.
|
||||||
|
*/
|
||||||
static void skip_comments(const char *src, size_t len, size_t *pos) {
|
static void skip_comments(const char *src, size_t len, size_t *pos) {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
skip_ws(src, len, pos);
|
skip_ws(src, len, pos);
|
||||||
|
|
@ -74,18 +104,29 @@ static void skip_comments(const char *src, size_t len, size_t *pos) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if src starting at pos begins with kw and fits in len.
|
||||||
|
* @return 1 if matches, 0 otherwise.
|
||||||
|
*/
|
||||||
static int starts_with(const char *src, size_t len, size_t pos, const char *kw) {
|
static int starts_with(const char *src, size_t len, size_t pos, const char *kw) {
|
||||||
size_t klen = strlen(kw);
|
size_t klen = strlen(kw);
|
||||||
if (pos + klen > len) return 0;
|
if (pos + klen > len) return 0;
|
||||||
return strncmp(src + pos, kw, klen) == 0;
|
return strncmp(src + pos, kw, klen) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Skip a top-of-file shebang line that starts with "#!" if present.
|
||||||
|
*/
|
||||||
static void skip_shebang_if_present(const char *src, size_t len, size_t *pos) {
|
static void skip_shebang_if_present(const char *src, size_t len, size_t *pos) {
|
||||||
if (*pos == 0 && starts_with(src, len, *pos, "#!")) {
|
if (*pos == 0 && starts_with(src, len, *pos, "#!")) {
|
||||||
skip_line(src, len, pos);
|
skip_line(src, len, pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief If an identifier starts at pos, advance pos to its end.
|
||||||
|
* Recognizes [A-Za-z_][A-Za-z0-9_]*.
|
||||||
|
*/
|
||||||
static void skip_identifier(const char *src, size_t len, size_t *pos) {
|
static void skip_identifier(const char *src, size_t len, size_t *pos) {
|
||||||
size_t p = *pos;
|
size_t p = *pos;
|
||||||
if (p < len && (isalpha((unsigned char)src[p]) || src[p] == '_')) {
|
if (p < len && (isalpha((unsigned char)src[p]) || src[p] == '_')) {
|
||||||
|
|
@ -96,6 +137,10 @@ static void skip_identifier(const char *src, size_t len, size_t *pos) {
|
||||||
*pos = p;
|
*pos = p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Consume expected character after skipping whitespace.
|
||||||
|
* @return 1 if consumed; 0 if not present.
|
||||||
|
*/
|
||||||
static int consume_char(const char *src, size_t len, size_t *pos, char expected) {
|
static int consume_char(const char *src, size_t len, size_t *pos, char expected) {
|
||||||
skip_ws(src, len, pos);
|
skip_ws(src, len, pos);
|
||||||
if (*pos < len && src[*pos] == expected) {
|
if (*pos < len && src[*pos] == expected) {
|
||||||
|
|
@ -105,6 +150,13 @@ static int consume_char(const char *src, size_t len, size_t *pos, char expected)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parse a single-quoted or double-quoted string literal.
|
||||||
|
*
|
||||||
|
* Supports common C-style escapes: \n, \r, \t, \\, \", \\' . Returns a newly
|
||||||
|
* allocated buffer on success and advances pos. On failure returns NULL and
|
||||||
|
* does not modify the source. Caller must free() returned buffer.
|
||||||
|
*/
|
||||||
static char *parse_string_literal_any_quote(const char *src, size_t len, size_t *pos) {
|
static char *parse_string_literal_any_quote(const char *src, size_t len, size_t *pos) {
|
||||||
skip_ws(src, len, pos);
|
skip_ws(src, len, pos);
|
||||||
if (*pos >= len) return NULL;
|
if (*pos >= len) return NULL;
|
||||||
|
|
@ -174,6 +226,9 @@ static char *parse_string_literal_any_quote(const char *src, size_t len, size_t
|
||||||
|
|
||||||
/* === Helpers for identifiers, numbers, booleans, and globals === */
|
/* === Helpers for identifiers, numbers, booleans, and globals === */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Skip only spaces, tabs and carriage returns (not newlines).
|
||||||
|
*/
|
||||||
static void skip_spaces(const char *src, size_t len, size_t *pos) {
|
static void skip_spaces(const char *src, size_t len, size_t *pos) {
|
||||||
while (*pos < len) {
|
while (*pos < len) {
|
||||||
char c = src[*pos];
|
char c = src[*pos];
|
||||||
|
|
@ -185,6 +240,11 @@ static void skip_spaces(const char *src, size_t len, size_t *pos) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read an identifier starting at pos and allocate its name.
|
||||||
|
* @param out_name Set to malloc'd NUL-terminated name on success.
|
||||||
|
* @return 1 on success (pos advanced), 0 otherwise.
|
||||||
|
*/
|
||||||
static int read_identifier_into(const char *src, size_t len, size_t *pos, char **out_name) {
|
static int read_identifier_into(const char *src, size_t len, size_t *pos, char **out_name) {
|
||||||
size_t p = *pos;
|
size_t p = *pos;
|
||||||
if (p < len && (isalpha((unsigned char)src[p]) || src[p] == '_')) {
|
if (p < len && (isalpha((unsigned char)src[p]) || src[p] == '_')) {
|
||||||
|
|
@ -204,6 +264,11 @@ static int read_identifier_into(const char *src, size_t len, size_t *pos, char *
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parse an integer literal (decimal or 0x-hex) with optional sign.
|
||||||
|
* @param ok Set to 1 on success, 0 on failure.
|
||||||
|
* @return Parsed value (two's complement cast for negative inputs).
|
||||||
|
*/
|
||||||
static uint64_t parse_int_literal_value(const char *src, size_t len, size_t *pos, int *ok) {
|
static uint64_t parse_int_literal_value(const char *src, size_t len, size_t *pos, int *ok) {
|
||||||
size_t p = *pos;
|
size_t p = *pos;
|
||||||
skip_spaces(src, len, &p);
|
skip_spaces(src, len, &p);
|
||||||
|
|
@ -262,17 +327,26 @@ static uint64_t parse_int_literal_value(const char *src, size_t len, size_t *pos
|
||||||
* Directives are recognized only when not inside strings or block comments.
|
* Directives are recognized only when not inside strings or block comments.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Thin wrapper over realloc used by local buffers.
|
||||||
|
*/
|
||||||
static void *xrealloc(void *ptr, size_t newcap) {
|
static void *xrealloc(void *ptr, size_t newcap) {
|
||||||
void *np = realloc(ptr, newcap);
|
void *np = realloc(ptr, newcap);
|
||||||
return np;
|
return np;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Simple growable string buffer.
|
||||||
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char *buf;
|
char *buf;
|
||||||
size_t len;
|
size_t len;
|
||||||
size_t cap;
|
size_t cap;
|
||||||
} StrBuf;
|
} StrBuf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize a StrBuf with a small starting capacity.
|
||||||
|
*/
|
||||||
static void sb_init(StrBuf *sb) {
|
static void sb_init(StrBuf *sb) {
|
||||||
sb->buf = (char *)malloc(256);
|
sb->buf = (char *)malloc(256);
|
||||||
sb->cap = sb->buf ? 256 : 0;
|
sb->cap = sb->buf ? 256 : 0;
|
||||||
|
|
@ -280,6 +354,9 @@ static void sb_init(StrBuf *sb) {
|
||||||
if (sb->buf) sb->buf[0] = '\0';
|
if (sb->buf) sb->buf[0] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Ensure buffer capacity for at least need bytes (including terminator).
|
||||||
|
*/
|
||||||
static void sb_reserve(StrBuf *sb, size_t need) {
|
static void sb_reserve(StrBuf *sb, size_t need) {
|
||||||
if (need <= sb->cap) return;
|
if (need <= sb->cap) return;
|
||||||
size_t nc = sb->cap ? sb->cap : 256;
|
size_t nc = sb->cap ? sb->cap : 256;
|
||||||
|
|
@ -291,6 +368,9 @@ static void sb_reserve(StrBuf *sb, size_t need) {
|
||||||
sb->cap = nc;
|
sb->cap = nc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Append n bytes from s to the buffer.
|
||||||
|
*/
|
||||||
static void sb_append_n(StrBuf *sb, const char *s, size_t n) {
|
static void sb_append_n(StrBuf *sb, const char *s, size_t n) {
|
||||||
if (n == 0) return;
|
if (n == 0) return;
|
||||||
sb_reserve(sb, sb->len + n + 1);
|
sb_reserve(sb, sb->len + n + 1);
|
||||||
|
|
@ -300,10 +380,16 @@ static void sb_append_n(StrBuf *sb, const char *s, size_t n) {
|
||||||
sb->buf[sb->len] = '\0';
|
sb->buf[sb->len] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Append a NUL-terminated string to the buffer.
|
||||||
|
*/
|
||||||
static void sb_append(StrBuf *sb, const char *s) {
|
static void sb_append(StrBuf *sb, const char *s) {
|
||||||
sb_append_n(sb, s, strlen(s));
|
sb_append_n(sb, s, strlen(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Append a single character to the buffer.
|
||||||
|
*/
|
||||||
static void sb_append_ch(StrBuf *sb, char c) {
|
static void sb_append_ch(StrBuf *sb, char c) {
|
||||||
sb_reserve(sb, sb->len + 2);
|
sb_reserve(sb, sb->len + 2);
|
||||||
if (!sb->buf) return;
|
if (!sb->buf) return;
|
||||||
|
|
@ -312,18 +398,27 @@ static void sb_append_ch(StrBuf *sb, char c) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- Export collection for include-as namespaces ---- */
|
/* ---- Export collection for include-as namespaces ---- */
|
||||||
|
/**
|
||||||
|
* @brief List of exported symbol names discovered at top level.
|
||||||
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char **names;
|
char **names;
|
||||||
int count;
|
int count;
|
||||||
int cap;
|
int cap;
|
||||||
} NameList;
|
} NameList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize an empty NameList.
|
||||||
|
*/
|
||||||
static void nl_init(NameList *nl) {
|
static void nl_init(NameList *nl) {
|
||||||
nl->names = NULL;
|
nl->names = NULL;
|
||||||
nl->count = 0;
|
nl->count = 0;
|
||||||
nl->cap = 0;
|
nl->cap = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add a copy of name to the list (ignores NULL/empty).
|
||||||
|
*/
|
||||||
static void nl_add(NameList *nl, const char *name) {
|
static void nl_add(NameList *nl, const char *name) {
|
||||||
if (!name || !name[0]) return;
|
if (!name || !name[0]) return;
|
||||||
if (nl->count >= nl->cap) {
|
if (nl->count >= nl->cap) {
|
||||||
|
|
@ -336,6 +431,9 @@ static void nl_add(NameList *nl, const char *name) {
|
||||||
nl->names[nl->count++] = strdup(name);
|
nl->names[nl->count++] = strdup(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Free all strings and internal storage in the list.
|
||||||
|
*/
|
||||||
static void nl_free(NameList *nl) {
|
static void nl_free(NameList *nl) {
|
||||||
if (!nl) return;
|
if (!nl) return;
|
||||||
for (int i = 0; i < nl->count; ++i)
|
for (int i = 0; i < nl->count; ++i)
|
||||||
|
|
@ -347,6 +445,10 @@ static void nl_free(NameList *nl) {
|
||||||
|
|
||||||
/* Collect top-level (indent=0) exported symbols: function and class names.
|
/* Collect top-level (indent=0) exported symbols: function and class names.
|
||||||
Ignores lines inside comments/strings and ignores nested indent. */
|
Ignores lines inside comments/strings and ignores nested indent. */
|
||||||
|
/**
|
||||||
|
* @brief Collect top-level exported symbols (fun/class) from source text.
|
||||||
|
* Ignores strings and comments. Only lines with zero indentation count.
|
||||||
|
*/
|
||||||
static void collect_exports_top_level(const char *text, NameList *out) {
|
static void collect_exports_top_level(const char *text, NameList *out) {
|
||||||
if (!text || !out) return;
|
if (!text || !out) return;
|
||||||
size_t len = strlen(text);
|
size_t len = strlen(text);
|
||||||
|
|
@ -499,6 +601,20 @@ static void collect_exports_top_level(const char *text, NameList *out) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Expand include directives in Fun source.
|
||||||
|
*
|
||||||
|
* Recognizes both `#include "..."` and `include "..."`/`include <...>` at
|
||||||
|
* the beginning of a line (after spaces/tabs). Angle-bracket includes search
|
||||||
|
* in FUN_LIB_DIR, then DEFAULT_LIB_DIR, then local lib/. Emits span markers of
|
||||||
|
* the form `// __include_begin__: <path> [as alias] @line N` to enable later
|
||||||
|
* mapping back to original files.
|
||||||
|
*
|
||||||
|
* @param src Source code to preprocess.
|
||||||
|
* @param current_path Optional path of the current file for initial marker.
|
||||||
|
* @param depth Recursion depth guard.
|
||||||
|
* @return Newly allocated expanded text or NULL on OOM.
|
||||||
|
*/
|
||||||
static char *preprocess_includes_internal(const char *src, const char *current_path, int depth) {
|
static char *preprocess_includes_internal(const char *src, const char *current_path, int depth) {
|
||||||
if (!src) return NULL;
|
if (!src) return NULL;
|
||||||
if (depth > 64) {
|
if (depth > 64) {
|
||||||
|
|
@ -843,11 +959,17 @@ static char *preprocess_includes_internal(const char *src, const char *current_p
|
||||||
return out.buf;
|
return out.buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Public wrapper to preprocess includes without a current path.
|
||||||
|
*/
|
||||||
char *preprocess_includes(const char *src) {
|
char *preprocess_includes(const char *src) {
|
||||||
return preprocess_includes_internal(src, NULL, 0);
|
return preprocess_includes_internal(src, NULL, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Variant with known current file path to allow precise resume markers. */
|
/* Variant with known current file path to allow precise resume markers. */
|
||||||
|
/**
|
||||||
|
* @brief Preprocess includes with a known file path to improve span markers.
|
||||||
|
*/
|
||||||
char *preprocess_includes_with_path(const char *src, const char *current_path) {
|
char *preprocess_includes_with_path(const char *src, const char *current_path) {
|
||||||
return preprocess_includes_internal(src, current_path, 0);
|
return preprocess_includes_internal(src, current_path, 0);
|
||||||
}
|
}
|
||||||
|
|
@ -858,6 +980,19 @@ char *preprocess_includes_with_path(const char *src, const char *current_path) {
|
||||||
* `// __include_begin__: <path>[ as <alias>]` markers injected by the
|
* `// __include_begin__: <path>[ as <alias>]` markers injected by the
|
||||||
* preprocessor. Returns 1 on success and fills out_path/out_line; 0 otherwise.
|
* preprocessor. Returns 1 on success and fills out_path/out_line; 0 otherwise.
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* @brief Map a line number in expanded source back to original include path/line.
|
||||||
|
*
|
||||||
|
* Scans the expanded text for the nearest preceding `__include_begin__` marker
|
||||||
|
* and computes the corresponding inner line number.
|
||||||
|
*
|
||||||
|
* @param path Path to the original top-level file that was expanded.
|
||||||
|
* @param line 1-based line number in the expanded text.
|
||||||
|
* @param out_path Output buffer for the resolved file path.
|
||||||
|
* @param out_path_cap Capacity of out_path.
|
||||||
|
* @param out_line Receives 1-based line number within resolved file.
|
||||||
|
* @return 1 on success, 0 on failure.
|
||||||
|
*/
|
||||||
int map_expanded_line_to_include_path(const char *path, int line,
|
int map_expanded_line_to_include_path(const char *path, int line,
|
||||||
char *out_path, size_t out_path_cap,
|
char *out_path, size_t out_path_cap,
|
||||||
int *out_line) {
|
int *out_line) {
|
||||||
|
|
@ -968,6 +1103,11 @@ next_scan_back:
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Float literal parser: supports decimal and scientific notation. Returns parsed double and advances pos on success. */
|
/* Float literal parser: supports decimal and scientific notation. Returns parsed double and advances pos on success. */
|
||||||
|
/**
|
||||||
|
* @brief Parse a floating-point literal (supports . and scientific notation).
|
||||||
|
* @param ok Set to 1 on success, 0 otherwise.
|
||||||
|
* @return Parsed double value.
|
||||||
|
*/
|
||||||
static double parse_float_literal_value(const char *src, size_t len, size_t *pos, int *ok) {
|
static double parse_float_literal_value(const char *src, size_t len, size_t *pos, int *ok) {
|
||||||
size_t p = *pos;
|
size_t p = *pos;
|
||||||
skip_spaces(src, len, &p);
|
skip_spaces(src, len, &p);
|
||||||
|
|
|
||||||
128
src/repl.c
128
src/repl.c
|
|
@ -5,13 +5,15 @@
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2025-10-05
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* REPL implementation for the Fun programming language.
|
* @file repl.c
|
||||||
* Built only when FUN_WITH_REPL is defined.
|
* @brief Interactive Read–Eval–Print Loop (REPL) for the Fun language.
|
||||||
|
*
|
||||||
|
* Provides line editing, history, simple completion for :load paths and
|
||||||
|
* stdlib identifiers, multi-line input heuristics, and a command interface
|
||||||
|
* (e.g. :help, :env, :load). Built only when FUN_WITH_REPL is defined.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "repl.h"
|
#include "repl.h"
|
||||||
|
|
@ -49,12 +51,19 @@ static char *rl_hist[RL_HIST_MAX];
|
||||||
static int rl_count = 0;
|
static int rl_count = 0;
|
||||||
|
|
||||||
/* last history entry (or NULL) */
|
/* last history entry (or NULL) */
|
||||||
|
/**
|
||||||
|
* @brief Return the last history line or NULL if history is empty.
|
||||||
|
*/
|
||||||
static const char *rl_hist_last(void) {
|
static const char *rl_hist_last(void) {
|
||||||
if (rl_count <= 0) return NULL;
|
if (rl_count <= 0) return NULL;
|
||||||
return rl_hist[rl_count - 1];
|
return rl_hist[rl_count - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* add one line to in-memory history (without trailing newline), dedup consecutive */
|
/* add one line to in-memory history (without trailing newline), dedup consecutive */
|
||||||
|
/**
|
||||||
|
* @brief Append a non-empty line to in-memory history, de-duplicating
|
||||||
|
* consecutive duplicates. Trailing newlines are stripped.
|
||||||
|
*/
|
||||||
static void rl_hist_add(const char *s) {
|
static void rl_hist_add(const char *s) {
|
||||||
if (!s) return;
|
if (!s) return;
|
||||||
size_t n = strlen(s);
|
size_t n = strlen(s);
|
||||||
|
|
@ -81,6 +90,9 @@ static void rl_hist_add(const char *s) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* preload history from file (one line per entry) */
|
/* preload history from file (one line per entry) */
|
||||||
|
/**
|
||||||
|
* @brief Load history entries from a file (one line per entry).
|
||||||
|
*/
|
||||||
static void rl_hist_load_file(const char *path) {
|
static void rl_hist_load_file(const char *path) {
|
||||||
if (!path) return;
|
if (!path) return;
|
||||||
FILE *f = fopen(path, "r");
|
FILE *f = fopen(path, "r");
|
||||||
|
|
@ -96,12 +108,19 @@ static void rl_hist_load_file(const char *path) {
|
||||||
static struct termios g_orig_tios;
|
static struct termios g_orig_tios;
|
||||||
static int g_raw_enabled = 0;
|
static int g_raw_enabled = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Disable terminal raw mode if previously enabled.
|
||||||
|
*/
|
||||||
static void repl_disable_raw(void) {
|
static void repl_disable_raw(void) {
|
||||||
if (g_raw_enabled) {
|
if (g_raw_enabled) {
|
||||||
tcsetattr(STDIN_FILENO, TCSAFLUSH, &g_orig_tios);
|
tcsetattr(STDIN_FILENO, TCSAFLUSH, &g_orig_tios);
|
||||||
g_raw_enabled = 0;
|
g_raw_enabled = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @brief Enable minimal terminal raw mode for interactive input.
|
||||||
|
* @return 1 on success, 0 otherwise.
|
||||||
|
*/
|
||||||
static int repl_enable_raw(void) {
|
static int repl_enable_raw(void) {
|
||||||
if (!isatty(STDIN_FILENO)) return 0;
|
if (!isatty(STDIN_FILENO)) return 0;
|
||||||
if (g_raw_enabled) return 1;
|
if (g_raw_enabled) return 1;
|
||||||
|
|
@ -116,6 +135,9 @@ static int repl_enable_raw(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* return 1 if path is a directory, else 0 */
|
/* return 1 if path is a directory, else 0 */
|
||||||
|
/**
|
||||||
|
* @brief Return 1 if path refers to a directory, 0 otherwise.
|
||||||
|
*/
|
||||||
static int is_dir_path(const char *path) {
|
static int is_dir_path(const char *path) {
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (stat(path, &st) != 0) return 0;
|
if (stat(path, &st) != 0) return 0;
|
||||||
|
|
@ -123,6 +145,9 @@ static int is_dir_path(const char *path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Compute longest common prefix of a set of strings (starting from offset base_len) */
|
/* Compute longest common prefix of a set of strings (starting from offset base_len) */
|
||||||
|
/**
|
||||||
|
* @brief Longest common suffix length among names[i] starting at base_len.
|
||||||
|
*/
|
||||||
static size_t lcp_suffix(const char **names, int count, size_t base_len) {
|
static size_t lcp_suffix(const char **names, int count, size_t base_len) {
|
||||||
if (count <= 0) return base_len;
|
if (count <= 0) return base_len;
|
||||||
size_t lcp = (size_t)-1;
|
size_t lcp = (size_t)-1;
|
||||||
|
|
@ -151,6 +176,9 @@ static size_t lcp_suffix(const char **names, int count, size_t base_len) {
|
||||||
/* Word-jump helpers (used by Ctrl+Left/Right in the REPL editor).
|
/* Word-jump helpers (used by Ctrl+Left/Right in the REPL editor).
|
||||||
* Words are runs of non-space characters; separators are spaces.
|
* Words are runs of non-space characters; separators are spaces.
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* @brief Move cursor left by one word.
|
||||||
|
*/
|
||||||
static void rl_word_left(const char *out, size_t len, size_t *pos) {
|
static void rl_word_left(const char *out, size_t len, size_t *pos) {
|
||||||
(void)len;
|
(void)len;
|
||||||
if (!out || !pos) return;
|
if (!out || !pos) return;
|
||||||
|
|
@ -160,6 +188,9 @@ static void rl_word_left(const char *out, size_t len, size_t *pos) {
|
||||||
while (*pos > 0 && out[*pos - 1] != ' ')
|
while (*pos > 0 && out[*pos - 1] != ' ')
|
||||||
(*pos)--;
|
(*pos)--;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @brief Move cursor right by one word.
|
||||||
|
*/
|
||||||
static void rl_word_right(const char *out, size_t len, size_t *pos) {
|
static void rl_word_right(const char *out, size_t len, size_t *pos) {
|
||||||
if (!out || !pos) return;
|
if (!out || !pos) return;
|
||||||
if (*pos >= len) return;
|
if (*pos >= len) return;
|
||||||
|
|
@ -170,6 +201,10 @@ static void rl_word_right(const char *out, size_t len, size_t *pos) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Expand file path for path-taking REPL commands (e.g., :load, :run) in-place; returns 1 if buffer changed (redraw) */
|
/* Expand file path for path-taking REPL commands (e.g., :load, :run) in-place; returns 1 if buffer changed (redraw) */
|
||||||
|
/**
|
||||||
|
* @brief Complete a :load file path in-place within the buffer.
|
||||||
|
* @return 1 if buffer changed, 2 if a menu was printed, 0 otherwise.
|
||||||
|
*/
|
||||||
static int complete_load_path(char *buf, size_t *len_io) {
|
static int complete_load_path(char *buf, size_t *len_io) {
|
||||||
size_t len = *len_io;
|
size_t len = *len_io;
|
||||||
if (len < 3) return 0; /* minimally ":x" */
|
if (len < 3) return 0; /* minimally ":x" */
|
||||||
|
|
@ -322,13 +357,22 @@ static char **g_std_syms = NULL;
|
||||||
static int g_std_syms_count = 0;
|
static int g_std_syms_count = 0;
|
||||||
static int g_std_syms_cap = 0;
|
static int g_std_syms_cap = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return 1 if c can start an identifier.
|
||||||
|
*/
|
||||||
static int is_ident_start(int c) {
|
static int is_ident_start(int c) {
|
||||||
return (c == '_' || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
|
return (c == '_' || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @brief Return 1 if c is a valid identifier continuation character.
|
||||||
|
*/
|
||||||
static int is_ident_char(int c) {
|
static int is_ident_char(int c) {
|
||||||
return is_ident_start(c) || (c >= '0' && c <= '9');
|
return is_ident_start(c) || (c >= '0' && c <= '9');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add a stdlib symbol name to the completion table.
|
||||||
|
*/
|
||||||
static void std_syms_add(const char *name) {
|
static void std_syms_add(const char *name) {
|
||||||
if (!name || !*name) return;
|
if (!name || !*name) return;
|
||||||
/* dedupe */
|
/* dedupe */
|
||||||
|
|
@ -349,6 +393,9 @@ static void std_syms_add(const char *name) {
|
||||||
g_std_syms[g_std_syms_count++] = cpy;
|
g_std_syms[g_std_syms_count++] = cpy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Scan a .fun file and collect top-level definitions for completion.
|
||||||
|
*/
|
||||||
static void scan_symbols_from_file(const char *path) {
|
static void scan_symbols_from_file(const char *path) {
|
||||||
FILE *f = fopen(path, "r");
|
FILE *f = fopen(path, "r");
|
||||||
if (!f) return;
|
if (!f) return;
|
||||||
|
|
@ -387,6 +434,9 @@ static void scan_symbols_from_file(const char *path) {
|
||||||
fclose(f);
|
fclose(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Recursively scan a directory tree for .fun files.
|
||||||
|
*/
|
||||||
static void scan_dir_recursive(const char *dir) {
|
static void scan_dir_recursive(const char *dir) {
|
||||||
DIR *dp = opendir(dir);
|
DIR *dp = opendir(dir);
|
||||||
if (!dp) return;
|
if (!dp) return;
|
||||||
|
|
@ -410,11 +460,17 @@ static void scan_dir_recursive(const char *dir) {
|
||||||
closedir(dp);
|
closedir(dp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Populate stdlib completion symbols from a base directory.
|
||||||
|
*/
|
||||||
static void load_stdlib_symbols(const char *libdir) {
|
static void load_stdlib_symbols(const char *libdir) {
|
||||||
if (!libdir || !*libdir) return;
|
if (!libdir || !*libdir) return;
|
||||||
scan_dir_recursive(libdir);
|
scan_dir_recursive(libdir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Free all memory held by stdlib completion symbols.
|
||||||
|
*/
|
||||||
static void free_stdlib_symbols(void) {
|
static void free_stdlib_symbols(void) {
|
||||||
for (int i = 0; i < g_std_syms_count; ++i)
|
for (int i = 0; i < g_std_syms_count; ++i)
|
||||||
free(g_std_syms[i]);
|
free(g_std_syms[i]);
|
||||||
|
|
@ -425,6 +481,10 @@ static void free_stdlib_symbols(void) {
|
||||||
|
|
||||||
/* Complete the trailing identifier in 'buf' using stdlib symbols.
|
/* Complete the trailing identifier in 'buf' using stdlib symbols.
|
||||||
Returns 1 if buffer changed, 2 if a menu was printed, 0 otherwise. */
|
Returns 1 if buffer changed, 2 if a menu was printed, 0 otherwise. */
|
||||||
|
/**
|
||||||
|
* @brief Complete the trailing identifier in buf from stdlib symbols.
|
||||||
|
* @return 1 if buffer changed, 2 if menu printed, 0 otherwise.
|
||||||
|
*/
|
||||||
static int complete_stdlib_ident(char *buf, size_t *len_io, size_t *pos_io, size_t cap) {
|
static int complete_stdlib_ident(char *buf, size_t *len_io, size_t *pos_io, size_t cap) {
|
||||||
size_t len = *len_io;
|
size_t len = *len_io;
|
||||||
size_t pos = *pos_io;
|
size_t pos = *pos_io;
|
||||||
|
|
@ -509,6 +569,13 @@ static int complete_stdlib_ident(char *buf, size_t *len_io, size_t *pos_io, size
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Read one line with prompt, handling backspace, Up/Down history and :load path completion (now with multi-line editing via Ctrl+O) */
|
/* Read one line with prompt, handling backspace, Up/Down history and :load path completion (now with multi-line editing via Ctrl+O) */
|
||||||
|
/**
|
||||||
|
* @brief Read a line with basic line-editing and history support.
|
||||||
|
* @param out Output buffer to receive the line (NUL-terminated).
|
||||||
|
* @param out_cap Capacity of the output buffer.
|
||||||
|
* @param prompt Prompt string to display (may be NULL).
|
||||||
|
* @return 1 on success, 0 on EOF/error.
|
||||||
|
*/
|
||||||
static int read_line_edit(char *out, size_t out_cap, const char *prompt) {
|
static int read_line_edit(char *out, size_t out_cap, const char *prompt) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
if (prompt) fputs(prompt, stdout), fflush(stdout);
|
if (prompt) fputs(prompt, stdout), fflush(stdout);
|
||||||
|
|
@ -791,18 +858,27 @@ static int read_line_edit(char *out, size_t out_cap, const char *prompt) {
|
||||||
/* ---------- Small utilities ---------- */
|
/* ---------- Small utilities ---------- */
|
||||||
|
|
||||||
static int is_blank_line(const char *s) {
|
static int is_blank_line(const char *s) {
|
||||||
|
/**
|
||||||
|
* @brief Return 1 if the line consists only of whitespace.
|
||||||
|
*/
|
||||||
for (const char *p = s; *p; ++p) {
|
for (const char *p = s; *p; ++p) {
|
||||||
if (*p != ' ' && *p != '\t' && *p != '\r' && *p != '\n') return 0;
|
if (*p != ' ' && *p != '\t' && *p != '\r' && *p != '\n') return 0;
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Advance pointer past leading spaces and tabs.
|
||||||
|
*/
|
||||||
static const char *lstrip(const char *s) {
|
static const char *lstrip(const char *s) {
|
||||||
while (*s == ' ' || *s == '\t')
|
while (*s == ' ' || *s == '\t')
|
||||||
s++;
|
s++;
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Heuristic: check if a line ends with an operator that suggests continuation.
|
||||||
|
*/
|
||||||
static int ends_with_opener(const char *line) {
|
static int ends_with_opener(const char *line) {
|
||||||
size_t n = strlen(line);
|
size_t n = strlen(line);
|
||||||
while (n > 0 && (line[n - 1] == ' ' || line[n - 1] == '\t' || line[n - 1] == '\r' || line[n - 1] == '\n'))
|
while (n > 0 && (line[n - 1] == ' ' || line[n - 1] == '\t' || line[n - 1] == '\r' || line[n - 1] == '\n'))
|
||||||
|
|
@ -817,6 +893,9 @@ static int ends_with_opener(const char *line) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Compute how many indentation levels (2 spaces per level) are still open. */
|
/* Compute how many indentation levels (2 spaces per level) are still open. */
|
||||||
|
/**
|
||||||
|
* @brief Compute number of open indentation blocks (2 spaces per level).
|
||||||
|
*/
|
||||||
static int compute_open_indent_blocks(const char *buf) {
|
static int compute_open_indent_blocks(const char *buf) {
|
||||||
int in_block_comment = 0;
|
int in_block_comment = 0;
|
||||||
int open = 0;
|
int open = 0;
|
||||||
|
|
@ -887,6 +966,10 @@ static int compute_open_indent_blocks(const char *buf) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Detect if current buffer looks incomplete. */
|
/* Detect if current buffer looks incomplete. */
|
||||||
|
/**
|
||||||
|
* @brief Heuristic to determine if the current buffer likely needs another line.
|
||||||
|
* Looks for open quotes/escapes or unmatched indentation.
|
||||||
|
*/
|
||||||
static int buffer_looks_incomplete(const char *buf) {
|
static int buffer_looks_incomplete(const char *buf) {
|
||||||
int in_single = 0, in_double = 0, escape = 0;
|
int in_single = 0, in_double = 0, escape = 0;
|
||||||
int in_block_comment = 0, in_line_comment = 0;
|
int in_block_comment = 0, in_line_comment = 0;
|
||||||
|
|
@ -1018,6 +1101,9 @@ static int buffer_looks_incomplete(const char *buf) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Print REPL help with available commands.
|
||||||
|
*/
|
||||||
static void show_repl_help(void) {
|
static void show_repl_help(void) {
|
||||||
printf("Commands:\n");
|
printf("Commands:\n");
|
||||||
printf(" :help | :h Show this help\n");
|
printf(" :help | :h Show this help\n");
|
||||||
|
|
@ -1057,6 +1143,10 @@ static void show_repl_help(void) {
|
||||||
printf(" :finish | :fi Run until the current frame returns\n");
|
printf(" :finish | :fi Run until the current frame returns\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read a file fully into a newly allocated buffer.
|
||||||
|
* @return Buffer on success (caller frees), or NULL on error.
|
||||||
|
*/
|
||||||
static char *read_entire_file(const char *path, size_t *out_len) {
|
static char *read_entire_file(const char *path, size_t *out_len) {
|
||||||
FILE *f = fopen(path, "rb");
|
FILE *f = fopen(path, "rb");
|
||||||
if (!f) return NULL;
|
if (!f) return NULL;
|
||||||
|
|
@ -1082,6 +1172,10 @@ static char *read_entire_file(const char *path, size_t *out_len) {
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write a buffer to a file path, replacing existing contents.
|
||||||
|
* @return 1 on success, 0 on error.
|
||||||
|
*/
|
||||||
static int write_entire_file(const char *path, const char *data, size_t len) {
|
static int write_entire_file(const char *path, const char *data, size_t len) {
|
||||||
FILE *f = fopen(path, "wb");
|
FILE *f = fopen(path, "wb");
|
||||||
if (!f) return 0;
|
if (!f) return 0;
|
||||||
|
|
@ -1091,6 +1185,9 @@ static int write_entire_file(const char *path, const char *data, size_t len) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- REPL command matching helper ---------- */
|
/* ---------- REPL command matching helper ---------- */
|
||||||
|
/**
|
||||||
|
* @brief Return 1 if cmd equals any non-NULL entry in names[] (terminated by NULL).
|
||||||
|
*/
|
||||||
static int cmd_is_one_of(const char *cmd, const char *const names[]) {
|
static int cmd_is_one_of(const char *cmd, const char *const names[]) {
|
||||||
if (!cmd || !*cmd) return 0;
|
if (!cmd || !*cmd) return 0;
|
||||||
for (int i = 0; names[i] != NULL; ++i) {
|
for (int i = 0; names[i] != NULL; ++i) {
|
||||||
|
|
@ -1100,6 +1197,9 @@ static int cmd_is_one_of(const char *cmd, const char *const names[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Hexdump helper ---------- */
|
/* ---------- Hexdump helper ---------- */
|
||||||
|
/**
|
||||||
|
* @brief Dump a byte buffer in hex to a FILE*, with offsets.
|
||||||
|
*/
|
||||||
static void hexdump_to(FILE *out, const unsigned char *data, size_t len, size_t base_off) {
|
static void hexdump_to(FILE *out, const unsigned char *data, size_t len, size_t base_off) {
|
||||||
if (!out || !data || len == 0) return;
|
if (!out || !data || len == 0) return;
|
||||||
const size_t width = 16;
|
const size_t width = 16;
|
||||||
|
|
@ -1126,6 +1226,9 @@ static void hexdump_to(FILE *out, const unsigned char *data, size_t len, size_t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Print the last n lines of a file to stdout (tail-like).
|
||||||
|
*/
|
||||||
static void print_last_n_lines(const char *path, int n) {
|
static void print_last_n_lines(const char *path, int n) {
|
||||||
if (n <= 0) n = 50;
|
if (n <= 0) n = 50;
|
||||||
size_t flen = 0;
|
size_t flen = 0;
|
||||||
|
|
@ -1150,6 +1253,9 @@ static void print_last_n_lines(const char *path, int n) {
|
||||||
free(content);
|
free(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Append multi-line buffer to history file, one line per entry.
|
||||||
|
*/
|
||||||
static void append_history(FILE *hist, const char *buffer) {
|
static void append_history(FILE *hist, const char *buffer) {
|
||||||
if (!hist || !buffer) return;
|
if (!hist || !buffer) return;
|
||||||
fputs(buffer, hist);
|
fputs(buffer, hist);
|
||||||
|
|
@ -1158,6 +1264,9 @@ static void append_history(FILE *hist, const char *buffer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Env helpers ---------- */
|
/* ---------- Env helpers ---------- */
|
||||||
|
/**
|
||||||
|
* @brief Print usage for :env commands.
|
||||||
|
*/
|
||||||
static void env_show_usage(void) {
|
static void env_show_usage(void) {
|
||||||
printf("Usage:\n");
|
printf("Usage:\n");
|
||||||
printf(" :env NAME Show environment variable NAME\n");
|
printf(" :env NAME Show environment variable NAME\n");
|
||||||
|
|
@ -1165,6 +1274,9 @@ static void env_show_usage(void) {
|
||||||
printf(" :env Show this usage\n");
|
printf(" :env Show this usage\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Show the value of an environment variable.
|
||||||
|
*/
|
||||||
static void env_get(const char *name) {
|
static void env_get(const char *name) {
|
||||||
const char *v = getenv(name);
|
const char *v = getenv(name);
|
||||||
if (v)
|
if (v)
|
||||||
|
|
@ -1173,6 +1285,9 @@ static void env_get(const char *name) {
|
||||||
printf("%s is not set\n", name);
|
printf("%s is not set\n", name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set or unset (when value is NULL) an environment variable.
|
||||||
|
*/
|
||||||
static void env_set(const char *name, const char *value) {
|
static void env_set(const char *name, const char *value) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
if (_putenv_s(name, value ? value : "") != 0) {
|
if (_putenv_s(name, value ? value : "") != 0) {
|
||||||
|
|
@ -1187,6 +1302,11 @@ static void env_set(const char *name, const char *value) {
|
||||||
|
|
||||||
/* ---------- REPL Entry ---------- */
|
/* ---------- REPL Entry ---------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Run the interactive Fun REPL session.
|
||||||
|
* @param vm Initialized VM to execute user input within.
|
||||||
|
* @return Exit status code for the REPL.
|
||||||
|
*/
|
||||||
int fun_run_repl(VM *vm) {
|
int fun_run_repl(VM *vm) {
|
||||||
int repl_timing = 0;
|
int repl_timing = 0;
|
||||||
int selected_frame = -1; /* -1 means use current top frame */
|
int selected_frame = -1; /* -1 means use current top frame */
|
||||||
|
|
|
||||||
25
src/repl.h
25
src/repl.h
|
|
@ -1,3 +1,16 @@
|
||||||
|
/**
|
||||||
|
* This file is part of the Fun programming language.
|
||||||
|
* https://fun-lang.xyz/
|
||||||
|
*
|
||||||
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file repl.h
|
||||||
|
* @brief Interactive Read-Eval-Print Loop (REPL) entry point.
|
||||||
|
*/
|
||||||
#ifndef FUN_REPL_H
|
#ifndef FUN_REPL_H
|
||||||
#define FUN_REPL_H
|
#define FUN_REPL_H
|
||||||
|
|
||||||
|
|
@ -8,8 +21,16 @@ extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef FUN_WITH_REPL
|
#ifdef FUN_WITH_REPL
|
||||||
// Runs the interactive REPL using the provided, already-initialized VM.
|
/**
|
||||||
// Returns 0 on normal exit.
|
* @brief Run the interactive REPL using an already-initialized VM.
|
||||||
|
*
|
||||||
|
* Reads lines from stdin, evaluates them in the provided VM context, and
|
||||||
|
* prints results/errors. The function returns on EOF or when the user issues
|
||||||
|
* a quit command supported by the REPL implementation.
|
||||||
|
*
|
||||||
|
* @param vm Pointer to an initialized VM; must not be NULL.
|
||||||
|
* @return 0 on normal exit, non-zero on fatal error.
|
||||||
|
*/
|
||||||
int fun_run_repl(VM *vm);
|
int fun_run_repl(VM *vm);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,58 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-01-27
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Use an FFI-safe opaque handle for the VM pointer
|
/**
|
||||||
|
* Rust FFI entry points and helpers used by the Fun VM (C).
|
||||||
|
*
|
||||||
|
* This crate builds a static library that exposes a handful of C-ABI
|
||||||
|
* functions callable by the C VM. The functions operate on an opaque VM
|
||||||
|
* pointer and follow the same conventions as native C opcode handlers:
|
||||||
|
* they return 0 on success and non‑zero on error, and they communicate
|
||||||
|
* values via VM stack helpers provided on the C side.
|
||||||
|
*
|
||||||
|
* Safety
|
||||||
|
* - All extern "C" functions are inherently unsafe due to raw pointers.
|
||||||
|
* - Callers must pass valid VM pointers provided by the C runtime.
|
||||||
|
* - Any strings returned across the FFI boundary are NUL‑terminated and
|
||||||
|
* must be freed using the dedicated free function documented below.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// Use an FFI‑safe opaque handle for the VM pointer owned by the C VM.
|
||||||
|
///
|
||||||
|
/// The Rust code treats this as an opaque blob and never dereferences it
|
||||||
|
/// directly, relying instead on C helper functions exposed via FFI.
|
||||||
pub type Vm = core::ffi::c_void;
|
pub type Vm = core::ffi::c_void;
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
/// Pop a 64‑bit integer from the VM stack.
|
||||||
|
///
|
||||||
|
/// Safety: `vm` must be a valid pointer to a VM instance.
|
||||||
fn vm_pop_i64(vm: *mut Vm) -> i64;
|
fn vm_pop_i64(vm: *mut Vm) -> i64;
|
||||||
|
/// Push a 64‑bit integer onto the VM stack.
|
||||||
|
///
|
||||||
|
/// Safety: `vm` must be a valid pointer to a VM instance.
|
||||||
fn vm_push_i64(vm: *mut Vm, v: i64);
|
fn vm_push_i64(vm: *mut Vm, v: i64);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Submodule with additional Rust VM math ops (exported via C ABI)
|
/// Submodule with additional Rust VM math ops (exported via C ABI).
|
||||||
pub mod vm;
|
pub mod vm;
|
||||||
|
|
||||||
|
/// Add the top two integers on the VM stack.
|
||||||
|
///
|
||||||
|
/// Stack effect
|
||||||
|
/// - Before: [..., a, b]
|
||||||
|
/// - After: [..., a+b]
|
||||||
|
///
|
||||||
|
/// Return value
|
||||||
|
/// - 0 on success; non‑zero on error (never used here).
|
||||||
|
///
|
||||||
|
/// Safety: `vm` must be a valid VM pointer.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn fun_op_radd(vm: *mut Vm) -> i32 {
|
pub extern "C" fn fun_op_radd(vm: *mut Vm) -> i32 {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
@ -30,12 +63,26 @@ pub extern "C" fn fun_op_radd(vm: *mut Vm) -> i32 {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a static NUL‑terminated greeting string owned by Rust.
|
||||||
|
///
|
||||||
|
/// The returned pointer remains valid for the duration of the process and
|
||||||
|
/// must NOT be freed by the caller.
|
||||||
|
///
|
||||||
|
/// Return const char* to a constant "Hello from Rust ops!" string.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn fun_rust_get_string() -> *const core::ffi::c_char {
|
pub extern "C" fn fun_rust_get_string() -> *const core::ffi::c_char {
|
||||||
b"Hello from Rust ops!\0".as_ptr() as *const _
|
b"Hello from Rust ops!\0".as_ptr() as *const _
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print a C string via libc printf to stdout
|
/// Print a C string to stdout using libc printf("%s\n").
|
||||||
|
///
|
||||||
|
/// - If `msg` is NULL, behavior is undefined (printf will likely crash).
|
||||||
|
/// - This is intended as a simple demo and not for performance‑critical use.
|
||||||
|
///
|
||||||
|
/// Param msg NUL‑terminated UTF‑8/bytes C string.
|
||||||
|
/// Return 0 on success.
|
||||||
|
///
|
||||||
|
/// Safety: `msg` must be a valid C string pointer when non‑NULL.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn fun_rust_print_string(msg: *const core::ffi::c_char) -> i32 {
|
pub extern "C" fn fun_rust_print_string(msg: *const core::ffi::c_char) -> i32 {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
@ -48,8 +95,17 @@ pub extern "C" fn fun_rust_print_string(msg: *const core::ffi::c_char) -> i32 {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a newly allocated duplicate of the given C string.
|
/// Duplicate a C string and return an owned copy allocated by Rust.
|
||||||
// Caller (C side) must free using fun_rust_string_free.
|
///
|
||||||
|
/// - On NULL input, returns an allocated empty string ("\0").
|
||||||
|
/// - On invalid UTF‑8, the raw byte sequence is duplicated as‑is.
|
||||||
|
/// - Memory ownership is transferred to the caller, who must free it with
|
||||||
|
/// fun_rust_string_free().
|
||||||
|
///
|
||||||
|
/// Param input NUL‑terminated C string (may be NULL).
|
||||||
|
/// Return Newly allocated NUL‑terminated C string owned by the caller.
|
||||||
|
///
|
||||||
|
/// Safety: `input` must be a valid pointer if non‑NULL.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn fun_rust_echo_string(input: *const core::ffi::c_char) -> *mut core::ffi::c_char {
|
pub extern "C" fn fun_rust_echo_string(input: *const core::ffi::c_char) -> *mut core::ffi::c_char {
|
||||||
use core::ffi::CStr;
|
use core::ffi::CStr;
|
||||||
|
|
@ -76,7 +132,13 @@ pub extern "C" fn fun_rust_echo_string(input: *const core::ffi::c_char) -> *mut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Free a C string previously returned by fun_rust_echo_string
|
/// Free a C string allocated by fun_rust_echo_string().
|
||||||
|
///
|
||||||
|
/// The pointer may be NULL, in which case the function is a no‑op.
|
||||||
|
///
|
||||||
|
/// Param ptr Pointer returned by fun_rust_echo_string().
|
||||||
|
///
|
||||||
|
/// Safety: `ptr` must have been allocated by fun_rust_echo_string().
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn fun_rust_string_free(ptr: *mut core::ffi::c_char) {
|
pub extern "C" fn fun_rust_string_free(ptr: *mut core::ffi::c_char) {
|
||||||
if ptr.is_null() { return; }
|
if ptr.is_null() { return; }
|
||||||
|
|
@ -90,4 +152,4 @@ pub extern "C" fn fun_rust_string_free(ptr: *mut core::ffi::c_char) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No custom panic handler; use std default
|
// No custom panic handler; use std default.
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,45 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-01-27
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//! Rust VM helpers and opcode handlers exposed via C ABI.
|
/**
|
||||||
//!
|
* Rust VM helpers and opcode handlers exposed via C ABI.
|
||||||
//! Functions in this module are compiled into the Rust static library and can
|
*
|
||||||
//! be called from the C VM. They operate on the VM stack using the minimal C
|
* Functions in this module are compiled into the Rust static library and can
|
||||||
//! ABI helpers declared on the C side (`vm_pop_i64`, `vm_push_i64`).
|
* be called from the C VM. They operate on the VM stack using the minimal C
|
||||||
|
* ABI helpers declared on the C side (e.g. `vm_pop_i64`, `vm_push_i64`).
|
||||||
|
*
|
||||||
|
* Safety
|
||||||
|
* - All extern "C" functions are unsafe; `vm` must be a valid pointer.
|
||||||
|
* - Raw field access helpers (offset reads/writes) assume the C `struct Vm`
|
||||||
|
* layout matches the offsets reported by the C side.
|
||||||
|
*/
|
||||||
|
|
||||||
use super::Vm;
|
use super::Vm;
|
||||||
//use core::mem::size_of;
|
//use core::mem::size_of;
|
||||||
use core::ptr;
|
use core::ptr;
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
/// Pop a 64‑bit integer from the VM stack.
|
||||||
fn vm_pop_i64(vm: *mut Vm) -> i64;
|
fn vm_pop_i64(vm: *mut Vm) -> i64;
|
||||||
|
/// Push a 64‑bit integer onto the VM stack.
|
||||||
fn vm_push_i64(vm: *mut Vm, v: i64);
|
fn vm_push_i64(vm: *mut Vm, v: i64);
|
||||||
|
/// Get a mutable pointer to the underlying C Vm as an opaque byte ptr.
|
||||||
fn vm_as_mut_ptr(vm: *mut Vm) -> *mut core::ffi::c_void;
|
fn vm_as_mut_ptr(vm: *mut Vm) -> *mut core::ffi::c_void;
|
||||||
|
/// Size of the C Vm struct (bytes).
|
||||||
fn vm_sizeof() -> usize;
|
fn vm_sizeof() -> usize;
|
||||||
|
/// Size of the C Value struct (bytes).
|
||||||
fn vm_value_sizeof() -> usize;
|
fn vm_value_sizeof() -> usize;
|
||||||
|
/// Byte offset of the `exit_code` field inside C Vm.
|
||||||
fn vm_offset_of_exit_code() -> usize;
|
fn vm_offset_of_exit_code() -> usize;
|
||||||
|
/// Byte offset of the `sp` field inside C Vm.
|
||||||
fn vm_offset_of_sp() -> usize;
|
fn vm_offset_of_sp() -> usize;
|
||||||
|
/// Byte offset of the `stack` field inside C Vm.
|
||||||
fn vm_offset_of_stack() -> usize;
|
fn vm_offset_of_stack() -> usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,31 @@
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file str_utils.c
|
||||||
|
* @brief Helpers for manipulating C strings and bridging with Value arrays.
|
||||||
|
*
|
||||||
|
* Functions here return newly allocated C strings or construct Value arrays
|
||||||
|
* from strings. Callers own returned allocations and must free them using
|
||||||
|
* free()/free_value() as appropriate.
|
||||||
|
*/
|
||||||
#include "value.h"
|
#include "value.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
/* string helpers returning newly allocated C strings or arrays */
|
/* string helpers returning newly allocated C strings or arrays */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a newly allocated substring of s.
|
||||||
|
*
|
||||||
|
* Indices are clamped into valid range. If s is NULL, an empty string is
|
||||||
|
* returned. The caller owns the returned buffer and must free() it.
|
||||||
|
*
|
||||||
|
* @param s Source C string (may be NULL).
|
||||||
|
* @param start Zero-based start index; clamped to [0, strlen(s)].
|
||||||
|
* @param len Maximum number of characters to copy; negative treated as 0.
|
||||||
|
* @return Newly allocated NUL-terminated substring; never NULL.
|
||||||
|
*/
|
||||||
char *string_substr(const char *s, int start, int len) {
|
char *string_substr(const char *s, int start, int len) {
|
||||||
if (!s) return strdup("");
|
if (!s) return strdup("");
|
||||||
int n = (int)strlen(s);
|
int n = (int)strlen(s);
|
||||||
|
|
@ -27,6 +46,13 @@ char *string_substr(const char *s, int start, int len) {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Find first occurrence of needle in hay.
|
||||||
|
*
|
||||||
|
* @param hay Haystack C string (may be NULL).
|
||||||
|
* @param needle Needle C string (may be NULL).
|
||||||
|
* @return Zero-based index or -1 if not found/invalid input.
|
||||||
|
*/
|
||||||
int string_find(const char *hay, const char *needle) {
|
int string_find(const char *hay, const char *needle) {
|
||||||
if (!hay || !needle) return -1;
|
if (!hay || !needle) return -1;
|
||||||
const char *p = strstr(hay, needle);
|
const char *p = strstr(hay, needle);
|
||||||
|
|
@ -34,6 +60,17 @@ int string_find(const char *hay, const char *needle) {
|
||||||
return (int)(p - hay);
|
return (int)(p - hay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Split a C string by separator into a Value array of strings.
|
||||||
|
*
|
||||||
|
* When sep is empty, splits into individual UTF-8 bytes (characters). Uses
|
||||||
|
* make_string/make_array_from_values; the returned Value owns internal memory
|
||||||
|
* per Value semantics. NULL inputs are treated as empty strings.
|
||||||
|
*
|
||||||
|
* @param s Source C string (may be NULL).
|
||||||
|
* @param sep Separator C string (may be NULL). Empty means split into chars.
|
||||||
|
* @return Value of type VAL_ARRAY with string elements.
|
||||||
|
*/
|
||||||
Value string_split_to_array(const char *s, const char *sep) {
|
Value string_split_to_array(const char *s, const char *sep) {
|
||||||
if (!s) s = "";
|
if (!s) s = "";
|
||||||
if (!sep) sep = "";
|
if (!sep) sep = "";
|
||||||
|
|
@ -90,6 +127,17 @@ Value string_split_to_array(const char *s, const char *sep) {
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Join the elements of a Value array into a single newly allocated C string.
|
||||||
|
*
|
||||||
|
* Each array element is converted to a string via value_to_string_alloc.
|
||||||
|
* NULL/invalid inputs yield an empty string. The caller owns the returned
|
||||||
|
* buffer and must free() it.
|
||||||
|
*
|
||||||
|
* @param v Pointer to Value (expected VAL_ARRAY).
|
||||||
|
* @param sep Separator C string inserted between items (may be NULL).
|
||||||
|
* @return Newly allocated joined string; never NULL.
|
||||||
|
*/
|
||||||
char *array_join_with_sep(const Value *v, const char *sep) {
|
char *array_join_with_sep(const Value *v, const char *sep) {
|
||||||
if (!v || v->type != VAL_ARRAY || !v->arr) return strdup("");
|
if (!v || v->type != VAL_ARRAY || !v->arr) return strdup("");
|
||||||
if (!sep) sep = "";
|
if (!sep) sep = "";
|
||||||
|
|
|
||||||
59
src/string.c
59
src/string.c
|
|
@ -7,17 +7,52 @@
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file string.c
|
||||||
|
* @brief String built-ins wrappers used by VM opcodes.
|
||||||
|
*
|
||||||
|
* Provides small wrapper functions around core string utilities to operate on
|
||||||
|
* the Value type used by the VM (split, join, substr, find).
|
||||||
|
*/
|
||||||
|
|
||||||
#include "value.h"
|
#include "value.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
/* String built-ins wrappers used by VM opcodes */
|
/* String built-ins wrappers used by VM opcodes */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Split a string by a separator into an array Value.
|
||||||
|
*
|
||||||
|
* Safely extracts C strings from the provided `Value` arguments. If either
|
||||||
|
* argument is NULL, not of type `VAL_STRING`, or has a NULL `s` pointer,
|
||||||
|
* an empty string ("") is used instead.
|
||||||
|
*
|
||||||
|
* @param str Input string (`VAL_STRING`) to split. May be NULL.
|
||||||
|
* @param sep Separator string (`VAL_STRING`). May be NULL. If empty,
|
||||||
|
* behavior is defined by `string_split_to_array`.
|
||||||
|
* @return A `Value` representing an array of substrings (each a `VAL_STRING`).
|
||||||
|
* The exact array layout and split semantics are delegated to
|
||||||
|
* `string_split_to_array`.
|
||||||
|
*/
|
||||||
Value bi_split(const Value *str, const Value *sep) {
|
Value bi_split(const Value *str, const Value *sep) {
|
||||||
const char *s = (str && str->type == VAL_STRING && str->s) ? str->s : "";
|
const char *s = (str && str->type == VAL_STRING && str->s) ? str->s : "";
|
||||||
const char *p = (sep && sep->type == VAL_STRING && sep->s) ? sep->s : "";
|
const char *p = (sep && sep->type == VAL_STRING && sep->s) ? sep->s : "";
|
||||||
return string_split_to_array(s, p);
|
return string_split_to_array(s, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Join an array of strings with a separator into a single string Value.
|
||||||
|
*
|
||||||
|
* Extracts the separator C string from `sep` if it is a `VAL_STRING`,
|
||||||
|
* otherwise uses an empty string (""). The join operation is performed by
|
||||||
|
* `array_join_with_sep`.
|
||||||
|
*
|
||||||
|
* @param arr Array `Value` expected to contain strings. Semantics for
|
||||||
|
* non-string elements are defined by `array_join_with_sep`.
|
||||||
|
* @param sep Separator string (`VAL_STRING`). May be NULL; defaults to empty.
|
||||||
|
* @return A `VAL_STRING` `Value` with the joined result. Never returns a NULL
|
||||||
|
* `Value`; if joining fails, an empty string is returned.
|
||||||
|
*/
|
||||||
Value bi_join(const Value *arr, const Value *sep) {
|
Value bi_join(const Value *arr, const Value *sep) {
|
||||||
const char *p = (sep && sep->type == VAL_STRING && sep->s) ? sep->s : "";
|
const char *p = (sep && sep->type == VAL_STRING && sep->s) ? sep->s : "";
|
||||||
char *s = array_join_with_sep(arr, p);
|
char *s = array_join_with_sep(arr, p);
|
||||||
|
|
@ -26,6 +61,19 @@ Value bi_join(const Value *arr, const Value *sep) {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Extract a substring from a string `Value`.
|
||||||
|
*
|
||||||
|
* If `str` is not a `VAL_STRING` or is NULL, an empty source string is used.
|
||||||
|
* The actual substring extraction semantics (e.g., handling of negative or
|
||||||
|
* out-of-range indices) are delegated to `string_substr`.
|
||||||
|
*
|
||||||
|
* @param str Source string (`VAL_STRING`). May be NULL.
|
||||||
|
* @param start Zero-based start index.
|
||||||
|
* @param len Maximum number of characters to include in the substring.
|
||||||
|
* @return A `VAL_STRING` `Value` containing the substring. Returns an empty
|
||||||
|
* string if extraction fails or inputs are treated as empty.
|
||||||
|
*/
|
||||||
Value bi_substr(const Value *str, int start, int len) {
|
Value bi_substr(const Value *str, int start, int len) {
|
||||||
const char *s = (str && str->type == VAL_STRING && str->s) ? str->s : "";
|
const char *s = (str && str->type == VAL_STRING && str->s) ? str->s : "";
|
||||||
char *sub = string_substr(s, start, len);
|
char *sub = string_substr(s, start, len);
|
||||||
|
|
@ -34,6 +82,17 @@ Value bi_substr(const Value *str, int start, int len) {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Find the first occurrence of a needle inside a haystack string.
|
||||||
|
*
|
||||||
|
* If either argument is not a `VAL_STRING` or is NULL, it is treated as an
|
||||||
|
* empty string (""). The search is performed by `string_find`.
|
||||||
|
*
|
||||||
|
* @param hay Haystack string (`VAL_STRING`). May be NULL.
|
||||||
|
* @param needle Needle string (`VAL_STRING`). May be NULL.
|
||||||
|
* @return The zero-based index of the first occurrence of `needle` in `hay`,
|
||||||
|
* or -1 if not found. Exact semantics are delegated to `string_find`.
|
||||||
|
*/
|
||||||
int bi_find(const Value *hay, const Value *needle) {
|
int bi_find(const Value *hay, const Value *needle) {
|
||||||
const char *h = (hay && hay->type == VAL_STRING && hay->s) ? hay->s : "";
|
const char *h = (hay && hay->type == VAL_STRING && hay->s) ? hay->s : "";
|
||||||
const char *n = (needle && needle->type == VAL_STRING && needle->s) ? needle->s : "";
|
const char *n = (needle && needle->type == VAL_STRING && needle->s) ? needle->s : "";
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,29 @@
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file test_opcodes.c
|
||||||
|
* @brief Minimal executable exercising a subset of Fun VM opcodes.
|
||||||
|
*
|
||||||
|
* Builds a tiny bytecode program that loads constants, performs arithmetic,
|
||||||
|
* prints results, and demonstrates optional Rust FFI calls. Used for quick
|
||||||
|
* manual verification of the VM dispatch and I/O helpers.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "bytecode.h"
|
#include "bytecode.h"
|
||||||
#include "value.h"
|
#include "value.h"
|
||||||
#include "vm.h"
|
#include "vm.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Minimal executable to exercise a subset of VM opcodes.
|
||||||
|
*
|
||||||
|
* Builds a tiny bytecode chunk that loads two constants, adds them, and prints
|
||||||
|
* the result. Also demonstrates dumping bytecode and, optionally, calling Rust
|
||||||
|
* FFI examples when FUN_WITH_RUST is enabled.
|
||||||
|
*
|
||||||
|
* @return Zero on success; non-zero on fatal VM or allocation errors.
|
||||||
|
*/
|
||||||
int main() {
|
int main() {
|
||||||
|
|
||||||
VM vm;
|
VM vm;
|
||||||
|
|
|
||||||
222
src/value.c
222
src/value.c
|
|
@ -7,6 +7,18 @@
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file value.c
|
||||||
|
* @brief Implementation of the runtime Value type, including constructors,
|
||||||
|
* dynamic array/map utilities, copying, comparison, printing, and
|
||||||
|
* string conversion helpers.
|
||||||
|
*
|
||||||
|
* This translation unit provides the concrete operations for the Fun
|
||||||
|
* programming language's Value structure (ints, floats, bools, strings,
|
||||||
|
* arrays, maps, functions and nil). It is used by the VM and standard
|
||||||
|
* library to construct and manipulate runtime values.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "value.h"
|
#include "value.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
@ -30,6 +42,12 @@ typedef struct Map {
|
||||||
Value *vals; /* each value owned here */
|
Value *vals; /* each value owned here */
|
||||||
} Map;
|
} Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a Value representing a 64-bit integer.
|
||||||
|
*
|
||||||
|
* @param v The integer payload.
|
||||||
|
* @return A Value with type VAL_INT holding v.
|
||||||
|
*/
|
||||||
Value make_int(int64_t v) {
|
Value make_int(int64_t v) {
|
||||||
Value val;
|
Value val;
|
||||||
val.type = VAL_INT;
|
val.type = VAL_INT;
|
||||||
|
|
@ -37,6 +55,12 @@ Value make_int(int64_t v) {
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a Value representing a double-precision float.
|
||||||
|
*
|
||||||
|
* @param v The floating-point payload.
|
||||||
|
* @return A Value with type VAL_FLOAT holding v.
|
||||||
|
*/
|
||||||
Value make_float(double v) {
|
Value make_float(double v) {
|
||||||
Value val;
|
Value val;
|
||||||
val.type = VAL_FLOAT;
|
val.type = VAL_FLOAT;
|
||||||
|
|
@ -44,6 +68,14 @@ Value make_float(double v) {
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a boolean Value.
|
||||||
|
*
|
||||||
|
* Any non-zero input is treated as true, zero as false.
|
||||||
|
*
|
||||||
|
* @param v Integer truthy/falsey indicator.
|
||||||
|
* @return A Value with type VAL_BOOL and normalized 0/1 payload.
|
||||||
|
*/
|
||||||
Value make_bool(int v) {
|
Value make_bool(int v) {
|
||||||
Value val;
|
Value val;
|
||||||
val.type = VAL_BOOL;
|
val.type = VAL_BOOL;
|
||||||
|
|
@ -51,6 +83,15 @@ Value make_bool(int v) {
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a string Value by duplicating the given C string.
|
||||||
|
*
|
||||||
|
* If s is NULL, an empty string is used. The returned Value owns an allocated
|
||||||
|
* copy which must be released via free_value.
|
||||||
|
*
|
||||||
|
* @param s NUL-terminated C string (may be NULL).
|
||||||
|
* @return A Value with type VAL_STRING.
|
||||||
|
*/
|
||||||
Value make_string(const char *s) {
|
Value make_string(const char *s) {
|
||||||
Value val;
|
Value val;
|
||||||
val.type = VAL_STRING;
|
val.type = VAL_STRING;
|
||||||
|
|
@ -61,6 +102,15 @@ Value make_string(const char *s) {
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a function Value referencing bytecode.
|
||||||
|
*
|
||||||
|
* The Bytecode pointer is stored as-is; ownership/lifetime is managed by the
|
||||||
|
* caller/VM and not freed by free_value.
|
||||||
|
*
|
||||||
|
* @param fn Pointer to function bytecode (may be NULL to represent an invalid function).
|
||||||
|
* @return A Value with type VAL_FUNCTION.
|
||||||
|
*/
|
||||||
Value make_function(struct Bytecode *fn) {
|
Value make_function(struct Bytecode *fn) {
|
||||||
Value val;
|
Value val;
|
||||||
val.type = VAL_FUNCTION;
|
val.type = VAL_FUNCTION;
|
||||||
|
|
@ -68,12 +118,27 @@ Value make_function(struct Bytecode *fn) {
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a nil Value.
|
||||||
|
*
|
||||||
|
* @return A Value with type VAL_NIL.
|
||||||
|
*/
|
||||||
Value make_nil(void) {
|
Value make_nil(void) {
|
||||||
Value v;
|
Value v;
|
||||||
v.type = VAL_NIL;
|
v.type = VAL_NIL;
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create an array Value by copying items from an input span.
|
||||||
|
*
|
||||||
|
* Performs a shallow copy for scalars and reference-counted copy for arrays/maps
|
||||||
|
* via copy_value. On allocation failure, returns VAL_NIL.
|
||||||
|
*
|
||||||
|
* @param vals Pointer to input items; may be NULL when count == 0.
|
||||||
|
* @param count Number of items to copy (negative treated as 0).
|
||||||
|
* @return A Value with type VAL_ARRAY or VAL_NIL on failure.
|
||||||
|
*/
|
||||||
Value make_array_from_values(const Value *vals, int count) {
|
Value make_array_from_values(const Value *vals, int count) {
|
||||||
if (count < 0) count = 0;
|
if (count < 0) count = 0;
|
||||||
Array *arr = (Array *)malloc(sizeof(Array));
|
Array *arr = (Array *)malloc(sizeof(Array));
|
||||||
|
|
@ -102,12 +167,28 @@ Value make_array_from_values(const Value *vals, int count) {
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the element count of an array Value.
|
||||||
|
*
|
||||||
|
* @param v Array Value.
|
||||||
|
* @return Number of elements, or -1 if v is not a valid array.
|
||||||
|
*/
|
||||||
int array_length(const Value *v) {
|
int array_length(const Value *v) {
|
||||||
if (!v || v->type != VAL_ARRAY || !v->arr) return -1;
|
if (!v || v->type != VAL_ARRAY || !v->arr) return -1;
|
||||||
const Array *a = (const Array *)v->arr;
|
const Array *a = (const Array *)v->arr;
|
||||||
return a->count;
|
return a->count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy an array element into out.
|
||||||
|
*
|
||||||
|
* The element is copied with copy_value; ownership of out remains with caller.
|
||||||
|
*
|
||||||
|
* @param v Array Value.
|
||||||
|
* @param index Zero-based index.
|
||||||
|
* @param out Destination pointer to receive the copied Value (may be NULL to only validate index).
|
||||||
|
* @return 1 on success, 0 on bounds/type error.
|
||||||
|
*/
|
||||||
int array_get_copy(const Value *v, int index, Value *out) {
|
int array_get_copy(const Value *v, int index, Value *out) {
|
||||||
if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
|
if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
|
||||||
const Array *a = (const Array *)v->arr;
|
const Array *a = (const Array *)v->arr;
|
||||||
|
|
@ -116,6 +197,16 @@ int array_get_copy(const Value *v, int index, Value *out) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Replace an element of an array with a new Value.
|
||||||
|
*
|
||||||
|
* Takes ownership of newElem and frees the old element.
|
||||||
|
*
|
||||||
|
* @param v Array Value to mutate.
|
||||||
|
* @param index Zero-based index to replace.
|
||||||
|
* @param newElem New element (ownership transferred to array).
|
||||||
|
* @return 1 on success, 0 on bounds/type error.
|
||||||
|
*/
|
||||||
int array_set(Value *v, int index, Value newElem) {
|
int array_set(Value *v, int index, Value newElem) {
|
||||||
if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
|
if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
|
||||||
Array *a = (Array *)v->arr;
|
Array *a = (Array *)v->arr;
|
||||||
|
|
@ -125,6 +216,15 @@ int array_set(Value *v, int index, Value newElem) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Ensure the internal items buffer can hold at least newCount items.
|
||||||
|
*
|
||||||
|
* May grow the allocation exponentially; initializes new slots to nil.
|
||||||
|
*
|
||||||
|
* @param a Internal Array pointer.
|
||||||
|
* @param newCount Required minimum logical capacity.
|
||||||
|
* @return 1 on success, 0 on allocation failure.
|
||||||
|
*/
|
||||||
static int ensure_array_capacity(Array *a, int newCount) {
|
static int ensure_array_capacity(Array *a, int newCount) {
|
||||||
if (newCount <= a->count) return 1;
|
if (newCount <= a->count) return 1;
|
||||||
/* grow to at least newCount; double strategy */
|
/* grow to at least newCount; double strategy */
|
||||||
|
|
@ -145,6 +245,15 @@ static int ensure_array_capacity(Array *a, int newCount) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Append a Value to an array.
|
||||||
|
*
|
||||||
|
* On success, ownership of newElem is transferred to the array.
|
||||||
|
*
|
||||||
|
* @param v Array Value to append to.
|
||||||
|
* @param newElem Element to append.
|
||||||
|
* @return New array length on success (>=0), or -1 on failure/type error.
|
||||||
|
*/
|
||||||
int array_push(Value *v, Value newElem) {
|
int array_push(Value *v, Value newElem) {
|
||||||
if (!v || v->type != VAL_ARRAY || !v->arr) return -1;
|
if (!v || v->type != VAL_ARRAY || !v->arr) return -1;
|
||||||
Array *a = (Array *)v->arr;
|
Array *a = (Array *)v->arr;
|
||||||
|
|
@ -160,6 +269,16 @@ int array_push(Value *v, Value newElem) {
|
||||||
return a->count;
|
return a->count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove the last element from an array.
|
||||||
|
*
|
||||||
|
* If out is provided, ownership of the removed element is transferred to *out;
|
||||||
|
* otherwise the element is freed.
|
||||||
|
*
|
||||||
|
* @param v Array Value to pop from.
|
||||||
|
* @param out Optional destination for removed element.
|
||||||
|
* @return 1 on success, 0 if array empty or invalid.
|
||||||
|
*/
|
||||||
int array_pop(Value *v, Value *out) {
|
int array_pop(Value *v, Value *out) {
|
||||||
if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
|
if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
|
||||||
Array *a = (Array *)v->arr;
|
Array *a = (Array *)v->arr;
|
||||||
|
|
@ -173,6 +292,16 @@ int array_pop(Value *v, Value *out) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Insert a new element at a specific position in an array.
|
||||||
|
*
|
||||||
|
* Index is clamped into [0, count]. Takes ownership of newElem.
|
||||||
|
*
|
||||||
|
* @param v Array Value to modify.
|
||||||
|
* @param index Insertion index.
|
||||||
|
* @param newElem Element to insert.
|
||||||
|
* @return New array length on success (>=0), or -1 on allocation/type error.
|
||||||
|
*/
|
||||||
int array_insert(Value *v, int index, Value newElem) {
|
int array_insert(Value *v, int index, Value newElem) {
|
||||||
if (!v || v->type != VAL_ARRAY || !v->arr) return -1;
|
if (!v || v->type != VAL_ARRAY || !v->arr) return -1;
|
||||||
Array *a = (Array *)v->arr;
|
Array *a = (Array *)v->arr;
|
||||||
|
|
@ -193,6 +322,17 @@ int array_insert(Value *v, int index, Value newElem) {
|
||||||
return a->count;
|
return a->count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove an element at index from an array.
|
||||||
|
*
|
||||||
|
* If out is provided, ownership of the removed element is transferred; else it
|
||||||
|
* is freed. Remaining items are shifted left.
|
||||||
|
*
|
||||||
|
* @param v Array Value to modify.
|
||||||
|
* @param index Zero-based index to remove.
|
||||||
|
* @param out Optional destination for removed element.
|
||||||
|
* @return 1 on success, 0 on bounds/type error.
|
||||||
|
*/
|
||||||
int array_remove(Value *v, int index, Value *out) {
|
int array_remove(Value *v, int index, Value *out) {
|
||||||
if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
|
if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
|
||||||
Array *a = (Array *)v->arr;
|
Array *a = (Array *)v->arr;
|
||||||
|
|
@ -209,6 +349,16 @@ int array_remove(Value *v, int index, Value *out) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a shallow-copied slice of an array Value.
|
||||||
|
*
|
||||||
|
* Start and end are clamped into valid bounds; end < start yields empty array.
|
||||||
|
*
|
||||||
|
* @param v Source array Value.
|
||||||
|
* @param start Inclusive zero-based start index (clamped to >= 0).
|
||||||
|
* @param end Exclusive end index (clamped to <= length; -1 means length).
|
||||||
|
* @return A new array Value (possibly empty) or VAL_NIL if v is not an array.
|
||||||
|
*/
|
||||||
Value array_slice(const Value *v, int start, int end) {
|
Value array_slice(const Value *v, int start, int end) {
|
||||||
if (!v || v->type != VAL_ARRAY || !v->arr) return make_nil();
|
if (!v || v->type != VAL_ARRAY || !v->arr) return make_nil();
|
||||||
const Array *a = (const Array *)v->arr;
|
const Array *a = (const Array *)v->arr;
|
||||||
|
|
@ -223,6 +373,16 @@ Value array_slice(const Value *v, int start, int end) {
|
||||||
return make_array_from_values(a->items + start, m);
|
return make_array_from_values(a->items + start, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Concatenate two array Values.
|
||||||
|
*
|
||||||
|
* Copies elements into a new array. If either input is not an array, returns
|
||||||
|
* VAL_NIL.
|
||||||
|
*
|
||||||
|
* @param av First array.
|
||||||
|
* @param bv Second array.
|
||||||
|
* @return A new concatenated array Value or VAL_NIL on type/alloc error.
|
||||||
|
*/
|
||||||
Value array_concat(const Value *av, const Value *bv) {
|
Value array_concat(const Value *av, const Value *bv) {
|
||||||
if (!av || !bv || av->type != VAL_ARRAY || bv->type != VAL_ARRAY) return make_nil();
|
if (!av || !bv || av->type != VAL_ARRAY || bv->type != VAL_ARRAY) return make_nil();
|
||||||
const Array *a = (const Array *)av->arr;
|
const Array *a = (const Array *)av->arr;
|
||||||
|
|
@ -243,6 +403,15 @@ Value array_concat(const Value *av, const Value *bv) {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Shallow copy a Value.
|
||||||
|
*
|
||||||
|
* Strings are duplicated, arrays/maps have their refcount incremented, and
|
||||||
|
* function pointers are copied as-is.
|
||||||
|
*
|
||||||
|
* @param v Source Value.
|
||||||
|
* @return A new Value with appropriate copy semantics.
|
||||||
|
*/
|
||||||
Value copy_value(const Value *v) {
|
Value copy_value(const Value *v) {
|
||||||
Value out;
|
Value out;
|
||||||
out.type = v->type;
|
out.type = v->type;
|
||||||
|
|
@ -282,6 +451,15 @@ Value copy_value(const Value *v) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* deep copy including arrays (recursively copies items) */
|
/* deep copy including arrays (recursively copies items) */
|
||||||
|
/**
|
||||||
|
* @brief Deep copy a Value, recursively copying arrays and maps.
|
||||||
|
*
|
||||||
|
* Function Values are copied shallowly. On allocation failure, returns nil or
|
||||||
|
* an empty container as appropriate.
|
||||||
|
*
|
||||||
|
* @param v Source Value.
|
||||||
|
* @return A deep-copied Value.
|
||||||
|
*/
|
||||||
Value deep_copy_value(const Value *v) {
|
Value deep_copy_value(const Value *v) {
|
||||||
switch (v->type) {
|
switch (v->type) {
|
||||||
case VAL_INT:
|
case VAL_INT:
|
||||||
|
|
@ -328,6 +506,14 @@ Value deep_copy_value(const Value *v) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Free dynamic storage owned by a Value.
|
||||||
|
*
|
||||||
|
* Strings are freed, arrays/maps are reference-counted and freed recursively
|
||||||
|
* when their refcount drops to zero. Functions are not freed here.
|
||||||
|
*
|
||||||
|
* @param v Value whose owned resources should be released.
|
||||||
|
*/
|
||||||
void free_value(Value v) {
|
void free_value(Value v) {
|
||||||
if (v.type == VAL_STRING && v.s) {
|
if (v.type == VAL_STRING && v.s) {
|
||||||
free(v.s);
|
free(v.s);
|
||||||
|
|
@ -355,6 +541,14 @@ void free_value(Value v) {
|
||||||
/* VAL_FUNCTION: we *do not* free the Bytecode here (caller frees it) */
|
/* VAL_FUNCTION: we *do not* free the Bytecode here (caller frees it) */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Print a human-readable representation of a Value to stdout.
|
||||||
|
*
|
||||||
|
* Numbers are printed in decimal; arrays/maps are formatted compactly; strings
|
||||||
|
* are printed without quotes.
|
||||||
|
*
|
||||||
|
* @param v Value to print.
|
||||||
|
*/
|
||||||
void print_value(const Value *v) {
|
void print_value(const Value *v) {
|
||||||
switch (v->type) {
|
switch (v->type) {
|
||||||
case VAL_INT:
|
case VAL_INT:
|
||||||
|
|
@ -404,6 +598,15 @@ void print_value(const Value *v) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Evaluate a Value's truthiness according to Fun language rules.
|
||||||
|
*
|
||||||
|
* Empty strings, zero numbers, nil and empty arrays are falsey; everything
|
||||||
|
* else is truthy.
|
||||||
|
*
|
||||||
|
* @param v Value to evaluate.
|
||||||
|
* @return 1 if truthy, 0 otherwise.
|
||||||
|
*/
|
||||||
int value_is_truthy(const Value *v) {
|
int value_is_truthy(const Value *v) {
|
||||||
switch (v->type) {
|
switch (v->type) {
|
||||||
case VAL_INT:
|
case VAL_INT:
|
||||||
|
|
@ -427,6 +630,14 @@ int value_is_truthy(const Value *v) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* allocate a printable C string for the value; caller must free */
|
/* allocate a printable C string for the value; caller must free */
|
||||||
|
/**
|
||||||
|
* @brief Allocate a printable C string for a Value.
|
||||||
|
*
|
||||||
|
* The returned string must be freed by the caller with free().
|
||||||
|
*
|
||||||
|
* @param v Value to convert.
|
||||||
|
* @return Newly allocated NUL-terminated string describing v.
|
||||||
|
*/
|
||||||
char *value_to_string_alloc(const Value *v) {
|
char *value_to_string_alloc(const Value *v) {
|
||||||
if (!v) return strdup("nil");
|
if (!v) return strdup("nil");
|
||||||
char buf[128];
|
char buf[128];
|
||||||
|
|
@ -470,6 +681,17 @@ char *value_to_string_alloc(const Value *v) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compare two Values for equality.
|
||||||
|
*
|
||||||
|
* Supports numeric cross-type equality between ints and floats. Strings are
|
||||||
|
* compared by content. Other types default to pointer/type equality as
|
||||||
|
* implemented in the switch.
|
||||||
|
*
|
||||||
|
* @param a First Value.
|
||||||
|
* @param b Second Value.
|
||||||
|
* @return 1 if equal, 0 otherwise.
|
||||||
|
*/
|
||||||
int value_equals(const Value *a, const Value *b) {
|
int value_equals(const Value *a, const Value *b) {
|
||||||
// Numeric cross-type equality: int vs float compares numerically
|
// Numeric cross-type equality: int vs float compares numerically
|
||||||
if ((a->type == VAL_INT || a->type == VAL_FLOAT) && (b->type == VAL_INT || b->type == VAL_FLOAT)) {
|
if ((a->type == VAL_INT || a->type == VAL_FLOAT) && (b->type == VAL_INT || b->type == VAL_FLOAT)) {
|
||||||
|
|
|
||||||
100
src/value.h
100
src/value.h
|
|
@ -44,6 +44,9 @@ struct Bytecode; /* forward */
|
||||||
struct Array; /* forward */
|
struct Array; /* forward */
|
||||||
struct Map; /* forward */
|
struct Map; /* forward */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enumeration of all runtime value types supported by Fun.
|
||||||
|
*/
|
||||||
typedef enum {
|
typedef enum {
|
||||||
VAL_INT,
|
VAL_INT,
|
||||||
VAL_BOOL,
|
VAL_BOOL,
|
||||||
|
|
@ -55,6 +58,13 @@ typedef enum {
|
||||||
VAL_FLOAT
|
VAL_FLOAT
|
||||||
} ValueType;
|
} ValueType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Tagged union representing a Fun value.
|
||||||
|
*
|
||||||
|
* The active field in the anonymous union is determined by @ref ValueType in
|
||||||
|
* the @c type tag. Ownership/RC semantics for complex members (arrays/maps)
|
||||||
|
* are defined by the array/map APIs.
|
||||||
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
ValueType type;
|
ValueType type;
|
||||||
union {
|
union {
|
||||||
|
|
@ -68,55 +78,91 @@ typedef struct {
|
||||||
} Value;
|
} Value;
|
||||||
|
|
||||||
/* constructors / helpers */
|
/* constructors / helpers */
|
||||||
|
/** Create an integer Value. */
|
||||||
Value make_int(int64_t v);
|
Value make_int(int64_t v);
|
||||||
|
/** Create a boolean Value (0=false, non-zero=true). */
|
||||||
Value make_bool(int v);
|
Value make_bool(int v);
|
||||||
|
/** Create a string Value by copying @p s. */
|
||||||
Value make_string(const char *s);
|
Value make_string(const char *s);
|
||||||
|
/** Create a function Value from bytecode pointer (shallow). */
|
||||||
Value make_function(struct Bytecode *fn);
|
Value make_function(struct Bytecode *fn);
|
||||||
|
/** Create a nil Value. */
|
||||||
Value make_nil(void);
|
Value make_nil(void);
|
||||||
|
/** Create a floating-point Value. */
|
||||||
Value make_float(double v);
|
Value make_float(double v);
|
||||||
|
|
||||||
/* arrays */
|
/* arrays */
|
||||||
Value make_array_from_values(const Value *vals, int count); /* deep-copies vals */
|
/** Build an array from a list of Values (deep-copies vals). */
|
||||||
int array_length(const Value *v); /* returns -1 if not array */
|
Value make_array_from_values(const Value *vals, int count);
|
||||||
int array_get_copy(const Value *v, int index, Value *out); /* returns 0 on error; out = copy_value(item) */
|
/** Get array length or -1 if @p v is not an array. */
|
||||||
int array_set(Value *v, int index, Value newElem); /* returns 0 on error; takes ownership of newElem */
|
int array_length(const Value *v);
|
||||||
int array_push(Value *v, Value newElem); /* returns new length or -1 on error */
|
/** Copy array item at index to out; returns 0 on error. */
|
||||||
int array_pop(Value *v, Value *out); /* returns 1 on success, out takes ownership */
|
int array_get_copy(const Value *v, int index, Value *out);
|
||||||
int array_insert(Value *v, int index, Value newElem); /* returns new length or -1 */
|
/** Set element at index; takes ownership of newElem; 0 on error. */
|
||||||
int array_remove(Value *v, int index, Value *out); /* returns 1 on success */
|
int array_set(Value *v, int index, Value newElem);
|
||||||
Value array_slice(const Value *v, int start, int end); /* negative end means till end */
|
/** Push new element; returns new length or -1 on error. */
|
||||||
Value array_concat(const Value *a, const Value *b); /* returns new array */
|
int array_push(Value *v, Value newElem);
|
||||||
|
/** Pop last element into out; returns 1 on success. */
|
||||||
|
int array_pop(Value *v, Value *out);
|
||||||
|
/** Insert at index; returns new length or -1 on error. */
|
||||||
|
int array_insert(Value *v, int index, Value newElem);
|
||||||
|
/** Remove at index into out; returns 1 on success. */
|
||||||
|
int array_remove(Value *v, int index, Value *out);
|
||||||
|
/** Return slice [start,end) (negative end means till end). */
|
||||||
|
Value array_slice(const Value *v, int start, int end);
|
||||||
|
/** Concatenate arrays a and b into a new array. */
|
||||||
|
Value array_concat(const Value *a, const Value *b);
|
||||||
|
|
||||||
/* maps (string keys) */
|
/* maps (string keys) */
|
||||||
Value make_map_empty(void); /* new empty map */
|
/** Create a new empty string-keyed map Value. */
|
||||||
int map_set(Value *m, const char *key, Value v); /* 1 on ok (takes ownership of v) */
|
Value make_map_empty(void);
|
||||||
int map_get_copy(const Value *m, const char *key, Value *out); /* 1 on found, out=copy */
|
/** Set key to v (takes ownership); returns 1 on success. */
|
||||||
int map_has(const Value *m, const char *key); /* 1/0 */
|
int map_set(Value *m, const char *key, Value v);
|
||||||
Value map_keys_array(const Value *m); /* array of strings */
|
/** Lookup key; returns 1 and copies value to out on success. */
|
||||||
Value map_values_array(const Value *m); /* array of values (copies) */
|
int map_get_copy(const Value *m, const char *key, Value *out);
|
||||||
|
/** Test if key exists; returns 1/0. */
|
||||||
|
int map_has(const Value *m, const char *key);
|
||||||
|
/** Return array of string keys. */
|
||||||
|
Value map_keys_array(const Value *m);
|
||||||
|
/** Return array of values (copies). */
|
||||||
|
Value map_values_array(const Value *m);
|
||||||
|
|
||||||
/* copy/free */
|
/* copy/free */
|
||||||
Value copy_value(const Value *v); /* deep for strings, RC for arrays/maps, shallow fn */
|
/** Shallow/deep copy depending on type (deep for strings, RC for arrays/maps). */
|
||||||
Value deep_copy_value(const Value *v); /* deep copy including arrays/maps */
|
Value copy_value(const Value *v);
|
||||||
void free_value(Value v); /* frees owned resources */
|
/** Deep copy including arrays/maps. */
|
||||||
|
Value deep_copy_value(const Value *v);
|
||||||
|
/** Free owned resources of v. */
|
||||||
|
void free_value(Value v);
|
||||||
|
|
||||||
/* utilities */
|
/* utilities */
|
||||||
|
/** Print value in a human-readable form to stdout. */
|
||||||
void print_value(const Value *v);
|
void print_value(const Value *v);
|
||||||
|
/** Truthiness predicate used by the language semantics. */
|
||||||
int value_is_truthy(const Value *v);
|
int value_is_truthy(const Value *v);
|
||||||
int value_equals(const Value *a, const Value *b); /* int/string equality */
|
/** Equality for ints/strings; other types may be pointer/semantic based. */
|
||||||
|
int value_equals(const Value *a, const Value *b);
|
||||||
|
|
||||||
/* stringify into a newly-allocated C string; caller must free */
|
/* stringify into a newly-allocated C string; caller must free */
|
||||||
|
/** Convert Value to a newly allocated C string; caller must free. */
|
||||||
char *value_to_string_alloc(const Value *v);
|
char *value_to_string_alloc(const Value *v);
|
||||||
|
|
||||||
/* array utils */
|
/* array utils */
|
||||||
int array_contains(const Value *arr, const Value *needle); /* 1/0 */
|
/** Return 1 if needle equals any element in arr. */
|
||||||
int array_index_of(const Value *arr, const Value *needle); /* idx or -1 */
|
int array_contains(const Value *arr, const Value *needle);
|
||||||
void array_clear(Value *arr); /* free elements, count=0 */
|
/** Return index of needle in arr or -1. */
|
||||||
|
int array_index_of(const Value *arr, const Value *needle);
|
||||||
|
/** Free elements and reset count to 0. */
|
||||||
|
void array_clear(Value *arr);
|
||||||
|
|
||||||
/* string helpers returning newly allocated C strings or arrays */
|
/* string helpers returning newly allocated C strings or arrays */
|
||||||
char *string_substr(const char *s, int start, int len); /* clamps bounds */
|
/** Create newly allocated substring (bounds clamped). */
|
||||||
int string_find(const char *hay, const char *needle); /* index or -1 */
|
char *string_substr(const char *s, int start, int len);
|
||||||
Value string_split_to_array(const char *s, const char *sep); /* array of strings */
|
/** Find first index of needle in hay or -1. */
|
||||||
char *array_join_with_sep(const Value *arr, const char *sep); /* join items as strings */
|
int string_find(const char *hay, const char *needle);
|
||||||
|
/** Split C string by sep into Value array of strings. */
|
||||||
|
Value string_split_to_array(const char *s, const char *sep);
|
||||||
|
/** Join Value array items into a newly allocated C string with separator. */
|
||||||
|
char *array_join_with_sep(const Value *arr, const char *sep);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
260
src/vm.c
260
src/vm.c
|
|
@ -7,6 +7,16 @@
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file vm.c
|
||||||
|
* @brief Core virtual machine implementation and opcode dispatch for Fun.
|
||||||
|
*
|
||||||
|
* Defines the VM state, stack helpers, debugger/stepping support, built-in
|
||||||
|
* opcode handlers (included as amalgamated C files), platform I/O helpers,
|
||||||
|
* and the main interpreter loop that executes bytecode produced by the Fun
|
||||||
|
* compiler. This file is the central runtime of the language.
|
||||||
|
*/
|
||||||
|
|
||||||
/* Ensure POSIX/XSI prototypes (nanosleep, wcwidth, etc.) are available
|
/* Ensure POSIX/XSI prototypes (nanosleep, wcwidth, etc.) are available
|
||||||
* before any system headers are included by amalgamated .c files. */
|
* before any system headers are included by amalgamated .c files. */
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
|
|
@ -85,7 +95,19 @@ static __declspec(thread) VM *g_active_vm = NULL;
|
||||||
static __thread VM *g_active_vm = NULL;
|
static __thread VM *g_active_vm = NULL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* fprintf wrapper that appends source line info for stderr messages */
|
/**
|
||||||
|
* @brief fprintf-like wrapper that annotates stderr messages with VM source context.
|
||||||
|
*
|
||||||
|
* When writing to stderr and a VM is active, this function appends file name,
|
||||||
|
* line number, function name, opcode and instruction pointer information to the
|
||||||
|
* message. It attempts to map expanded preprocessed line numbers back to the
|
||||||
|
* original included file for clearer diagnostics.
|
||||||
|
*
|
||||||
|
* @param stream Output stream (typically stderr or stdout).
|
||||||
|
* @param fmt printf-style format string.
|
||||||
|
* @param ap Variable argument list corresponding to fmt.
|
||||||
|
* @return Number of characters written, as returned by vfprintf.
|
||||||
|
*/
|
||||||
static int fun_vm_vfprintf(FILE *stream, const char *fmt, va_list ap) {
|
static int fun_vm_vfprintf(FILE *stream, const char *fmt, va_list ap) {
|
||||||
int written = vfprintf(stream, fmt, ap);
|
int written = vfprintf(stream, fmt, ap);
|
||||||
if (stream == stderr && g_active_vm) {
|
if (stream == stderr && g_active_vm) {
|
||||||
|
|
@ -155,6 +177,16 @@ static int fun_vm_vfprintf(FILE *stream, const char *fmt, va_list ap) {
|
||||||
return written;
|
return written;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief fprintf wrapper forwarding to fun_vm_vfprintf.
|
||||||
|
*
|
||||||
|
* Convenience wrapper that collects varargs and calls fun_vm_vfprintf so that
|
||||||
|
* VM-aware diagnostics are consistently applied in this translation unit.
|
||||||
|
*
|
||||||
|
* @param stream Output stream.
|
||||||
|
* @param fmt printf-style format string.
|
||||||
|
* @return Number of characters written.
|
||||||
|
*/
|
||||||
static int fun_vm_fprintf(FILE *stream, const char *fmt, ...) {
|
static int fun_vm_fprintf(FILE *stream, const char *fmt, ...) {
|
||||||
va_list ap;
|
va_list ap;
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
|
|
@ -172,6 +204,15 @@ static int fun_vm_fprintf(FILE *stream, const char *fmt, ...) {
|
||||||
|
|
||||||
static __thread jmp_buf g_vm_err_jmp;
|
static __thread jmp_buf g_vm_err_jmp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Replacement for exit() used within this translation unit.
|
||||||
|
*
|
||||||
|
* If REPL-on-error is enabled for the active VM, this performs a longjmp back
|
||||||
|
* to vm_run so the REPL can be entered with the current VM state intact.
|
||||||
|
* Otherwise, terminates the process immediately using _Exit/_exit.
|
||||||
|
*
|
||||||
|
* @param code Exit status code (non-zero values indicate error conditions).
|
||||||
|
*/
|
||||||
static void fun_vm_exit(int code) {
|
static void fun_vm_exit(int code) {
|
||||||
if (g_active_vm && g_active_vm->repl_on_error) {
|
if (g_active_vm && g_active_vm->repl_on_error) {
|
||||||
/* Jump back to vm_run to allow dropping into the REPL with intact VM state */
|
/* Jump back to vm_run to allow dropping into the REPL with intact VM state */
|
||||||
|
|
@ -194,6 +235,16 @@ static void push_value(VM *vm, Value v);
|
||||||
/* Raise a runtime error that respects try/catch/finally semantics.
|
/* Raise a runtime error that respects try/catch/finally semantics.
|
||||||
* If a handler is installed for the current frame, jump to it and push
|
* If a handler is installed for the current frame, jump to it and push
|
||||||
* an error string for the catch clause. Otherwise, print and stop VM. */
|
* an error string for the catch clause. Otherwise, print and stop VM. */
|
||||||
|
/**
|
||||||
|
* @brief Raise a runtime error inside the VM, honoring try/catch/finally.
|
||||||
|
*
|
||||||
|
* If the current frame has a pending try handler, control is transferred to
|
||||||
|
* that handler and the error message is pushed onto the stack for the catch
|
||||||
|
* clause. If no handler exists, the error is printed and the VM is stopped.
|
||||||
|
*
|
||||||
|
* @param vm Pointer to the VM instance.
|
||||||
|
* @param msg Human-readable error message (may be NULL).
|
||||||
|
*/
|
||||||
void vm_raise_error(VM *vm, const char *msg) {
|
void vm_raise_error(VM *vm, const char *msg) {
|
||||||
if (!vm || vm->fp < 0) {
|
if (!vm || vm->fp < 0) {
|
||||||
fprintf(stderr, "Runtime error: %s\n", msg ? msg : "<error>");
|
fprintf(stderr, "Runtime error: %s\n", msg ? msg : "<error>");
|
||||||
|
|
@ -258,6 +309,12 @@ Dev tips:
|
||||||
- You can run scripts/run_examples.sh to sanity-check examples quickly.
|
- You can run scripts/run_examples.sh to sanity-check examples quickly.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get a human-readable name for a ValueType.
|
||||||
|
*
|
||||||
|
* @param t The value type enum.
|
||||||
|
* @return Constant string naming the type (e.g., "int", "string").
|
||||||
|
*/
|
||||||
static const char *value_type_name(ValueType t) {
|
static const char *value_type_name(ValueType t) {
|
||||||
switch (t) {
|
switch (t) {
|
||||||
case VAL_FUNCTION:
|
case VAL_FUNCTION:
|
||||||
|
|
@ -281,6 +338,14 @@ static const char *value_type_name(ValueType t) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clear the VM's buffered output values and partial flags.
|
||||||
|
*
|
||||||
|
* Frees any dynamic storage held by buffered output Values and resets the
|
||||||
|
* output counters and partial line indicators.
|
||||||
|
*
|
||||||
|
* @param vm VM instance whose output buffer should be cleared.
|
||||||
|
*/
|
||||||
void vm_clear_output(VM *vm) {
|
void vm_clear_output(VM *vm) {
|
||||||
for (int i = 0; i < vm->output_count; ++i) {
|
for (int i = 0; i < vm->output_count; ++i) {
|
||||||
free_value(vm->output[i]);
|
free_value(vm->output[i]);
|
||||||
|
|
@ -291,6 +356,14 @@ void vm_clear_output(VM *vm) {
|
||||||
vm->output_is_partial[i] = 0;
|
vm->output_is_partial[i] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Free resources owned directly by the VM structure.
|
||||||
|
*
|
||||||
|
* Currently a no-op because the VM does not allocate persistent internal
|
||||||
|
* resources outside frames, globals and outputs, which are managed elsewhere.
|
||||||
|
*
|
||||||
|
* @param vm VM instance to free resources for.
|
||||||
|
*/
|
||||||
void vm_free(VM *vm) {
|
void vm_free(VM *vm) {
|
||||||
// currently nothing persistent allocated inside VM itself
|
// currently nothing persistent allocated inside VM itself
|
||||||
}
|
}
|
||||||
|
|
@ -298,6 +371,14 @@ void vm_free(VM *vm) {
|
||||||
/* forward declaration for helper used in vm_reset */
|
/* forward declaration for helper used in vm_reset */
|
||||||
static void vm_pop_frame(VM *vm);
|
static void vm_pop_frame(VM *vm);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset the VM to a clean state.
|
||||||
|
*
|
||||||
|
* Pops all frames (releasing local variables), clears the operand stack and
|
||||||
|
* globals, resets the output buffer and debugger state, and zeros the exit code.
|
||||||
|
*
|
||||||
|
* @param vm VM instance to reset.
|
||||||
|
*/
|
||||||
void vm_reset(VM *vm) {
|
void vm_reset(VM *vm) {
|
||||||
// Pop all frames (free locals)
|
// Pop all frames (free locals)
|
||||||
while (vm->fp >= 0) {
|
while (vm->fp >= 0) {
|
||||||
|
|
@ -319,6 +400,11 @@ void vm_reset(VM *vm) {
|
||||||
vm_debug_reset(vm);
|
vm_debug_reset(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Print all non-nil global variables to stdout for debugging.
|
||||||
|
*
|
||||||
|
* @param vm VM whose globals should be dumped.
|
||||||
|
*/
|
||||||
void vm_dump_globals(VM *vm) {
|
void vm_dump_globals(VM *vm) {
|
||||||
printf("=== globals ===\n");
|
printf("=== globals ===\n");
|
||||||
for (int i = 0; i < MAX_GLOBALS; ++i) {
|
for (int i = 0; i < MAX_GLOBALS; ++i) {
|
||||||
|
|
@ -333,6 +419,13 @@ void vm_dump_globals(VM *vm) {
|
||||||
|
|
||||||
/* --- Debugger API impl --- */
|
/* --- Debugger API impl --- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset debugger state: breakpoints and stepping controls.
|
||||||
|
*
|
||||||
|
* Clears all breakpoints, disables stepping modes, and resets counters.
|
||||||
|
*
|
||||||
|
* @param vm VM instance with debugger state to reset.
|
||||||
|
*/
|
||||||
void vm_debug_reset(VM *vm) {
|
void vm_debug_reset(VM *vm) {
|
||||||
for (int i = 0; i < vm->break_count; ++i) {
|
for (int i = 0; i < vm->break_count; ++i) {
|
||||||
if (vm->breakpoints[i].file) {
|
if (vm->breakpoints[i].file) {
|
||||||
|
|
@ -349,6 +442,14 @@ void vm_debug_reset(VM *vm) {
|
||||||
vm->debug_stop_requested = 0;
|
vm->debug_stop_requested = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add a source breakpoint.
|
||||||
|
*
|
||||||
|
* @param vm VM instance.
|
||||||
|
* @param file Source file path (must not be NULL).
|
||||||
|
* @param line One-based source line number (> 0).
|
||||||
|
* @return Breakpoint id (>=0) on success, -1 on failure (invalid args or full).
|
||||||
|
*/
|
||||||
int vm_debug_add_breakpoint(VM *vm, const char *file, int line) {
|
int vm_debug_add_breakpoint(VM *vm, const char *file, int line) {
|
||||||
if (!file || line <= 0) return -1;
|
if (!file || line <= 0) return -1;
|
||||||
if (vm->break_count >= (int)(sizeof(vm->breakpoints) / sizeof(vm->breakpoints[0]))) return -1;
|
if (vm->break_count >= (int)(sizeof(vm->breakpoints) / sizeof(vm->breakpoints[0]))) return -1;
|
||||||
|
|
@ -359,6 +460,15 @@ int vm_debug_add_breakpoint(VM *vm, const char *file, int line) {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Delete a breakpoint by id.
|
||||||
|
*
|
||||||
|
* Compacts the internal breakpoint list to keep ids dense.
|
||||||
|
*
|
||||||
|
* @param vm VM instance.
|
||||||
|
* @param id Breakpoint identifier previously returned by add.
|
||||||
|
* @return 1 if deleted, 0 if id was invalid.
|
||||||
|
*/
|
||||||
int vm_debug_delete_breakpoint(VM *vm, int id) {
|
int vm_debug_delete_breakpoint(VM *vm, int id) {
|
||||||
if (id < 0 || id >= vm->break_count) return 0;
|
if (id < 0 || id >= vm->break_count) return 0;
|
||||||
if (vm->breakpoints[id].file) free(vm->breakpoints[id].file);
|
if (vm->breakpoints[id].file) free(vm->breakpoints[id].file);
|
||||||
|
|
@ -374,10 +484,20 @@ int vm_debug_delete_breakpoint(VM *vm, int id) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove all breakpoints from the VM.
|
||||||
|
*
|
||||||
|
* @param vm VM instance.
|
||||||
|
*/
|
||||||
void vm_debug_clear_breakpoints(VM *vm) {
|
void vm_debug_clear_breakpoints(VM *vm) {
|
||||||
vm_debug_reset(vm);
|
vm_debug_reset(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Print active breakpoints to stdout.
|
||||||
|
*
|
||||||
|
* @param vm VM instance.
|
||||||
|
*/
|
||||||
void vm_debug_list_breakpoints(VM *vm) {
|
void vm_debug_list_breakpoints(VM *vm) {
|
||||||
if (vm->break_count <= 0) {
|
if (vm->break_count <= 0) {
|
||||||
printf("(no breakpoints)\n");
|
printf("(no breakpoints)\n");
|
||||||
|
|
@ -389,12 +509,22 @@ void vm_debug_list_breakpoints(VM *vm) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Request single-step execution (stop after next instruction).
|
||||||
|
*
|
||||||
|
* @param vm VM instance.
|
||||||
|
*/
|
||||||
void vm_debug_request_step(VM *vm) {
|
void vm_debug_request_step(VM *vm) {
|
||||||
vm->debug_step_mode = 1; // step
|
vm->debug_step_mode = 1; // step
|
||||||
vm->debug_step_start_ic = vm->instr_count;
|
vm->debug_step_start_ic = vm->instr_count;
|
||||||
vm->debug_stop_requested = 0;
|
vm->debug_stop_requested = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Request step-over (stop after next instruction in current frame).
|
||||||
|
*
|
||||||
|
* @param vm VM instance.
|
||||||
|
*/
|
||||||
void vm_debug_request_next(VM *vm) {
|
void vm_debug_request_next(VM *vm) {
|
||||||
vm->debug_step_mode = 2; // next (step over)
|
vm->debug_step_mode = 2; // next (step over)
|
||||||
vm->debug_step_target_fp = vm->fp;
|
vm->debug_step_target_fp = vm->fp;
|
||||||
|
|
@ -402,17 +532,35 @@ void vm_debug_request_next(VM *vm) {
|
||||||
vm->debug_stop_requested = 0;
|
vm->debug_stop_requested = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Request finish (run until the current frame returns).
|
||||||
|
*
|
||||||
|
* @param vm VM instance.
|
||||||
|
*/
|
||||||
void vm_debug_request_finish(VM *vm) {
|
void vm_debug_request_finish(VM *vm) {
|
||||||
vm->debug_step_mode = 3; // finish (until return)
|
vm->debug_step_mode = 3; // finish (until return)
|
||||||
vm->debug_step_target_fp = vm->fp;
|
vm->debug_step_target_fp = vm->fp;
|
||||||
vm->debug_stop_requested = 0;
|
vm->debug_stop_requested = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Resume normal execution (clear stepping state and stop flag).
|
||||||
|
*
|
||||||
|
* @param vm VM instance.
|
||||||
|
*/
|
||||||
void vm_debug_request_continue(VM *vm) {
|
void vm_debug_request_continue(VM *vm) {
|
||||||
vm->debug_step_mode = 0;
|
vm->debug_step_mode = 0;
|
||||||
vm->debug_stop_requested = 0;
|
vm->debug_stop_requested = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Push a Value onto the VM operand stack.
|
||||||
|
*
|
||||||
|
* Takes ownership of the provided Value. Aborts execution on overflow.
|
||||||
|
*
|
||||||
|
* @param vm VM instance.
|
||||||
|
* @param v Value to push (ownership transferred).
|
||||||
|
*/
|
||||||
static void push_value(VM *vm, Value v) {
|
static void push_value(VM *vm, Value v) {
|
||||||
if (vm->sp >= STACK_SIZE - 1) {
|
if (vm->sp >= STACK_SIZE - 1) {
|
||||||
fprintf(stderr, "Runtime error: stack overflow\n");
|
fprintf(stderr, "Runtime error: stack overflow\n");
|
||||||
|
|
@ -421,6 +569,14 @@ static void push_value(VM *vm, Value v) {
|
||||||
vm->stack[++vm->sp] = v; /* take ownership of v */
|
vm->stack[++vm->sp] = v; /* take ownership of v */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Pop a Value from the VM operand stack.
|
||||||
|
*
|
||||||
|
* Caller takes ownership of the returned Value. Aborts execution on underflow.
|
||||||
|
*
|
||||||
|
* @param vm VM instance.
|
||||||
|
* @return The top Value from the stack.
|
||||||
|
*/
|
||||||
static Value pop_value(VM *vm) {
|
static Value pop_value(VM *vm) {
|
||||||
if (vm->sp < 0) {
|
if (vm->sp < 0) {
|
||||||
fprintf(stderr, "Runtime error: stack underflow\n");
|
fprintf(stderr, "Runtime error: stack underflow\n");
|
||||||
|
|
@ -430,6 +586,15 @@ static Value pop_value(VM *vm) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- C ABI helpers for Rust FFI --- */
|
/* --- C ABI helpers for Rust FFI --- */
|
||||||
|
/**
|
||||||
|
* @brief Pop a numeric Value and convert it to a 64-bit integer (C ABI helper).
|
||||||
|
*
|
||||||
|
* Accepts int or float Values on the stack. Other types raise a runtime type
|
||||||
|
* error. The popped Value is freed.
|
||||||
|
*
|
||||||
|
* @param vm VM instance.
|
||||||
|
* @return The numeric value converted to int64_t.
|
||||||
|
*/
|
||||||
int64_t vm_pop_i64(VM *vm) {
|
int64_t vm_pop_i64(VM *vm) {
|
||||||
Value v = pop_value(vm);
|
Value v = pop_value(vm);
|
||||||
int64_t out = 0;
|
int64_t out = 0;
|
||||||
|
|
@ -447,39 +612,89 @@ int64_t vm_pop_i64(VM *vm) {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Push a 64-bit integer as a VM int Value (C ABI helper).
|
||||||
|
*
|
||||||
|
* @param vm VM instance.
|
||||||
|
* @param v Integer value to push.
|
||||||
|
*/
|
||||||
void vm_push_i64(VM *vm, int64_t v) {
|
void vm_push_i64(VM *vm, int64_t v) {
|
||||||
push_value(vm, make_int(v));
|
push_value(vm, make_int(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Extended C ABI for Rust to access VM internals (unsafe) --- */
|
/* --- Extended C ABI for Rust to access VM internals (unsafe) --- */
|
||||||
|
/**
|
||||||
|
* @brief Return sizeof(VM) for external FFI consumers.
|
||||||
|
*
|
||||||
|
* @return Size of the VM struct in bytes.
|
||||||
|
*/
|
||||||
size_t vm_sizeof(void) {
|
size_t vm_sizeof(void) {
|
||||||
return sizeof(VM);
|
return sizeof(VM);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return sizeof(Value) for external FFI consumers.
|
||||||
|
*
|
||||||
|
* @return Size of the Value struct in bytes.
|
||||||
|
*/
|
||||||
size_t vm_value_sizeof(void) {
|
size_t vm_value_sizeof(void) {
|
||||||
return sizeof(Value);
|
return sizeof(Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Cast the VM pointer to an opaque mutable void* (unsafe FFI helper).
|
||||||
|
*
|
||||||
|
* @param vm VM instance pointer.
|
||||||
|
* @return The same pointer reinterpreted as void*.
|
||||||
|
*/
|
||||||
void *vm_as_mut_ptr(VM *vm) {
|
void *vm_as_mut_ptr(VM *vm) {
|
||||||
return (void *)vm;
|
return (void *)vm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Obtain offsetof(VM, exit_code) for FFI struct field access.
|
||||||
|
*
|
||||||
|
* @return Byte offset of the exit_code field within VM.
|
||||||
|
*/
|
||||||
size_t vm_offset_of_exit_code(void) {
|
size_t vm_offset_of_exit_code(void) {
|
||||||
return offsetof(VM, exit_code);
|
return offsetof(VM, exit_code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Obtain offsetof(VM, sp) for FFI struct field access.
|
||||||
|
*
|
||||||
|
* @return Byte offset of the sp field within VM.
|
||||||
|
*/
|
||||||
size_t vm_offset_of_sp(void) {
|
size_t vm_offset_of_sp(void) {
|
||||||
return offsetof(VM, sp);
|
return offsetof(VM, sp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Obtain offsetof(VM, stack) for FFI struct field access.
|
||||||
|
*
|
||||||
|
* @return Byte offset of the stack field within VM.
|
||||||
|
*/
|
||||||
size_t vm_offset_of_stack(void) {
|
size_t vm_offset_of_stack(void) {
|
||||||
return offsetof(VM, stack);
|
return offsetof(VM, stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Obtain offsetof(VM, globals) for FFI struct field access.
|
||||||
|
*
|
||||||
|
* @return Byte offset of the globals field within VM.
|
||||||
|
*/
|
||||||
size_t vm_offset_of_globals(void) {
|
size_t vm_offset_of_globals(void) {
|
||||||
return offsetof(VM, globals);
|
return offsetof(VM, globals);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize a call frame to a clean state.
|
||||||
|
*
|
||||||
|
* Sets function pointer and instruction pointer, zeroes locals to nil and
|
||||||
|
* resets the try-stack pointer.
|
||||||
|
*
|
||||||
|
* @param f Frame to initialize.
|
||||||
|
*/
|
||||||
static void frame_init(Frame *f) {
|
static void frame_init(Frame *f) {
|
||||||
f->fn = NULL;
|
f->fn = NULL;
|
||||||
f->ip = 0;
|
f->ip = 0;
|
||||||
|
|
@ -488,6 +703,14 @@ static void frame_init(Frame *f) {
|
||||||
f->try_sp = -1;
|
f->try_sp = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize a VM instance to its default state.
|
||||||
|
*
|
||||||
|
* Resets stack/frame pointers, output buffers, instruction counters, debugger
|
||||||
|
* state and globals. Does not allocate memory.
|
||||||
|
*
|
||||||
|
* @param vm VM instance to initialize.
|
||||||
|
*/
|
||||||
void vm_init(VM *vm) {
|
void vm_init(VM *vm) {
|
||||||
vm->sp = -1;
|
vm->sp = -1;
|
||||||
vm->fp = -1;
|
vm->fp = -1;
|
||||||
|
|
@ -517,6 +740,17 @@ void vm_init(VM *vm) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* push a new frame, transferring ownership of args[] into frame->locals[0..argc-1] */
|
/* push a new frame, transferring ownership of args[] into frame->locals[0..argc-1] */
|
||||||
|
/**
|
||||||
|
* @brief Push a new call frame for a function and transfer arguments.
|
||||||
|
*
|
||||||
|
* The first argc Values from args are moved (ownership transfer) into the new
|
||||||
|
* frame's local slots starting at index 0. Aborts on frame stack overflow.
|
||||||
|
*
|
||||||
|
* @param vm VM instance.
|
||||||
|
* @param fn Function bytecode to execute in the new frame.
|
||||||
|
* @param argc Number of arguments provided.
|
||||||
|
* @param args Array of argument Values (may be NULL if argc == 0).
|
||||||
|
*/
|
||||||
static void vm_push_frame(VM *vm, Bytecode *fn, int argc, Value *args) {
|
static void vm_push_frame(VM *vm, Bytecode *fn, int argc, Value *args) {
|
||||||
if (vm->fp >= MAX_FRAMES - 1) {
|
if (vm->fp >= MAX_FRAMES - 1) {
|
||||||
fprintf(stderr, "Runtime error: too many frames\n");
|
fprintf(stderr, "Runtime error: too many frames\n");
|
||||||
|
|
@ -533,6 +767,13 @@ static void vm_push_frame(VM *vm, Bytecode *fn, int argc, Value *args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* pop current frame and free its locals */
|
/* pop current frame and free its locals */
|
||||||
|
/**
|
||||||
|
* @brief Pop the current call frame and free its local variables.
|
||||||
|
*
|
||||||
|
* Aborts if there is no active frame.
|
||||||
|
*
|
||||||
|
* @param vm VM instance.
|
||||||
|
*/
|
||||||
static void vm_pop_frame(VM *vm) {
|
static void vm_pop_frame(VM *vm) {
|
||||||
if (vm->fp < 0) {
|
if (vm->fp < 0) {
|
||||||
fprintf(stderr, "Runtime error: pop frame with empty frame stack\n");
|
fprintf(stderr, "Runtime error: pop frame with empty frame stack\n");
|
||||||
|
|
@ -546,6 +787,13 @@ static void vm_pop_frame(VM *vm) {
|
||||||
vm->fp--;
|
vm->fp--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Print the VM's buffered output values to stdout.
|
||||||
|
*
|
||||||
|
* Emits a newline after each value unless the corresponding partial flag is set.
|
||||||
|
*
|
||||||
|
* @param vm VM instance whose output should be printed.
|
||||||
|
*/
|
||||||
void vm_print_output(VM *vm) {
|
void vm_print_output(VM *vm) {
|
||||||
for (int i = 0; i < vm->output_count; ++i) {
|
for (int i = 0; i < vm->output_count; ++i) {
|
||||||
print_value(&vm->output[i]);
|
print_value(&vm->output[i]);
|
||||||
|
|
@ -555,6 +803,16 @@ void vm_print_output(VM *vm) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Execute a bytecode program starting from the given entry point.
|
||||||
|
*
|
||||||
|
* Sets up the initial frame and runs the main interpreter loop until there are
|
||||||
|
* no more frames. Honors debugger stepping/finish/continue requests and, when
|
||||||
|
* enabled, traps exit paths to enter a REPL via on_error_repl.
|
||||||
|
*
|
||||||
|
* @param vm VM instance to run.
|
||||||
|
* @param entry Entry function bytecode (must not be NULL).
|
||||||
|
*/
|
||||||
void vm_run(VM *vm, Bytecode *entry) {
|
void vm_run(VM *vm, Bytecode *entry) {
|
||||||
/* reset instruction count for this run */
|
/* reset instruction count for this run */
|
||||||
vm->instr_count = 0;
|
vm->instr_count = 0;
|
||||||
|
|
|
||||||
119
src/vm.h
119
src/vm.h
|
|
@ -7,6 +7,15 @@
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file vm.h
|
||||||
|
* @brief Core virtual machine data structures and public VM API.
|
||||||
|
*
|
||||||
|
* Declares the execution stack/frame layout, global state of the Fun VM,
|
||||||
|
* human-readable opcode names for diagnostics, and the public functions for
|
||||||
|
* initializing, running, resetting, and debugging the VM. FFI helper
|
||||||
|
* declarations for Rust/C++ experiments are also exposed here.
|
||||||
|
*/
|
||||||
#ifndef FUN_VM_H
|
#ifndef FUN_VM_H
|
||||||
#define FUN_VM_H
|
#define FUN_VM_H
|
||||||
|
|
||||||
|
|
@ -74,6 +83,13 @@ static const char *opcode_names[] = {
|
||||||
/* C++ demo */
|
/* C++ demo */
|
||||||
"CPP_ADD"};
|
"CPP_ADD"};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Call frame representing one active function invocation.
|
||||||
|
*
|
||||||
|
* Each frame keeps a pointer to its function bytecode, the current
|
||||||
|
* instruction pointer within that bytecode, a fixed-size array of local
|
||||||
|
* variables, and a small try/catch stack for exception handling.
|
||||||
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
Bytecode *fn;
|
Bytecode *fn;
|
||||||
int ip;
|
int ip;
|
||||||
|
|
@ -83,6 +99,14 @@ typedef struct {
|
||||||
int try_sp; /* -1 when empty */
|
int try_sp; /* -1 when empty */
|
||||||
} Frame;
|
} Frame;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The Fun virtual machine state.
|
||||||
|
*
|
||||||
|
* Holds the operand stack, call frames, globals, standard output capture,
|
||||||
|
* runtime counters, and debugger state. Functions in this header operate on
|
||||||
|
* this structure; callers must ensure proper initialization with vm_init()
|
||||||
|
* before use and call vm_free()/vm_reset() as appropriate.
|
||||||
|
*/
|
||||||
struct VM {
|
struct VM {
|
||||||
Value stack[STACK_SIZE];
|
Value stack[STACK_SIZE];
|
||||||
int sp;
|
int sp;
|
||||||
|
|
@ -120,76 +144,125 @@ struct VM {
|
||||||
int break_count; // number of active breakpoints
|
int break_count; // number of active breakpoints
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @brief Opaque VM alias for external users. */
|
||||||
typedef struct VM VM;
|
typedef struct VM VM;
|
||||||
|
|
||||||
// initialize VM (zero state)
|
/**
|
||||||
|
* @brief Initialize a VM instance to zero/initial state.
|
||||||
|
* @param vm Non-NULL pointer to VM storage to initialize.
|
||||||
|
*/
|
||||||
void vm_init(VM *vm);
|
void vm_init(VM *vm);
|
||||||
|
|
||||||
// helper function to clear the output
|
/**
|
||||||
|
* @brief Clear the buffered output captured by the VM.
|
||||||
|
* @param vm VM instance.
|
||||||
|
*/
|
||||||
void vm_clear_output(VM *vm);
|
void vm_clear_output(VM *vm);
|
||||||
|
/**
|
||||||
|
* @brief Print buffered output entries to stdout (debug aid).
|
||||||
|
* @param vm VM instance.
|
||||||
|
*/
|
||||||
void vm_print_output(VM *vm);
|
void vm_print_output(VM *vm);
|
||||||
|
/**
|
||||||
|
* @brief Free all resources owned by the VM (globals, frames, output buffers).
|
||||||
|
* The VM object itself is not freed when allocated on the stack.
|
||||||
|
* @param vm VM instance to dispose.
|
||||||
|
*/
|
||||||
void vm_free(VM *vm);
|
void vm_free(VM *vm);
|
||||||
|
|
||||||
// reset VM to initial state (free globals/locals/output; keep VM object)
|
/**
|
||||||
|
* @brief Reset VM to initial state, freeing globals/locals/output.
|
||||||
|
* The VM object remains valid for reuse after this call.
|
||||||
|
* @param vm VM instance to reset.
|
||||||
|
*/
|
||||||
void vm_reset(VM *vm);
|
void vm_reset(VM *vm);
|
||||||
|
|
||||||
// print non-nil globals (index and value) to stdout
|
/**
|
||||||
|
* @brief Print non-nil globals (index and value) to stdout.
|
||||||
|
* @param vm VM instance.
|
||||||
|
*/
|
||||||
void vm_dump_globals(VM *vm);
|
void vm_dump_globals(VM *vm);
|
||||||
// run entry Bytecode (pushes first frame)
|
/**
|
||||||
|
* @brief Execute the provided entry bytecode in the VM.
|
||||||
|
* Pushes an initial frame and runs until HALT or an unrecoverable error.
|
||||||
|
* @param vm VM instance.
|
||||||
|
* @param entry Entry bytecode to execute; must outlive the call.
|
||||||
|
*/
|
||||||
void vm_run(VM *vm, Bytecode *entry);
|
void vm_run(VM *vm, Bytecode *entry);
|
||||||
|
|
||||||
/* Raise a runtime error that respects try/catch/finally.
|
/**
|
||||||
* If a try handler is active in the current frame, control jumps to it
|
* @brief Raise a runtime error honoring active try/catch/finally handlers.
|
||||||
* with an error string pushed on the stack. Otherwise, prints the error
|
* If a try handler is active in the current frame, control jumps to it with
|
||||||
* (annotated with location) and terminates execution. */
|
* an error string pushed on the stack. Otherwise, prints the error (with
|
||||||
|
* location) and terminates execution.
|
||||||
|
* @param vm VM instance.
|
||||||
|
* @param msg Null-terminated error message.
|
||||||
|
*/
|
||||||
void vm_raise_error(VM *vm, const char *msg);
|
void vm_raise_error(VM *vm, const char *msg);
|
||||||
|
|
||||||
/* --- Debugger API --- */
|
/* --- Debugger API --- */
|
||||||
|
/** Reset debugger state (clear step mode and breakpoints). */
|
||||||
void vm_debug_reset(VM *vm);
|
void vm_debug_reset(VM *vm);
|
||||||
int vm_debug_add_breakpoint(VM *vm, const char *file, int line); // returns id >=0 or -1
|
/** Add a breakpoint at file:line; returns non-negative id on success or -1. */
|
||||||
int vm_debug_delete_breakpoint(VM *vm, int id); // returns 1 on success
|
int vm_debug_add_breakpoint(VM *vm, const char *file, int line);
|
||||||
|
/** Delete a breakpoint by id; returns 1 on success, 0 on failure. */
|
||||||
|
int vm_debug_delete_breakpoint(VM *vm, int id);
|
||||||
|
/** Remove all breakpoints. */
|
||||||
void vm_debug_clear_breakpoints(VM *vm);
|
void vm_debug_clear_breakpoints(VM *vm);
|
||||||
|
/** Print the current list of breakpoints to stdout. */
|
||||||
void vm_debug_list_breakpoints(VM *vm);
|
void vm_debug_list_breakpoints(VM *vm);
|
||||||
|
/** Request single-step execution mode. */
|
||||||
void vm_debug_request_step(VM *vm);
|
void vm_debug_request_step(VM *vm);
|
||||||
|
/** Step over (next) within the current frame. */
|
||||||
void vm_debug_request_next(VM *vm);
|
void vm_debug_request_next(VM *vm);
|
||||||
|
/** Run until the current frame returns (finish). */
|
||||||
void vm_debug_request_finish(VM *vm);
|
void vm_debug_request_finish(VM *vm);
|
||||||
|
/** Continue execution until next breakpoint/stop. */
|
||||||
void vm_debug_request_continue(VM *vm);
|
void vm_debug_request_continue(VM *vm);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check whether an integer value corresponds to a defined opcode.
|
||||||
|
* @param op Numeric opcode to validate.
|
||||||
|
* @return 1 if valid, 0 otherwise.
|
||||||
|
*/
|
||||||
static inline int opcode_is_valid(int op) {
|
static inline int opcode_is_valid(int op) {
|
||||||
return op >= OP_NOP && op <= OP_CPP_ADD; // all current opcodes
|
return op >= OP_NOP && op <= OP_CPP_ADD; // all current opcodes
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Minimal C ABI helpers for FFI (Rust opcode experiments) --- */
|
/* --- Minimal C ABI helpers for FFI (Rust opcode experiments) --- */
|
||||||
/* Pop an int64 from VM stack (errors if not an int/float); returns integer-converted value. */
|
/** Pop an int64 from the VM stack; accepts int/float; returns truncated value. */
|
||||||
int64_t vm_pop_i64(VM *vm);
|
int64_t vm_pop_i64(VM *vm);
|
||||||
/* Push an int64 onto VM stack. */
|
/** Push an int64 onto the VM stack. */
|
||||||
void vm_push_i64(VM *vm, int64_t v);
|
void vm_push_i64(VM *vm, int64_t v);
|
||||||
|
|
||||||
/* Example Rust-implemented opcode (adds top two ints on stack) */
|
/** Example Rust-implemented opcode (adds top two ints on stack). */
|
||||||
int fun_op_radd(VM *vm);
|
int fun_op_radd(VM *vm);
|
||||||
|
|
||||||
/* Example Rust function returning a demo C string (null-terminated). */
|
/** Return a demo null-terminated C string owned by Rust. */
|
||||||
const char *fun_rust_get_string(void);
|
const char *fun_rust_get_string(void);
|
||||||
/* Rust function that prints a passed C string, returns 0 on success. */
|
/** Print a C string via Rust; returns 0 on success. */
|
||||||
int fun_rust_print_string(const char *msg);
|
int fun_rust_print_string(const char *msg);
|
||||||
/* Rust function that returns a newly allocated duplicate of the input C string. */
|
/** Return a newly allocated duplicate of the input C string (caller frees). */
|
||||||
char *fun_rust_echo_string(const char *input);
|
char *fun_rust_echo_string(const char *input);
|
||||||
/* Free a C string previously returned by fun_rust_echo_string. */
|
/** Free a C string previously returned by fun_rust_echo_string(). */
|
||||||
void fun_rust_string_free(char *ptr);
|
void fun_rust_string_free(char *ptr);
|
||||||
|
|
||||||
/* C++ demo opcode entry point (C ABI) */
|
/** C++ demo opcode entry point (C ABI). */
|
||||||
int fun_op_cpp_add(struct VM *vm);
|
int fun_op_cpp_add(struct VM *vm);
|
||||||
|
|
||||||
/* --- Extended C ABI for Rust to access VM internals (unsafe) --- */
|
/* --- Extended C ABI for Rust to access VM internals (unsafe) --- */
|
||||||
/* Size helpers for Rust side to compute offsets and do pointer math */
|
/** Size of struct VM in bytes. */
|
||||||
size_t vm_sizeof(void);
|
size_t vm_sizeof(void);
|
||||||
|
/** Size of struct Value in bytes. */
|
||||||
size_t vm_value_sizeof(void);
|
size_t vm_value_sizeof(void);
|
||||||
|
|
||||||
/* Get a mutable byte pointer to the VM object. Extremely unsafe; intended for
|
/**
|
||||||
* low-level FFI where Rust wants parity access with C code. */
|
* @brief Get a mutable byte pointer to the VM object.
|
||||||
|
* Extremely unsafe; for low-level FFI use only.
|
||||||
|
*/
|
||||||
void *vm_as_mut_ptr(VM *vm);
|
void *vm_as_mut_ptr(VM *vm);
|
||||||
|
|
||||||
/* Offsets of commonly accessed VM fields to avoid re-declaring the struct layout in Rust */
|
/** Offsets of commonly accessed VM fields (for Rust FFI). */
|
||||||
size_t vm_offset_of_exit_code(void);
|
size_t vm_offset_of_exit_code(void);
|
||||||
size_t vm_offset_of_sp(void);
|
size_t vm_offset_of_sp(void);
|
||||||
size_t vm_offset_of_stack(void);
|
size_t vm_offset_of_stack(void);
|
||||||
|
|
|
||||||
|
|
@ -9,24 +9,28 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file div.c
|
* @file div.c
|
||||||
* @brief Implements the OP_DIV opcode for integer division in the VM.
|
* @brief Implements the OP_DIV opcode (numeric division) in the VM.
|
||||||
*
|
*
|
||||||
* This file handles the OP_DIV instruction, which performs integer division
|
* Handles the OP_DIV instruction, dividing two numeric operands and pushing
|
||||||
* on two integer values popped from the stack and pushes the result back onto the stack.
|
* the result. If either operand is a float, division is performed in double
|
||||||
|
* precision and a VAL_FLOAT is produced; otherwise integer division is
|
||||||
|
* used and a VAL_INT is produced.
|
||||||
*
|
*
|
||||||
* Behavior:
|
* Behavior:
|
||||||
* - Pops two integer values from the stack.
|
* - Pops two values from the stack.
|
||||||
* - Performs integer division (`a / b`).
|
* - If any operand is VAL_FLOAT, computes (double)a / (double)b and pushes a VAL_FLOAT.
|
||||||
* - Pushes the result back onto the stack.
|
* - Else computes a.i / b.i and pushes a VAL_INT.
|
||||||
*
|
*
|
||||||
* Error Handling:
|
* Error Handling:
|
||||||
* - Exits with an error if the operands are not integers.
|
* - Raises a runtime error and aborts execution if operands are not numeric.
|
||||||
* - Exits with an error if division by zero is attempted.
|
* - Raises a runtime error on division by zero (both integer and floating cases).
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* // Bytecode: OP_DIV
|
* // Bytecode: OP_DIV
|
||||||
* // Stack before: [10, 2]
|
* // Stack before: [10, 2]
|
||||||
* // Stack after: [5]
|
* // Stack after: [5]
|
||||||
|
* // Stack before: [5.0, 2]
|
||||||
|
* // Stack after: [2.5]
|
||||||
*
|
*
|
||||||
* @author Johannes Findeisen
|
* @author Johannes Findeisen
|
||||||
* @date 2025-10-16
|
* @date 2025-10-16
|
||||||
|
|
|
||||||
|
|
@ -9,23 +9,27 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file mul.c
|
* @file mul.c
|
||||||
* @brief Implements the OP_MUL opcode for integer multiplication in the VM.
|
* @brief Implements the OP_MUL opcode (numeric multiplication) in the VM.
|
||||||
*
|
*
|
||||||
* This file handles the OP_MUL instruction, which performs integer multiplication
|
* Handles the OP_MUL instruction, multiplying two numeric operands and
|
||||||
* on two integer values popped from the stack and pushes the result back onto the stack.
|
* pushing the result. If either operand is a float, multiplication is
|
||||||
|
* performed in double precision; otherwise it is 64-bit integer
|
||||||
|
* multiplication.
|
||||||
*
|
*
|
||||||
* Behavior:
|
* Behavior:
|
||||||
* - Pops two integer values from the stack.
|
* - Pops two values from the stack.
|
||||||
* - Performs integer multiplication (`a * b`).
|
* - If any operand is VAL_FLOAT, computes (double)a * (double)b and pushes a VAL_FLOAT.
|
||||||
* - Pushes the result back onto the stack.
|
* - Else computes a.i * b.i and pushes a VAL_INT.
|
||||||
*
|
*
|
||||||
* Error Handling:
|
* Error Handling:
|
||||||
* - Exits with an error if the operands are not integers.
|
* - Raises a runtime error and aborts execution if operands are not numeric.
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* // Bytecode: OP_MUL
|
* // Bytecode: OP_MUL
|
||||||
* // Stack before: [3, 4]
|
* // Stack before: [3, 4]
|
||||||
* // Stack after: [12]
|
* // Stack after: [12]
|
||||||
|
* // Stack before: [2.5, 4]
|
||||||
|
* // Stack after: [10.0]
|
||||||
*
|
*
|
||||||
* @author Johannes Findeisen
|
* @author Johannes Findeisen
|
||||||
* @date 2025-10-16
|
* @date 2025-10-16
|
||||||
|
|
|
||||||
|
|
@ -9,23 +9,27 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file sub.c
|
* @file sub.c
|
||||||
* @brief Implements the OP_SUB opcode for integer subtraction in the VM.
|
* @brief Implements the OP_SUB opcode (numeric subtraction) in the VM.
|
||||||
*
|
*
|
||||||
* This file handles the OP_SUB instruction, which performs integer subtraction
|
* Handles the OP_SUB instruction, subtracting two numeric operands and
|
||||||
* on two integer values popped from the stack and pushes the result back onto the stack.
|
* pushing the result. If either operand is a float, subtraction is
|
||||||
|
* performed in double precision; otherwise it is 64-bit integer
|
||||||
|
* subtraction.
|
||||||
*
|
*
|
||||||
* Behavior:
|
* Behavior:
|
||||||
* - Pops two integer values from the stack.
|
* - Pops two values from the stack.
|
||||||
* - Performs integer subtraction (`a - b`).
|
* - If any operand is VAL_FLOAT, computes (double)a - (double)b and pushes a VAL_FLOAT.
|
||||||
* - Pushes the result back onto the stack.
|
* - Else computes a.i - b.i and pushes a VAL_INT.
|
||||||
*
|
*
|
||||||
* Error Handling:
|
* Error Handling:
|
||||||
* - Exits with an error if the operands are not integers.
|
* - Raises a runtime error and aborts execution if operands are not numeric.
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* // Bytecode: OP_SUB
|
* // Bytecode: OP_SUB
|
||||||
* // Stack before: [10, 4]
|
* // Stack before: [10, 4]
|
||||||
* // Stack after: [6]
|
* // Stack after: [6]
|
||||||
|
* // Stack before: [10.0, 3]
|
||||||
|
* // Stack after: [7.0]
|
||||||
*
|
*
|
||||||
* @author Johannes Findeisen
|
* @author Johannes Findeisen
|
||||||
* @date 2025-10-16
|
* @date 2025-10-16
|
||||||
|
|
|
||||||
|
|
@ -8,26 +8,27 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file clear.c
|
* @file clear.c
|
||||||
* @brief Implements the OP_CLEAR opcode for clearing arrays in the VM.
|
* @brief Implements the OP_CLEAR opcode for clearing arrays in the VM.
|
||||||
*
|
|
||||||
* This file handles the OP_CLEAR instruction, which clears all elements from an array.
|
* Handles the OP_CLEAR instruction, which removes all elements from an array.
|
||||||
* The array is popped from the stack, and nothing is pushed back.
|
* The array is popped from the stack; the opcode pushes an integer result
|
||||||
*
|
* (currently 0) as an acknowledgement.
|
||||||
|
|
||||||
* Behavior:
|
* Behavior:
|
||||||
* - Pops the array from the stack.
|
* - Pops the array from the stack.
|
||||||
* - Clears all elements from the array.
|
* - Clears all elements from the array (array becomes empty in place).
|
||||||
*
|
* - Pushes 0 (integer) to acknowledge success.
|
||||||
* Error Handling:
|
|
||||||
* - Exits with an error if the array is of the wrong type.
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
|
|
||||||
|
* Error Handling:
|
||||||
|
* - Exits with a runtime error if the operand is not an array.
|
||||||
|
|
||||||
|
* Example:
|
||||||
* // Bytecode: OP_CLEAR
|
* // Bytecode: OP_CLEAR
|
||||||
* // Stack before: [[10, 20, 30]]
|
* // Stack before: [[10, 20, 30]]
|
||||||
* // Stack after: []
|
* // Stack after: [0]
|
||||||
*
|
|
||||||
* @author Johannes Findeise
|
* @author Johannes Findeisen
|
||||||
* @date 2025-10-16
|
* @date 2025-10-16
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
* // Stack before: [1, 2, 3]
|
* // Stack before: [1, 2, 3]
|
||||||
* // Stack after: [[1, 2, 3]]
|
* // Stack after: [[1, 2, 3]]
|
||||||
*
|
*
|
||||||
* @author Johanes Findeisen
|
* @author Johannes Findeisen
|
||||||
* @date 2025-10-16
|
* @date 2025-10-16
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file arr_push.c
|
* @file push.c
|
||||||
* @brief Implements the OP_ARR_PUSH opcode for appending elements to arrays in the VM.
|
* @brief Implements the OP_PUSH opcode for appending elements to arrays in the VM.
|
||||||
*
|
*
|
||||||
* This file handles the OP_ARR_PUSH instruction, which appends a value to the end of an array.
|
* Handles the OP_PUSH instruction, which appends a value to the end of an array.
|
||||||
* The array and value are popped from the stack, and the new length of the array is pushed back onto the stack.
|
* The array and value are popped from the stack; the opcode pushes the new array
|
||||||
|
* length (integer) as a result.
|
||||||
*
|
*
|
||||||
* Behavior:
|
* Behavior:
|
||||||
* - Pops the value and array from the stack.
|
* - Pops the value and array from the stack.
|
||||||
|
|
@ -20,11 +21,11 @@
|
||||||
* - Pushes the new length of the array onto the stack.
|
* - Pushes the new length of the array onto the stack.
|
||||||
*
|
*
|
||||||
* Error Handling:
|
* Error Handling:
|
||||||
* - Exits with an error if the array is of the wrong type.
|
* - Exits with a runtime error if the first operand is not an array.
|
||||||
* - Exits with an error if memory allocation fails during the append operation.
|
* - Exits with a runtime error if memory allocation fails.
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* // Bytecode: OP_ARR_PUSH
|
* // Bytecode: OP_PUSH
|
||||||
* // Stack before: [42, [10, 20, 30]]
|
* // Stack before: [42, [10, 20, 30]]
|
||||||
* // Stack after: [4]
|
* // Stack after: [4]
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file arr_remove.c
|
* @file remove.c
|
||||||
* @brief Implements the OP_ARR_REMOVE opcode for removing elements from arrays in the VM.
|
* @brief Implements the OP_REMOVE opcode for removing elements from arrays in the VM.
|
||||||
*
|
*
|
||||||
* This file handles the OP_ARR_REMOVE instruction, which removes an element from an array
|
* Handles the OP_REMOVE instruction, which removes an element from an array at the
|
||||||
* at a specified index. The array and index are popped from the stack, and the removed
|
* specified index. The array and index are popped from the stack; the removed
|
||||||
* element is pushed back onto the stack.
|
* element is pushed back onto the stack.
|
||||||
*
|
*
|
||||||
* Behavior:
|
* Behavior:
|
||||||
|
|
@ -21,11 +21,11 @@
|
||||||
* - Pushes the removed element onto the stack.
|
* - Pushes the removed element onto the stack.
|
||||||
*
|
*
|
||||||
* Error Handling:
|
* Error Handling:
|
||||||
* - Exits with an error if the array or index is of the wrong type.
|
* - Exits with a runtime error if the container is not an array or index is not an int.
|
||||||
* - Exits with an error if the index is out of bounds.
|
* - Exits with a runtime error if the index is out of bounds.
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* // Bytecode: OP_ARR_REMOVE
|
* // Bytecode: OP_REMOVE
|
||||||
* // Stack before: [1, [10, 20, 30]]
|
* // Stack before: [1, [10, 20, 30]]
|
||||||
* // Stack after: [20]
|
* // Stack after: [20]
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -8,23 +8,24 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file arr_set.c
|
* @file set.c
|
||||||
* @brief Implements the OP_ARR_SET opcode for setting elements in arrays in the VM.
|
* @brief Implements the OP_SET opcode for setting elements in arrays in the VM.
|
||||||
*
|
*
|
||||||
* This file handles the OP_ARR_SET instruction, which sets a value at a specified index in an array.
|
* Handles the OP_SET instruction, which sets a value at a specified index in an array.
|
||||||
* The value, index, and array are popped from the stack, and the value is pushed back onto the stack.
|
* The value, index, and array are popped from the stack; the value is returned back
|
||||||
|
* on the stack (as a copy) to mirror expression semantics.
|
||||||
*
|
*
|
||||||
* Behavior:
|
* Behavior:
|
||||||
* - Pops the value, index, and array from the stack.
|
* - Pops the value, index, and array from the stack.
|
||||||
* - Sets the value at the specified index in the array.
|
* - Sets the value at the specified index in the array.
|
||||||
* - Pushes the value back onto the stack.
|
* - Pushes a copy of the value back onto the stack.
|
||||||
*
|
*
|
||||||
* Error Handling:
|
* Error Handling:
|
||||||
* - Exits with an error if the array or index is of the wrong type.
|
* - Exits with a runtime error if the array or index is of the wrong type.
|
||||||
* - Exits with an error if the index is out of bounds.
|
* - Exits with a runtime error if the index is out of bounds.
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* // Bytecode: OP_ARR_SET
|
* // Bytecode: OP_SET
|
||||||
* // Stack before: [42, 1, [10, 20, 30]]
|
* // Stack before: [42, 1, [10, 20, 30]]
|
||||||
* // Stack after: [42]
|
* // Stack after: [42]
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,28 @@
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2025-09-29
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file band.c
|
||||||
|
* @brief Implements the OP_BAND opcode (bitwise AND).
|
||||||
|
*
|
||||||
|
* Opcode snippet included by vm.c. Performs a 32-bit unsigned bitwise AND
|
||||||
|
* on two integer operands from the VM stack.
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OP_BAND: bitwise AND (uint32 32-bit)
|
* OP_BAND: bitwise AND (uint32)
|
||||||
* pops: b, a
|
*
|
||||||
* pushes: (uint32_t)(a & b)
|
* Stack effects:
|
||||||
|
* - pops: b, a
|
||||||
|
* - pushes: (uint32_t)(a & b)
|
||||||
|
*
|
||||||
|
* Notes:
|
||||||
|
* - Operands are interpreted as 32-bit unsigned when of type VAL_INT;
|
||||||
|
* non-integer values are treated as 0.
|
||||||
|
* - The result is pushed as VAL_INT with the 32-bit value preserved in the
|
||||||
|
* low bits.
|
||||||
*/
|
*/
|
||||||
case OP_BAND: {
|
case OP_BAND: {
|
||||||
Value vb = pop_value(vm);
|
Value vb = pop_value(vm);
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,28 @@
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2025-09-29
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file bnot.c
|
||||||
|
* @brief Implements the OP_BNOT opcode (bitwise NOT).
|
||||||
|
*
|
||||||
|
* Opcode snippet included by vm.c. Performs a 32-bit unsigned bitwise NOT
|
||||||
|
* on a single integer operand from the VM stack.
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OP_BNOT: bitwise NOT (uint32 32-bit)
|
* OP_BNOT: bitwise NOT (uint32)
|
||||||
* pops: a
|
*
|
||||||
* pushes: (uint32_t)(~a)
|
* Stack effects:
|
||||||
|
* - pops: a
|
||||||
|
* - pushes: (uint32_t)(~a)
|
||||||
|
*
|
||||||
|
* Notes:
|
||||||
|
* - Operand is interpreted as 32-bit unsigned when of type VAL_INT;
|
||||||
|
* non-integer values are treated as 0.
|
||||||
|
* - The result is pushed as VAL_INT with the 32-bit value preserved in the
|
||||||
|
* low bits.
|
||||||
*/
|
*/
|
||||||
case OP_BNOT: {
|
case OP_BNOT: {
|
||||||
Value va = pop_value(vm);
|
Value va = pop_value(vm);
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,28 @@
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2025-09-29
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file bor.c
|
||||||
|
* @brief Implements the OP_BOR opcode (bitwise OR).
|
||||||
|
*
|
||||||
|
* Opcode snippet included by vm.c. Performs a 32-bit unsigned bitwise OR
|
||||||
|
* on two integer operands from the VM stack.
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OP_BOR: bitwise OR (uint32 32-bit)
|
* OP_BOR: bitwise OR (uint32)
|
||||||
* pops: b, a
|
*
|
||||||
* pushes: (uint32_t)(a | b)
|
* Stack effects:
|
||||||
|
* - pops: b, a
|
||||||
|
* - pushes: (uint32_t)(a | b)
|
||||||
|
*
|
||||||
|
* Notes:
|
||||||
|
* - Operands are interpreted as 32-bit unsigned when of type VAL_INT;
|
||||||
|
* non-integer values are treated as 0.
|
||||||
|
* - The result is pushed as VAL_INT with the 32-bit value preserved in the
|
||||||
|
* low bits.
|
||||||
*/
|
*/
|
||||||
case OP_BOR: {
|
case OP_BOR: {
|
||||||
Value vb = pop_value(vm);
|
Value vb = pop_value(vm);
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,28 @@
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2025-09-29
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OP_BXOR: bitwise XOR (uint32 32-bit)
|
* @file bxor.c
|
||||||
* pops: b, a
|
* @brief Implements the OP_BXOR opcode (bitwise XOR).
|
||||||
* pushes: (uint32_t)(a ^ b)
|
*
|
||||||
|
* This file is an opcode snippet included by vm.c. It implements a
|
||||||
|
* 32-bit unsigned bitwise XOR of two integer operands from the VM stack.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OP_BXOR: bitwise XOR (uint32)
|
||||||
|
*
|
||||||
|
* Stack effects:
|
||||||
|
* - pops: b, a
|
||||||
|
* - pushes: (uint32_t)(a ^ b)
|
||||||
|
*
|
||||||
|
* Notes:
|
||||||
|
* - Operands are taken as 32-bit unsigned integers when of type VAL_INT;
|
||||||
|
* non-integer values are treated as 0.
|
||||||
|
* - Result is pushed back as VAL_INT, preserving 32-bit value in the
|
||||||
|
* low bits of the 64-bit integer storage.
|
||||||
*/
|
*/
|
||||||
case OP_BXOR: {
|
case OP_BXOR: {
|
||||||
Value vb = pop_value(vm);
|
Value vb = pop_value(vm);
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,27 @@
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file rol.c
|
||||||
|
* @brief Implements the OP_ROTL opcode (rotate-left).
|
||||||
*
|
*
|
||||||
* Added: 2025-09-29
|
* Opcode snippet included by vm.c. Performs a 32-bit unsigned rotate-left of
|
||||||
|
* an integer operand by a masked rotation count.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OP_ROTL: rotate left (uint32)
|
* OP_ROTL: rotate left (uint32)
|
||||||
* pops: s, a
|
*
|
||||||
* pushes: rotl32(a, s)
|
* Stack effects:
|
||||||
|
* - pops: s, a
|
||||||
|
* - pushes: (a << s) | (a >> (32 - s)), with s masked to 0..31
|
||||||
|
*
|
||||||
|
* Notes:
|
||||||
|
* - Both a (value) and s (count) are taken from VAL_INT; non-integers are 0.
|
||||||
|
* - The rotation count is masked to 0..31. A zero rotation returns a unchanged.
|
||||||
|
* - Result is pushed as VAL_INT with the 32-bit value preserved in the low bits.
|
||||||
*/
|
*/
|
||||||
case OP_ROTL: {
|
case OP_ROTL: {
|
||||||
Value vs = pop_value(vm);
|
Value vs = pop_value(vm);
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,27 @@
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file ror.c
|
||||||
|
* @brief Implements the OP_ROTR opcode (rotate-right).
|
||||||
*
|
*
|
||||||
* Added: 2025-09-29
|
* Opcode snippet included by vm.c. Performs a 32-bit unsigned rotate-right of
|
||||||
|
* an integer operand by a masked rotation count.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OP_ROTR: rotate right (uint32)
|
* OP_ROTR: rotate right (uint32)
|
||||||
* pops: s, a
|
*
|
||||||
* pushes: rotr32(a, s)
|
* Stack effects:
|
||||||
|
* - pops: s, a
|
||||||
|
* - pushes: (a >> s) | (a << (32 - s)), with s masked to 0..31
|
||||||
|
*
|
||||||
|
* Notes:
|
||||||
|
* - Both a (value) and s (count) are taken from VAL_INT; non-integers are 0.
|
||||||
|
* - The rotation count is masked to 0..31. A zero rotation returns a unchanged.
|
||||||
|
* - Result is pushed as VAL_INT with the 32-bit value preserved in the low bits.
|
||||||
*/
|
*/
|
||||||
case OP_ROTR: {
|
case OP_ROTR: {
|
||||||
Value vs = pop_value(vm);
|
Value vs = pop_value(vm);
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,27 @@
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file shl.c
|
||||||
|
* @brief Implements the OP_SHL opcode (logical left shift).
|
||||||
*
|
*
|
||||||
* Added: 2025-09-29
|
* Opcode snippet included by vm.c. Performs a 32-bit unsigned logical left
|
||||||
|
* shift of an integer operand by a masked shift count.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OP_SHL: logical left shift (uint32)
|
* OP_SHL: logical left shift (uint32)
|
||||||
* pops: s, a
|
*
|
||||||
* pushes: (uint32_t)(a << (s&31))
|
* Stack effects:
|
||||||
|
* - pops: s, a
|
||||||
|
* - pushes: (uint32_t)(a << (s & 31))
|
||||||
|
*
|
||||||
|
* Notes:
|
||||||
|
* - Both a (value) and s (shift) are taken from VAL_INT; non-integers are 0.
|
||||||
|
* - The shift count is masked to 0..31. A zero shift returns a unchanged.
|
||||||
|
* - Result is pushed as VAL_INT with the 32-bit value preserved in the low bits.
|
||||||
*/
|
*/
|
||||||
case OP_SHL: {
|
case OP_SHL: {
|
||||||
Value vs = pop_value(vm);
|
Value vs = pop_value(vm);
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,28 @@
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file shr.c
|
||||||
|
* @brief Implements the OP_SHR opcode (logical right shift).
|
||||||
*
|
*
|
||||||
* Added: 2025-09-29
|
* Opcode snippet included by vm.c. Performs a 32-bit unsigned logical right
|
||||||
|
* shift of an integer operand by a masked shift count.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OP_SHR: logical right shift (uint32)
|
* OP_SHR: logical right shift (uint32)
|
||||||
* pops: s, a
|
*
|
||||||
* pushes: (uint32_t)(a >> (s&31)) using logical shift
|
* Stack effects:
|
||||||
|
* - pops: s, a
|
||||||
|
* - pushes: (uint32_t)(a >> (s & 31))
|
||||||
|
*
|
||||||
|
* Notes:
|
||||||
|
* - Both a (value) and s (shift) are taken from VAL_INT; non-integers are 0.
|
||||||
|
* - The shift count is masked to 0..31. A zero shift returns a unchanged.
|
||||||
|
* - Logical (zero-filling) right shift is used (no sign extend).
|
||||||
|
* - Result is pushed as VAL_INT with the 32-bit value preserved in the low bits.
|
||||||
*/
|
*/
|
||||||
case OP_SHR: {
|
case OP_SHR: {
|
||||||
Value vs = pop_value(vm);
|
Value vs = pop_value(vm);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,32 @@
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file throw.c
|
||||||
|
* @brief Implements the OP_THROW opcode for raising exceptions in the VM.
|
||||||
|
*
|
||||||
|
* This file handles the OP_THROW instruction, which raises an exception. If a
|
||||||
|
* matching TRY handler exists in the current frame (pushed via OP_TRY_PUSH),
|
||||||
|
* control flow jumps to the handler location and the error value is made
|
||||||
|
* available to the catch block via the stack. If no handler is present in the
|
||||||
|
* current frame, the error is printed and the VM terminates execution by
|
||||||
|
* clearing the frame stack.
|
||||||
|
*
|
||||||
|
* Behavior:
|
||||||
|
* - Pops the error `Value` from the stack.
|
||||||
|
* - If the current frame has a pending TRY handler (f->try_sp >= 0):
|
||||||
|
* - Retrieves the handler target IP from the TRY instruction's operand.
|
||||||
|
* - Pushes the error back on the stack for the catch block to consume.
|
||||||
|
* - Sets the instruction pointer (IP) to the handler target.
|
||||||
|
* - Otherwise (no handler):
|
||||||
|
* - Prints the error in a human-readable form.
|
||||||
|
* - Frees the error value and clears all frames (vm->fp = -1) to stop the VM.
|
||||||
|
*
|
||||||
|
* Errors:
|
||||||
|
* - None explicitly thrown here; if unhandled, the VM stops after printing the
|
||||||
|
* error message.
|
||||||
|
*/
|
||||||
|
|
||||||
case OP_THROW: {
|
case OP_THROW: {
|
||||||
Value err = pop_value(vm);
|
Value err = pop_value(vm);
|
||||||
/* if there is a handler in this frame, jump to it and push err for catch */
|
/* if there is a handler in this frame, jump to it and push err for catch */
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,22 @@
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file try_pop.c
|
||||||
|
* @brief Implements the OP_TRY_POP opcode to end a try/catch region.
|
||||||
|
*
|
||||||
|
* This file handles the OP_TRY_POP instruction, which marks the end of the
|
||||||
|
* most recently started try/catch region in the current frame by popping its
|
||||||
|
* entry from the per-frame TRY stack.
|
||||||
|
*
|
||||||
|
* Behavior:
|
||||||
|
* - If f->try_sp >= 0, decrements f->try_sp (pops one TRY region).
|
||||||
|
* - Does not modify the value stack.
|
||||||
|
*
|
||||||
|
* Errors:
|
||||||
|
* - None; popping when no TRY is active is a no-op.
|
||||||
|
*/
|
||||||
|
|
||||||
case OP_TRY_POP: {
|
case OP_TRY_POP: {
|
||||||
if (f->try_sp >= 0) f->try_sp--;
|
if (f->try_sp >= 0) f->try_sp--;
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,25 @@
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file try_push.c
|
||||||
|
* @brief Implements the OP_TRY_PUSH opcode to begin a try/catch region.
|
||||||
|
*
|
||||||
|
* This file handles the OP_TRY_PUSH instruction, which marks the start of a
|
||||||
|
* try/catch region in the current frame by pushing the index of the TRY
|
||||||
|
* instruction onto a small per-frame stack. The actual catch target IP is
|
||||||
|
* stored in the TRY instruction's operand and may be patched later by the
|
||||||
|
* compiler/linker.
|
||||||
|
*
|
||||||
|
* Behavior:
|
||||||
|
* - Pushes the index of this TRY instruction (f->ip - 1) onto f->try_stack.
|
||||||
|
* - Does not modify the value stack.
|
||||||
|
*
|
||||||
|
* Errors:
|
||||||
|
* - If the try depth exceeds the size of f->try_stack, prints a runtime error
|
||||||
|
* and terminates the process.
|
||||||
|
*/
|
||||||
|
|
||||||
case OP_TRY_PUSH: {
|
case OP_TRY_PUSH: {
|
||||||
/* push index of this TRY instruction; handler ip is in its operand (may be patched later) */
|
/* push index of this TRY instruction; handler ip is in its operand (may be patched later) */
|
||||||
if (f->try_sp >= (int)(sizeof(f->try_stack) / sizeof(f->try_stack[0])) - 1) {
|
if (f->try_sp >= (int)(sizeof(f->try_stack) / sizeof(f->try_stack[0])) - 1) {
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,26 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-01-29
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Minimal C++ opcode for Fun VM — cpp_add
|
* @file add.cpp
|
||||||
* Build is gated by -DFUN_WITH_CPP=ON (see CMake).
|
* @brief Fun VM C++ opcode snippet: add two 64-bit integers (cpp_add).
|
||||||
|
*
|
||||||
|
* This opcode is compiled and linked only when the CMake option
|
||||||
|
* `-DFUN_WITH_CPP=ON` is enabled. It demonstrates how to implement a
|
||||||
|
* VM opcode in C++ while exposing a C ABI symbol for the VM dispatcher.
|
||||||
|
*
|
||||||
|
* Stack behavior:
|
||||||
|
* - Pops: b:int64, a:int64
|
||||||
|
* - Pushes: (a + b):int64
|
||||||
|
*
|
||||||
|
* Error handling and type conversions (e.g., from other numeric types to
|
||||||
|
* int64) are delegated to the VM helpers `vm_pop_i64` and `vm_push_i64`.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
@ -20,6 +29,21 @@ extern "C" {
|
||||||
#include "vm.h" // C header; provides VM, vm_pop_i64, vm_push_i64
|
#include "vm.h" // C header; provides VM, vm_pop_i64, vm_push_i64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add two 64-bit integers from the VM stack and push the sum.
|
||||||
|
*
|
||||||
|
* Pops two values from the VM stack using `vm_pop_i64`, adds them as
|
||||||
|
* 64-bit signed integers, and pushes the result via `vm_push_i64`.
|
||||||
|
*
|
||||||
|
* Stack effect:
|
||||||
|
* - Input: [..., a:int64, b:int64]
|
||||||
|
* - Output: [..., (a+b):int64]
|
||||||
|
*
|
||||||
|
* @param vm Pointer to the VM instance. Must not be NULL.
|
||||||
|
* @return 0 on success. Any stack underflow or conversion errors are
|
||||||
|
* handled by the VM helpers; non-zero may be used by future
|
||||||
|
* implementations to indicate a runtime error.
|
||||||
|
*/
|
||||||
extern "C" int fun_op_cpp_add(VM *vm) {
|
extern "C" int fun_op_cpp_add(VM *vm) {
|
||||||
int64_t a = vm_pop_i64(vm);
|
int64_t a = vm_pop_i64(vm);
|
||||||
int64_t b = vm_pop_i64(vm);
|
int64_t b = vm_pop_i64(vm);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,41 @@
|
||||||
/**
|
/**
|
||||||
* libcurl DOWNLOAD builtin
|
* This file is part of the Fun programming language.
|
||||||
|
* https://fun-lang.xyz/
|
||||||
|
*
|
||||||
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file download.c
|
||||||
|
* @brief Fun VM opcode snippet: HTTP download to file via libcurl (OP_CURL_DOWNLOAD).
|
||||||
|
*
|
||||||
|
* This snippet is included by vm.c and implements the OP_CURL_DOWNLOAD
|
||||||
|
* instruction. When FUN_WITH_CURL is enabled, it downloads the content at
|
||||||
|
* the given URL and writes it to the specified filesystem path.
|
||||||
|
*
|
||||||
|
* Stack behavior:
|
||||||
|
* - Pops: path:string, url:string (values are converted via value_to_string_alloc)
|
||||||
|
* - Pushes: int (1 on success, 0 on error or when CURL is disabled)
|
||||||
|
*
|
||||||
|
* Error handling:
|
||||||
|
* - Returns 0 if URL/path conversion fails, file open fails, CURL init
|
||||||
|
* or perform fails.
|
||||||
|
* - Follows redirects (CURLOPT_FOLLOWLOCATION = 1L).
|
||||||
|
* - Writes via fun_curl_file_write_cb directly into the opened FILE*.
|
||||||
|
*
|
||||||
|
* Notes:
|
||||||
|
* - All temporary allocations (URL, path) are freed; FILE* is closed.
|
||||||
|
* - On builds without FUN_WITH_CURL, consumes two values and pushes 0.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @brief Opcode handler for OP_CURL_DOWNLOAD.
|
||||||
|
*
|
||||||
|
* Pops a destination path and URL, streams the HTTP response body
|
||||||
|
* into the file, and pushes 1 on success or 0 on any error. Without
|
||||||
|
* FUN_WITH_CURL, behaves as a no-op that consumes two values and
|
||||||
|
* pushes 0.
|
||||||
*/
|
*/
|
||||||
case OP_CURL_DOWNLOAD: {
|
case OP_CURL_DOWNLOAD: {
|
||||||
#ifdef FUN_WITH_CURL
|
#ifdef FUN_WITH_CURL
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,42 @@
|
||||||
/**
|
/**
|
||||||
* libcurl GET builtin
|
* This file is part of the Fun programming language.
|
||||||
|
* https://fun-lang.xyz/
|
||||||
|
*
|
||||||
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file get.c
|
||||||
|
* @brief Fun VM opcode snippet: HTTP GET via libcurl (OP_CURL_GET).
|
||||||
|
*
|
||||||
|
* This snippet is included by vm.c and implements the OP_CURL_GET
|
||||||
|
* instruction. When FUN_WITH_CURL is enabled, it performs an HTTP GET
|
||||||
|
* request for the provided URL and pushes the response body as a string.
|
||||||
|
* If libcurl support is not built in, or an error occurs, an empty string
|
||||||
|
* is pushed instead.
|
||||||
|
*
|
||||||
|
* Stack behavior:
|
||||||
|
* - Pops: url:string (any value is converted via value_to_string_alloc)
|
||||||
|
* - Pushes: body:string ("" on error or when CURL is disabled)
|
||||||
|
*
|
||||||
|
* Error handling:
|
||||||
|
* - If URL conversion fails or CURL initialization/performance fails,
|
||||||
|
* the opcode pushes an empty string.
|
||||||
|
* - Follows HTTP redirects (CURLOPT_FOLLOWLOCATION = 1L).
|
||||||
|
*
|
||||||
|
* Notes:
|
||||||
|
* - Uses FunCurlBuf and fun_curl_write_cb from the curl extension helpers.
|
||||||
|
* - Memory allocated for temporary strings and buffers is freed before exit.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @brief Opcode handler for OP_CURL_GET.
|
||||||
|
*
|
||||||
|
* Converts the top stack value to a URL string, issues a GET request
|
||||||
|
* using libcurl, and pushes the response body as a string. When
|
||||||
|
* compiled without FUN_WITH_CURL, the opcode becomes a no-op that
|
||||||
|
* consumes one value and pushes an empty string.
|
||||||
*/
|
*/
|
||||||
case OP_CURL_GET: {
|
case OP_CURL_GET: {
|
||||||
#ifdef FUN_WITH_CURL
|
#ifdef FUN_WITH_CURL
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,42 @@
|
||||||
/**
|
/**
|
||||||
* libcurl POST builtin
|
* This file is part of the Fun programming language.
|
||||||
|
* https://fun-lang.xyz/
|
||||||
|
*
|
||||||
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file post.c
|
||||||
|
* @brief Fun VM opcode snippet: HTTP POST via libcurl (OP_CURL_POST).
|
||||||
|
*
|
||||||
|
* This snippet is included by vm.c and implements the OP_CURL_POST
|
||||||
|
* instruction. When FUN_WITH_CURL is enabled, it performs an HTTP POST
|
||||||
|
* to the given URL with the provided request body and pushes the response
|
||||||
|
* body as a string. If libcurl support is not built in, or an error occurs,
|
||||||
|
* an empty string is pushed instead.
|
||||||
|
*
|
||||||
|
* Stack behavior:
|
||||||
|
* - Pops: body:string, url:string (values are converted via value_to_string_alloc)
|
||||||
|
* - Pushes: body:string (server response; "" on error or when CURL is disabled)
|
||||||
|
*
|
||||||
|
* Error handling:
|
||||||
|
* - If URL conversion fails, the opcode pushes an empty string and discards
|
||||||
|
* any converted body.
|
||||||
|
* - Follows redirects (CURLOPT_FOLLOWLOCATION = 1L).
|
||||||
|
* - Sets CURLOPT_POST=1L and CURLOPT_POSTFIELDS to submit the body.
|
||||||
|
*
|
||||||
|
* Notes:
|
||||||
|
* - Uses FunCurlBuf and fun_curl_write_cb from the curl extension helpers.
|
||||||
|
* - All temporary allocations (URL, body, response buffer) are freed.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @brief Opcode handler for OP_CURL_POST.
|
||||||
|
*
|
||||||
|
* Pops the POST body and URL, performs an HTTP POST, and pushes the
|
||||||
|
* response as a string. Without FUN_WITH_CURL, consumes two values and
|
||||||
|
* pushes an empty string.
|
||||||
*/
|
*/
|
||||||
case OP_CURL_POST: {
|
case OP_CURL_POST: {
|
||||||
#ifdef FUN_WITH_CURL
|
#ifdef FUN_WITH_CURL
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
|
|
@ -8,11 +8,21 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements OP_ECHO: print top-of-stack value without trailing newline.
|
* @file echo.c
|
||||||
* Now stores the value into the VM's output buffer and marks it as partial,
|
* @brief Implements the OP_ECHO opcode for printing without a trailing newline.
|
||||||
* so the CLI can render echo output together with following print output.
|
*
|
||||||
|
* This snippet is included into the VM dispatch loop and handles OP_ECHO.
|
||||||
|
* It pops the top value from the stack and appends it to the VM's output buffer
|
||||||
|
* but marks the entry as partial so that subsequent OP_PRINT may continue the
|
||||||
|
* same line.
|
||||||
|
*
|
||||||
|
* Stack contract:
|
||||||
|
* - Pops: value (any)
|
||||||
|
* - Pushes: (none)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* Implements OP_ECHO: print top-of-stack value without trailing newline. */
|
||||||
|
|
||||||
case OP_ECHO: {
|
case OP_ECHO: {
|
||||||
Value v = pop_value(vm);
|
Value v = pop_value(vm);
|
||||||
Value snap = deep_copy_value(&v);
|
Value snap = deep_copy_value(&v);
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,34 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2025-11-30
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file free.c
|
||||||
|
* @brief VM opcode snippet for releasing an INI handle (OP_INI_FREE).
|
||||||
|
*
|
||||||
|
* This file is included into the main VM dispatch switch in vm.c. It is only
|
||||||
|
* compiled when FUN_WITH_INI is enabled and iniparser headers are available.
|
||||||
|
*
|
||||||
|
* Opcode: OP_INI_FREE
|
||||||
|
* Stack: [handle:int] -> [ok:int]
|
||||||
|
*
|
||||||
|
* Behavior
|
||||||
|
* - Pops an integer handle referring to an INI dictionary previously returned
|
||||||
|
* by OP_INI_LOAD.
|
||||||
|
* - Attempts to close the underlying dictionary and free the registry slot.
|
||||||
|
* - Pushes 1 on success, 0 if the handle was invalid or already freed.
|
||||||
|
*
|
||||||
|
* Errors
|
||||||
|
* - No VM error is thrown for invalid handles; the opcode simply returns 0.
|
||||||
|
*
|
||||||
|
* See also
|
||||||
|
* - ini_alloc_handle(), ini_free_handle() in src/vm/ini/handles.c
|
||||||
|
*/
|
||||||
/* OP_INI_FREE: pops handle; pushes 1/0 */
|
/* OP_INI_FREE: pops handle; pushes 1/0 */
|
||||||
#ifdef FUN_WITH_INI
|
#ifdef FUN_WITH_INI
|
||||||
case OP_INI_FREE: {
|
case OP_INI_FREE: {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,33 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2025-12-10 (split from getters.c)
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file get_bool.c
|
||||||
|
* @brief VM opcode snippet for reading a boolean from an INI dictionary (OP_INI_GET_BOOL).
|
||||||
|
*
|
||||||
|
* Included by vm.c when FUN_WITH_INI is enabled.
|
||||||
|
*
|
||||||
|
* Opcode: OP_INI_GET_BOOL
|
||||||
|
* Stack: [default:int|bool] [key:string] [section:string] [handle:int] -> [out:int]
|
||||||
|
*
|
||||||
|
* Behavior
|
||||||
|
* - Pops default value (0/1), key, section, and handle.
|
||||||
|
* - Looks up the entry "section:key" in the referenced dictionary. If not found,
|
||||||
|
* also tries a dotted variant "section.key" for compatibility.
|
||||||
|
* - Accepts textual booleans (true/false, yes/no, on/off; case-insensitive) and
|
||||||
|
* numeric values (non-zero => true). Falls back to the provided default when
|
||||||
|
* parsing fails or entry is missing.
|
||||||
|
* - Pushes 1 for true or 0 for false.
|
||||||
|
*
|
||||||
|
* Errors
|
||||||
|
* - Invalid handle or arguments simply yield the default value; no exception.
|
||||||
|
*/
|
||||||
/* OP_INI_GET_BOOL */
|
/* OP_INI_GET_BOOL */
|
||||||
#ifdef FUN_WITH_INI
|
#ifdef FUN_WITH_INI
|
||||||
case OP_INI_GET_BOOL: {
|
case OP_INI_GET_BOOL: {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,27 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2025-12-10 (split from getters.c)
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file get_double.c
|
||||||
|
* @brief VM opcode snippet for reading a floating-point value from INI (OP_INI_GET_DOUBLE).
|
||||||
|
*
|
||||||
|
* Opcode: OP_INI_GET_DOUBLE
|
||||||
|
* Stack: [default:float|int] [key:string] [section:string] [handle:int] -> [out:float]
|
||||||
|
*
|
||||||
|
* Behavior
|
||||||
|
* - Pops default, key, section, and handle; looks up "section:key" (and
|
||||||
|
* dotted fallback) and attempts to parse as double using strtod().
|
||||||
|
* - If lookup or parsing fails, pushes the provided default.
|
||||||
|
*
|
||||||
|
* Errors
|
||||||
|
* - Invalid handle/args simply produce the default; no exception raised.
|
||||||
|
*/
|
||||||
/* OP_INI_GET_DOUBLE */
|
/* OP_INI_GET_DOUBLE */
|
||||||
#ifdef FUN_WITH_INI
|
#ifdef FUN_WITH_INI
|
||||||
case OP_INI_GET_DOUBLE: {
|
case OP_INI_GET_DOUBLE: {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,27 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2025-12-10 (split from getters.c)
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file get_int.c
|
||||||
|
* @brief VM opcode snippet for reading an integer from INI (OP_INI_GET_INT).
|
||||||
|
*
|
||||||
|
* Opcode: OP_INI_GET_INT
|
||||||
|
* Stack: [default:int] [key:string] [section:string] [handle:int] -> [out:int]
|
||||||
|
*
|
||||||
|
* Behavior
|
||||||
|
* - Pops default, key, section, and handle; looks up "section:key" (and dotted
|
||||||
|
* fallback) and attempts to parse as base-10 integer using strtol().
|
||||||
|
* - If lookup or parsing fails, returns the provided default.
|
||||||
|
*
|
||||||
|
* Errors
|
||||||
|
* - Invalid handle/args produce the default; no VM exception is raised.
|
||||||
|
*/
|
||||||
/* OP_INI_GET_INT */
|
/* OP_INI_GET_INT */
|
||||||
#ifdef FUN_WITH_INI
|
#ifdef FUN_WITH_INI
|
||||||
case OP_INI_GET_INT: {
|
case OP_INI_GET_INT: {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,27 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2025-12-10 (split from getters.c)
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file get_string.c
|
||||||
|
* @brief VM opcode snippet for reading a string from INI (OP_INI_GET_STRING).
|
||||||
|
*
|
||||||
|
* Opcode: OP_INI_GET_STRING
|
||||||
|
* Stack: [default:string] [key:string] [section:string] [handle:int] -> [out:string]
|
||||||
|
*
|
||||||
|
* Behavior
|
||||||
|
* - Pops default string, key, section, and handle; looks up "section:key" and
|
||||||
|
* a dotted fallback. If not found, uses the provided default.
|
||||||
|
* - Pushes the resulting string (copied into a VM Value).
|
||||||
|
*
|
||||||
|
* Errors
|
||||||
|
* - Invalid handle/args result in pushing the default (or empty string).
|
||||||
|
*/
|
||||||
/* OP_INI_GET_STRING */
|
/* OP_INI_GET_STRING */
|
||||||
#ifdef FUN_WITH_INI
|
#ifdef FUN_WITH_INI
|
||||||
case OP_INI_GET_STRING: {
|
case OP_INI_GET_STRING: {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
|
|
@ -7,6 +7,15 @@
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file handles.c
|
||||||
|
* @brief INI handle registry implementation used by VM INI opcodes.
|
||||||
|
*
|
||||||
|
* Provides a tiny fixed-size registry mapping small integer handles to
|
||||||
|
* iniparser dictionary pointers. Not thread-safe. Handles are positive
|
||||||
|
* integers in range [1, 63].
|
||||||
|
*/
|
||||||
|
|
||||||
#ifdef FUN_WITH_INI
|
#ifdef FUN_WITH_INI
|
||||||
#if defined(__has_include)
|
#if defined(__has_include)
|
||||||
#if __has_include(<iniparser/iniparser.h>)
|
#if __has_include(<iniparser/iniparser.h>)
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,12 @@
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2025-11-30
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** INI handle registry for iniparser 4.2.6 */
|
/**
|
||||||
|
* @file handles.h
|
||||||
|
* @brief INI handle registry for iniparser 4.2.6 used by INI VM opcodes.
|
||||||
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef FUN_WITH_INI
|
#ifdef FUN_WITH_INI
|
||||||
|
|
@ -29,19 +30,43 @@
|
||||||
#endif
|
#endif
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief One slot in the global INI handle registry.
|
||||||
|
* @details Associates an iniparser dictionary pointer with an in-use flag.
|
||||||
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
dictionary *dict;
|
dictionary *dict;
|
||||||
int in_use;
|
int in_use;
|
||||||
} IniSlot;
|
} IniSlot;
|
||||||
|
|
||||||
/* Single global registry (defined in handles.c) */
|
/** Single global registry (defined in handles.c). */
|
||||||
extern IniSlot g_ini[64];
|
extern IniSlot g_ini[64];
|
||||||
|
|
||||||
/* Registry API (implemented in handles.c) */
|
/**
|
||||||
|
* @brief Allocate a registry handle for a newly created dictionary.
|
||||||
|
* @param d Pointer to an iniparser dictionary.
|
||||||
|
* @return Handle id (>0) on success or 0 on failure.
|
||||||
|
*/
|
||||||
int ini_alloc_handle(dictionary *d);
|
int ini_alloc_handle(dictionary *d);
|
||||||
|
/**
|
||||||
|
* @brief Look up a dictionary pointer by registry handle.
|
||||||
|
* @param h Handle id previously returned by ini_alloc_handle().
|
||||||
|
* @return Pointer to dictionary or NULL if not found.
|
||||||
|
*/
|
||||||
dictionary *ini_get(int h);
|
dictionary *ini_get(int h);
|
||||||
|
/**
|
||||||
|
* @brief Free a previously allocated handle and close its dictionary.
|
||||||
|
* @param h Handle id to free.
|
||||||
|
* @return 1 on success, 0 on error (invalid handle or not in use).
|
||||||
|
*/
|
||||||
int ini_free_handle(int h);
|
int ini_free_handle(int h);
|
||||||
|
|
||||||
/* Helper to build section:key string safely into provided buffer (implemented in handles.c) */
|
/**
|
||||||
|
* @brief Build a fully qualified key "section:key" into a caller-provided buffer.
|
||||||
|
* @param buf Destination buffer.
|
||||||
|
* @param cap Capacity of buf in bytes (including terminator).
|
||||||
|
* @param sec Section name (may be NULL for default section).
|
||||||
|
* @param key Key name (must not be NULL).
|
||||||
|
*/
|
||||||
void ini_make_full_key(char *buf, size_t cap, const char *sec, const char *key);
|
void ini_make_full_key(char *buf, size_t cap, const char *sec, const char *key);
|
||||||
#endif /* FUN_WITH_INI */
|
#endif /* FUN_WITH_INI */
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,28 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2025-11-30
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file load.c
|
||||||
|
* @brief VM opcode snippet for loading an INI file (OP_INI_LOAD).
|
||||||
|
*
|
||||||
|
* Opcode: OP_INI_LOAD
|
||||||
|
* Stack: [path:string] -> [handle:int]
|
||||||
|
*
|
||||||
|
* Behavior
|
||||||
|
* - Pops a filesystem path and attempts to parse it via iniparser_load().
|
||||||
|
* - On success, registers the resulting dictionary and pushes a positive
|
||||||
|
* handle. On failure, pushes 0.
|
||||||
|
*
|
||||||
|
* Notes
|
||||||
|
* - The returned handle must later be released with OP_INI_FREE to avoid
|
||||||
|
* leaking dictionary objects.
|
||||||
|
*/
|
||||||
/* OP_INI_LOAD: pops path string; pushes handle (>0) or 0 */
|
/* OP_INI_LOAD: pops path string; pushes handle (>0) or 0 */
|
||||||
#ifdef FUN_WITH_INI
|
#ifdef FUN_WITH_INI
|
||||||
case OP_INI_LOAD: {
|
case OP_INI_LOAD: {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,27 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2025-12-10 (split from set_unset_save.c)
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file save.c
|
||||||
|
* @brief VM opcode snippet for saving an INI dictionary to a file (OP_INI_SAVE).
|
||||||
|
*
|
||||||
|
* Opcode: OP_INI_SAVE
|
||||||
|
* Stack: [path:string] [handle:int] -> [ok:int]
|
||||||
|
*
|
||||||
|
* Behavior
|
||||||
|
* - Pops a path and a handle. If the handle is valid, opens the path for
|
||||||
|
* writing and dumps the dictionary in INI format. Pushes 1 on success,
|
||||||
|
* otherwise 0.
|
||||||
|
*
|
||||||
|
* Errors
|
||||||
|
* - Failing fopen() or invalid handle simply return 0; no exception is thrown.
|
||||||
|
*/
|
||||||
/* OP_INI_SAVE */
|
/* OP_INI_SAVE */
|
||||||
#ifdef FUN_WITH_INI
|
#ifdef FUN_WITH_INI
|
||||||
case OP_INI_SAVE: {
|
case OP_INI_SAVE: {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,28 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2025-12-10 (split from set_unset_save.c)
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file set.c
|
||||||
|
* @brief VM opcode snippet for setting an INI value (OP_INI_SET).
|
||||||
|
*
|
||||||
|
* Opcode: OP_INI_SET
|
||||||
|
* Stack: [value:any] [key:string] [section:string] [handle:int] -> [ok:int]
|
||||||
|
*
|
||||||
|
* Behavior
|
||||||
|
* - Pops value, key, section, and handle. Converts the value to a string using
|
||||||
|
* value_to_string_alloc() and stores it under "section:key" (and a dotted
|
||||||
|
* fallback) via dictionary_set().
|
||||||
|
* - Pushes 1 on success, 0 on failure (invalid args/handle or allocation fail).
|
||||||
|
*
|
||||||
|
* Errors
|
||||||
|
* - No VM exception is thrown; failures return 0.
|
||||||
|
*/
|
||||||
/* OP_INI_SET */
|
/* OP_INI_SET */
|
||||||
#ifdef FUN_WITH_INI
|
#ifdef FUN_WITH_INI
|
||||||
case OP_INI_SET: {
|
case OP_INI_SET: {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
/*
|
/**
|
||||||
* This file provides stub handlers for INI opcodes when FUN_WITH_INI is disabled.
|
* @file stubs.c
|
||||||
* Each opcode reports a clear runtime error and returns a safe default.
|
* @brief Stub opcode implementations for INI support when FUN_WITH_INI is disabled.
|
||||||
|
*
|
||||||
|
* These cases are compiled into the VM dispatch when the INI feature is not
|
||||||
|
* enabled. Each opcode prints a descriptive runtime error and pushes a safe
|
||||||
|
* default (0, 0.0, or empty string) to keep execution proceeding without
|
||||||
|
* crashing.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* OP_INI_LOAD: pops path string; pushes 0 (invalid handle) */
|
/* OP_INI_LOAD: pops path string; pushes 0 (invalid handle) */
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,28 @@
|
||||||
/*
|
/**
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://fun-lang.xyz/
|
* https://fun-lang.xyz/
|
||||||
*
|
*
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2025-12-10 (split from set_unset_save.c)
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file unset.c
|
||||||
|
* @brief VM opcode snippet for removing an INI entry (OP_INI_UNSET).
|
||||||
|
*
|
||||||
|
* Opcode: OP_INI_UNSET
|
||||||
|
* Stack: [key:string] [section:string] [handle:int] -> [ok:int]
|
||||||
|
*
|
||||||
|
* Behavior
|
||||||
|
* - Pops key, section, and handle. Removes both "section:key" and a dotted
|
||||||
|
* fallback key from the dictionary. Pushes 1 if the operation was attempted
|
||||||
|
* (with a valid handle and arguments), otherwise 0.
|
||||||
|
*
|
||||||
|
* Notes
|
||||||
|
* - iniparser 4.2.6 dictionary_unset() returns void; we assume success when
|
||||||
|
* called with valid parameters.
|
||||||
|
*/
|
||||||
/* OP_INI_UNSET */
|
/* OP_INI_UNSET */
|
||||||
#ifdef FUN_WITH_INI
|
#ifdef FUN_WITH_INI
|
||||||
case OP_INI_UNSET: {
|
case OP_INI_UNSET: {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,53 @@
|
||||||
|
/**
|
||||||
|
* This file is part of the Fun programming language.
|
||||||
|
* https://fun-lang.xyz/
|
||||||
|
*
|
||||||
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file input_line.c
|
||||||
|
* @brief Implements the OP_INPUT_LINE opcode for interactive console input.
|
||||||
|
*
|
||||||
|
* This snippet handles the OP_INPUT_LINE instruction in the VM dispatch. It can
|
||||||
|
* optionally print a prompt (taken from the stack) and can read input in a
|
||||||
|
* hidden mode (terminal echo disabled) suitable for passwords.
|
||||||
|
*
|
||||||
|
* Operand bits (inst.operand):
|
||||||
|
* - bit0 (1): Has prompt. When set, the top of the stack is popped and
|
||||||
|
* converted to string, printed without a trailing newline.
|
||||||
|
* - bit1 (2): Hidden input. When set, terminal echo is temporarily disabled
|
||||||
|
* while reading the line (best-effort, platform dependent).
|
||||||
|
*
|
||||||
|
* Stack effects:
|
||||||
|
* - If bit0 is set: pop(prompt)
|
||||||
|
* - Always: push(result_string)
|
||||||
|
*
|
||||||
|
* Behavior:
|
||||||
|
* - Converts an optional prompt Value to string using value_to_string_alloc,
|
||||||
|
* prints it to stdout without a newline, and flushes the stream.
|
||||||
|
* - If hidden is requested, disables terminal echo (POSIX termios or Win32
|
||||||
|
* console modes) before reading.
|
||||||
|
* - Reads a single line from stdin, accepting both "\n" and "\r\n" endings.
|
||||||
|
* - Restores terminal echo if it was disabled and, when a prompt was printed,
|
||||||
|
* emits a newline for a better UX.
|
||||||
|
* - Pushes the captured line as a Fun string (never NULL; empty string on
|
||||||
|
* failure or EOF).
|
||||||
|
*
|
||||||
|
* Errors and corner cases:
|
||||||
|
* - Memory allocation failures are reported to stderr; an empty string is
|
||||||
|
* pushed in such cases to keep execution flowing.
|
||||||
|
* - If echo toggling fails, input still proceeds with echo enabled.
|
||||||
|
* - EOF before any character yields an empty string.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* // Bytecode: [optional PUSH prompt], OP_INPUT_LINE(operand)
|
||||||
|
* // operand bit0=1 (has prompt), bit1=2 (hidden) can be combined
|
||||||
|
* // Stack before (bit0=1): ["Enter password: "]
|
||||||
|
* // Stack after: ["user-typed-line"]
|
||||||
|
*/
|
||||||
case OP_INPUT_LINE: {
|
case OP_INPUT_LINE: {
|
||||||
/* operand bit flags:
|
/* operand bit flags:
|
||||||
* bit0 (1): has prompt (string or any value convertible to string) — top of stack holds prompt when set
|
* bit0 (1): has prompt (string or any value convertible to string) — top of stack holds prompt when set
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,30 @@
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file from_file.c
|
||||||
|
* @brief VM opcode snippet for loading a JSON document from a file.
|
||||||
*
|
*
|
||||||
* Added: 2025-11-24
|
* This snippet is included by vm.c and implements the OP_JSON_FROM_FILE
|
||||||
|
* instruction. It expects a path on the VM stack, reads the file using
|
||||||
|
* json-c, converts the resulting json_object tree into a Fun Value, and
|
||||||
|
* pushes that Value back on the VM stack.
|
||||||
|
*
|
||||||
|
* Build gating: compiled only when FUN_WITH_JSON is enabled (json-c
|
||||||
|
* available). When disabled, the opcode consumes its argument (if any)
|
||||||
|
* and pushes Nil.
|
||||||
|
*
|
||||||
|
* Stack effect (with FUN_WITH_JSON):
|
||||||
|
* - Pops: path (any; converted to string)
|
||||||
|
* - Pushes: Value converted from JSON, or Nil on error
|
||||||
|
*
|
||||||
|
* Errors and edge cases:
|
||||||
|
* - If the path cannot be converted to a C string or json_object_from_file
|
||||||
|
* fails (e.g., missing file, invalid JSON), the opcode pushes Nil.
|
||||||
|
* - The created json_object is released after conversion; ownership of the
|
||||||
|
* pushed Fun Value follows normal VM semantics.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* JSON_FROM_FILE */
|
/* JSON_FROM_FILE */
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,28 @@
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file parse.c
|
||||||
|
* @brief VM opcode snippet for parsing a JSON string into a Fun Value.
|
||||||
*
|
*
|
||||||
* Added: 2025-11-24
|
* Implements the OP_JSON_PARSE instruction. Expects a string (or any value
|
||||||
|
* convertible to string) on the stack, parses it with json-c, converts the
|
||||||
|
* resulting json_object to a Fun Value and pushes it.
|
||||||
|
*
|
||||||
|
* Build gating: compiled only when FUN_WITH_JSON is enabled. Otherwise the
|
||||||
|
* opcode consumes its argument and pushes Nil.
|
||||||
|
*
|
||||||
|
* Stack effect (with FUN_WITH_JSON):
|
||||||
|
* - Pops: text (any; converted to string)
|
||||||
|
* - Pushes: Value converted from JSON, or Nil on error
|
||||||
|
*
|
||||||
|
* Errors and edge cases:
|
||||||
|
* - If allocation fails, tokenization fails, or the text is not valid JSON,
|
||||||
|
* the opcode pushes Nil.
|
||||||
|
* - The temporary json-c objects are released after conversion; ownership of
|
||||||
|
* the pushed Fun Value follows normal VM semantics.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* JSON_PARSE */
|
/* JSON_PARSE */
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,27 @@
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file stringify.c
|
||||||
|
* @brief VM opcode snippet for converting a Fun Value to a JSON string.
|
||||||
*
|
*
|
||||||
* Added: 2025-11-24
|
* Implements the OP_JSON_STRINGIFY instruction. Expects a boolean/integer flag
|
||||||
|
* indicating pretty-printing and a Value to serialize. Uses json-c to build a
|
||||||
|
* json_object from the Value and then renders it to a string, which is pushed
|
||||||
|
* back to the stack.
|
||||||
|
*
|
||||||
|
* Build gating: compiled only when FUN_WITH_JSON is enabled. Otherwise the
|
||||||
|
* opcode consumes its two arguments and pushes the string "null".
|
||||||
|
*
|
||||||
|
* Stack effect (with FUN_WITH_JSON):
|
||||||
|
* - Pops: pretty (bool/int), value (any)
|
||||||
|
* - Pushes: string (JSON representation)
|
||||||
|
*
|
||||||
|
* Errors and edge cases:
|
||||||
|
* - If conversion to json_object fails, an empty string is pushed.
|
||||||
|
* - Pretty printing selects JSON_C_TO_STRING_PRETTY; otherwise plain output.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* JSON_STRINGIFY */
|
/* JSON_STRINGIFY */
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,27 @@
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file to_file.c
|
||||||
|
* @brief VM opcode snippet for writing a Fun Value as JSON to a file.
|
||||||
*
|
*
|
||||||
* Added: 2025-11-24
|
* Implements the OP_JSON_TO_FILE instruction. Expects a pretty-print flag,
|
||||||
|
* a Value to serialize, and a path. Serializes the Value via json-c and
|
||||||
|
* writes it to the specified file path.
|
||||||
|
*
|
||||||
|
* Build gating: compiled only when FUN_WITH_JSON is enabled. Otherwise the
|
||||||
|
* opcode consumes three arguments and pushes 0 (failure).
|
||||||
|
*
|
||||||
|
* Stack effect (with FUN_WITH_JSON):
|
||||||
|
* - Pops: pretty (bool/int), value (any), path (any; converted to string)
|
||||||
|
* - Pushes: int (1 on success, 0 on failure)
|
||||||
|
*
|
||||||
|
* Errors and edge cases:
|
||||||
|
* - If the path cannot be converted to a C string or file writing fails,
|
||||||
|
* the opcode pushes 0.
|
||||||
|
* - Pretty printing selects JSON_C_TO_STRING_PRETTY; otherwise plain output.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* JSON_TO_FILE */
|
/* JSON_TO_FILE */
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,18 @@
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file line.c
|
||||||
|
* @brief Implements the OP_LINE pseudo-opcode to update the current source line.
|
||||||
|
*
|
||||||
|
* This snippet is included into the VM dispatch loop and handles the OP_LINE
|
||||||
|
* instruction. It records the source line number carried in the instruction's
|
||||||
|
* operand so that runtime errors and debugger output can reference the correct
|
||||||
|
* line in the original program.
|
||||||
|
*
|
||||||
|
* Stack contract: none (does not read or write the VM value stack).
|
||||||
|
*/
|
||||||
|
|
||||||
case OP_LINE: {
|
case OP_LINE: {
|
||||||
/* operand holds the source line number */
|
/* operand holds the source line number */
|
||||||
vm->current_line = inst.operand;
|
vm->current_line = inst.operand;
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,33 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-01-03
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file ceil.c
|
* @file ceil.c
|
||||||
* @brief Implements the OP_CEIL opcode using C99 math.h ceil().
|
* @brief Implements the OP_CEIL opcode using C99 math.h ceil().
|
||||||
|
*
|
||||||
|
* VM opcode snippet included by vm.c. Provides numeric ceiling operation.
|
||||||
|
*
|
||||||
|
* Behavior:
|
||||||
|
* - Pops one numeric operand (int or float) from the stack.
|
||||||
|
* - Applies ceil(x) in double precision.
|
||||||
|
* - If the result is an exact 64-bit integer, pushes VAL_INT; otherwise VAL_FLOAT.
|
||||||
|
*
|
||||||
|
* Stack effect:
|
||||||
|
* - Pop: x
|
||||||
|
* - Push: ceil(x)
|
||||||
|
*
|
||||||
|
* Types:
|
||||||
|
* - Accepts VAL_INT and VAL_FLOAT.
|
||||||
|
* - Other types cause a runtime error.
|
||||||
|
*
|
||||||
|
* Errors:
|
||||||
|
* - Exits with an error message if the operand is not a number.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* - Input stack: [2.1] → Output stack: [3]
|
||||||
|
* - Input stack: [-2.1] → Output stack: [-2]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,29 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-01-03
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file cos.c
|
* @file cos.c
|
||||||
* @brief Implements the OP_COS opcode using C99 math.h cos().
|
* @brief Implements the OP_COS opcode using C99 math.h cos().
|
||||||
|
*
|
||||||
|
* VM opcode snippet included by vm.c. Provides cosine function.
|
||||||
|
*
|
||||||
|
* Behavior:
|
||||||
|
* - Pops one numeric operand (int or float) from the stack.
|
||||||
|
* - Computes cos(x) in double precision.
|
||||||
|
* - Always pushes a VAL_FLOAT result.
|
||||||
|
*
|
||||||
|
* Stack effect:
|
||||||
|
* - Pop: x
|
||||||
|
* - Push: cos(x)
|
||||||
|
*
|
||||||
|
* Types:
|
||||||
|
* - Accepts VAL_INT and VAL_FLOAT.
|
||||||
|
* - Other types cause a runtime error.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* - Input [0] → Output [1.0]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,25 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-01-03
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file exp.c
|
* @file exp.c
|
||||||
* @brief Implements the OP_EXP opcode using C99 math.h exp().
|
* @brief Implements the OP_EXP opcode using C99 math.h exp().
|
||||||
|
*
|
||||||
|
* VM opcode snippet included by vm.c. Provides the natural exponential function.
|
||||||
|
*
|
||||||
|
* Behavior:
|
||||||
|
* - Pops one numeric operand (int or float).
|
||||||
|
* - Computes e^x in double precision.
|
||||||
|
* - Pushes a VAL_FLOAT result.
|
||||||
|
*
|
||||||
|
* Stack effect:
|
||||||
|
* - Pop: x
|
||||||
|
* - Push: exp(x)
|
||||||
|
*
|
||||||
|
* Types:
|
||||||
|
* - Accepts VAL_INT and VAL_FLOAT; others raise a runtime error.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,33 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-01-03
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file floor.c
|
* @file floor.c
|
||||||
* @brief Implements the OP_FLOOR opcode using C99 math.h floor().
|
* @brief Implements the OP_FLOOR opcode using C99 math.h floor().
|
||||||
|
*
|
||||||
|
* VM opcode snippet included by vm.c. Provides numeric floor operation.
|
||||||
|
*
|
||||||
|
* Behavior:
|
||||||
|
* - Pops one numeric operand (int or float) from the stack.
|
||||||
|
* - Applies floor(x) in double precision.
|
||||||
|
* - If the result is an exact 64-bit integer, pushes VAL_INT; otherwise VAL_FLOAT.
|
||||||
|
*
|
||||||
|
* Stack effect:
|
||||||
|
* - Pop: x
|
||||||
|
* - Push: floor(x)
|
||||||
|
*
|
||||||
|
* Types:
|
||||||
|
* - Accepts VAL_INT and VAL_FLOAT.
|
||||||
|
* - Other types cause a runtime error.
|
||||||
|
*
|
||||||
|
* Errors:
|
||||||
|
* - Exits with an error message if the operand is not a number.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* - Input stack: [2.9] → Output stack: [2]
|
||||||
|
* - Input stack: [-2.1] → Output stack: [-3]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-01-03
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-01-03
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,23 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-01-03
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file gcd.c
|
* @file gcd.c
|
||||||
* @brief Implements the OP_GCD opcode for greatest common divisor.
|
* @brief Implements the OP_GCD opcode for greatest common divisor.
|
||||||
|
*
|
||||||
|
* Behavior:
|
||||||
|
* - Pops two numeric operands (a, b). Floats are truncated to int64.
|
||||||
|
* - Computes gcd(|a|, |b|) using Euclid's algorithm.
|
||||||
|
* - Pushes VAL_INT result.
|
||||||
|
*
|
||||||
|
* Stack effect:
|
||||||
|
* - Pop: b, a
|
||||||
|
* - Push: gcd(a, b)
|
||||||
|
*
|
||||||
|
* Types:
|
||||||
|
* - Accepts VAL_INT and VAL_FLOAT; others cause a runtime error.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
case OP_GCD: {
|
case OP_GCD: {
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,23 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-01-03
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file isqrt.c
|
* @file isqrt.c
|
||||||
* @brief Implements the OP_ISQRT opcode for integer square root (floor).
|
* @brief Implements the OP_ISQRT opcode for integer square root (floor).
|
||||||
|
*
|
||||||
|
* Behavior:
|
||||||
|
* - Pops one numeric operand (int or float), converts to int64.
|
||||||
|
* - Computes floor(sqrt(max(0, x))) as an integer without floating point.
|
||||||
|
* - Pushes VAL_INT result.
|
||||||
|
*
|
||||||
|
* Stack effect:
|
||||||
|
* - Pop: x
|
||||||
|
* - Push: isqrt(x)
|
||||||
|
*
|
||||||
|
* Types:
|
||||||
|
* - Accepts VAL_INT and VAL_FLOAT; others cause a runtime error.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
case OP_ISQRT: {
|
case OP_ISQRT: {
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,23 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-01-03
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file lcm.c
|
* @file lcm.c
|
||||||
* @brief Implements the OP_LCM opcode for least common multiple.
|
* @brief Implements the OP_LCM opcode for least common multiple.
|
||||||
|
*
|
||||||
|
* Behavior:
|
||||||
|
* - Pops two numeric operands (a, b). Floats are truncated to int64.
|
||||||
|
* - Computes lcm(|a|, |b|) using gcd; returns 0 if either input is 0.
|
||||||
|
* - Pushes VAL_INT result. May overflow silently on extreme inputs.
|
||||||
|
*
|
||||||
|
* Stack effect:
|
||||||
|
* - Pop: b, a
|
||||||
|
* - Push: lcm(a, b)
|
||||||
|
*
|
||||||
|
* Types:
|
||||||
|
* - Accepts VAL_INT and VAL_FLOAT; others cause a runtime error.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
case OP_LCM: {
|
case OP_LCM: {
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,23 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-01-03
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file log.c
|
* @file log.c
|
||||||
* @brief Implements the OP_LOG opcode using C99 math.h log() (natural logarithm).
|
* @brief Implements the OP_LOG opcode using C99 math.h log() (natural logarithm).
|
||||||
|
*
|
||||||
|
* Behavior:
|
||||||
|
* - Pops one numeric operand (int or float).
|
||||||
|
* - If x <= 0, pushes NaN to indicate domain error; otherwise pushes ln(x).
|
||||||
|
* - Result type is VAL_FLOAT.
|
||||||
|
*
|
||||||
|
* Stack effect:
|
||||||
|
* - Pop: x
|
||||||
|
* - Push: ln(x) | NaN
|
||||||
|
*
|
||||||
|
* Types:
|
||||||
|
* - Accepts VAL_INT and VAL_FLOAT; errors on others.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,19 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-01-03
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file log10.c
|
* @file log10.c
|
||||||
* @brief Implements the OP_LOG10 opcode using C99 math.h log10().
|
* @brief Implements the OP_LOG10 opcode using C99 math.h log10().
|
||||||
|
*
|
||||||
|
* Behavior:
|
||||||
|
* - Pops one numeric operand (int or float).
|
||||||
|
* - If x <= 0, pushes NaN; else pushes log10(x) as VAL_FLOAT.
|
||||||
|
*
|
||||||
|
* Stack effect:
|
||||||
|
* - Pop: x
|
||||||
|
* - Push: log10(x) | NaN
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-01-03
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-01-03
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,28 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-01-03
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file sin.c
|
* @file sin.c
|
||||||
* @brief Implements the OP_SIN opcode using C99 math.h sin().
|
* @brief Implements the OP_SIN opcode using C99 math.h sin().
|
||||||
|
*
|
||||||
|
* VM opcode snippet included by vm.c. Provides sine function.
|
||||||
|
*
|
||||||
|
* Behavior:
|
||||||
|
* - Pops one numeric operand (int or float) from the stack.
|
||||||
|
* - Computes sin(x) in double precision.
|
||||||
|
* - Always pushes a VAL_FLOAT result.
|
||||||
|
*
|
||||||
|
* Stack effect:
|
||||||
|
* - Pop: x
|
||||||
|
* - Push: sin(x)
|
||||||
|
*
|
||||||
|
* Types:
|
||||||
|
* - Accepts VAL_INT and VAL_FLOAT; others cause a runtime error.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* - Input [0] → Output [0.0]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,20 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-01-03
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file sqrt.c
|
* @file sqrt.c
|
||||||
* @brief Implements the OP_SQRT opcode using C99 math.h sqrt().
|
* @brief Implements the OP_SQRT opcode using C99 math.h sqrt().
|
||||||
|
*
|
||||||
|
* Behavior:
|
||||||
|
* - Pops one numeric operand (int or float).
|
||||||
|
* - If x < 0, pushes NaN; else pushes sqrt(x).
|
||||||
|
* - Returns VAL_INT when the result fits exactly in int64, otherwise VAL_FLOAT.
|
||||||
|
*
|
||||||
|
* Stack effect:
|
||||||
|
* - Pop: x
|
||||||
|
* - Push: sqrt(x) | NaN
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,25 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-01-03
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file tan.c
|
* @file tan.c
|
||||||
* @brief Implements the OP_TAN opcode using C99 math.h tan().
|
* @brief Implements the OP_TAN opcode using C99 math.h tan().
|
||||||
|
*
|
||||||
|
* VM opcode snippet included by vm.c. Provides tangent function.
|
||||||
|
*
|
||||||
|
* Behavior:
|
||||||
|
* - Pops one numeric operand (int or float) from the stack.
|
||||||
|
* - Computes tan(x) in double precision.
|
||||||
|
* - Always pushes a VAL_FLOAT result.
|
||||||
|
*
|
||||||
|
* Stack effect:
|
||||||
|
* - Pop: x
|
||||||
|
* - Push: tan(x)
|
||||||
|
*
|
||||||
|
* Types:
|
||||||
|
* - Accepts VAL_INT and VAL_FLOAT; others cause a runtime error.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-01-03
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,13 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-02-19
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file md5.c
|
||||||
|
* @brief VM opcode snippet: compute MD5 hash (OP_MD5). Included by vm.c.
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpenSSL MD5 builtin
|
* OpenSSL MD5 builtin
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-02-19
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-02-19
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2026-02-19
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2025-10-04
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2025-10-04
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,17 @@
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Get environment variables of the operation system.
|
/**
|
||||||
|
* @file env.c
|
||||||
|
* @brief Implements OP_ENV to read an environment variable by name.
|
||||||
|
*
|
||||||
|
* Behavior:
|
||||||
|
* - Pops a string key from the stack and pushes the associated environment value as string.
|
||||||
|
* - If the variable is not set, pushes an empty string ("") rather than Nil.
|
||||||
|
*
|
||||||
|
* Errors:
|
||||||
|
* - If the key is not a string, prints an error and terminates the VM with exit(1).
|
||||||
|
*/
|
||||||
|
|
||||||
case OP_ENV: {
|
case OP_ENV: {
|
||||||
Value key = pop_value(vm);
|
Value key = pop_value(vm);
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,16 @@
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
*
|
|
||||||
* Added: 2025-12-28
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Get all environment variables of the operation system and push them as a map.
|
/**
|
||||||
|
* @file env_all.c
|
||||||
|
* @brief Implements OP_ENV_ALL to read the full environment into a map.
|
||||||
|
*
|
||||||
|
* Behavior:
|
||||||
|
* - Pushes a new map where each key is an environment variable and each value is its string value.
|
||||||
|
* - Keys and values are copied; the caller owns the returned map Value.
|
||||||
|
*/
|
||||||
|
|
||||||
case OP_ENV_ALL: {
|
case OP_ENV_ALL: {
|
||||||
extern char **environ;
|
extern char **environ;
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,18 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file fd_poll_read.c
|
||||||
|
* @brief Implements OP_FD_POLL_READ to check if a file descriptor is readable.
|
||||||
*
|
*
|
||||||
* Added: 2026-03-26
|
* Behavior:
|
||||||
|
* - Pops timeout_ms (int) and fd (int); waits up to timeout for readability; pushes 1 if readable, 0 otherwise.
|
||||||
|
* - On non-UNIX platforms, returns 0 (unsupported).
|
||||||
|
*
|
||||||
|
* Errors:
|
||||||
|
* - If types are wrong, prints an error and returns 0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
case OP_FD_POLL_READ: {
|
case OP_FD_POLL_READ: {
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,18 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file fd_poll_write.c
|
||||||
|
* @brief Implements OP_FD_POLL_WRITE to check if a file descriptor is writable.
|
||||||
*
|
*
|
||||||
* Added: 2026-03-26
|
* Behavior:
|
||||||
|
* - Pops timeout_ms (int) and fd (int); waits up to timeout for writability; pushes 1 if writable, 0 otherwise.
|
||||||
|
* - On non-UNIX platforms, returns 0 (unsupported).
|
||||||
|
*
|
||||||
|
* Errors:
|
||||||
|
* - If types are wrong, prints an error and returns 0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
case OP_FD_POLL_WRITE: {
|
case OP_FD_POLL_WRITE: {
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,18 @@
|
||||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the Apache-2.0 license.
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
* https://opensource.org/license/apache-2-0
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file fd_set_nonblock.c
|
||||||
|
* @brief Implements OP_FD_SET_NONBLOCK to toggle O_NONBLOCK on a file descriptor.
|
||||||
*
|
*
|
||||||
* Added: 2026-03-26
|
* Behavior:
|
||||||
|
* - Pops on (int, 0/1) and fd (int); sets or clears O_NONBLOCK via fcntl; pushes 1 on success, 0 otherwise.
|
||||||
|
* - On non-UNIX platforms, returns 0 (unsupported).
|
||||||
|
*
|
||||||
|
* Errors:
|
||||||
|
* - If types are wrong, prints an error and returns 0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
case OP_FD_SET_NONBLOCK: {
|
case OP_FD_SET_NONBLOCK: {
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue