1
0
Fork 0
forked from fun/fun

Added tons of AI generated docs and a lot of cleanups. No code changes. (0.41.5)

This commit is contained in:
Johannes Findeisen 2026-05-01 02:20:25 +02:00
commit 4e69a3ce63
151 changed files with 3897 additions and 427 deletions

View file

@ -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);

View file

@ -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");

View file

@ -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

View file

@ -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);

View file

@ -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

View file

@ -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:

View file

@ -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;

View file

@ -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.

View file

@ -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;

View file

@ -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) {

View file

@ -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 */

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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) {

View file

@ -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

View file

@ -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 (/* ... *&#47;) 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);

View file

@ -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 ReadEvalPrint 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 */

View file

@ -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

View file

@ -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 nonzero 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 NULterminated and
* must be freed using the dedicated free function documented below.
*/
/// Use an FFIsafe 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 64bit 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 64bit 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; nonzero 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 NULterminated 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 performancecritical use.
///
/// Param msg NULterminated UTF8/bytes C string.
/// Return 0 on success.
///
/// Safety: `msg` must be a valid C string pointer when nonNULL.
#[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 UTF8, the raw byte sequence is duplicated asis.
/// - Memory ownership is transferred to the caller, who must free it with
/// fun_rust_string_free().
///
/// Param input NULterminated C string (may be NULL).
/// Return Newly allocated NULterminated C string owned by the caller.
///
/// Safety: `input` must be a valid pointer if nonNULL.
#[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 noop.
///
/// 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.

View file

@ -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 64bit integer from the VM stack.
fn vm_pop_i64(vm: *mut Vm) -> i64; fn vm_pop_i64(vm: *mut Vm) -> i64;
/// Push a 64bit 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;
} }

View file

@ -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 = "";

View file

@ -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 : "";

View file

@ -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;

View file

@ -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)) {

View file

@ -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
View file

@ -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
View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
*/ */

View file

@ -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
*/ */

View file

@ -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]
* *

View file

@ -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]
* *

View file

@ -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]
* *

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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 */

View file

@ -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;

View file

@ -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) {

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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);

View file

@ -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: {

View file

@ -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: {

View file

@ -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: {

View file

@ -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: {

View file

@ -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: {

View file

@ -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>)

View file

@ -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 */

View file

@ -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: {

View file

@ -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: {

View file

@ -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: {

View file

@ -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) */

View file

@ -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: {

View file

@ -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

View file

@ -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 */

View 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 */

View file

@ -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 */

View file

@ -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 */

View 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;

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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
*/ */
/** /**

View file

@ -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
*/ */
/** /**

View file

@ -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: {

View file

@ -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: {

View file

@ -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: {

View file

@ -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>

View file

@ -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>

View file

@ -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
*/ */
/** /**

View file

@ -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
*/ */
/** /**

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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
*/ */
/** /**

View file

@ -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
*/ */

View file

@ -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
*/ */
/* /*

View file

@ -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
*/ */
/** /**

View file

@ -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
*/ */
/** /**

View file

@ -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
*/ */
/** /**

View file

@ -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
*/ */
/** /**

View file

@ -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);

View file

@ -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;

View file

@ -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: {

View file

@ -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: {

View file

@ -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