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"
/* 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) {
if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
int n = array_length(v);
@ -25,6 +37,13 @@ int array_contains(const Value *v, const Value *needle) {
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) {
if (!v || v->type != VAL_ARRAY || !v->arr) return -1;
int n = array_length(v);
@ -39,6 +58,13 @@ int array_index_of(const Value *v, const Value *needle) {
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) {
if (!v || v->type != VAL_ARRAY || !v->arr) return;
int n = array_length(v);

View file

@ -11,6 +11,19 @@
#include <stdio.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 *bc = (Bytecode *)malloc(sizeof(Bytecode));
bc->instructions = NULL;
@ -22,12 +35,30 @@ Bytecode *bytecode_new(void) {
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) {
bc->constants = (Value *)realloc(bc->constants, sizeof(Value) * (bc->const_count + 1));
bc->constants[bc->const_count] = copy_value(&v);
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) {
bc->instructions = (Instruction *)realloc(bc->instructions, sizeof(Instruction) * (bc->instr_count + 1));
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++;
}
/**
* @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) {
if (idx >= 0 && idx < bc->instr_count) {
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) {
if (!bc) return;
for (int i = 0; i < bc->const_count; ++i) {
@ -53,6 +101,12 @@ void bytecode_free(Bytecode *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) {
switch (op) {
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) {
if (!bc) {
printf("<null bytecode>\n");

View file

@ -7,13 +7,31 @@
* 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
#define FUN_BYTECODE_H
#include "value.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 {
OP_NOP,
OP_LOAD_CONST, // operand = constant index

View file

@ -1,21 +1,47 @@
/*
/**
* 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 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) */
#ifdef FUN_WITH_CURL
#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 {
char *d;
size_t n;
char *d; /**< Data pointer (NUL-terminated). */
size_t n; /**< Number of bytes stored (excluding NUL). */
} 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) {
size_t add = sz * nm;
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';
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) {
FILE *f = (FILE *)ud;
return fwrite(ptr, sz, nm, f);

View file

@ -1,12 +1,18 @@
/*
/**
* 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 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

View file

@ -1,12 +1,19 @@
/*
/**
* 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 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) */
@ -19,6 +26,23 @@
#include <string.h>
/* --- 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) {
if (!j) return make_nil();
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) {
switch (v->type) {
case VAL_NIL:

View file

@ -1,12 +1,20 @@
/*
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* 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
#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,
* returns an allocated empty string ("") to keep behavior consistent with
* other optional extensions. */
/**
* @brief Compute MD5 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 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 const char hexdig[] = "0123456789abcdef";
if (!data && len != 0) return NULL;
@ -68,8 +82,15 @@ static char *fun_openssl_md5_hex(const unsigned char *data, size_t len) {
#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 const char hexdig[] = "0123456789abcdef";
if (!data && len != 0) return NULL;
@ -110,8 +131,15 @@ static char *fun_openssl_sha256_hex(const unsigned char *data, size_t len) {
#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 const char hexdig[] = "0123456789abcdef";
if (!data && len != 0) return NULL;
@ -152,10 +180,19 @@ static char *fun_openssl_sha512_hex(const unsigned char *data, size_t len) {
#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,
* 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. */
/**
* @brief Compute RIPEMD-160 digest and return it as a lowercase hex 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 const char hexdig[] = "0123456789abcdef";
if (!data && len != 0) return NULL;

View file

@ -1,12 +1,18 @@
/*
/**
* 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 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.

View file

@ -1,18 +1,20 @@
/*
/**
* 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
*
* Added: 2025-12-11 (2025-12-11 migrated from src/vm.c)
*/
/*
PCSC helpers: registries and helper functions.
Included the file scope from vm.c.
*/
/**
* @file pcsc.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
#if defined(__has_include)
@ -44,6 +46,11 @@ typedef struct {
static pcsc_ctx_entry g_pcsc_ctx[8];
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) {
for (int i = 0; i < (int)(sizeof(g_pcsc_ctx) / sizeof(g_pcsc_ctx[0])); ++i) {
if (!g_pcsc_ctx[i].in_use) {
@ -55,6 +62,11 @@ static int pcsc_alloc_ctx_slot(void) {
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) {
for (int i = 0; i < (int)(sizeof(g_pcsc_card) / sizeof(g_pcsc_card[0])); ++i) {
if (!g_pcsc_card[i].in_use) {
@ -67,6 +79,12 @@ static int pcsc_alloc_card_slot(void) {
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) {
if (id <= 0) return NULL;
int idx = id - 1;
@ -75,6 +93,12 @@ static pcsc_ctx_entry *pcsc_get_ctx(int id) {
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) {
if (id <= 0) return NULL;
int idx = id - 1;

View file

@ -1,29 +1,56 @@
/*
/**
* 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
*
* 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
#include <sqlite3.h>
/**
* @brief Node in a singly-linked list of registered SQLite handles.
*/
typedef struct SqlHandle {
int id;
sqlite3 *db;
struct SqlHandle *next;
} SqlHandle;
/** Global head of the SQLite handle list. */
static SqlHandle *g_sql_handles = NULL;
/** Next positive identifier to assign to a newly added handle. */
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) {
SqlHandle *h = (SqlHandle *)calloc(1, sizeof(SqlHandle));
if (!h) return NULL;
@ -34,12 +61,27 @@ static SqlHandle *sql_reg_add(sqlite3 *db) {
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) {
for (SqlHandle *p = g_sql_handles; p; p = p->next)
if (p->id == id) return p;
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) {
SqlHandle **pp = &g_sql_handles;
while (*pp) {

View file

@ -1,22 +1,48 @@
/*
/**
* 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
*
* 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
#include <libxml/parser.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 {
xmlDocPtr doc;
int in_use;
} 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 {
xmlNodePtr node;
int in_use;
@ -25,6 +51,14 @@ typedef struct {
static XmlDocSlot g_xml_docs[64];
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) {
for (int i = 1; i < (int)(sizeof(g_xml_docs) / sizeof(g_xml_docs[0])); ++i) {
if (!g_xml_docs[i].in_use) {
@ -35,10 +69,22 @@ static int xml_doc_alloc(xmlDocPtr d) {
}
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) {
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;
}
/**
* @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) {
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);
@ -47,6 +93,14 @@ static int xml_doc_free_handle(int h) {
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) {
for (int i = 1; i < (int)(sizeof(g_xml_nodes) / sizeof(g_xml_nodes[0])); ++i) {
if (!g_xml_nodes[i].in_use) {
@ -57,10 +111,25 @@ static int xml_node_alloc(xmlNodePtr n) {
}
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) {
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;
}
/**
* @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) {
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 */

View file

@ -1,4 +1,4 @@
/*
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
@ -7,7 +7,10 @@
* 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.
* Builds a CLI that runs a script file if provided; otherwise starts the REPL
* when compiled with FUN_WITH_REPL enabled.
@ -29,6 +32,14 @@
#define FUN_VERSION "0.0.0-dev"
#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) {
printf("Fun %s\n", FUN_VERSION);
printf("Usage:\n");
@ -51,6 +62,17 @@ static void print_usage(const char *prog) {
#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) {
/* Set FUN_EXECUTABLE environment variable to the path of this binary */
setenv("FUN_EXECUTABLE", argv[0], 1);

View file

@ -7,12 +7,23 @@
* 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 "value.h"
#include "vm.h"
#include <math.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) \
if ((val).type != VAL_INT || (val).i != (expected)) { \
fprintf(stderr, "Assertion failed: expected %lld, got ", (long long)(expected)); \
@ -21,6 +32,15 @@
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) {
VM vm;
vm_init(&vm);

View file

@ -1,4 +1,4 @@
/*
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
@ -7,6 +7,11 @@
* 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 <stdio.h>
#include <stdlib.h>
@ -15,11 +20,24 @@
#include "bytecode.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) {
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) {
*out_buf = NULL;
*out_len = 0;
@ -51,7 +69,14 @@ static int read_all(const char *path, char **out_buf, size_t *out_len) {
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) {
FILE *f = fopen(path, "wb");
if (!f) return 0;
@ -61,14 +86,32 @@ static int write_all(const char *path, const char *buf, size_t len) {
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) {
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,
* CRLF->LF, trim trailing spaces, ensure final newline, normalize 'sint*' to 'int*'.
/**
* @brief Apply conservative style/syntax auto-fixes to a Fun source buffer.
*
* 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) {
/* 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;
}
/**
* @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 do_fix = 0;
int quiet = 0;

View file

@ -7,10 +7,26 @@
* https://opensource.org/license/apache-2-0
*/
/**
* @file iter.c
* @brief Iterator-style helpers exposed as built-ins (enumerate, zip).
*/
#include "value.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) {
int n = array_length(arr);
if (n <= 0) return make_array_from_values(NULL, 0);
@ -34,7 +50,19 @@ Value bi_enumerate(const Value *arr) {
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) {
int na = array_length(a);
int nb = array_length(b);

View file

@ -7,6 +7,11 @@
* https://opensource.org/license/apache-2-0
*/
/**
* @file map.c
* @brief Simple string-keyed map implementation backing VAL_MAP Values.
*/
#include "value.h"
#include <stdlib.h>
#include <string.h>
@ -20,6 +25,13 @@ typedef struct Map {
Value *vals; /* each value owned here */
} 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) {
Map *m = (Map *)malloc(sizeof(Map));
if (!m) return make_nil();
@ -34,6 +46,12 @@ Value make_map_empty(void) {
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) {
if (m->cap >= need) return 1;
int ncap = m->cap == 0 ? 4 : m->cap * 2;
@ -48,6 +66,16 @@ static int map_ensure_cap(Map *m, int need) {
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) {
if (!vm || vm->type != VAL_MAP || !vm->map || !key) {
free_value(v);
@ -71,6 +99,16 @@ int map_set(Value *vm, const char *key, Value v) {
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) {
if (!vm || vm->type != VAL_MAP || !vm->map || !key) return 0;
Map *m = (Map *)vm->map;
@ -83,6 +121,12 @@ int map_get_copy(const Value *vm, const char *key, Value *out) {
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) {
if (!vm || vm->type != VAL_MAP || !vm->map || !key) return 0;
Map *m = (Map *)vm->map;
@ -92,6 +136,14 @@ int map_has(const Value *vm, const char *key) {
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) {
if (!vm || vm->type != VAL_MAP || !vm->map) return make_array_from_values(NULL, 0);
Map *m = (Map *)vm->map;
@ -108,6 +160,14 @@ Value map_keys_array(const Value *vm) {
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) {
if (!vm || vm->type != VAL_MAP || !vm->map) return make_array_from_values(NULL, 0);
Map *m = (Map *)vm->map;

View file

@ -72,6 +72,15 @@ static int g_err_col = 0;
static int g_temp_counter = 0;
/* ---- 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) {
const char *v = getenv(name);
if (!v) return 0;
@ -85,6 +94,14 @@ static int env_truthy(const char *name) {
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) {
/* Allow enabling debug dumps at runtime via environment.
* 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);
TYPE_META_STRING/BOOLEAN/NIL mark non-integer enforced types;
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
/** @brief Type metadata tag used for boolean enforcement in declared types. */
#define TYPE_META_BOOLEAN 10002
/** @brief Type metadata tag indicating explicit nil type. */
#define TYPE_META_NIL 10003
/** @brief Type metadata tag marking class/instance values. */
#define TYPE_META_CLASS 10004
/** @brief Type metadata tag marking floating point numbers. */
#define TYPE_META_FLOAT 10005
/** @brief Type metadata tag marking array values. */
#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, ...) {
g_has_error = 1;
g_err_pos = pos;
@ -115,6 +148,18 @@ static void parser_fail(size_t pos, const char *fmt, ...) {
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) {
int line = 1, col = 1;
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 int g_ns_alias_count = 0;
/**
* @brief Reset and free all tracked namespace aliases.
*/
static void ns_aliases_reset(void) {
for (int i = 0; i < g_ns_alias_count; ++i) {
free(g_ns_aliases[i]);
@ -146,6 +194,16 @@ static void ns_aliases_reset(void) {
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) {
const char *marker = "// __ns_alias__: ";
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) {
if (!name) return 0;
for (int i = 0; i < g_ns_alias_count; ++i) {
@ -202,6 +266,12 @@ static struct {
int count;
} 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) {
for (int i = 0; i < G.count; ++i) {
if (strcmp(G.names[i], name) == 0) return i;
@ -209,6 +279,16 @@ static int sym_find(const char *name) {
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) {
int existing = sym_find(name);
if (existing >= 0) return existing;
@ -235,6 +315,14 @@ static LocalEnv *g_locals = NULL;
static LocalEnv *g_func_env_stack[64];
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) {
if (g_func_env_depth <= 0) return 0;
for (int d = g_func_env_depth - 1; d >= 0; --d) {
@ -258,6 +346,12 @@ typedef struct LoopCtx {
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) {
if (!g_locals) return -1;
for (int i = 0; i < g_locals->count; ++i) {
@ -266,6 +360,12 @@ static int local_find(const char *name) {
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) {
if (!g_locals) return -1;
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);
/* 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) {
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;
}
/* 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) {
skip_spaces(src, len, 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);
}
/* 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) {
if (!emit_unary(bc, src, len, pos)) return 0;
for (;;) {
@ -4381,7 +4514,17 @@ static int emit_multiplicative(Bytecode *bc, const char *src, size_t len, size_t
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) {
if (!emit_multiplicative(bc, src, len, pos)) return 0;
for (;;) {
@ -4409,7 +4552,17 @@ static int emit_additive(Bytecode *bc, const char *src, size_t len, size_t *pos)
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) {
if (!emit_additive(bc, src, len, pos)) return 0;
for (;;) {
@ -4455,7 +4608,17 @@ static int emit_relational(Bytecode *bc, const char *src, size_t len, size_t *po
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) {
if (!emit_relational(bc, src, len, pos)) return 0;
for (;;) {
@ -4483,7 +4646,18 @@ static int emit_equality(Bytecode *bc, const char *src, size_t len, size_t *pos)
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) {
int jf_idxs[64];
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;
}
/* 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) {
int true_jumps[64];
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;
}
/* 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) {
/* parse condition (logical OR precedence or higher) */
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 */
/**
* @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) {
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 */
/**
* @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) {
/* Strict mode: only allow trailing spaces and comments until end-of-line. */
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) {
while (*pos < len) {
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);
/* 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) {
size_t local_pos = *pos;
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 */
/**
* @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) {
while (*pos < len) {
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) {
Bytecode *bc = bytecode_new();
size_t pos = 0;
@ -7240,6 +7516,16 @@ static Bytecode *compile_minimal(const char *src, size_t len) {
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) {
size_t len = 0;
char *src = read_file_all(path, &len);
@ -7416,6 +7702,15 @@ Bytecode *parse_file_to_bytecode(const char *path) {
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) {
if (!source) {
fprintf(stderr, "Error: null source provided\n");
@ -7459,6 +7754,19 @@ Bytecode *parse_string_to_bytecode(const char *source) {
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) {
if (!g_has_error) return 0;
if (msgBuf && msgCap > 0) {

View file

@ -8,14 +8,8 @@
*/
/**
* Parse a .fun source file and compile it into entry bytecode.
* Minimal support:
* - 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.
* @file parser.h
* @brief Public API for parsing Fun source into bytecode.
*/
#ifndef FUN_PARSER_H
@ -23,12 +17,37 @@
#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);
/* 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);
/* 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);
#endif

View file

@ -7,12 +7,29 @@
* 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 <ctype.h>
#include <stdio.h>
#include <stdlib.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) {
FILE *f = fopen(path, "rb");
if (!f) return NULL;
@ -38,6 +55,12 @@ static char *read_file_all(const char *path, size_t *out_len) {
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) {
while (*pos < len) {
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) {
while (*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) {
for (;;) {
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) {
size_t klen = strlen(kw);
if (pos + klen > len) return 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) {
if (*pos == 0 && starts_with(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) {
size_t p = *pos;
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;
}
/**
* @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) {
skip_ws(src, len, pos);
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;
}
/**
* @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) {
skip_ws(src, len, pos);
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 === */
/**
* @brief Skip only spaces, tabs and carriage returns (not newlines).
*/
static void skip_spaces(const char *src, size_t len, size_t *pos) {
while (*pos < len) {
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) {
size_t p = *pos;
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;
}
/**
* @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) {
size_t p = *pos;
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.
*/
/**
* @brief Thin wrapper over realloc used by local buffers.
*/
static void *xrealloc(void *ptr, size_t newcap) {
void *np = realloc(ptr, newcap);
return np;
}
/**
* @brief Simple growable string buffer.
*/
typedef struct {
char *buf;
size_t len;
size_t cap;
} StrBuf;
/**
* @brief Initialize a StrBuf with a small starting capacity.
*/
static void sb_init(StrBuf *sb) {
sb->buf = (char *)malloc(256);
sb->cap = sb->buf ? 256 : 0;
@ -280,6 +354,9 @@ static void sb_init(StrBuf *sb) {
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) {
if (need <= sb->cap) return;
size_t nc = sb->cap ? sb->cap : 256;
@ -291,6 +368,9 @@ static void sb_reserve(StrBuf *sb, size_t need) {
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) {
if (n == 0) return;
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';
}
/**
* @brief Append a NUL-terminated string to the buffer.
*/
static void sb_append(StrBuf *sb, const char *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) {
sb_reserve(sb, sb->len + 2);
if (!sb->buf) return;
@ -312,18 +398,27 @@ static void sb_append_ch(StrBuf *sb, char c) {
}
/* ---- Export collection for include-as namespaces ---- */
/**
* @brief List of exported symbol names discovered at top level.
*/
typedef struct {
char **names;
int count;
int cap;
} NameList;
/**
* @brief Initialize an empty NameList.
*/
static void nl_init(NameList *nl) {
nl->names = NULL;
nl->count = 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) {
if (!name || !name[0]) return;
if (nl->count >= nl->cap) {
@ -336,6 +431,9 @@ static void nl_add(NameList *nl, const char *name) {
nl->names[nl->count++] = strdup(name);
}
/**
* @brief Free all strings and internal storage in the list.
*/
static void nl_free(NameList *nl) {
if (!nl) return;
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.
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) {
if (!text || !out) return;
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) {
if (!src) return NULL;
if (depth > 64) {
@ -843,11 +959,17 @@ static char *preprocess_includes_internal(const char *src, const char *current_p
return out.buf;
}
/**
* @brief Public wrapper to preprocess includes without a current path.
*/
char *preprocess_includes(const char *src) {
return preprocess_includes_internal(src, NULL, 0);
}
/* 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) {
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
* 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,
char *out_path, size_t out_path_cap,
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. */
/**
* @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) {
size_t p = *pos;
skip_spaces(src, len, &p);

View file

@ -5,13 +5,15 @@
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2025-10-05
*/
/**
* REPL implementation for the Fun programming language.
* Built only when FUN_WITH_REPL is defined.
* @file repl.c
* @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"
@ -49,12 +51,19 @@ static char *rl_hist[RL_HIST_MAX];
static int rl_count = 0;
/* last history entry (or NULL) */
/**
* @brief Return the last history line or NULL if history is empty.
*/
static const char *rl_hist_last(void) {
if (rl_count <= 0) return NULL;
return rl_hist[rl_count - 1];
}
/* 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) {
if (!s) return;
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) */
/**
* @brief Load history entries from a file (one line per entry).
*/
static void rl_hist_load_file(const char *path) {
if (!path) return;
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 int g_raw_enabled = 0;
/**
* @brief Disable terminal raw mode if previously enabled.
*/
static void repl_disable_raw(void) {
if (g_raw_enabled) {
tcsetattr(STDIN_FILENO, TCSAFLUSH, &g_orig_tios);
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) {
if (!isatty(STDIN_FILENO)) return 0;
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 */
/**
* @brief Return 1 if path refers to a directory, 0 otherwise.
*/
static int is_dir_path(const char *path) {
struct stat st;
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) */
/**
* @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) {
if (count <= 0) return base_len;
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).
* 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) {
(void)len;
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] != ' ')
(*pos)--;
}
/**
* @brief Move cursor right by one word.
*/
static void rl_word_right(const char *out, size_t len, size_t *pos) {
if (!out || !pos) 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) */
/**
* @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) {
size_t len = *len_io;
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_cap = 0;
/**
* @brief Return 1 if c can start an identifier.
*/
static int is_ident_start(int c) {
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) {
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) {
if (!name || !*name) return;
/* dedupe */
@ -349,6 +393,9 @@ static void std_syms_add(const char *name) {
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) {
FILE *f = fopen(path, "r");
if (!f) return;
@ -387,6 +434,9 @@ static void scan_symbols_from_file(const char *path) {
fclose(f);
}
/**
* @brief Recursively scan a directory tree for .fun files.
*/
static void scan_dir_recursive(const char *dir) {
DIR *dp = opendir(dir);
if (!dp) return;
@ -410,11 +460,17 @@ static void scan_dir_recursive(const char *dir) {
closedir(dp);
}
/**
* @brief Populate stdlib completion symbols from a base directory.
*/
static void load_stdlib_symbols(const char *libdir) {
if (!libdir || !*libdir) return;
scan_dir_recursive(libdir);
}
/**
* @brief Free all memory held by stdlib completion symbols.
*/
static void free_stdlib_symbols(void) {
for (int i = 0; i < g_std_syms_count; ++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.
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) {
size_t len = *len_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) */
/**
* @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) {
#ifdef _WIN32
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 ---------- */
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) {
if (*p != ' ' && *p != '\t' && *p != '\r' && *p != '\n') return 0;
}
return 1;
}
/**
* @brief Advance pointer past leading spaces and tabs.
*/
static const char *lstrip(const char *s) {
while (*s == ' ' || *s == '\t')
s++;
return s;
}
/**
* @brief Heuristic: check if a line ends with an operator that suggests continuation.
*/
static int ends_with_opener(const char *line) {
size_t n = strlen(line);
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. */
/**
* @brief Compute number of open indentation blocks (2 spaces per level).
*/
static int compute_open_indent_blocks(const char *buf) {
int in_block_comment = 0;
int open = 0;
@ -887,6 +966,10 @@ static int compute_open_indent_blocks(const char *buf) {
}
/* 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) {
int in_single = 0, in_double = 0, escape = 0;
int in_block_comment = 0, in_line_comment = 0;
@ -1018,6 +1101,9 @@ static int buffer_looks_incomplete(const char *buf) {
return 0;
}
/**
* @brief Print REPL help with available commands.
*/
static void show_repl_help(void) {
printf("Commands:\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");
}
/**
* @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) {
FILE *f = fopen(path, "rb");
if (!f) return NULL;
@ -1082,6 +1172,10 @@ static char *read_entire_file(const char *path, size_t *out_len) {
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) {
FILE *f = fopen(path, "wb");
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 ---------- */
/**
* @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[]) {
if (!cmd || !*cmd) return 0;
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 ---------- */
/**
* @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) {
if (!out || !data || len == 0) return;
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) {
if (n <= 0) n = 50;
size_t flen = 0;
@ -1150,6 +1253,9 @@ static void print_last_n_lines(const char *path, int n) {
free(content);
}
/**
* @brief Append multi-line buffer to history file, one line per entry.
*/
static void append_history(FILE *hist, const char *buffer) {
if (!hist || !buffer) return;
fputs(buffer, hist);
@ -1158,6 +1264,9 @@ static void append_history(FILE *hist, const char *buffer) {
}
/* ---------- Env helpers ---------- */
/**
* @brief Print usage for :env commands.
*/
static void env_show_usage(void) {
printf("Usage:\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");
}
/**
* @brief Show the value of an environment variable.
*/
static void env_get(const char *name) {
const char *v = getenv(name);
if (v)
@ -1173,6 +1285,9 @@ static void env_get(const char *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) {
#ifdef _WIN32
if (_putenv_s(name, value ? value : "") != 0) {
@ -1187,6 +1302,11 @@ static void env_set(const char *name, const char *value) {
/* ---------- 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 repl_timing = 0;
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
#define FUN_REPL_H
@ -8,8 +21,16 @@ extern "C" {
#endif
#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);
#endif

View file

@ -1,25 +1,58 @@
/*
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* 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;
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;
/// 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);
}
// 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;
/// 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]
pub extern "C" fn fun_op_radd(vm: *mut Vm) -> i32 {
unsafe {
@ -30,12 +63,26 @@ pub extern "C" fn fun_op_radd(vm: *mut Vm) -> i32 {
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]
pub extern "C" fn fun_rust_get_string() -> *const core::ffi::c_char {
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]
pub extern "C" fn fun_rust_print_string(msg: *const core::ffi::c_char) -> i32 {
unsafe {
@ -48,8 +95,17 @@ pub extern "C" fn fun_rust_print_string(msg: *const core::ffi::c_char) -> i32 {
0
}
// Return a newly allocated duplicate of the given C string.
// Caller (C side) must free using fun_rust_string_free.
/// Duplicate a C string and return an owned copy allocated by Rust.
///
/// - 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]
pub extern "C" fn fun_rust_echo_string(input: *const core::ffi::c_char) -> *mut core::ffi::c_char {
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]
pub extern "C" fn fun_rust_string_free(ptr: *mut core::ffi::c_char) {
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.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-27
*/
//! 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
//! ABI helpers declared on the C side (`vm_pop_i64`, `vm_push_i64`).
/**
* 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
* 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 core::mem::size_of;
use core::ptr;
extern "C" {
/// Pop a 64bit integer from the VM stack.
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);
/// 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;
/// Size of the C Vm struct (bytes).
fn vm_sizeof() -> usize;
/// Size of the C Value struct (bytes).
fn vm_value_sizeof() -> usize;
/// Byte offset of the `exit_code` field inside C Vm.
fn vm_offset_of_exit_code() -> usize;
/// Byte offset of the `sp` field inside C Vm.
fn vm_offset_of_sp() -> usize;
/// Byte offset of the `stack` field inside C Vm.
fn vm_offset_of_stack() -> usize;
}

View file

@ -7,12 +7,31 @@
* 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 <stdlib.h>
#include <string.h>
/* 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) {
if (!s) return strdup("");
int n = (int)strlen(s);
@ -27,6 +46,13 @@ char *string_substr(const char *s, int start, int len) {
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) {
if (!hay || !needle) return -1;
const char *p = strstr(hay, needle);
@ -34,6 +60,17 @@ int string_find(const char *hay, const char *needle) {
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) {
if (!s) s = "";
if (!sep) sep = "";
@ -90,6 +127,17 @@ Value string_split_to_array(const char *s, const char *sep) {
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) {
if (!v || v->type != VAL_ARRAY || !v->arr) return strdup("");
if (!sep) sep = "";

View file

@ -7,17 +7,52 @@
* 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 <stdlib.h>
/* 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) {
const char *s = (str && str->type == VAL_STRING && str->s) ? str->s : "";
const char *p = (sep && sep->type == VAL_STRING && sep->s) ? sep->s : "";
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) {
const char *p = (sep && sep->type == VAL_STRING && sep->s) ? sep->s : "";
char *s = array_join_with_sep(arr, p);
@ -26,6 +61,19 @@ Value bi_join(const Value *arr, const Value *sep) {
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) {
const char *s = (str && str->type == VAL_STRING && str->s) ? str->s : "";
char *sub = string_substr(s, start, len);
@ -34,6 +82,17 @@ Value bi_substr(const Value *str, int start, int len) {
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) {
const char *h = (hay && hay->type == VAL_STRING && hay->s) ? hay->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
*/
/**
* @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 "value.h"
#include "vm.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() {
VM vm;

View file

@ -7,6 +7,18 @@
* 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 <stdio.h>
#include <stdlib.h>
@ -30,6 +42,12 @@ typedef struct Map {
Value *vals; /* each value owned here */
} 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 val;
val.type = VAL_INT;
@ -37,6 +55,12 @@ Value make_int(int64_t v) {
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 val;
val.type = VAL_FLOAT;
@ -44,6 +68,14 @@ Value make_float(double v) {
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 val;
val.type = VAL_BOOL;
@ -51,6 +83,15 @@ Value make_bool(int v) {
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 val;
val.type = VAL_STRING;
@ -61,6 +102,15 @@ Value make_string(const char *s) {
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 val;
val.type = VAL_FUNCTION;
@ -68,12 +118,27 @@ Value make_function(struct Bytecode *fn) {
return val;
}
/**
* @brief Construct a nil Value.
*
* @return A Value with type VAL_NIL.
*/
Value make_nil(void) {
Value v;
v.type = VAL_NIL;
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) {
if (count < 0) count = 0;
Array *arr = (Array *)malloc(sizeof(Array));
@ -102,12 +167,28 @@ Value make_array_from_values(const Value *vals, int count) {
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) {
if (!v || v->type != VAL_ARRAY || !v->arr) return -1;
const Array *a = (const Array *)v->arr;
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) {
if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
const Array *a = (const Array *)v->arr;
@ -116,6 +197,16 @@ int array_get_copy(const Value *v, int index, Value *out) {
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) {
if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
Array *a = (Array *)v->arr;
@ -125,6 +216,15 @@ int array_set(Value *v, int index, Value newElem) {
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) {
if (newCount <= a->count) return 1;
/* grow to at least newCount; double strategy */
@ -145,6 +245,15 @@ static int ensure_array_capacity(Array *a, int newCount) {
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) {
if (!v || v->type != VAL_ARRAY || !v->arr) return -1;
Array *a = (Array *)v->arr;
@ -160,6 +269,16 @@ int array_push(Value *v, Value newElem) {
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) {
if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
Array *a = (Array *)v->arr;
@ -173,6 +292,16 @@ int array_pop(Value *v, Value *out) {
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) {
if (!v || v->type != VAL_ARRAY || !v->arr) return -1;
Array *a = (Array *)v->arr;
@ -193,6 +322,17 @@ int array_insert(Value *v, int index, Value newElem) {
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) {
if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
Array *a = (Array *)v->arr;
@ -209,6 +349,16 @@ int array_remove(Value *v, int index, Value *out) {
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) {
if (!v || v->type != VAL_ARRAY || !v->arr) return make_nil();
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);
}
/**
* @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) {
if (!av || !bv || av->type != VAL_ARRAY || bv->type != VAL_ARRAY) return make_nil();
const Array *a = (const Array *)av->arr;
@ -243,6 +403,15 @@ Value array_concat(const Value *av, const Value *bv) {
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 out;
out.type = v->type;
@ -282,6 +451,15 @@ Value copy_value(const Value *v) {
}
/* 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) {
switch (v->type) {
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) {
if (v.type == VAL_STRING && 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) */
}
/**
* @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) {
switch (v->type) {
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) {
switch (v->type) {
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 */
/**
* @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) {
if (!v) return strdup("nil");
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) {
// 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)) {

View file

@ -44,6 +44,9 @@ struct Bytecode; /* forward */
struct Array; /* forward */
struct Map; /* forward */
/**
* @brief Enumeration of all runtime value types supported by Fun.
*/
typedef enum {
VAL_INT,
VAL_BOOL,
@ -55,6 +58,13 @@ typedef enum {
VAL_FLOAT
} 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 {
ValueType type;
union {
@ -68,55 +78,91 @@ typedef struct {
} Value;
/* constructors / helpers */
/** Create an integer Value. */
Value make_int(int64_t v);
/** Create a boolean Value (0=false, non-zero=true). */
Value make_bool(int v);
/** Create a string Value by copying @p s. */
Value make_string(const char *s);
/** Create a function Value from bytecode pointer (shallow). */
Value make_function(struct Bytecode *fn);
/** Create a nil Value. */
Value make_nil(void);
/** Create a floating-point Value. */
Value make_float(double v);
/* arrays */
Value make_array_from_values(const Value *vals, int count); /* deep-copies vals */
int array_length(const Value *v); /* returns -1 if not array */
int array_get_copy(const Value *v, int index, Value *out); /* returns 0 on error; out = copy_value(item) */
int array_set(Value *v, int index, Value newElem); /* returns 0 on error; takes ownership of newElem */
int array_push(Value *v, Value newElem); /* returns new length or -1 on error */
int array_pop(Value *v, Value *out); /* returns 1 on success, out takes ownership */
int array_insert(Value *v, int index, Value newElem); /* returns new length or -1 */
int array_remove(Value *v, int index, Value *out); /* returns 1 on success */
Value array_slice(const Value *v, int start, int end); /* negative end means till end */
Value array_concat(const Value *a, const Value *b); /* returns new array */
/** Build an array from a list of Values (deep-copies vals). */
Value make_array_from_values(const Value *vals, int count);
/** Get array length or -1 if @p v is not an array. */
int array_length(const Value *v);
/** Copy array item at index to out; returns 0 on error. */
int array_get_copy(const Value *v, int index, Value *out);
/** Set element at index; takes ownership of newElem; 0 on error. */
int array_set(Value *v, int index, Value newElem);
/** Push new element; returns new length or -1 on error. */
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) */
Value make_map_empty(void); /* new empty map */
int map_set(Value *m, const char *key, Value v); /* 1 on ok (takes ownership of v) */
int map_get_copy(const Value *m, const char *key, Value *out); /* 1 on found, out=copy */
int map_has(const Value *m, const char *key); /* 1/0 */
Value map_keys_array(const Value *m); /* array of strings */
Value map_values_array(const Value *m); /* array of values (copies) */
/** Create a new empty string-keyed map Value. */
Value make_map_empty(void);
/** Set key to v (takes ownership); returns 1 on success. */
int map_set(Value *m, const char *key, Value v);
/** Lookup key; returns 1 and copies value to out on success. */
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 */
Value copy_value(const Value *v); /* deep for strings, RC for arrays/maps, shallow fn */
Value deep_copy_value(const Value *v); /* deep copy including arrays/maps */
void free_value(Value v); /* frees owned resources */
/** Shallow/deep copy depending on type (deep for strings, RC for arrays/maps). */
Value copy_value(const Value *v);
/** Deep copy including arrays/maps. */
Value deep_copy_value(const Value *v);
/** Free owned resources of v. */
void free_value(Value v);
/* utilities */
/** Print value in a human-readable form to stdout. */
void print_value(const Value *v);
/** Truthiness predicate used by the language semantics. */
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 */
/** Convert Value to a newly allocated C string; caller must free. */
char *value_to_string_alloc(const Value *v);
/* array utils */
int array_contains(const Value *arr, const Value *needle); /* 1/0 */
int array_index_of(const Value *arr, const Value *needle); /* idx or -1 */
void array_clear(Value *arr); /* free elements, count=0 */
/** Return 1 if needle equals any element in arr. */
int array_contains(const Value *arr, const Value *needle);
/** 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 */
char *string_substr(const char *s, int start, int len); /* clamps bounds */
int string_find(const char *hay, const char *needle); /* index or -1 */
Value string_split_to_array(const char *s, const char *sep); /* array of strings */
char *array_join_with_sep(const Value *arr, const char *sep); /* join items as strings */
/** Create newly allocated substring (bounds clamped). */
char *string_substr(const char *s, int start, int len);
/** Find first index of needle in hay or -1. */
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

260
src/vm.c
View file

@ -7,6 +7,16 @@
* 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
* before any system headers are included by amalgamated .c files. */
#ifndef _WIN32
@ -85,7 +95,19 @@ static __declspec(thread) VM *g_active_vm = NULL;
static __thread VM *g_active_vm = NULL;
#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) {
int written = vfprintf(stream, fmt, ap);
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;
}
/**
* @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, ...) {
va_list ap;
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;
/**
* @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) {
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 */
@ -194,6 +235,16 @@ static void push_value(VM *vm, Value v);
/* Raise a runtime error that respects try/catch/finally semantics.
* 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. */
/**
* @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) {
if (!vm || vm->fp < 0) {
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.
*/
/**
* @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) {
switch (t) {
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) {
for (int i = 0; i < vm->output_count; ++i) {
free_value(vm->output[i]);
@ -291,6 +356,14 @@ void vm_clear_output(VM *vm) {
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) {
// currently nothing persistent allocated inside VM itself
}
@ -298,6 +371,14 @@ void vm_free(VM *vm) {
/* forward declaration for helper used in vm_reset */
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) {
// Pop all frames (free locals)
while (vm->fp >= 0) {
@ -319,6 +400,11 @@ void vm_reset(VM *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) {
printf("=== globals ===\n");
for (int i = 0; i < MAX_GLOBALS; ++i) {
@ -333,6 +419,13 @@ void vm_dump_globals(VM *vm) {
/* --- 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) {
for (int i = 0; i < vm->break_count; ++i) {
if (vm->breakpoints[i].file) {
@ -349,6 +442,14 @@ void vm_debug_reset(VM *vm) {
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) {
if (!file || line <= 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;
}
/**
* @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) {
if (id < 0 || id >= vm->break_count) return 0;
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;
}
/**
* @brief Remove all breakpoints from the VM.
*
* @param vm VM instance.
*/
void vm_debug_clear_breakpoints(VM *vm) {
vm_debug_reset(vm);
}
/**
* @brief Print active breakpoints to stdout.
*
* @param vm VM instance.
*/
void vm_debug_list_breakpoints(VM *vm) {
if (vm->break_count <= 0) {
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) {
vm->debug_step_mode = 1; // step
vm->debug_step_start_ic = vm->instr_count;
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) {
vm->debug_step_mode = 2; // next (step over)
vm->debug_step_target_fp = vm->fp;
@ -402,17 +532,35 @@ void vm_debug_request_next(VM *vm) {
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) {
vm->debug_step_mode = 3; // finish (until return)
vm->debug_step_target_fp = vm->fp;
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) {
vm->debug_step_mode = 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) {
if (vm->sp >= STACK_SIZE - 1) {
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 */
}
/**
* @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) {
if (vm->sp < 0) {
fprintf(stderr, "Runtime error: stack underflow\n");
@ -430,6 +586,15 @@ static Value pop_value(VM *vm) {
}
/* --- 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) {
Value v = pop_value(vm);
int64_t out = 0;
@ -447,39 +612,89 @@ int64_t vm_pop_i64(VM *vm) {
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) {
push_value(vm, make_int(v));
}
/* --- 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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
f->fn = NULL;
f->ip = 0;
@ -488,6 +703,14 @@ static void frame_init(Frame *f) {
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) {
vm->sp = -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] */
/**
* @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) {
if (vm->fp >= MAX_FRAMES - 1) {
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 */
/**
* @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) {
if (vm->fp < 0) {
fprintf(stderr, "Runtime error: pop frame with empty frame stack\n");
@ -546,6 +787,13 @@ static void vm_pop_frame(VM *vm) {
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) {
for (int i = 0; i < vm->output_count; ++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) {
/* reset instruction count for this run */
vm->instr_count = 0;

119
src/vm.h
View file

@ -7,6 +7,15 @@
* 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
#define FUN_VM_H
@ -74,6 +83,13 @@ static const char *opcode_names[] = {
/* C++ demo */
"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 {
Bytecode *fn;
int ip;
@ -83,6 +99,14 @@ typedef struct {
int try_sp; /* -1 when empty */
} 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 {
Value stack[STACK_SIZE];
int sp;
@ -120,76 +144,125 @@ struct VM {
int break_count; // number of active breakpoints
};
/** @brief Opaque VM alias for external users. */
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);
// 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);
/**
* @brief Print buffered output entries to stdout (debug aid).
* @param vm VM instance.
*/
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);
// 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);
// 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);
// 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);
/* Raise a runtime error that respects try/catch/finally.
* If a try handler is active in the current frame, control jumps to it
* with an error string pushed on the stack. Otherwise, prints the error
* (annotated with location) and terminates execution. */
/**
* @brief Raise a runtime error honoring active try/catch/finally handlers.
* If a try handler is active in the current frame, control jumps to it with
* 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);
/* --- Debugger API --- */
/** Reset debugger state (clear step mode and breakpoints). */
void vm_debug_reset(VM *vm);
int vm_debug_add_breakpoint(VM *vm, const char *file, int line); // returns id >=0 or -1
int vm_debug_delete_breakpoint(VM *vm, int id); // returns 1 on success
/** Add a breakpoint at file:line; returns non-negative id on success or -1. */
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);
/** Print the current list of breakpoints to stdout. */
void vm_debug_list_breakpoints(VM *vm);
/** Request single-step execution mode. */
void vm_debug_request_step(VM *vm);
/** Step over (next) within the current frame. */
void vm_debug_request_next(VM *vm);
/** Run until the current frame returns (finish). */
void vm_debug_request_finish(VM *vm);
/** Continue execution until next breakpoint/stop. */
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) {
return op >= OP_NOP && op <= OP_CPP_ADD; // all current opcodes
}
/* --- 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);
/* Push an int64 onto VM stack. */
/** Push an int64 onto the VM stack. */
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);
/* 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);
/* 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);
/* 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);
/* 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);
/* C++ demo opcode entry point (C ABI) */
/** C++ demo opcode entry point (C ABI). */
int fun_op_cpp_add(struct VM *vm);
/* --- 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 of struct Value in bytes. */
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);
/* 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_sp(void);
size_t vm_offset_of_stack(void);

View file

@ -9,24 +9,28 @@
/**
* @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
* on two integer values popped from the stack and pushes the result back onto the stack.
* Handles the OP_DIV instruction, dividing two numeric operands and pushing
* 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:
* - Pops two integer values from the stack.
* - Performs integer division (`a / b`).
* - Pushes the result back onto the stack.
* - Pops two values from the stack.
* - If any operand is VAL_FLOAT, computes (double)a / (double)b and pushes a VAL_FLOAT.
* - Else computes a.i / b.i and pushes a VAL_INT.
*
* Error Handling:
* - Exits with an error if the operands are not integers.
* - Exits with an error if division by zero is attempted.
* - Raises a runtime error and aborts execution if operands are not numeric.
* - Raises a runtime error on division by zero (both integer and floating cases).
*
* Example:
* // Bytecode: OP_DIV
* // Stack before: [10, 2]
* // Stack after: [5]
* // Stack before: [5.0, 2]
* // Stack after: [2.5]
*
* @author Johannes Findeisen
* @date 2025-10-16

View file

@ -9,23 +9,27 @@
/**
* @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
* on two integer values popped from the stack and pushes the result back onto the stack.
* Handles the OP_MUL instruction, multiplying two numeric operands and
* pushing the result. If either operand is a float, multiplication is
* performed in double precision; otherwise it is 64-bit integer
* multiplication.
*
* Behavior:
* - Pops two integer values from the stack.
* - Performs integer multiplication (`a * b`).
* - Pushes the result back onto the stack.
* - Pops two values from the stack.
* - If any operand is VAL_FLOAT, computes (double)a * (double)b and pushes a VAL_FLOAT.
* - Else computes a.i * b.i and pushes a VAL_INT.
*
* 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:
* // Bytecode: OP_MUL
* // Stack before: [3, 4]
* // Stack after: [12]
* // Stack before: [2.5, 4]
* // Stack after: [10.0]
*
* @author Johannes Findeisen
* @date 2025-10-16

View file

@ -9,23 +9,27 @@
/**
* @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
* on two integer values popped from the stack and pushes the result back onto the stack.
* Handles the OP_SUB instruction, subtracting two numeric operands and
* pushing the result. If either operand is a float, subtraction is
* performed in double precision; otherwise it is 64-bit integer
* subtraction.
*
* Behavior:
* - Pops two integer values from the stack.
* - Performs integer subtraction (`a - b`).
* - Pushes the result back onto the stack.
* - Pops two values from the stack.
* - If any operand is VAL_FLOAT, computes (double)a - (double)b and pushes a VAL_FLOAT.
* - Else computes a.i - b.i and pushes a VAL_INT.
*
* 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:
* // Bytecode: OP_SUB
* // Stack before: [10, 4]
* // Stack after: [6]
* // Stack before: [10.0, 3]
* // Stack after: [7.0]
*
* @author Johannes Findeisen
* @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.
*
* This file handles the OP_CLEAR instruction, which clears all elements from an array.
* The array is popped from the stack, and nothing is pushed back.
*
* Handles the OP_CLEAR instruction, which removes all elements from an array.
* The array is popped from the stack; the opcode pushes an integer result
* (currently 0) as an acknowledgement.
* Behavior:
* - Pops the array from the stack.
* - Clears all elements from the array.
*
* Error Handling:
* - Exits with an error if the array is of the wrong type.
*
* Example:
* - Clears all elements from the array (array becomes empty in place).
* - Pushes 0 (integer) to acknowledge success.
* Error Handling:
* - Exits with a runtime error if the operand is not an array.
* Example:
* // Bytecode: OP_CLEAR
* // Stack before: [[10, 20, 30]]
* // Stack after: []
*
* @author Johannes Findeise
* // Stack after: [0]
* @author Johannes Findeisen
* @date 2025-10-16
*/

View file

@ -28,7 +28,7 @@
* // Stack before: [1, 2, 3]
* // Stack after: [[1, 2, 3]]
*
* @author Johanes Findeisen
* @author Johannes Findeisen
* @date 2025-10-16
*/

View file

@ -8,11 +8,12 @@
*/
/**
* @file arr_push.c
* @brief Implements the OP_ARR_PUSH opcode for appending elements to arrays in the VM.
* @file push.c
* @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.
* The array and value are popped from the stack, and the new length of the array is pushed back onto the stack.
* Handles the OP_PUSH instruction, which appends a value to the end of an array.
* The array and value are popped from the stack; the opcode pushes the new array
* length (integer) as a result.
*
* Behavior:
* - Pops the value and array from the stack.
@ -20,11 +21,11 @@
* - Pushes the new length of the array onto the stack.
*
* Error Handling:
* - Exits with an error if the array is of the wrong type.
* - Exits with an error if memory allocation fails during the append operation.
* - Exits with a runtime error if the first operand is not an array.
* - Exits with a runtime error if memory allocation fails.
*
* Example:
* // Bytecode: OP_ARR_PUSH
* // Bytecode: OP_PUSH
* // Stack before: [42, [10, 20, 30]]
* // Stack after: [4]
*

View file

@ -8,11 +8,11 @@
*/
/**
* @file arr_remove.c
* @brief Implements the OP_ARR_REMOVE opcode for removing elements from arrays in the VM.
* @file remove.c
* @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
* at a specified index. The array and index are popped from the stack, and the removed
* Handles the OP_REMOVE instruction, which removes an element from an array at the
* specified index. The array and index are popped from the stack; the removed
* element is pushed back onto the stack.
*
* Behavior:
@ -21,11 +21,11 @@
* - Pushes the removed element onto the stack.
*
* Error Handling:
* - Exits with an 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 container is not an array or index is not an int.
* - Exits with a runtime error if the index is out of bounds.
*
* Example:
* // Bytecode: OP_ARR_REMOVE
* // Bytecode: OP_REMOVE
* // Stack before: [1, [10, 20, 30]]
* // Stack after: [20]
*

View file

@ -8,23 +8,24 @@
*/
/**
* @file arr_set.c
* @brief Implements the OP_ARR_SET opcode for setting elements in arrays in the VM.
* @file set.c
* @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.
* The value, index, and array are popped from the stack, and the value is pushed back onto the stack.
* 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; the value is returned back
* on the stack (as a copy) to mirror expression semantics.
*
* Behavior:
* - Pops the value, index, and array from the stack.
* - 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:
* - Exits with an 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 array or index is of the wrong type.
* - Exits with a runtime error if the index is out of bounds.
*
* Example:
* // Bytecode: OP_ARR_SET
* // Bytecode: OP_SET
* // Stack before: [42, 1, [10, 20, 30]]
* // Stack after: [42]
*

View file

@ -5,14 +5,28 @@
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* 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)
* pops: b, a
* pushes: (uint32_t)(a & b)
* OP_BAND: bitwise AND (uint32)
*
* 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: {
Value vb = pop_value(vm);

View file

@ -5,14 +5,28 @@
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* 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)
* pops: a
* pushes: (uint32_t)(~a)
* OP_BNOT: bitwise NOT (uint32)
*
* 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: {
Value va = pop_value(vm);

View file

@ -5,14 +5,28 @@
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* 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)
* pops: b, a
* pushes: (uint32_t)(a | b)
* OP_BOR: bitwise OR (uint32)
*
* 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: {
Value vb = pop_value(vm);

View file

@ -5,14 +5,28 @@
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2025-09-29
*/
/**
* OP_BXOR: bitwise XOR (uint32 32-bit)
* pops: b, a
* pushes: (uint32_t)(a ^ b)
* @file bxor.c
* @brief Implements the OP_BXOR opcode (bitwise XOR).
*
* 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: {
Value vb = pop_value(vm);

View file

@ -5,14 +5,27 @@
* 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 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)
* 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: {
Value vs = pop_value(vm);

View file

@ -5,14 +5,27 @@
* 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 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)
* 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: {
Value vs = pop_value(vm);

View file

@ -5,14 +5,27 @@
* 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 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)
* 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: {
Value vs = pop_value(vm);

View file

@ -5,14 +5,28 @@
* 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 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)
* 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: {
Value vs = pop_value(vm);

View file

@ -7,6 +7,32 @@
* 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: {
Value err = pop_value(vm);
/* 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
*/
/**
* @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: {
if (f->try_sp >= 0) f->try_sp--;
break;

View file

@ -7,6 +7,25 @@
* 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: {
/* 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) {

View file

@ -1,17 +1,26 @@
/*
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-29
*/
/*
* Minimal C++ opcode for Fun VM cpp_add
* Build is gated by -DFUN_WITH_CPP=ON (see CMake).
/**
* @file add.cpp
* @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>
@ -20,6 +29,21 @@ extern "C" {
#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) {
int64_t a = 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: {
#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: {
#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: {
#ifdef FUN_WITH_CURL

View file

@ -1,4 +1,4 @@
/*
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
@ -8,11 +8,21 @@
*/
/**
* Implements OP_ECHO: print top-of-stack value without trailing newline.
* Now stores the value into the VM's output buffer and marks it as partial,
* so the CLI can render echo output together with following print output.
* @file echo.c
* @brief Implements the OP_ECHO opcode for printing without a trailing newline.
*
* 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: {
Value v = pop_value(vm);
Value snap = deep_copy_value(&v);

View file

@ -1,14 +1,34 @@
/*
/**
* 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
*
* 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 */
#ifdef FUN_WITH_INI
case OP_INI_FREE: {

View file

@ -1,14 +1,33 @@
/*
/**
* 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
*
* 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 */
#ifdef FUN_WITH_INI
case OP_INI_GET_BOOL: {

View file

@ -1,14 +1,27 @@
/*
/**
* 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
*
* 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 */
#ifdef FUN_WITH_INI
case OP_INI_GET_DOUBLE: {

View file

@ -1,14 +1,27 @@
/*
/**
* 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
*
* 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 */
#ifdef FUN_WITH_INI
case OP_INI_GET_INT: {

View file

@ -1,14 +1,27 @@
/*
/**
* 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
*
* 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 */
#ifdef FUN_WITH_INI
case OP_INI_GET_STRING: {

View file

@ -1,4 +1,4 @@
/*
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
@ -7,6 +7,15 @@
* 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
#if defined(__has_include)
#if __has_include(<iniparser/iniparser.h>)

View file

@ -5,11 +5,12 @@
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* 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
#ifdef FUN_WITH_INI
@ -29,19 +30,43 @@
#endif
#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 {
dictionary *dict;
int in_use;
} IniSlot;
/* Single global registry (defined in handles.c) */
/** Single global registry (defined in handles.c). */
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);
/**
* @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);
/**
* @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);
/* 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);
#endif /* FUN_WITH_INI */

View file

@ -1,14 +1,28 @@
/*
/**
* 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
*
* 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 */
#ifdef FUN_WITH_INI
case OP_INI_LOAD: {

View file

@ -1,14 +1,27 @@
/*
/**
* 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
*
* 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 */
#ifdef FUN_WITH_INI
case OP_INI_SAVE: {

View file

@ -1,14 +1,28 @@
/*
/**
* 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
*
* 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 */
#ifdef FUN_WITH_INI
case OP_INI_SET: {

View file

@ -1,6 +1,11 @@
/*
* This file provides stub handlers for INI opcodes when FUN_WITH_INI is disabled.
* Each opcode reports a clear runtime error and returns a safe default.
/**
* @file stubs.c
* @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) */

View file

@ -1,14 +1,28 @@
/*
/**
* 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
*
* 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 */
#ifdef FUN_WITH_INI
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: {
/* operand bit flags:
* 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>
* Licensed under the terms of the Apache-2.0 license.
* 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 */

View file

@ -5,8 +5,28 @@
* 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 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 */

View file

@ -5,8 +5,27 @@
* 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 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 */

View file

@ -5,8 +5,27 @@
* 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 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 */

View file

@ -7,6 +7,18 @@
* 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: {
/* operand holds the source line number */
vm->current_line = inst.operand;

View file

@ -5,13 +5,33 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-03
*/
/**
* @file ceil.c
* @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>

View file

@ -5,13 +5,29 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-03
*/
/**
* @file cos.c
* @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>

View file

@ -5,13 +5,25 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-03
*/
/**
* @file exp.c
* @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>

View file

@ -5,13 +5,33 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-03
*/
/**
* @file floor.c
* @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>

View file

@ -5,8 +5,6 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-03
*/
/**

View file

@ -5,8 +5,6 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-03
*/
/**

View file

@ -5,13 +5,23 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-03
*/
/**
* @file gcd.c
* @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: {

View file

@ -5,13 +5,23 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-03
*/
/**
* @file isqrt.c
* @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: {

View file

@ -5,13 +5,23 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-03
*/
/**
* @file lcm.c
* @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: {

View file

@ -5,13 +5,23 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-03
*/
/**
* @file log.c
* @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>

View file

@ -5,13 +5,19 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-03
*/
/**
* @file log10.c
* @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>

View file

@ -5,8 +5,6 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-03
*/
/**

View file

@ -5,8 +5,6 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-03
*/
/**

View file

@ -5,13 +5,28 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-03
*/
/**
* @file sin.c
* @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>

View file

@ -5,13 +5,20 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-03
*/
/**
* @file sqrt.c
* @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>

View file

@ -5,13 +5,25 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-03
*/
/**
* @file tan.c
* @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>

View file

@ -5,8 +5,6 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-03
*/
/**

View file

@ -5,10 +5,13 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* 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
*/

View file

@ -5,8 +5,6 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-02-19
*/
/*

View file

@ -5,8 +5,6 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-02-19
*/
/**

View file

@ -5,8 +5,6 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-02-19
*/
/**

View file

@ -5,8 +5,6 @@
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2025-10-04
*/
/**

View file

@ -5,8 +5,6 @@
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2025-10-04
*/
/**

View file

@ -7,7 +7,17 @@
* 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: {
Value key = pop_value(vm);

View file

@ -5,11 +5,16 @@
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* 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: {
extern char **environ;

View file

@ -5,8 +5,18 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* 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: {

View file

@ -5,8 +5,18 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* 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: {

View file

@ -5,8 +5,18 @@
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* 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: {

Some files were not shown because too many files have changed in this diff Show more