1
0
Fork 0
forked from fun/fun

Compare commits

...

17 commits

Author SHA1 Message Date
6fd3497032 More documentation about internals. No code changes (0.38.0) 2026-01-28 02:02:19 +01:00
da183962d4 Missed a file in the last commit. Sorry! (0.38.0) 2026-01-27 04:46:37 +01:00
149e135318 Added Rust support to the Fun core. (0.38.0) 2026-01-27 04:26:54 +01:00
8aa150f8f7 README update. No code changes. (0.37.62) 2026-01-27 01:31:07 +01:00
c164bb80ae README update. No code changes. (0.37.62) 2026-01-27 01:30:25 +01:00
08620d9359 Added documentation for opcodes to ./docs/opcodes.md. No code changes. (0.37.62) 2026-01-26 13:03:26 +01:00
c95134669a Added documentation for internals to ./docs/internals.md. No code changes. (0.37.62) 2026-01-26 12:41:54 +01:00
592f6d2215 Only some ./play.fun updates. Some code changes. (0.37.62) 2026-01-26 04:18:48 +01:00
14c85c030f Added REPL documentation to ./docs/. No code changes. (0.37.62) 2026-01-26 03:45:35 +01:00
869d68b992 Fixed try/catch to run finally again. exit() in catch if you want to stop execution. (0.37.62) 2026-01-24 21:17:40 +01:00
6648fa1e27 Some code style fixes in the parser and bug fixes. (0.37.61) 2026-01-24 20:15:25 +01:00
29db3bc63e Some array type fixes in the parser. Needs more investigation. (0.37.60) 2026-01-24 19:57:39 +01:00
97cfc74a01 Small README update. No code changes. (0.37.59) 2026-01-19 23:45:17 +01:00
f073df040c Added a maps usage example. (0.37.59) 2026-01-19 23:40:39 +01:00
b513cf0463 Added a basic TCP server that provides access to an sqlite database. (0.37.58) 2026-01-19 23:24:36 +01:00
8478c49240 Directory layout changes. No code changes. (0.37.57) 2026-01-19 20:52:00 +01:00
de50e11148 Handbook update. No code changes. (0.37.57) 2026-01-19 20:45:09 +01:00
29 changed files with 2403 additions and 106 deletions

2
.gitignore vendored
View file

@ -13,5 +13,7 @@ json.xml
lib/*.so
out/
src/*.o
src/rust/Cargo.lock
src/rust/target
*.swp
tmp*

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.10)
project(fun VERSION 0.37.57 LANGUAGES C)
project(fun VERSION 0.38.0 LANGUAGES C)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
@ -11,11 +11,114 @@ include(${CMAKE_SOURCE_DIR}/cmake/Dependencies.cmake)
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/Extensions.cmake)
include(${CMAKE_SOURCE_DIR}/cmake/Targets.cmake)
# --- Always show key build toggles ---
# Normalize to ENABLED/DISABLED like the "Fun extension summary"
if(FUN_DEBUG)
set(_FUN_DEBUG_STATE "ENABLED")
else()
set(_FUN_DEBUG_STATE "DISABLED")
endif()
if(FUN_WITH_RUST)
set(_FUN_WITH_RUST_STATE "ENABLED")
else()
set(_FUN_WITH_RUST_STATE "DISABLED")
endif()
message(STATUS "==== Fun build options ====")
message(STATUS " FUN_DEBUG: ${_FUN_DEBUG_STATE}")
message(STATUS " FUN_WITH_RUST: ${_FUN_WITH_RUST_STATE}")
message(STATUS "===========================")
# Convenience aggregate target (like 'build' in Makefile)
add_custom_target(build
DEPENDS fun fun_test test_opcodes
)
# --- Rust (Cargo) integration: optionally build and link a staticlib with opcode examples ---
option(FUN_WITH_RUST "Build and link Rust opcode library" OFF)
if(FUN_WITH_RUST)
find_program(CARGO_EXECUTABLE cargo)
if(NOT CARGO_EXECUTABLE)
message(FATAL_ERROR "FUN_WITH_RUST=ON but 'cargo' not found in PATH")
endif()
set(RUST_CRATE_DIR ${CMAKE_SOURCE_DIR}/src/rust)
# Map CMake build type to Cargo profile and output path
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(RUST_PROFILE "debug")
set(RUST_BUILD_ARGS)
else()
set(RUST_PROFILE "release")
set(RUST_BUILD_ARGS --release)
endif()
# Crate name as per Cargo.toml
# Crate name on-disk replaces '-' with '_' in artifact names
set(RUST_CRATE_NAME hello-c-world)
string(REPLACE "-" "_" RUST_CRATE_BASENAME "${RUST_CRATE_NAME}")
set(RUST_LIB_NAME lib${RUST_CRATE_BASENAME}.a)
set(RUST_LIB_PATH ${RUST_CRATE_DIR}/target/${RUST_PROFILE}/${RUST_LIB_NAME})
add_custom_command(
OUTPUT ${RUST_LIB_PATH}
COMMAND ${CARGO_EXECUTABLE} build ${RUST_BUILD_ARGS}
WORKING_DIRECTORY ${RUST_CRATE_DIR}
COMMENT "Building Rust static library (${RUST_PROFILE})"
VERBATIM
)
add_custom_target(rust_ops_build DEPENDS ${RUST_LIB_PATH})
add_library(fun_ops STATIC IMPORTED GLOBAL)
set_target_properties(fun_ops PROPERTIES
IMPORTED_LOCATION ${RUST_LIB_PATH}
IMPORTED_LINK_INTERFACE_LANGUAGES C
)
add_dependencies(fun_ops rust_ops_build)
# Link Rust ops into the core so executables can call them
if(TARGET fun_core)
add_dependencies(fun_core rust_ops_build)
target_link_libraries(fun_core PRIVATE fun_ops)
target_compile_definitions(fun_core PRIVATE FUN_WITH_RUST)
endif()
# Propagate define to test targets that might call Rust
if(TARGET test_opcodes)
add_dependencies(test_opcodes rust_ops_build)
target_link_libraries(test_opcodes PRIVATE fun_ops)
target_compile_definitions(test_opcodes PRIVATE FUN_WITH_RUST)
endif()
if(TARGET fun)
target_compile_definitions(fun PRIVATE FUN_WITH_RUST)
endif()
endif()
# --- Size optimization for final binaries (Release) ---
# Enable function/data sectioning for better GC; safe for all configs.
add_compile_options(-ffunction-sections -fdata-sections)
# Link-time garbage collection and stripping for the main executable in Release
if(TARGET fun)
# Garbage-collect unused sections; also strip symbols (-s) in Release
target_link_options(fun PRIVATE
$<$<CONFIG:Release>:-Wl,--gc-sections -s>
)
# Prefer enabling LTO/IPO for Release builds
set_property(TARGET fun PROPERTY INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
endif()
if(TARGET fun_core)
# LTO/IPO for the core library helps the final link DCE more Rust/C glue
set_property(TARGET fun_core PROPERTY INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
# Ensure objects compiled with sectioning (added globally above) benefit from GC
target_link_options(fun_core PRIVATE $<$<CONFIG:Release>:-Wl,--gc-sections>)
endif()
# Convenience targets: repl, run, threads-demo, ops, examples
set(FUN_RUN_SCRIPT "" CACHE STRING "Script to run with the 'run' target, e.g. -DFUN_RUN_SCRIPT=examples/strings_test.fun")

View file

@ -2,6 +2,8 @@
## What is Fun?
Fun is a small, strict, and simple programming language that runs on a compact stack-based virtual machine. The C core is intentionally minimal; most functionality and standard libraries are implemented in Fun itself. The language emphasizes simplicity, consistency, and joy in coding.
Fun is an experiment, just for fun, but Fun works!
Fun is a highly strict programming language, but also highly simple. It looks like Python (My favorite language), but there are differences.
@ -14,6 +16,8 @@ Fun is and will ever be 100% free under the terms of the [Apache-2.0 License](ht
- Simplicity
- Consistency
- Simple to extend
- Hackable
- Joy in coding
- Fun!
@ -52,6 +56,7 @@ Fun is not about being the fastest or the most feature-rich. Its about sharin
- Respectful
- Curious
- Creative
- Open for everyone
Like the name says: Fun Unites Nerds.
@ -77,47 +82,9 @@ Fun may not change the world — but it will make programming a little more fun.
### Lib (./lib/)
```
arrays.fun
crypt/
crc32c.fun
crc32.fun
md5.fun
md5_legacy.fun
ripemd160.fun - Broken!
sha1.fun
sha256.fun
sha384.fun
sha512.fun
encoding/
base64.fun
hello.fun
hex.fun
io/
console.fun
ini.fun
json.fun
pcsc2.fun
pcsc.fun
process.fun
serial.fun
socket.fun
thread.fun
xml.fun
math.fun
regex
pcre2.fun
regex.fun
strings.fun
ui/
tk.fun
utils/
datetime.fun
math.fun
range.fun
```
See [./lib/](https://git.xw3.org/fun/fun/src/branch/main/lib) for what the standard library provides.
### Extensions (only Linux actually)
### Optional extensions (build-time selectable / only testing this on Linux actually):
- [CGI](https://en.wikipedia.org/wiki/Common_Gateway_Interface) support builtin using [kcgi](https://kristaps.bsd.lv/kcgi/) (optional) &#9744;
- [cURL](https://curl.se/) support builtin using [libcurl](https://curl.se/libcurl/) (optional) &#9745;
@ -140,7 +107,7 @@ There are some libs written in Fun available in the [./lib/](https://git.xw3.org
This is actually a work in progress...
Current documentation is only found in the [Fun Handbook](https://git.xw3.org/fun/fun/src/branch/main/docs/handbook.md).
Current documentation is only found in the [Fun Handbook](https://git.xw3.org/fun/fun/src/branch/main/docs/handbook.md) and the [specification](https://git.xw3.org/fun/fun/src/branch/main/spec/v0.3.md).
In the [./examples/](https://git.xw3.org/fun/fun/src/branch/main/examples) directory should be an example of most Fun features.

View file

@ -100,6 +100,8 @@ Pass all options as -DNAME=VALUE. The most relevant toggles are:
- FUN_WITH_REPL=ON|OFF — enable the interactive REPL (default OFF)
- FUN_WITH_SQLITE=ON|OFF — enable SQLite (sqlite3) support (default OFF)
- FUN_WITH_TCLTK=ON|OFF — enable Tk (GUI via Tcl/Tk) support (default OFF)
- FUN_WITH_INI=ON|OFF — enable INI (iniparser) support (default OFF)
- FUN_WITH_NOTCURSES=ON|OFF — enable Notcurses TUI support (default OFF)
You can also set the default search path for the bundled stdlib with DEFAULT_LIB_DIR:
@ -321,20 +323,29 @@ Error handling and debugging:
Types:
- number: signed integer (with helpers for unsigned behavior)
- float: 64-bit IEEE-754 floating point
- string: immutable bytes; len(s), join, split, substr, find
- array: ordered list; len, push, apop, insert, remove, slice
- map: associative dictionary typically keyed by strings
- boolean: represented as 1 (true) or 0 (false); operators &&, ||, !
- nil: absence of value
Numeric variants:
- byte (uint8)
- Fixed width ints: int8/16/32/64 and uint8/16/32/64
Control flow:
- if/else, while; range helpers in utils.range
- if/else, while; break, continue; range helpers in utils.range
Functions and classes:
- Define a function: fun name(args) ...
- Define a class: class Name(constructor params) with method definitions fun method(this, ...)
- _construct acts as the constructor if present; methods use explicit this
Exceptions:
- try { ... } catch (e) { ... } finally { ... }
- Throwing/catching is defined by the spec; some runtimes may implement handling progressively. Examples: try_catch_finally.fun
Modules and includes:
- #include <path/to/module.fun> for libs under FUN_LIB_DIR
- #include "relative/path.fun" for local includes
@ -357,6 +368,10 @@ Conversion and type:
Math and random:
- min(a,b), max(a,b), clamp(x, lo, hi), abs(x), pow(a,b), random_seed(seed), random_int(lo, hiExclusive)
Bitwise (uint32 operations):
- band(a,b), bor(a,b), bxor(a,b), bnot(a)
- shl(a, s), shr(a, s)
Regex (requires PCRE2 when enabled):
- regex_match(text, pattern) -> 1/0
- regex_search(text, pattern) -> map { match, start, end, groups }
@ -491,20 +506,99 @@ Module lib/encoding/base64.fun: base64_encode(string), base64_decode(string)
### JSON (optional)
Build flag: -DFUN_WITH_JSON=ON; requires json-c. VM functions: json_parse, json_stringify, json_from_file, json_to_file. Stdlib class JSON wraps these with light ergonomics. Example: examples/json_showcase.fun.
Build flag: -DFUN_WITH_JSON=ON; requires json-c.
VM API:
- json_parse(text) -> value or nil
- json_stringify(value, prettyFlag) -> string
- json_from_file(path) -> value or nil
- json_to_file(path, value, prettyFlag) -> 1/0
Stdlib wrapper:
- class JSON (lib/io/json.fun) — convenience methods mirroring the VM API.
Example:
```
include <io/json.fun>
j = JSON()
data = j.parse('{"name":"Fun","year":2026,"ok":true,"tags":["vm","lang"]}')
print(typeof(data)) // map
print(data["name"])
ok = j.to_file("./tmp/out.json", data, 1) // pretty = 1
print("saved:", ok)
```
Notes:
- JSON types map to Fun types: object -> map, array -> array, string -> string, number -> number/float, true/false -> 1/0, null -> nil.
- When writing, prettyFlag=1 enables pretty printing.
### CURL (optional)
Build flag: -DFUN_WITH_CURL=ON; requires libcurl. VM provides:
- curl_get(url) -> string ("" on error)
- curl_post(url, body) -> string ("" on error)
Build flag: -DFUN_WITH_CURL=ON; requires libcurl.
VM API:
- curl_get(url) -> string (empty string on error)
- curl_post(url, body) -> string (empty string on error)
- curl_download(url, path) -> 1/0
Examples: curl_get_json.fun, curl_post.fun, curl_download.fun
Stdlib wrapper:
- None (call built-ins directly). See examples in examples/extra/.
Example:
```
url = "https://httpbin.org/get"
resp = curl_get(url)
if (len(resp) == 0)
print("GET failed")
else
print(substr(resp, 0, 60), "...")
```
Examples:
- curl_get_json.fun, curl_post.fun, curl_download.fun
### PCSC (optional)
Build flag: -DFUN_WITH_PCSC=ON; provides pcsc_* built-ins and a stdlib wrapper class PCSC. Example: pcsc_example.fun.
Build flag: -DFUN_WITH_PCSC=ON; requires PC/SC (pcsc-lite).
VM API:
- pcsc_establish() -> context id (>0) or 0
- pcsc_list_readers(ctx) -> array of reader names or nil
- pcsc_connect(ctx, readerName) -> handle id (>0) or 0
- pcsc_disconnect(handle) -> 1/0
- pcsc_transmit(handle, bytesArray) -> { data: array<byte>, sw1: number, sw2: number, code: number }
Stdlib wrapper:
- class PCSC (lib/io/pcsc.fun) — higher-level helpers for listing readers, connecting, and APDU I/O.
Example:
```
include <io/pcsc.fun>
sc = PCSC()
ctx = pcsc_establish()
if (ctx == 0)
print("PCSC not available")
else
readers = pcsc_list_readers(ctx)
if (readers == nil || len(readers) == 0)
print("No readers found")
else
h = pcsc_connect(ctx, readers[0])
if (h)
// Example APDU: GET RESPONSE (illustrative only)
res = pcsc_transmit(h, [0x00, 0xC0, 0x00, 0x00, 0x00])
print("SW:", res["sw1"], res["sw2"], "code:", res["code"])
pcsc_disconnect(h)
```
Examples:
- examples/extra/pcsc_example.fun, examples/extra/pcsc_demo.fun
Notes:
- Smart card operations depend on the reader, card, and drivers installed. Ensure pcscd/service is running.
### SQLite (optional)
@ -530,13 +624,174 @@ Example flow (examples/sqlite_example.fun):
4) rows2 = sqlite_query(h, "SELECT count(*) AS cnt FROM tasks;")
5) sqlite_close(h)
### INI (optional)
Build flag: -DFUN_WITH_INI=ON; requires iniparser (>= 4.2.6).
VM API:
- ini_load(path) -> handle (>0) or 0 on error
- ini_free(handle) -> 1/0
- ini_get_string(handle, section, key, default) -> string
- ini_get_int(handle, section, key, defaultNumber) -> number
- ini_get_double(handle, section, key, defaultFloat) -> float
- ini_get_bool(handle, section, key, defaultBool) -> 1/0
- ini_set(handle, section, key, valueString) -> 1/0
- ini_unset(handle, section, key) -> 1/0
- ini_save(handle, path) -> 1/0
Notes:
- Section/key may be looked up flexibly ("section:key" and "section.key").
- Always free handles with ini_free when done.
Example:
```
h = ini_load("./examples/data/example.ini")
if (h == 0)
print("Failed to load INI")
else
host = ini_get_string(h, "server", "host", "localhost")
port = ini_get_int(h, "server", "port", 8080)
print("server:", host, port)
ok = ini_set(h, "server", "host", "127.0.0.1")
if (ok)
ini_save(h, "./tmp/updated.ini")
ini_free(h)
```
### Notcurses (optional)
Build flag: -DFUN_WITH_NOTCURSES=ON; requires notcurses (pkg-config: notcurses or notcurses-core).
VM API:
- nc_init() -> 1/0 (initialize TUI; call once)
- nc_draw_text(x, y, text) -> 0 on success, -1 on error
- nc_clear() -> 0 on success, -1 on error
- nc_getch(timeout_ms) -> key code (int), -1 if timeout/no input
- nc_shutdown() -> 1/0 (restore terminal)
Example:
```
if (nc_init())
nc_draw_text(2, 1, "Fun + Notcurses")
print("Press any key...")
k = nc_getch(0) // 0 = blocking
nc_shutdown()
else
print("Notcurses init failed")
```
### XML (optional)
Build flag: -DFUN_WITH_XML2=ON; requires libxml2.
VM API:
- xml_parse(text) -> doc_handle (>0) or 0 on error
- xml_root(doc_handle) -> node_handle (>0) or 0 if missing
- xml_name(node_handle) -> string
- xml_text(node_handle) -> string
Stdlib wrapper:
- class XML (lib/io/xml.fun)
- parse(text): int
- from_file(path): int
- root(doc): int
- name(node): string
- text(node): string
Example:
```
include <io/xml.fun>
xml = XML()
doc = xml.from_file("./examples/data/example.xml")
if (doc == 0)
print("Failed to load XML file")
else
root = xml.root(doc)
print(xml.name(root))
print(xml.text(root))
```
Notes:
- Handles are integers managed by the VM; nodes belong to their document.
### libSQL (optional)
Build flag: -DFUN_WITH_LIBSQL=ON; requires libSQL client (compatible with sqlite3 C API).
VM API:
- libsql_open(path_or_url) -> handle (>0) or 0 on error
- libsql_close(handle) -> nil
- libsql_exec(handle, sql) -> rc (0 on success)
- libsql_query(handle, sql) -> array of row maps
Example flow:
1) h = libsql_open("./database.sqlite")
2) rows = libsql_query(h, "SELECT id, title FROM tasks;")
3) rc = libsql_exec(h, "INSERT INTO tasks (title) VALUES ('Try libSQL');")
4) libsql_close(h)
### PCRE2 / Regex (optional)
Build flag: -DFUN_WITH_PCRE2=ON; requires PCRE2 (8-bit API).
VM API:
- regex_match(text, pattern) -> 1/0
- regex_search(text, pattern) -> map { match, start, end, groups }
- regex_replace(text, pattern, repl) -> string
Stdlib wrapper:
- class Regex (lib/regex.fun) providing match/search/replace helpers.
Examples: regex_demo.fun, regex_procedural.fun
Notes:
- Patterns use PCRE2 syntax.
### Tcl/Tk GUI (optional)
Build flag: -DFUN_WITH_TCLTK=ON; requires Tcl/Tk (8.6+).
VM API:
- tk_title(title) -> rc
- tk_label(id, text) -> rc
- tk_button(id, text) -> rc
- tk_pack(id) -> rc
- tk_loop() -> nil (enters event loop)
Stdlib wrapper:
- class TK (lib/ui/tk.fun) mirrors the VM API.
Example:
```
include <ui/tk.fun>
tk = TK()
tk.title("Fun + Tk GUI")
tk.label("hello", "Hello, world!")
tk.pack("hello")
tk.button("ok", "OK")
tk.pack("ok")
tk.loop()
```
Notes:
- Ensure Tcl/Tk runtime libraries are available on your system.
### REPL (optional)
Build flag: -DFUN_WITH_REPL=ON.
Description:
- Enables the interactive ReadEvalPrint Loop and the --repl-on-error mode. No additional VM API functions; this feature is part of the executable.
---
## Examples reference
You can run examples without installing by pointing FUN_LIB_DIR to the repository lib directory:
FUN_LIB_DIR="$(pwd)/lib" ./build/fun examples/<name>.fun
FUN_LIB_DIR="$(pwd)/lib" ./build/fun examples/<path>.fun
Highlights (not exhaustive):
- arrays.fun, arrays_advanced.fun, arrays_iter.fun — array operations
@ -555,14 +810,14 @@ Highlights (not exhaustive):
- have_fun.fun — quick sanity check
- if_else_test.fun — branching
- include_lib.fun, include_local.fun, include_namespace.fun — includes and namespacing
- input_example.fun — console input
- json_showcase.fun — JSON usage
- curl_get_json.fun, curl_post.fun, curl_download.fun — HTTP via CURL
- interactive/input_example.fun — console input
- extra/json_showcase.fun — JSON usage
- extra/curl_get_json.fun, extra/curl_post.fun, extra/curl_download.fun — HTTP via CURL
- loops_break_continue.fun, nested_loops.fun, while_test.fun — loops
- md5_demo.fun, sha1_demo.fun, sha256_demo.fun, sha256_str_demo.fun, sha384_example.fun, sha512_demo.fun, sha512_str_demo.fun — hashing
- crypto/md5_demo.fun, crypto/sha1_demo.fun, crypto/sha256_demo.fun, crypto/sha256_str_demo.fun, crypto/sha384_example.fun, crypto/sha512_demo.fun, crypto/sha512_str_demo.fun — hashing
- objects_basic.fun, objects_more.fun — map/object patterns
- os_env.fun — environment variables
- pcsc_example.fun — smart card demo
- extra/pcsc_example.fun — smart card demo
- process_example.fun — running external commands
- regex_demo.fun, regex_procedural.fun — regex usage
- stdlib_showcase.fun — tour through stdlib
@ -574,7 +829,7 @@ Highlights (not exhaustive):
- type_safety.fun, type_safety_fails.fun — type safety
- types_integers.fun, signed_ints.fun, uint_types.fun — integers
- unix_socket_echo.fun — UNIX domain sockets
- sqlite_example.fun — SQLite usage
- extra/sqlite_example.fun — SQLite usage
Notes:
- Some examples rely on optional features (JSON, CURL, PCSC, SQLite) and degrade gracefully when disabled.

332
docs/internals.md Normal file
View file

@ -0,0 +1,332 @@
# Fun Internals
This document describes how Fun is implemented under the hood: the bytecode format, the virtual machine (VM) execution model, how opcodes are organized, error handling and debugging, and how the parser translates source code into bytecode.
It complements the Handbook and Spec by focusing on implementation details in the C core, with pointers to the relevant source files.
- VM implementation: src/vm.c (and many small opcode handlers in src/vm/...)
- Parser and compiler: src/parser.c
- Bytecode and values: src/bytecode.h, src/value.h
- VM API/structure: src/vm.h
## Highlevel architecture
Fun programs are compiled from source text to a compact bytecode representation (Bytecode). The VM executes this bytecode using a simple stack machine with call frames, locals, and globals.
- Parser produces a Bytecode object per compiled unit (e.g., a module or function).
- Bytecode is a linear array of Instruction { op, operand } entries with a parallel constants table.
- The VM maintains:
- A value stack for computation
- A frame stack for function calls (locals, instruction pointer, try/catch state)
- A globals array
- An output buffer (captures printed values), plus tracing/debugger state
Almost all operations are implemented as small, focused opcode handlers. The VMs main interpreter loop dispatches these opcodes and performs typeaware operations on Value instances.
## Values, bytecode, and instructions
Relevant headers:
- src/value.h — the tagged value type used by the VM (ints, floats, strings, arrays, maps, nil, booleans, etc.).
- src/bytecode.h — the instruction set and bytecode container types.
Instruction set:
- enum OpCode defines all opcodes (OP_NOP, OP_LOAD_CONST, OP_ADD, …). See src/bytecode.h.
- Instruction is a pair { OpCode op; int32_t operand; }.
- Some opcodes encode immediate arguments in operand (e.g., local slot index, constant index, jump target, arg count).
Bytecode container (Bytecode):
- instructions: dynamic array of Instruction
- constants: dynamic array of Value copies (literals, strings, numbers, etc.)
- debug metadata: name (function or module), source_file (for error mapping)
Utilities:
- bytecode_add_constant, bytecode_add_instruction, bytecode_set_operand, bytecode_dump…
## VM structure and execution model
See src/vm.h for the main VM and Frame shapes.
Frame:
- fn: pointer to the current Bytecode (function or module entry)
- ip: instruction pointer (index into instructions)
- locals[MAX_FRAME_LOCALS]: local slots for this frame
- try_stack[16], try_sp: perframe exception handler stack (see exceptions section)
VM:
- stack[STACK_SIZE], sp: data stack and stack pointer
- frames[MAX_FRAMES], fp: call frame stack and frame pointer
- globals[MAX_GLOBALS]: global slots
- output[OUTPUT_SIZE], output_count, output_is_partial[]: captures output of OP_PRINT/OP_ECHO
- instr_count: instructions executed during the last vm_run
- current_line: last known source line (maintained via OP_LINE)
- exit_code: set by OP_EXIT
- tracing flags and REPLonerror hook
- debugger state (step/next/finish, breakpoints)
Initialization and lifecycle:
- vm_init(VM*): zeroes state and prepares stacks
- vm_reset(VM*): frees/clears dynamic state while keeping the VM instance
- vm_free(VM*): teardown helper
- vm_run(VM*, Bytecode* entry): pushes an initial frame and enters the interpreter loop
## The interpreter loop and opcode dispatch
The interpreter loop lives in src/vm.c: vm_run. Opcodes are executed in a tight loop that:
- Fetches the current instruction (op, operand) from the active frame (frames[fp])
- Optionally updates debug/tracing state (e.g., OP_LINE updates VM.current_line)
- Executes the handler for the opcode
- Advances ip, or jumps/returns/halts as needed
Opcode handlers organization:
- To keep vm.c readable, most opcode implementations are factored into small .c files included directly into vm.c (e.g., vm/core/load_const.c, vm/logic/and.c, vm/arrays/push.c, vm/math/abs.c, vm/os/thread_spawn.c, etc.).
- This is a deliberate “amalgamation” style: small singlepurpose C units compiled as part of vm.c.
- Optional subsystems (JSON, PCRE2, CURL, SQLite, libSQL, PC/SC, XML2, Tcl/Tk, Notcurses, INI, sockets, serial, OS helpers) are grouped under src/external and src/vm/<domain>/.
Dispatch naming and visibility:
- Humanreadable names for opcodes live in vm.h: opcode_names[]. These are used in debug prints and error messages.
## Stacks, frames, locals, globals
Data stack:
- push_value/pop_value manage stack items of type Value.
- Most opcodes pop their arguments (righttoleft) and push a result.
Call frames:
- vm_push_frame sets up a new frame for a function call (OP_CALL), transferring arguments into the callees local slots per the calling convention implemented by the compiler.
- vm_pop_frame unwinds one frame, restoring caller context and optionally leaving a return value on the data stack.
Locals and globals:
- OP_LOAD_LOCAL/STORE_LOCAL address MAX_FRAME_LOCALS slots in the current frame.
- OP_LOAD_GLOBAL/STORE_GLOBAL access the VMwide globals table.
## Control flow, calls, and returns
- OP_JUMP and OP_JUMP_IF_FALSE implement structured control flow compiled by the parser (if/elif/else, loops, conditionals).
- OP_CALL pops function + N args, sets up a callee frame, and transfers control.
- OP_RETURN unwinds the current frame. The interpreter returns from vm_run when the entry frame is popped or a HALT/EXIT is executed.
## Type system and operations
The VM is dynamically typed. Values carry a tag; operations check types at runtime and coerce where sensible (e.g., number parsing in OP_TO_NUMBER). Representative groups:
- Core arithmetic: OP_ADD, OP_SUB, OP_MUL, OP_DIV, OP_MOD, plus floataware rounding and transcendental ops (FLOOR/CEIL/TRUNC/ROUND, SIN/COS/TAN, EXP/LOG/LOG10/SQRT, FMIN/FMAX, GCD/LCM/ISQRT/SIGN).
- Logic and comparisons: OP_LT/LTE/GT/GTE/EQ/NEQ, OP_AND/OR/NOT.
- Stack utils: OP_DUP/OP_SWAP/OP_POP.
- Arrays: OP_MAKE_ARRAY/INDEX_GET/INDEX_SET/LEN/PUSH/APOP/SET/INSERT/REMOVE/SLICE/CONTAINS/INDEX_OF/CLEAR/ENUMERATE/ZIP.
- Strings and regex: OP_SPLIT/JOIN/SUBSTR/FIND and OP_REGEX_MATCH/SEARCH/REPLACE (and PCRE2 variants if enabled).
- Maps: OP_MAKE_MAP/KEYS/VALUES/HAS_KEY.
- Conversions/reflection: OP_TO_NUMBER/TO_STRING/CAST/TYPEOF, OP_UCLAMP/SCLAMP.
- I/O and OS: OP_READ_FILE/WRITE_FILE/INPUT_LINE/ENV/PROC_RUN/PROC_SYSTEM/TIME_NOW_MS/CLOCK_MONO_MS/DATE_FORMAT/OS_LIST_DIR/RANDOM_NUMBER, sockets, serial.
- External integrations (optional): JSON, CURL, SQLite, libSQL, PC/SC, XML2, Tcl/Tk, Notcurses, INI.
Each handler enforces argument types and returns clear error messages via vm_raise_error on misuse.
## Source lines, tracing, and error reporting
Source line tracking:
- The compiler emits OP_LINE markers carrying 1based source line numbers. vm_run updates VM.current_line when these execute.
Tracing:
- When VM.trace_enabled is set, vm_run prints each opcode and stack state. This is helpful for debugging compiled programs.
Enhanced error messages:
- vm.c wraps fprintf for stderr to append context: source file, line, function name, opcode, and ip of the last executed instruction.
- For sources expanded via include preprocessing, vm.c maps the line back to the included file using preprocess_includes and inline markers (see below).
## Exceptions: try/catch/finally
Minimal structured exceptions are implemented with three opcodes and a perframe try stack:
- OP_TRY_PUSH operand = handler ip; pushes a handler location to Frame.try_stack
- OP_TRY_POP pops the current handler
- OP_THROW pops an error value; if a handler exists in the current frame, control jumps to it with the error value available on the stack; otherwise, vm_raise_error terminates execution (or triggers REPL if configured)
This design keeps exception metadata strictly per frame and avoids VMglobal unwind state.
## REPLonerror and debugger
Runtime error path:
- vm_raise_error consults the current frames try handlers. If none match, it formats a message with location, prints it, and sets exit_code.
- If VM.repl_on_error is enabled and a REPL hook is installed, the VM drops into the REPL to inspect state.
Debugger state (vm.h):
- Step/Next/Finish modes, breakpoints stored in VM.breakpoints[].
- vm_debug_* helpers manage breakpoints and stepping requests. vm_run consults this state at loop boundaries to pause execution.
## Include preprocessing and source mapping
Fun supports a lightweight include mechanism at the source text level (handled before/around compilation) to allow composing modules. vm.c provides two helpers used for error mapping:
- preprocess_includes(const char* src) expands the source by inlining included files and injecting marker comments of the form:
// __include_begin__: <path>:<line>
…included lines…
// __include_end__: <path>:<line>
- map_expanded_line_to_include(path, line, out_path, out_line) uses these markers to map a line in the expanded text back to the original file:line that contributed it.
When stderr output is produced by the VM, fun_vm_vfprintf annotates messages with the mapped file and line if possible.
## Parser and compiler pipeline (src/parser.c)
The parser compiles directly to bytecode in a single pass with localized backpatching. The code is organized by precedence levels and statement/block parsing.
Key components:
- Namespaces and aliases: ns_aliases_scan detects alias directives at the top of the source (for module resolution) before parsing proper.
- Symbol tables: sym_index for globals; LocalEnv tracks locals in the current function scope. local_find/local_add manage local slots.
- Expression parser: a classic precedenceclimbing/recursivedescent set of emit_* functions:
- emit_primary: literals, identifiers, grouping, array/map literals, function literals, calls, indexing
- emit_unary: prefix ops like !, unary -, type conversions
- emit_multiplicative/additive/relational/equality/and/or: binary operators by precedence
- emit_conditional: ternary/conditional constructs if supported by the grammar
- emit_expression: entry point that threads all the above
- Statement and block parsing:
- read_line_start and skip_to_eol implement indentation and line/whitespace/comment handling (Fun uses indentationbased blocks).
- parse_simple_statement emits bytecode for assignments, declarations, expression statements, control flow (if/elif/else, while/for), returns, breaks/continues, try/catch/finally constructs, print/echo, etc.
- parse_block handles nested blocks, indentation tracking, and emits OP_LINE markers for accurate source positioning.
- Controlflow codegen:
- Conditional and loop constructs emit OP_JUMP/OP_JUMP_IF_FALSE with forward jump placeholders patched later via bytecode_set_operand.
- try/catch/finally: emit OP_TRY_PUSH/OP_TRY_POP and arrange handler ips; OP_THROW for explicit throw.
- Functions and calls:
- Functions compile to their own Bytecode with a fresh LocalEnv; callers use OP_CALL with operand = arg count. Arguments are pushed lefttoright; the callee consumes them from the stack into local slots as per the compilers calling convention.
- Constants and literals:
- String/number/boolean/nil literals are interned into the Bytecode.constants table. OP_LOAD_CONST references them by index.
- Line information and files:
- The parser emits OP_LINE as it advances through source lines. Bytecode.source_file is set so the VM can report accurate errors.
Frontend entry points:
- parse_string_to_bytecode(const char* source)
- parse_file_to_bytecode(const char* path)
- compile_minimal for very small snippets/tests
These return an owned Bytecode* that the VM can execute.
## Libraries and builtins layout
The VM includes small, standalone C files for each feature group under src/vm/…
- Core: src/vm/core/*.c (load/store, jumps, call/return, stack ops, halt/exit, try/throw)
- Numbers and logic: src/vm/arithmetic/*.c, src/vm/logic/*.c, src/vm/bitwise/*.c, src/vm/math/*.c
- Collections and strings: src/vm/arrays/*.c, src/vm/maps/*.c, src/vm/strings/*.c
- Conversions/reflection: src/vm/*.c (to_number, to_string, cast, typeof, uclamp, sclamp)
- OS and I/O: src/vm/io/*.c, src/vm/os/*.c, sockets and serial
- External integrations: src/external/*.c glue with opcode handlers in src/vm/<domain> when enabled by CMake options
Feature flags (CMake):
- Many subsystems are guarded by -DFUN_WITH_… options (JSON, PCRE2, CURL, PCSC, SQLITE, LIBSQL, XML2, TCLTK, NOTCURSES, INI, REPL). See CMake options in the Handbook.
## Debugging and development tips
- Use the --trace flag (or VM.trace_enabled) to inspect execution stepbystep.
- Use OP_LINE markers (visible via bytecode_dump) to correlate bytecode with source lines.
- vm_dump_globals helps inspect nonnil globals at runtime.
- When adding a new opcode:
1) Extend enum OpCode and opcode_names[]
2) Implement a handler (small C file) and include it from src/vm.c
3) Teach the parser/emitter to generate the opcode
4) Update docs/spec and examples
## Data limits and sizes
From vm.h defaults (tuned for simplicity; adjust if needed):
- STACK_SIZE = 1024
- MAX_FRAMES = 128
- MAX_FRAME_LOCALS = 64
- MAX_GLOBALS = 128
- OUTPUT_SIZE = 1024
## Entry points recap
- VM execution: vm_init → vm_run(entry) → vm_print_output/vm_clear_output → vm_reset/vm_free.
- Parsing: parse_string_to_bytecode / parse_file_to_bytecode → Bytecode*.
- Bytecode helpers: bytecode_add_instruction/constant, bytecode_set_operand, bytecode_dump.
## Where to look in the source
- src/vm.c — interpreter loop, error/trace, and amalgamated opcode includes
- src/vm.h — VM/Frame definitions and debugger API
- src/bytecode.h — instruction set and bytecode container
- src/parser.c — compiler, expression/statement/block parsing, emission, indentation handling
- src/vm/* — small focused opcode handlers by domain
- src/external/* — integration shims for optional dependencies
## Concurrency, isolates, and garbage collection
### Short answer
We chose isolated state (like Lua) rather than a single global lock (like Pythons GIL). Each VM has its own heap, scheduler, and GC. There are no crossVM pointers. Concurrency and data exchange happen via message passing and a few carefully scoped sharedmemory primitives for highthroughput use cases. This keeps the C API simple, predictable, and safe to embed in multithreaded hosts.
### Concurrency model
- Isolates/VMs: Each `fun_vm_t*` is an isolate with its own heap, bytecode, scheduler, and GC. Multiple VMs can run truly in parallel on different OS threads/cores.
- IntraVM concurrency: Userspace tasks (green threads/fibers) scheduled by the VM. Within a single VM, you get structured concurrency; the VM is singleowner from the hosts perspective.
- InterVM concurrency: Use message passing (channels/ports) and zerocopy shared buffers where explicitly opted in.
### Memory and sharing
- PerVM heaps: Objects are allocated, owned, and collected per VM. No object from VM A may be referenced by VM B.
- No global runtime lock: There is no GIL. VMs never contend on a global lock and can scale across cores.
- Message passing: `fun_send(port, value)` and `fun_recv(port, timeout)` copy values across VM boundaries using a compact, GCsafe serialization format.
- Zerocopy fast path (optin): For large payloads, hosts can create `fun_shared_buffer` objects which are referencecounted, immutable within VMs, and can be shared across VMs without copying. Mutating a shared buffer requires creating a new buffer (copyonwrite style), preserving safety.
### Threadsafety rules for the C API
- VM affinity: A `fun_vm_t*` has thread affinity. Host code must interact with a VM from its owning thread. If you need to call into the same VM from multiple OS threads, you post work to the VMs run loop via `fun_vm_post(vm, callback, user_data)`.
- Opaque handles: All handles (`fun_vm_t*`, `fun_value_t`, `fun_port_t`, `fun_shared_buffer_t`) are opaque. There are no raw pointers to VM heap objects in the API.
- No crossVM objects: You cannot pass `fun_value_t` directly across VMs. Use `fun_send`/`fun_serialize`/`fun_deserialize` or `fun_shared_buffer`.
- Optional locking helpers: For the rare case where a host wants shared mutable state outside the VM, we expose thin wrappers over atomics and locks (`fun_atomic_*`, `fun_mutex_t`, `fun_rwlock_t`) so extension code doesnt need to mix threading libraries. These do not participate in GC and are outside VM heaps.
### Scheduling and GC
- PerVM scheduler: Green threads are multiplexed within a VM. Preemption is cooperative with periodic safe points; an optional timeslice can yield between bytecode instruction groups when `FUN_DEBUG` or tracing is enabled.
- PerVM GC: Stoptheworld, perVM. No global stoptheworld across VMs. A GC in one VM does not pause others.
### Embedding patterns
- Parallelism: Create N VMs for N cores, wire them with channels or shared buffers. No global lock contention.
- UI/game loop: Keep one VM on the main thread; background workers operate their own VMs and communicate via ports.
- Native callbacks/FFI: Callbacks into a VM must occur on its owning thread (use `fun_vm_post`). For bulk data (e.g., images, tensors), pass `fun_shared_buffer` to avoid copies.
### Why not a GIL?
- A GIL simplifies internal invariants but serializes all CPUbound work and penalizes embedded hosts with existing thread pools. Our isolate + messagepassing design preserves safety while scaling with cores.
### When you must share state
- Prefer `fun_shared_buffer` (immutable) or message passing.
- If you truly need shared mutability from native code, use `fun_atomic_*` or `fun_mutex_t`/`fun_rwlock_t` around your own data structures outside the VM. The VM treats these as external resources.
### Minimal API surface (illustrative)
```c
// Create/destroy VMs
fun_vm_t* vm = fun_vm_create(const fun_vm_config_t*);
void fun_vm_destroy(fun_vm_t*);
// Thread-affine execution and posting work
int fun_vm_run(fun_vm_t*, const fun_script_t*);
int fun_vm_post(fun_vm_t*, void (*cb)(fun_vm_t*, void*), void* user);
// Ports/channels for inter-VM comms
fun_port_t* fun_port_create(fun_vm_t*);
int fun_send(fun_port_t*, fun_value_t value);
int fun_recv(fun_port_t*, fun_value_t* out, uint64_t timeout_ms);
// Serialization for cross-VM values
int fun_serialize(fun_value_t v, fun_buffer_t* out);
int fun_deserialize(fun_vm_t*, const fun_buffer_t*, fun_value_t* out);
// Zero-copy shared buffers (immutable inside VMs)
fun_shared_buffer_t* fun_shared_buffer_new(size_t n);
void* fun_shared_buffer_data(fun_shared_buffer_t*);
void fun_shared_buffer_retain(fun_shared_buffer_t*);
void fun_shared_buffer_release(fun_shared_buffer_t*);
```
### Glossary
- GC: garbage collection. In our context, each `fun_vm_t` isolate has its own GC (stoptheworld, perVM). There is no global stoptheworld and no global lock; a GC pause in one VM does not affect others.

250
docs/opcodes.md Normal file
View file

@ -0,0 +1,250 @@
# Fun VM Opcodes Overview
This document provides an overview of the available VM opcodes implemented under src/vm and its subdirectories. Opcodes are grouped by module. For each opcode, we briefly note its purpose and, where available, the expected stack arguments and return value.
## Notes
- Arguments are pushed on the stack before the opcode executes; results are pushed back on the stack.
- Types are indicative; many opcodes accept integers and/or strings depending on context. See code for exact type checks and error behavior.
## Core
- OP_NOP: No operation.
- OP_HALT: Stop the VM.
- OP_EXIT: Terminate script with an exit code; pops int exitCode.
- OP_LOAD_CONST: Push a constant (by index embedded in bytecode).
- OP_LOAD_LOCAL: Push local variable value (by slot index).
- OP_STORE_LOCAL: Pop value and store into local (by slot index).
- OP_LOAD_GLOBAL: Push global variable value (by slot index/name id).
- OP_STORE_GLOBAL: Pop value and store into global (by slot index/name id).
- OP_CALL: Call a function; pops N args and a callable; pushes return value.
- OP_RETURN: Return from current function; optionally pops return value and pushes it to caller frame.
- OP_POP: Pop and discard the top of the stack.
- OP_DUP: Duplicate the top stack value.
- OP_SWAP: Swap the top two stack values.
- OP_JUMP: Unconditional jump to bytecode offset.
- OP_JUMP_IF_FALSE: Pop condition; jump if falsey.
- OP_THROW: Pop error and raise; unwinds to nearest try.
- OP_TRY_PUSH: Begin try handler (internal to exception handling).
- OP_TRY_POP: End try handler (internal to exception handling).
## Arithmetic
- OP_ADD: Add two numbers or concatenate two strings; pops b, a; pushes a+b or a..b.
- OP_SUB: Integer subtraction; pops b, a; pushes a-b.
- OP_MUL: Integer multiplication; pops b, a; pushes a*b.
- OP_DIV: Integer division; pops b, a; pushes a/b.
- OP_MOD: Integer modulus; pops b, a; pushes a%b. (math/mod.c)
## Logic and Comparison
- OP_NOT: Logical NOT; pops v; pushes bool.
- OP_AND: Logical AND; pops b, a; pushes bool.
- OP_OR: Logical OR; pops b, a; pushes bool.
- OP_EQ: Equality; pops b, a; pushes bool.
- OP_NEQ: Inequality; pops b, a; pushes bool.
- OP_LT: Less than; pops b, a; pushes bool.
- OP_LTE: Less than or equal; pops b, a; pushes bool.
- OP_GT: Greater than; pops b, a; pushes bool.
- OP_GTE: Greater than or equal; pops b, a; pushes bool.
## Arrays and Indexing
- OP_MAKE_ARRAY: Create array from N values; pops N values (count encoded in bytecode); pushes array.
- OP_INDEX_GET: Indexing into array or map; pops index/key, container; pushes value or Nil on missing.
- OP_INDEX_SET: Assign into array or map; pops value, index/key, container; pushes 1/0 for success (see code for exact behavior).
- OP_INSERT: Insert value at index in array; pops value, index, array; pushes 1/0.
- OP_REMOVE: Remove element at index in array; pops index, array; pushes removed value or Nil.
- OP_PUSH: Append element to end of array; pops value, array; pushes 1/0.
- OP_APOP: Pop last element from array; pops array; pushes removed value or Nil.
- OP_SLICE: Slice array; pops end, start, array; pushes new array slice.
- OP_INDEX_OF: Find index of value in array; pops value, array; pushes index or -1.
- OP_CONTAINS: Membership test for arrays; pops value, array; pushes bool.
- OP_JOIN: Join array of strings; pops separator, array; pushes string.
- OP_CLEAR: Clear all elements in array; pops array; pushes 1/0.
- OP_ENUMERATE: Produce array of [index, value] pairs from array; pops array; pushes array of pairs.
## Maps
- OP_MAKE_MAP: Create a map from N key-value pairs; pops 2*N values (val, key ...); pushes map.
- OP_HAS_KEY: Check if key exists; pops key, map; pushes bool.
- OP_KEYS: Return array of keys; pops map; pushes array.
- OP_VALUES: Return array of values; pops map; pushes array.
## Strings and Regex
- OP_SUBSTR: Substring; pops len, start, string; pushes substring.
- OP_FIND: Find substring; pops needle, haystack; pushes index or -1.
- OP_SPLIT: Split string; pops separator, string; pushes array of strings.
- OP_REGEX_MATCH: Regex full match; pops pattern, string; pushes bool or captures (see strings/regex_match.c).
- OP_REGEX_SEARCH: Regex search/find; pops pattern, string; pushes match details or -1.
- OP_REGEX_REPLACE: Regex replace; pops replacement, pattern, string; pushes new string.
## Bitwise (uint32)
- OP_BAND: Bitwise AND; pops b, a; pushes a & b.
- OP_BOR: Bitwise OR; pops b, a; pushes a | b.
- OP_BXOR: Bitwise XOR; pops b, a; pushes a ^ b.
- OP_BNOT: Bitwise NOT; pops a; pushes ~a.
- OP_SHL: Logical left shift; pops shift, value; pushes value << shift.
- OP_SHR: Logical right shift; pops shift, value; pushes value >> shift.
- OP_ROTL: Rotate left; pops shift, value; pushes rotl(value, shift).
- OP_ROTR: Rotate right; pops shift, value; pushes rotr(value, shift).
## Math
- OP_ABS: Absolute value; pops x; pushes |x|.
- OP_CEIL: Ceiling; pops x; pushes ceil(x).
- OP_FLOOR: Floor; pops x; pushes floor(x).
- OP_ROUND: Round; pops x; pushes round(x).
- OP_TRUNC: Truncate; pops x; pushes trunc(x).
- OP_SIN: Sine; pops x; pushes sin(x).
- OP_COS: Cosine; pops x; pushes cos(x).
- OP_TAN: Tangent; pops x; pushes tan(x).
- OP_SQRT: Square root; pops x; pushes sqrt(x) (float).
- OP_ISQRT: Integer square root; pops x; pushes isqrt(x) (int).
- OP_LOG: Natural logarithm; pops x; pushes log(x).
- OP_LOG10: Base-10 logarithm; pops x; pushes log10(x).
- OP_EXP: Exponential; pops x; pushes exp(x).
- OP_MAX: Max of two ints; pops b, a; pushes max(a, b).
- OP_MIN: Min of two ints; pops b, a; pushes min(a, b).
- OP_FMAX: Max of two floats; pops b, a; pushes max(a, b).
- OP_FMIN: Min of two floats; pops b, a; pushes min(a, b).
- OP_CLAMP: Clamp; pops hi, lo, x; pushes clamp(x, lo, hi).
- OP_SIGN: Sign; pops x; pushes -1, 0, or 1.
- OP_POW: Power; pops exp, base; pushes base^exp.
- OP_GCD: Greatest common divisor; pops b, a; pushes gcd(a, b).
- OP_LCM: Least common multiple; pops b, a; pushes lcm(a, b).
- OP_RANDOM_SEED: Seed RNG; pops int seed; pushes 1/0.
- OP_RANDOM_INT: Random integer in [lo, hi]; pops hi, lo; pushes int.
## I/O
- OP_READ_FILE: Read file contents; pops path:string; pushes data:string or Nil.
- OP_WRITE_FILE: Write data to file; pops data:string, path:string; pushes 1/0.
- OP_INPUT_LINE: Read a line from stdin; optional prompt on stack; pushes string (may be empty) or Nil.
## JSON
- OP_JSON_PARSE: Parse JSON text; pops text:string; pushes value (map/array/number/string/bool/Nil) or Nil on error.
- OP_JSON_STRINGIFY: Stringify a value; pops pretty:int(0/1), any; pushes json:string.
- OP_JSON_TO_FILE: Write value as JSON to file; pops pretty:int(0/1), any, path; pushes 1/0.
- OP_JSON_FROM_FILE: Read and parse JSON file; pops path; pushes value or Nil.
## XML
- OP_XML_PARSE: Parse XML text; pops text:string; pushes doc handle (>0) or 0 on error.
- OP_XML_ROOT: Get root node; pops doc handle; pushes node handle (>0) or 0.
- OP_XML_NAME: Get node name; pops node handle; pushes string.
- OP_XML_TEXT: Get node text (concatenated); pops node handle; pushes string.
## INI
- OP_INI_LOAD: Load INI; pops path:string; pushes handle (>0) or 0.
- OP_INI_FREE: Free INI handle; pops handle; pushes 1/0.
- OP_INI_GET_STRING: Get string; pops default, key, section, handle; pushes string.
- OP_INI_GET_INT: Get integer; pops default, key, section, handle; pushes int.
- OP_INI_GET_DOUBLE: Get double; pops default, key, section, handle; pushes float.
- OP_INI_GET_BOOL: Get bool; pops default, key, section, handle; pushes 1/0.
- OP_INI_SET: Set value; pops value, key, section, handle; pushes 1/0.
- OP_INI_UNSET: Remove key; pops key, section, handle; pushes 1/0.
- OP_INI_SAVE: Save to file; pops path, handle; pushes 1/0.
## SQLite
- OP_SQLITE_OPEN: Open database; pops path:string; pushes handle (>0) or 0.
- OP_SQLITE_CLOSE: Close database; pops handle; pushes Nil.
- OP_SQLITE_EXEC: Execute statement; pops handle:int, sql:string; pushes rc:int (0=OK).
- OP_SQLITE_QUERY: Run query; pops handle:int, sql:string; pushes array<map<string,any>>.
## libSQL
- OP_LIBSQL_OPEN: Open database (url or path); pops string; pushes handle (>0) or 0.
- OP_LIBSQL_CLOSE: Close database; pops handle; pushes Nil.
- OP_LIBSQL_EXEC: Execute statement; pops handle:int, sql:string; pushes rc:int (0=OK).
- OP_LIBSQL_QUERY: Run query; pops handle:int, sql:string; pushes array<map<string,any>>.
## OS, Time, Processes, Threads, Sockets, Serial
- OP_ENV: Get environment variable; pops key:string; pushes value:string or Nil.
- OP_ENV_ALL: Get all environment variables; pushes map<string,string>.
- OP_FUN_VERSION: Push Fun version string; no args.
- OP_SLEEP_MS: Sleep; pops ms:int; pushes Nil.
- OP_TIME_NOW_MS: Current wall-clock time in ms since epoch; pushes int.
- OP_CLOCK_MONO_MS: Monotonic clock in ms; pushes int.
- OP_DATE_FORMAT: Format epoch ms with strftime; pops format:string, ms:int; pushes string.
- OP_RANDOM_NUMBER: Random float in [0,1); optional lower/upper bound handling; see os/random_number.c.
- OP_PROC_SYSTEM: Run command via system(); pops cmd:string; pushes exit code:int.
- OP_PROC_RUN: Run command and capture stdout/stderr; pops cmd:string; pushes map or string (see os/proc_run.c).
- OP_LIST_DIR / OP_OS_LIST_DIR: List directory; pops path; pushes array of file names.
- Threads:
- OP_THREAD_SPAWN: Spawn a thread to run a function; pops args (array or scalar), fn; pushes thread id.
- OP_THREAD_JOIN: Join thread; pops thread id; pushes thread result.
- Sockets (TCP/Unix):
- OP_SOCK_TCP_LISTEN: Listen on TCP port; pops backlog:int, port:int; pushes fd:int or -1.
- OP_SOCK_TCP_ACCEPT: Accept a connection; pops fd:int; pushes client fd:int or -1.
- OP_SOCK_TCP_CONNECT: Connect to host:port; pops port:int, host:string; pushes fd:int or -1.
- OP_SOCK_UNIX_LISTEN: Listen on Unix domain socket; pops backlog:int, path:string; pushes fd:int or -1.
- OP_SOCK_UNIX_CONNECT: Connect to Unix domain socket; pops path:string; pushes fd:int or -1.
- OP_SOCK_SEND: Send bytes; pops data:string, fd:int; pushes bytesSent:int or -1.
- OP_SOCK_RECV: Receive bytes; pops max:int, fd:int; pushes data:string or Nil.
- OP_SOCK_CLOSE: Close socket; pops fd:int; pushes 1/0.
- Serial (TTY):
- OP_SERIAL_OPEN: Open serial port; pops baud:int, path:string; pushes fd:int or -1.
- OP_SERIAL_CONFIG: Configure port; pops flow_ctrl, stop_bits, parity, data_bits, fd; pushes 1/0.
- OP_SERIAL_SEND: Send bytes; pops data:string, fd:int; pushes bytesSent:int or -1.
- OP_SERIAL_RECV: Receive bytes; pops max:int, fd:int; pushes data:string or Nil.
- OP_SERIAL_CLOSE: Close; pops fd:int; pushes 1/0.
## Curl (HTTP)
- OP_CURL_GET: HTTP GET; pops url:string; pushes body:string or Nil.
- OP_CURL_POST: HTTP POST; pops body:string, url:string; pushes response:string or Nil.
- OP_CURL_DOWNLOAD: Download URL to file; pops path:string, url:string; pushes 1/0.
## PCRE2 (Regex)
- OP_PCRE2_TEST: Test pattern; pops flags:int, text:string, pattern:string; pushes 1/0.
- OP_PCRE2_MATCH: Match pattern; pops flags:int, text:string, pattern:string; pushes array/map with groups or 0.
- OP_PCRE2_FINDALL: Find all matches; pops flags:int, text:string, pattern:string; pushes array of matches.
## PC/SC (Smart cards)
- OP_PCSC_ESTABLISH: Establish context; pushes ctx handle (>0) or 0.
- OP_PCSC_LIST_READERS: List readers; pops scope/id; pushes array of strings.
- OP_PCSC_CONNECT: Connect to reader; pops reader:string, ctx; pushes handle or 0.
- OP_PCSC_TRANSMIT: Send APDU; pops apdu:array<byte>/string, handle; pushes response bytes or map incl. SW.
- OP_PCSC_DISCONNECT: Disconnect; pops handle; pushes 1/0.
- OP_PCSC_RELEASE: Release context; pops scope/id; pushes 1/0.
## Notcurses (Terminal UI)
- OP_NC_INIT: Initialize notcurses; pushes handle or 0.
- OP_NC_SHUTDOWN: Shutdown; no args; pushes 1/0.
- OP_NC_CLEAR: Clear screen; pushes 1/0.
- OP_NC_DRAW_TEXT: Draw text at (x,y); pops text, x, y; pushes 1/0.
- OP_NC_GETCH: Get key with timeout; pops timeout_ms:int; pushes int key or -1.
## SQLite-compatible lib (libSQL)
- See “libSQL” section above; identical opcode set with different backend.
## TK (Tcl/Tk UI)
- OP_TK_EVAL: Evaluate Tcl code; pops text:string; pushes result string or error.
- OP_TK_LABEL: Create/update label; pops text, id; pushes 1/0.
- OP_TK_BUTTON: Create/update button; pops text, id; pushes 1/0.
- OP_TK_PACK: Pack widget; pops id; pushes 1/0.
- OP_TK_BIND: Bind event; pops command, event, id; pushes 1/0.
- OP_TK_WM_TITLE: Set window title; pops title; pushes 1/0.
- OP_TK_LOOP: Enter main event loop; no args; blocks until exit.
- OP_TK_RESULT: Retrieve last Tcl result; pushes string.
## Miscellaneous
- OP_KEYS / OP_VALUES: Map utilities (see Maps).
- Additional opcodes may exist for modules under src/vm that are stubbed or platform-dependent. Refer to the corresponding C file for precise semantics and edge cases.
## How to explore
- Each opcode implementation lives in its own file and begins with a comment documenting its behavior and stack contract. Grep for “case OP_” or open files under src/vm/<module>/*.c to see details and error handling.

241
docs/repl.md Normal file
View file

@ -0,0 +1,241 @@
# Fun REPL Guide
This document describes the interactive ReadEvalPrint Loop (REPL) for the Fun programming language: how to build/launch it, how input and execution work, line editing and completion, the REPL buffer workflow, commands, debugging helpers, and tips.
The REPL is optional at build time. It provides a fast feedback loop for experimenting with Fun code, inspecting VM state, and debugging scripts interactively.
## Enable and build
- Build flag: -DFUN_WITH_REPL=ON
- Typical CMake configuration example:
```
cmake -S . -B build \
-DFUN_WITH_REPL=ON
cmake --build build --target fun
```
You can also set a default search path for the bundled stdlib using DEFAULT_LIB_DIR at configure time (used for completions and library loading):
```
cmake -S . -B build -DFUN_WITH_REPL=ON -DDEFAULT_LIB_DIR="/usr/share/fun/lib"
```
## Launching the REPL
- Directly run the main executable (ensure FUN_WITH_REPL=ON):
```
FUN_LIB_DIR="$(pwd)/lib" ./build/fun
```
- With the CMake “repl” convenience target (available only if built with FUN_WITH_REPL=ON):
```
cmake --build build --target repl
```
On startup, you should see something like:
```
Fun X.Y.Z REPL
Type :help for commands. Submit an empty line to run.
```
Environment variable FUN_LIB_DIR can be used to point the REPL to the standard library directory for symbol completion and library loading. If not set, a compile-time DEFAULT_LIB_DIR (if provided) or "lib" is used.
## Running scripts and REPL-on-error
- Run a script file normally:
```
FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./demo.fun
```
- Enable tracing, and drop into a REPL automatically when a runtime error occurs:
```
FUN_LIB_DIR="$(pwd)/lib" ./build/fun --repl-on-error --trace ./demo.fun
```
Inside REPL-on-error, you can inspect frames, locals, disassembly, set breakpoints, and continue or step. Use :help to see available commands.
## Prompts and input model
- Primary prompt: `fun> ` when the input buffer is empty.
- Continuation prompt:
- `... ` when text exists but no extra indentation is required.
- `...N> ` (e.g., `...1> `, `...2> `) when the REPL detects open blocks needing additional indentation (2 spaces per level). This helps write multi-line constructs.
- Execution trigger: submit an empty line (press Enter on a blank line). The REPL parses the current buffer and executes it if complete; otherwise it will tell you the input looks incomplete and keep the buffer.
Notes on completeness detection:
- The REPL considers code incomplete if there are unbalanced blocks or the last significant line ends with an operator/comma.
- If incomplete, it prints a hint like “(incomplete, open block indent +N)” or “(incomplete, continue typing)”.
## Line editing and history
The REPL includes an integrated line editor (on UNIX-like systems) with support for:
- Left/Right arrows: move cursor (single-line mode). In multi-line input the cursor stays at end for simplicity.
- Up/Down arrows: navigate input history. Your current line is saved/restored while browsing.
- Ctrl+Left / Ctrl+Right: jump by words.
- Backspace/Delete: erase characters.
- Ctrl+O: insert a newline at the cursor (quick multi-line editing).
- Enter: accept line (adds a newline to the buffer; an empty line executes the buffer).
History is persisted in a file named .fun_history in your home directory (HOME/USERPROFILE) when available, otherwise in the current directory.
## Tab completion
Tab provides two kinds of completions:
1) :load path completion
- When typing a :load command, Tab completes filesystem paths, including directories. A trailing slash is handled as expected. Completion attempts to compute common suffixes across candidates.
2) Standard library identifier completion
- For general input, Tab attempts to complete identifiers from the standard library symbols scanned from FUN_LIB_DIR (or DEFAULT_LIB_DIR/lib). If there are multiple matches, a menu of candidates is printed; otherwise the identifier is completed in place.
If Tab is pressed in the middle of the line (not at end), the REPL beeps instead of completing.
## Buffer workflow
You typically build code incrementally:
1) Type lines; they accumulate in an internal buffer.
2) Press Enter on a blank line to parse and execute the buffer.
3) Output is printed and the buffer is cleared.
Alternatively, use the :run command to execute the buffer immediately (without needing a blank line), or :run <file> to execute a files contents.
## Timing and profiling
- Toggle a simple elapsed time measurement for executions with :time on|off|toggle.
- Use :profile to parse+run and report parse time, run time, total, and instruction count.
## Command reference
Type :help to print the built-in command summary. Full list with clarifications:
- :help | :h
Show the help.
- :quit | :q | :exit
Exit the REPL.
- :reset | :re
Reset VM state (clears globals).
- :dump | :du | :globals | :gl
Dump current globals (indexes and stringified values).
- :globals [pattern] / :vars | :v [pattern]
Dump globals, filtering by substring match on the value when a pattern is provided.
- :clear | :cl
Clear the current input buffer.
- :print | :pr
Show the current buffer content.
- :run | :ru [file]
Execute current buffer, or execute the specified file immediately. Parsing errors are reported with caret highlighting.
- :profile | :pf
Execute buffer and show timing for parse and run plus instruction count.
- :save | :sa <file>
Save the current buffer to a file.
- :load | :lo <file>
Load a file into the buffer (does not run). Use :run or a blank line to execute afterward.
- :paste | :pa [run]
Enter paste mode to insert multiple lines verbatim. Finish with a single dot line: `.`. If the optional argument `run` (or `exec`) is given, the REPL will run the pasted buffer immediately.
- :history | :hi [N]
Show the last N lines of persistent history (default 50).
- :time | :ti on|off|toggle
Toggle/enable/disable timing for subsequent runs.
- :env | :en [NAME[=VALUE]]
Get or set an environment variable. With NAME only, prints NAME=value. With NAME=VALUE, sets the variable for the current process.
- :backtrace | :bt | :ba
Show a backtrace of VM frames (most recent first), including function name, source file, IP, and line.
- :frame | :fr N
Select a frame N (0..top) to target with :locals, :list, :disasm and value inspections. By default, the top frame is used.
- :list | :li [±K]
Show K lines of source around the current frames line (default 5). The current line is marked with `>`.
- :disasm | :di [±N]
Disassemble around current frames instruction pointer (default 5 on each side). Shows index, opcode name, and operand.
- :mdump | :md WHAT [offset [len]] [raw] [to <file>]
Dump a VM memory region. WHAT is one of: code | stack | globals | consts. Offset and length are optional; if omitted, a sensible default (up to 256 bytes) is used. With `raw`, write binary bytes. With `to <file>`, write output to a file; otherwise print to stdout as a formatted hexdump.
- :stack | :st [N]
Show top N (or all) stack values, stringified.
- :top | :to
Show the value at the top of the VM stack.
- :locals | :lc [FRAME]
Show non-nil locals for the selected frame (or the provided frame index).
- :printv | :pv WHAT
Print a specific value: `local[i]`, `stack[i]`, or `global[i]`.
- :break | :br [file:]line
Set a breakpoint. If file is omitted, the current frames source file is used. Prints a numeric breakpoint ID on success.
- :info | :in breaks
List breakpoints.
- :delete | :de ID
Delete a breakpoint by ID.
- :clear breaks | :cb
Remove all breakpoints.
- :cont | :co
Continue execution. In REPL-on-error/debug stops, this exits the REPL and resumes the program.
- :step | :sp
Step a single instruction (REPL-on-error/debug mode).
- :next | :ne
Step over in the current frame (REPL-on-error/debug mode).
- :finish | :fi
Run until the current frame returns (REPL-on-error/debug mode).
If an unknown command is entered, the REPL prints a hint to use :help.
## Output handling
Program output produced by VM execution is collected and then printed after each run. After printing, the VMs output buffer is cleared.
## Errors and diagnostics
- Parse errors are reported with file:line and a caret pointing to the column. When tracing is enabled during normal execution, the VM annotates output with file/line and function names to aid debugging.
- In FUN_DEBUG builds, parse errors are also appended as comments to the history file to assist in later review.
## Environment and library path
- FUN_LIB_DIR: Overrides the standard library search path and the directory scanned for identifier completion.
- DEFAULT_LIB_DIR: Compile-time fallback path used when FUN_LIB_DIR is not set. If neither is available, the REPL defaults to the relative path "lib".
## Tips
- Use :paste run to quickly paste and execute multi-line code from the clipboard.
- :profile is handy to compare parser vs. VM time and to observe instruction counts for larger snippets.
- Combine --repl-on-error with --trace when running scripts to drop into an interactive diagnostic session at the point of failure.
- Use :mdump code/consts/stack/globals to inspect raw VM data, and :disasm to view bytecode in a human-readable form.
## See also
- docs/handbook.md — full language and VM handbook, including build options and ecosystem overview.
- examples/error/repl_on_error.fun — example showing the REPL-on-error workflow.

View file

@ -22,6 +22,7 @@
// Arrays basics
arr = [1, 2, 3]
print(typeof(arr))
print(arr) // -> [1, 2, 3]
print(arr[0] + arr[1]) // -> 3

240
examples/features.fun Normal file
View file

@ -0,0 +1,240 @@
#!/usr/bin/env fun
// features.fun - Showcase of Fun's neat features
// Demonstrates modern language capabilities in pure Fun
print("=== Fun Language Feature Showcase ===")
print("")
// ============================================
// 1. Type System & Type Safety
// ============================================
print("1. Strong Type System:")
string name = "Fun Language"
float version = 0.3
boolean is_awesome = true
items = [1, 2, 3, 4, 5]
config = {"debug": true, "port": 8080}
print(" Language: " + name + " v" + to_string(version))
print(" Awesome: " + to_string(is_awesome))
print("")
// ============================================
// 2. Modern Array Operations
// ============================================
print("2. Array Operations:")
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(" Original: " + to_string(numbers))
// Array helpers from spec: push, join, map, filter, reduce
joined = join([10, 20, 30], ", ")
print(" Joined: " + joined)
// Iterate arrays
print(" Iteration:")
for item in ["apple", "banana", "cherry"]
print(" " + item)
print("")
// ============================================
// 3. Maps (Dictionaries)
// ============================================
print("3. Map Operations:")
person = {"name": "Alice", "age": 30, "role": "Developer"}
print(" Person: " + to_string(person))
print(" Has 'age' key: " + to_string(has(person, "age")))
print(" Keys: " + to_string(keys(person)))
print(" Values: " + to_string(values(person)))
print("")
// ============================================
// 4. Object-Oriented Programming
// ============================================
print("4. Classes & Objects:")
class Counter(number initial, string label)
count = 0
name = ""
fun _construct(this, initial, label)
this.count = initial
this.name = label
fun increment(this)
this.count = this.count + 1
fun get_value(this)
return this.count
fun display(this)
print(" " + this.name + ": " + to_string(this.count))
counter = Counter(100, "Score")
counter.display()
counter.increment()
counter.increment()
counter.display()
print("")
// ============================================
// 5. Inheritance
// ============================================
print("5. Inheritance:")
class Animal(string type)
species = ""
fun _construct(this, type)
this.species = type
fun speak(this)
return "Some sound"
class Dog(string dog_name) extends Animal
name = ""
fun _construct(this, dog_name)
this.species = "Canine"
this.name = dog_name
fun speak(this)
return "Woof! I'm " + this.name
dog = Dog("Buddy")
print(" " + dog.speak())
print(" Species: " + dog.species)
print("")
// ============================================
// 6. Error Handling
// ============================================
print("6. Exception Handling:")
try
print(" Attempting risky operation...")
result = 42 / 2
print(" Result: " + to_string(result))
catch err
print(" Caught error! Handled gracefully.")
finally
print(" Cleanup completed.")
print("")
// ============================================
// 7. String Manipulation
// ============================================
print("7. String Features:")
string text = "Hello, Fun Language!"
print(" Original: " + text)
// Note: Using stdlib functions (assumed to exist in utils modules)
// len, substr, find, split would come from stdlib
print("")
// ============================================
// 8. Mathematical Operations
// ============================================
print("8. Math Functions:")
float x = 16.7
print(" x = " + to_string(x))
// Note: Math functions like sqrt, floor, ceil, abs, gcd, lcm
// would come from stdlib <utils/math.fun> or similar
print("")
// ============================================
// 9. Bitwise Operations
// ============================================
print("9. Bitwise Operations:")
number bits1 = 12
number bits2 = 10
print(" 12 & 10 = " + to_string(band(bits1, bits2)))
print(" 12 | 10 = " + to_string(bor(bits1, bits2)))
print(" 12 ^ 10 = " + to_string(bxor(bits1, bits2)))
print(" 12 << 2 = " + to_string(shl(bits1, 2)))
print(" ~12 = " + to_string(bnot(bits1)))
print("")
// ============================================
// 10. Control Flow
// ============================================
print("10. Control Flow:")
// For loop with array
print(" Countdown:")
for i in [5, 4, 3, 2, 1]
print(" " + to_string(i) + "...")
print(" Liftoff!")
// While with break/continue
print(" Skip evens:")
number n = 0
while n < 10
n = n + 1
if n % 2 == 0
continue
print(" " + to_string(n))
if n >= 7
break
print("")
// ============================================
// 11. Type Introspection
// ============================================
print("11. Type Introspection:")
number check_int = 42
string check_str = "hello"
check_arr = [1, 2, 3]
check_map = {"key": "value"}
print(" typeof(42) = " + typeof(check_int))
print(" typeof(\"hello\") = " + typeof(check_str))
print(" typeof([1,2,3]) = " + typeof(check_arr))
print(" typeof(map) = " + typeof(check_map))
print("")
// ============================================
// 12. Functional Programming
// ============================================
print("12. Higher-Order Functions:")
fun double(n)
return n * 2
fun apply_twice(x, func)
return func(func(x))
result = apply_twice(5, double)
print(" apply_twice(5, double) = " + to_string(result))
print("")
// ============================================
// 13. Array Operations with Spec Functions
// ============================================
print("13. Array Higher-Order Functions:")
nums = [1, 2, 3, 4, 5]
fun square(x)
return x * x
squared = map(nums, square)
print(" Squared: " + to_string(squared))
fun is_even(x)
return x % 2 == 0
evens = filter(nums, is_even)
print(" Evens: " + to_string(evens))
fun sum(acc, x)
return acc + x
total = reduce(nums, 0, sum)
print(" Sum: " + to_string(total))
print("")
// ============================================
// Conclusion
// ============================================
print("=== Feature Showcase Complete! ===")
print("Fun combines modern language features with simplicity.")
print("Explore more examples in ./examples/ directory!")

62
examples/maps.fun Executable file
View file

@ -0,0 +1,62 @@
#!/usr/bin/env fun
/*
* 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-19
*/
/*
* Maps datatype focused examples
*
* This script exclusively demonstrates the built-in maps datatype:
* - Creating maps with literals and empty maps
* - Reading and writing entries via [key]
* - Checking for key existence with has(map, key)
* - Getting keys(map) and values(map)
* - Using nested maps
*/
// Create an empty map and add entries
user = {}
user["name"] = "Ada"
user["age"] = 37
print(user) // -> {"name": "Ada", "age": 37}
// Read an entry by key
print(user["name"]) // -> Ada
// Update an existing entry
user["age"] = 38
print(user["age"]) // -> 38
// Check if a key exists
print(has(user, "age")) // -> 1 (true)
print(has(user, "email")) // -> 0 (false)
// Keys and values (order may vary)
print(keys(user)) // -> [name, age]
print(values(user)) // -> ["Ada", 38]
// Nested maps
address = { "city": "London", "zip": "E1" }
user["address"] = address
print(user["address"]) // -> {"city": "London", "zip": "E1"}
print(user["address"]["city"]) // -> London
/* Expected output:
{"name": Ada, "age": 37}
Ada
38
1
0
[name, age]
[Ada, 38]
{"zip": E1, "city": London}
London
*/

32
examples/rust_hello.fun Normal file
View file

@ -0,0 +1,32 @@
#!/usr/bin/env fun
/*
* 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: 2026-01-27
*/
/*
* Rust-backed opcode demo: rust_hello()
*
* Build instructions:
* - Default builds disable Rust integration.
* - Enable it via: cmake -S . -B build_debug -DFUN_WITH_RUST=ON
* - Then: cmake --build build_debug --target fun
*
* Run:
* build_debug/fun examples/rust_hello.fun
*
* Expected output (with FUN_WITH_RUST=ON):
* Hello from Rust ops!
*
* If built without Rust, calling rust_hello() will raise a runtime error
* explaining that Rust integration is disabled.
*/
print(rust_hello())

View file

@ -0,0 +1,42 @@
Fun SQL TCP Demo (sqlited)
This example provides a minimal TCP server that executes SQL against a local SQLite database and a matching client.
Files
- server.fun — TCP server daemon
- client.fun — simple CLI client
- protocol.md — wire protocol specification (line-based TSV)
Prerequisites
- Build Fun with SQLite support enabled: configure with -DFUN_WITH_SQLITE=ON
- Ensure the sqlite3 development headers and runtime are installed
Create a sample database
- A schema is provided at examples/data/database.sql
- Create ./database.sqlite at the repository root using the sqlite3 CLI:
sqlite3 ./database.sqlite < ./examples/data/database.sql
Run the server
- Set FUN_LIB_DIR to the repos lib directory or install Fun libs system-wide
- Example (Debug profile path may differ):
FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/sqlited/server.fun 127.0.0.1 5555
Run the client
- Query:
FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/sqlited/client.fun 127.0.0.1 5555 "SELECT id, title FROM tasks;"
- Exec/DDL:
FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/sqlited/client.fun 127.0.0.1 5555 "UPDATE tasks SET done=1 WHERE id=1;"
Protocol summary
- Client sends one line with SQL ended by a newline (\n)
- Server responds with either:
- RESULT block (header + rows as TSV) ending with END
- OK rc (for exec/DDL)
- ERROR message (on error)
See protocol.md for details.
Notes and limitations
- Demo only; do not expose to untrusted networks (no auth/TLS; arbitrary SQL)
- BLOBs and binary data are not specially handled in this v1
- Very long SQL lines are capped at 64 KiB
- The server handles one client at a time (simple model); extend with threads if desired

142
examples/sqlited/client.fun Executable file
View file

@ -0,0 +1,142 @@
#!/usr/bin/env fun
/*
* 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-19
*/
// Simple TCP SQL client for Fun
// Connects to host:port, sends a single-line SQL (from CLI args or default),
// prints the server response, and exits.
// Run the server:
// FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/sqlited/server.fun 127.0.0.1 5555
// Run the client:
// FUN_LIB_DIR="$(pwd)/lib" ./build/fun --repl-on-error ./examples/sqlited/client.fun 127.0.0.1 5555 "SELECT * FROM tasks"
#include <cli.fun>
fun arg_or_default(args, i, d)
if (len(args) > i)
return args[i]
else
return d
fun read_all(fd)
buf = ""
while (true)
chunk = sock_recv(fd, 1024)
if (chunk == nil || len(chunk) == 0)
break
buf = buf + chunk
return buf
fun main()
args = argv()
host = arg_or_default(args, 0, "127.0.0.1")
port = to_number(arg_or_default(args, 1, 5555))
sql = arg_or_default(args, 2, "SELECT 1 AS one;")
fd = tcp_connect(host, port)
if (fd == 0)
print("Connect failed to " + host + " " + to_string(port))
return 1
// Ensure a single line terminated by \n
if (len(sql) == 0 || substr(sql, len(sql)-1, 1) != "\n")
sql = sql + "\n"
sent = sock_send(fd, sql)
if (sent < 0)
print("Send failed")
sock_close(fd)
return 1
resp = read_all(fd)
sock_close(fd)
if (resp == nil)
resp = ""
print(resp)
// Explicitly invoke main when the script is run
main()
/* Possible result with 67 entries in the tasks table:
RESULT
value
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
{map n=4}
END
*/

View file

@ -0,0 +1,35 @@
Fun SQL TCP Demo Protocol (TSV, line-based)
- Client sends exactly one line with the SQL text terminated by a newline ("\n"). The server reads up to 64 KiB.
Responses
1) Query returning rows (e.g., SELECT):
RESULT
col1\tcol2\t...\n
v11\tv12\t...\n
...
END
Notes:
- First line is the literal word RESULT followed by a newline.
- Second line is a header with column names separated by a single tab ("\t").
- Each subsequent line is one row; fields are tab-separated. Nil/NULL are encoded as empty strings.
- The block terminates with a line containing the literal END.
2) Exec/DDL (e.g., INSERT/UPDATE/CREATE):
OK rc
Notes:
- rc is the sqlite3 result code (0 indicates success).
3) Error:
ERROR message
Notes:
- The error message is human-readable and not machine-stable.
General
- Newlines are Unix style ("\n").
- Tabs and newlines in data are replaced with spaces for TSV safety.
- The server closes the connection after sending the response.

282
examples/sqlited/server.fun Executable file
View file

@ -0,0 +1,282 @@
#!/usr/bin/env fun
/*
* 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-19
*/
// Simple TCP SQL server for Fun
// Listens on a TCP port, opens ./database.sqlite, executes one-line SQL per connection,
// and returns results over the socket in a simple TSV protocol.
// Run the server:
// FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/sqlited/server.fun 127.0.0.1 5555
// Run the client:
// FUN_LIB_DIR="$(pwd)/lib" ./build/fun --repl-on-error ./examples/sqlited/client.fun 127.0.0.1 5555 "SELECT * FROM tasks"
// Protocol (per protocol.md):
// - Client sends a single line of SQL ending with \n
// - If query returns rows: respond with
// RESULT\n
// <col1>\t<col2>\t...\n
// <v11>\t<v12>\t...\n
// ...
// END\n
// - If exec/DDL: respond with
// OK <rc>\n
// - On error: respond with
// ERROR <message>\n
// Helper: CLI args via stdlib
#include <cli.fun>
#include <strings.fun>
fun arg_or_default(args, i, d)
if (len(args) > i)
return args[i]
else
return d
// Helper: send a string (no newline added)
fun send(fd, s)
// sock_send returns bytes or -1
return sock_send(fd, s)
// Helper: read a single line (up to max_len) ending with \n; returns string without trailing \r?\n or nil on EOF
fun read_line(fd)
max_len = 65536
buf = ""
while (len(buf) < max_len)
chunk = sock_recv(fd, 256)
if (chunk == nil || len(chunk) == 0)
break
buf = buf + chunk
pos = find(buf, "\n")
if (pos >= 0)
line = substr(buf, 0, pos)
// trim trailing \r if present
if (len(line) > 0 && substr(line, len(line)-1, 1) == "\r")
line = substr(line, 0, len(line)-1)
return line
if (len(buf) == 0)
return nil
// no newline; return whole buffer (trim any trailing CR)
if (len(buf) > 0 && substr(buf, len(buf)-1, 1) == "\r")
buf = substr(buf, 0, len(buf)-1)
return buf
// Replace tab/newline with spaces for TSV safety
fun sanitize_tsv(s)
if (s == nil)
return ""
out = ""
i = 0
while (i < len(s))
ch = substr(s, i, 1)
if (ch == "\t" || ch == "\n" || ch == "\r")
out = out + " "
else
out = out + ch
i = i + 1
return out
fun trim(s)
// trim spaces and tabs
i = 0
j = len(s)
while (i < j && (substr(s, i, 1) == " " || substr(s, i, 1) == "\t"))
i = i + 1
while (j > i && (substr(s, j-1, 1) == " " || substr(s, j-1, 1) == "\t" || substr(s, j-1, 1) == ";"))
j = j - 1
return substr(s, i, j - i)
fun split_on_comma(s)
parts = []
cur = ""
i = 0
while (i < len(s))
ch = substr(s, i, 1)
if (ch == ",")
push(parts, trim(cur))
cur = ""
else
cur = cur + ch
i = i + 1
push(parts, trim(cur))
return parts
// Parse header from SQL SELECT list; for SELECT * tries PRAGMA table_info(table)
fun parse_header_from_sql(sql, dbh)
// Use stdlib helper for lowercase
lower_sql = str_to_lower(sql)
psel = find(lower_sql, "select ")
pfrom = find(lower_sql, " from ")
if (psel < 0 || pfrom < 0 || pfrom <= psel)
return nil
cols_str = substr(sql, psel + 7, pfrom - (psel + 7))
cols_str = trim(cols_str)
if (find(cols_str, "*") >= 0)
// Attempt to detect table name after FROM
rest = substr(sql, pfrom + 6, len(sql) - (pfrom + 6))
rest = trim(rest)
// table name is up to next space or semicolon
sp = find(rest, " ")
tname = rest
if (sp > 0)
tname = substr(rest, 0, sp)
// remove trailing semicolon if any
tname = trim(tname)
if (len(tname) > 0)
pragma_sql = "PRAGMA table_info(" + tname + ");"
ti = sqlite_query(dbh, pragma_sql)
if (ti != nil && len(ti) > 0)
cols = []
i = 0
while (i < len(ti))
nm = ti[i]["name"]
if (nm != nil)
push(cols, to_string(nm))
i = i + 1
if (len(cols) > 0)
return cols
// Parse explicit column list
parts = split_on_comma(cols_str)
cols = []
i = 0
while (i < len(parts))
p = parts[i]
pl = lower(p)
// handle AS alias
aspos = find(pl, " as ")
if (aspos >= 0)
alias = trim(substr(p, aspos + 4, len(p) - (aspos + 4)))
push(cols, alias)
else
// take last token after dot
dot = find(p, ".")
if (dot >= 0)
push(cols, trim(substr(p, dot + 1, len(p) - (dot + 1))))
else
push(cols, trim(p))
i = i + 1
if (len(cols) > 0)
return cols
return nil
// Attempt to build a deterministic header and row order using enumerate(row).
// Falls back to attempting common column names if enumerate is unavailable.
fun extract_header(row)
// Build a header by probing a set of common keys present in many queries.
// If none are present, fall back to a single synthetic column "value" and
// the caller will print the entire row using to_string(row).
hdr_candidates = [
"id", "name", "title", "value", "count", "cnt",
"done", "created_at", "updated_at", "rowid"
]
cols = []
found = 0
i = 0
while (i < len(hdr_candidates))
k = hdr_candidates[i]
v = row[k]
if (v != nil)
push(cols, k)
found = 1
i = i + 1
if (found == 1)
return [cols, 0] // is_synthetic = 0
else
return [["value"], 1] // is_synthetic = 1
// Try to obtain map keys via enumerate(row). Returns [keys, is_synthetic]
fun header_from_enumerate(row)
keys = []
pairs = enumerate(row)
if (pairs == nil)
return [["value"], 1]
i = 0
while (i < len(pairs))
p = pairs[i]
// Expect pair to be [key, value]
if (p != nil && len(p) >= 1)
push(keys, p[0])
i = i + 1
if (len(keys) == 0)
return [["value"], 1]
return [keys, 0]
fun handle_client(fd, dbh)
print("[sqlited] client connected: fd=" + to_string(fd))
sql = read_line(fd)
print("[sqlited] received SQL: '" + (sql == nil ? "" : sql) + "'")
if (sql == nil || len(sql) == 0)
send(fd, "ERROR empty\n")
sock_close(fd)
return 0
// Try query first
rows = sqlite_query(dbh, sql)
if (rows != nil)
print("[sqlited] query path; rows array obtained")
// Build response in the stable synthetic format used in the 5558 build:
// RESULT\n
// value\n
// {map n=...}\n (per row)
resp = "RESULT\n"
// Always emit single-column header 'value' for compatibility
resp = resp + "value\n"
// Emit rows
r = 0
while (r < len(rows))
row = rows[r]
print("[sqlited] sending row #" + to_string(r))
resp = resp + sanitize_tsv(to_string(row)) + "\n"
print("[sqlited] row #" + to_string(r) + " appended (synth)")
r = r + 1
// Terminate block
print("[sqlited] finished building response; sending END and closing")
resp = resp + "END\n"
sb = send(fd, resp)
print("[sqlited] total bytes sent=" + to_string(sb))
sock_close(fd)
return 1
else
// Exec path
print("[sqlited] exec/DDL path")
rc = sqlite_exec(dbh, sql)
print("[sqlited] exec rc=" + to_string(rc))
send(fd, "OK " + to_string(rc) + "\n")
sock_close(fd)
return 1
fun main()
args = argv()
host = arg_or_default(args, 0, "127.0.0.1")
port = to_number(arg_or_default(args, 1, 5555))
dbh = sqlite_open("./database.sqlite")
if (dbh == 0)
print("Failed to open ./database.sqlite; create it first (sqlite3 ./database.sqlite < ./examples/data/database.sql)")
return 1
lfd = tcp_listen(port, 16)
if (lfd == 0)
print("Failed to listen on port " + to_string(port))
return 1
print("sqlited: listening on " + host + " " + to_string(port))
while (true)
cfd = tcp_accept(lfd)
if (cfd > 0)
// Handle sequentially to keep it simple for a demo
handle_client(cfd, dbh)
// Explicitly invoke main when the script is run
main()

View file

@ -71,7 +71,7 @@ class Person(number age, string name)
// typeof(instance) will call this and return it
return "Class"
Class p = Person(33, "Jo")
p = Person(33, "Jo")
print(typeof(p)) // -> "Class"
// p = "nope" // Uncomment to see TypeError: expected Class

View file

@ -6,7 +6,10 @@
* - Executes each example as a subprocess
*/
#include <strings.fun>
#include <io/console.fun>
#include <io/process.fun>
fun pick_fun_bin()
// Prefer an explicit FUN_BIN override; otherwise rely on PATH
@ -49,18 +52,42 @@ fun main()
"examples/cast_demo.fun",
"examples/class_constructor.fun",
"examples/classes_demo.fun",
"examples/crc32_example.fun",
"examples/crc32c_example.fun",
"examples/curl_download.fun",
"examples/curl_get_json.fun",
"examples/curl_post.fun",
"examples/crypto/crc32_example.fun",
"examples/crypto/crc32c_example.fun",
"examples/crypto/md5_demo.fun",
"examples/crypto/sha1_demo.fun",
"examples/crypto/sha256_demo.fun",
"examples/crypto/sha256_str_demo.fun",
"examples/crypto/sha384_example.fun",
"examples/crypto/sha512_demo.fun",
"examples/crypto/sha512_str_demo.fun",
"examples/datetime_basic.fun",
"examples/datetime_extended.fun",
"examples/datetime_timer.fun",
"examples/debug_reporting.fun",
"examples/echo_example.fun",
"examples/exit_example.fun",
"examples/error/debug_reporting.fun",
"examples/error/exit_example.fun",
"examples/error/repl_on_error.fun",
"examples/expressions_test.fun",
"examples/extra/curl_download.fun",
"examples/extra/curl_get_json.fun",
"examples/extra/curl_post.fun",
"examples/extra/ini_class_demo.fun",
"examples/extra/ini_complex.fun",
"examples/extra/ini_demo.fun",
"examples/extra/ini_subsections.fun",
"examples/extra/json_showcase.fun",
"examples/extra/libsql_example.fun",
"examples/extra/pcre2_opcodes.fun",
"examples/extra/pcre2_showcase.fun",
"examples/extra/pcsc_example.fun",
"examples/extra/sqlite_example.fun",
"examples/extra/tk_hello.fun",
"examples/extra/xml_access_catalog.fun",
"examples/extra/xml_access_employees.fun",
"examples/extra/xml_access_ns.fun",
"examples/extra/xml_class_example.fun",
"examples/extra/xml_minimal.fun",
"examples/fail.fun",
"examples/file_io.fun",
"examples/file_print_for_file_line_by_line.fun",
@ -75,59 +102,35 @@ fun main()
"examples/include_local_util.fun",
"examples/include_namespace.fun",
"examples/inheritance_demo.fun",
"examples/ini_class_demo.fun",
"examples/ini_complex.fun",
"examples/ini_demo.fun",
"examples/ini_subsections.fun",
"examples/input_example.fun",
"examples/json_showcase.fun",
"examples/libsql_example.fun",
"examples/interactive/input_example.fun",
"examples/loops_break_continue.fun",
"examples/md5_demo.fun",
"examples/namespaced_mod.fun",
"examples/nested_loops.fun",
"examples/objects_basic.fun",
"examples/objects_more.fun",
"examples/os_env.fun",
"examples/pcre2_opcodes.fun",
"examples/pcre2_showcase.fun",
"examples/pcsc_example.fun",
"examples/process_example.fun",
"examples/regex_demo.fun",
"examples/regex_procedural.fun",
"examples/repl_on_error.fun",
"examples/sha1_demo.fun",
"examples/sha256_demo.fun",
"examples/sha256_str_demo.fun",
"examples/sha384_example.fun",
"examples/sha512_demo.fun",
"examples/sha512_str_demo.fun",
"examples/short_circuit_test.fun",
"examples/signed_ints.fun",
"examples/sqlite_example.fun",
"examples/stdlib_showcase.fun",
"examples/strings_test.fun",
"examples/tcp_http_get.fun",
"examples/tcp_http_get_class.fun",
"examples/thread_class_example.fun",
"examples/threads_demo.fun",
"examples/tk_hello.fun",
"examples/try_catch_finally.fun",
"examples/try_catch_with_error.fun",
"examples/typeof.fun",
"examples/typeof_features.fun",
"examples/type_safety.fun",
"examples/type_safety_fails.fun",
"examples/types_integers.fun",
"examples/types_overview.fun",
"examples/typeof.fun",
"examples/typeof_features.fun",
"examples/uint_types.fun",
"examples/unix_socket_echo.fun",
"examples/while_test.fun",
"examples/xml_access_catalog.fun",
"examples/xml_access_employees.fun",
"examples/xml_access_ns.fun",
"examples/xml_class_example.fun",
"examples/xml_minimal.fun"
"examples/while_test.fun"
]
failures = []

View file

@ -206,6 +206,7 @@ static const char *opcode_name(OpCode op) {
case OP_SIGN: return "SIGN";
case OP_FMIN: return "FMIN";
case OP_FMAX: return "FMAX";
case OP_RUST_HELLO: return "RUST_HELLO";
default: return "???";
}
}

View file

@ -263,6 +263,9 @@ typedef enum {
OP_FMIN, // pops b, a (int/float); pushes fmin(a,b) (NaN handling per C99)
OP_FMAX, // pops b, a (int/float); pushes fmax(a,b) (NaN handling per C99)
// Rust FFI demo opcode(s)
OP_RUST_HELLO, // pushes string returned from Rust (hello world)
/* Notcurses TUI (optional) */
OP_NC_INIT, // initializes Notcurses; returns 1 on success, 0 on failure
OP_NC_SHUTDOWN, // shuts down Notcurses; returns 0

View file

@ -73,6 +73,7 @@ static int g_temp_counter = 0;
#define TYPE_META_NIL 10003
#define TYPE_META_CLASS 10004
#define TYPE_META_FLOAT 10005
#define TYPE_META_ARRAY 10006
static void parser_fail(size_t pos, const char *fmt, ...) {
g_has_error = 1;
@ -785,6 +786,13 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
free(name);
return 1;
}
if (strcmp(name, "rust_hello") == 0) {
(*pos)++; /* '(' */
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "rust_hello expects ()"); free(name); return 0; }
bytecode_add_instruction(bc, OP_RUST_HELLO, 0);
free(name);
return 1;
}
if (strcmp(name, "os_list_dir") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "os_list_dir expects (path)"); free(name); return 0; }
@ -2845,7 +2853,8 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
Note: 'number' maps to signed 64-bit here. 'byte' is an alias of unsigned 8-bit. 'Class' restricts to class instances.
*/
if (strcmp(name, "number") == 0 || strcmp(name, "string") == 0 || strcmp(name, "boolean") == 0 || strcmp(name, "nil") == 0
|| strcmp(name, "Class") == 0 || strcmp(name, "float") == 0
|| strcmp(name, "class") == 0 || strcmp(name, "float") == 0
|| strcmp(name, "array") == 0
|| strcmp(name, "byte") == 0
|| strcmp(name, "uint8") == 0 || strcmp(name, "uint16") == 0 || strcmp(name, "uint32") == 0 || strcmp(name, "uint64") == 0
|| strcmp(name, "int8") == 0 || strcmp(name, "int16") == 0 || strcmp(name, "int32") == 0 || strcmp(name, "int64") == 0) {
@ -2853,8 +2862,9 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
int is_string = (strcmp(name, "string") == 0);
int is_boolean = (strcmp(name, "boolean") == 0);
int is_nil = (strcmp(name, "nil") == 0);
int is_class_tkn = (strcmp(name, "Class") == 0);
int is_float_tkn = (strcmp(name, "float") == 0);
int is_class = (strcmp(name, "class") == 0);
int is_float = (strcmp(name, "float") == 0);
int is_array = (strcmp(name, "array") == 0);
int is_byte = (strcmp(name, "byte") == 0);
int is_u8 = (strcmp(name, "uint8") == 0) || is_byte;
int is_u16 = (strcmp(name, "uint16") == 0);
@ -2865,12 +2875,12 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
int is_s32 = (strcmp(name, "int32") == 0);
int is_s64 = (strcmp(name, "int64") == 0) || is_number; /* number maps to int64 (signed) */
int decl_bits = is_u8 ? 8 : is_u16 ? 16 : is_u32 ? 32 : is_u64 ? 64
: is_s8 ? 8 : is_s16 ? 16 : is_s32 ? 32 : is_s64 ? 64 : 0;
: is_s8 ? 8 : is_s16 ? 16 : is_s32 ? 32 : is_s64 ? 64 : 0;
int decl_signed = (is_s8 || is_s16 || is_s32 || is_s64) ? 1 : 0;
/* store decl bits with sign encoded: negative means signed (number is signed 64-bit) */
/* store decl bits with sign encoded: negative means signed (number is signed 64-bit) */
if (decl_signed) decl_bits = -decl_bits;
/* declared type metadata: integers use decl_bits; string/boolean/nil/Class/float use special markers */
/* declared type metadata: integers use decl_bits; string/boolean/nil/Class/float/array use special markers */
int decl_meta = decl_bits;
if (is_string) {
decl_meta = TYPE_META_STRING;
@ -2878,10 +2888,12 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
decl_meta = TYPE_META_BOOLEAN;
} else if (is_nil) {
decl_meta = TYPE_META_NIL;
} else if (is_class_tkn) {
} else if (is_class) {
decl_meta = TYPE_META_CLASS;
} else if (is_float_tkn) {
} else if (is_float) {
decl_meta = TYPE_META_FLOAT;
} else if (is_array) {
decl_meta = TYPE_META_ARRAY;
}
free(name);
@ -2989,6 +3001,23 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
bytecode_add_instruction(bc, OP_HALT, 0);
}
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
} else if (decl_meta == TYPE_META_ARRAY) {
/* expect Array */
bytecode_add_instruction(bc, OP_DUP, 0);
bytecode_add_instruction(bc, OP_TYPEOF, 0);
int ciArr = bytecode_add_constant(bc, make_string("Array"));
bytecode_add_instruction(bc, OP_LOAD_CONST, ciArr);
bytecode_add_instruction(bc, OP_EQ, 0);
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
bytecode_set_operand(bc, j_to_error, bc->instr_count);
{
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Array"));
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
bytecode_add_instruction(bc, OP_PRINT, 0);
bytecode_add_instruction(bc, OP_HALT, 0);
}
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
} else if (decl_meta == TYPE_META_BOOLEAN) {
/* accept Boolean literal or Number; if Number, clamp to 0/1 */
/* check if value is Boolean */
@ -3077,7 +3106,7 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
ci = bytecode_add_constant(bc, make_string(""));
} else if (is_nil) {
ci = bytecode_add_constant(bc, make_nil());
} else if (is_class_tkn) {
} else if (is_class) {
/* Class-typed variable defaults to Nil until assigned an instance */
ci = bytecode_add_constant(bc, make_nil());
} else if (is_boolean) {
@ -3349,6 +3378,23 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
bytecode_add_instruction(bc, OP_HALT, 0);
}
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
} else if (meta == TYPE_META_ARRAY) {
/* expect Array */
bytecode_add_instruction(bc, OP_DUP, 0);
bytecode_add_instruction(bc, OP_TYPEOF, 0);
int ciArr = bytecode_add_constant(bc, make_string("Array"));
bytecode_add_instruction(bc, OP_LOAD_CONST, ciArr);
bytecode_add_instruction(bc, OP_EQ, 0);
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
bytecode_set_operand(bc, j_to_error, bc->instr_count);
{
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Array"));
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
bytecode_add_instruction(bc, OP_PRINT, 0);
bytecode_add_instruction(bc, OP_HALT, 0);
}
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
} else if (meta == TYPE_META_BOOLEAN) {
/* expect Number then clamp to 1 bit (unsigned) */
bytecode_add_instruction(bc, OP_DUP, 0);

16
src/rust/Cargo.toml Normal file
View file

@ -0,0 +1,16 @@
[package]
name = "hello-c-world"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["staticlib"]
[profile.release]
panic = "abort"
# Optimize for minimal size
opt-level = "z"
codegen-units = 1
lto = true
# If Cargo is new enough, this strips symbols from Rust objects
strip = "symbols"

44
src/rust/src/lib.rs Normal file
View file

@ -0,0 +1,44 @@
/*
* 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: 2026-01-27
*/
#![no_std]
#[repr(C)]
pub struct Vm;
extern "C" {
fn vm_pop_i64(vm: *mut Vm) -> i64;
fn vm_push_i64(vm: *mut Vm, v: i64);
}
// Submodule with additional Rust VM math ops (exported via C ABI)
pub mod vm;
#[no_mangle]
pub extern "C" fn fun_op_radd(vm: *mut Vm) -> i32 {
unsafe {
let b = vm_pop_i64(vm);
let a = vm_pop_i64(vm);
vm_push_i64(vm, a + b);
}
0
}
#[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 _
}
// Minimal panic handler for no_std; abort behavior requested via Cargo profile
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}

70
src/rust/src/vm/mod.rs Normal file
View file

@ -0,0 +1,70 @@
/*
* 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: 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`).
use super::Vm;
extern "C" {
fn vm_pop_i64(vm: *mut Vm) -> i64;
fn vm_push_i64(vm: *mut Vm, v: i64);
}
/// Multiply the top two integers on the VM stack.
///
/// Before: [..., a, b]
/// After: [..., a*b]
#[no_mangle]
pub extern "C" fn fun_op_rmul(vm: *mut Vm) -> i32 {
unsafe {
let b = vm_pop_i64(vm);
let a = vm_pop_i64(vm);
vm_push_i64(vm, a.saturating_mul(b));
}
0
}
/// Subtract the top two integers on the VM stack.
///
/// Before: [..., a, b]
/// After: [..., a-b]
#[no_mangle]
pub extern "C" fn fun_op_rsub(vm: *mut Vm) -> i32 {
unsafe {
let b = vm_pop_i64(vm);
let a = vm_pop_i64(vm);
vm_push_i64(vm, a.wrapping_sub(b));
}
0
}
/// Integer division of the top two integers on the VM stack (a / b).
/// Pushes 0 and returns 1 when dividing by zero.
///
/// Before: [..., a, b]
/// After: [..., a/b]
#[no_mangle]
pub extern "C" fn fun_op_rdiv(vm: *mut Vm) -> i32 {
unsafe {
let b = vm_pop_i64(vm);
let a = vm_pop_i64(vm);
if b == 0 {
vm_push_i64(vm, 0);
return 1; // indicate error (div by zero)
}
vm_push_i64(vm, a / b);
}
0
}

View file

@ -46,6 +46,29 @@ int main() {
}
vm_clear_output(&vm);
/* --- Rust FFI demo: call a Rust opcode and string function --- */
#ifdef FUN_WITH_RUST
extern int fun_op_radd(VM *vm);
extern const char *fun_rust_get_string(void);
printf("=== Rust FFI demo ===\n");
const char *rs = fun_rust_get_string();
if (rs) {
printf("Rust says: %s\n", rs);
}
/* prepare stack: push 10 and 32, then call Rust add -> expect 42 */
vm_push_i64(&vm, 10);
vm_push_i64(&vm, 32);
int rc = fun_op_radd(&vm);
printf("fun_op_radd rc=%d\n", rc);
long long sum = (long long)vm_pop_i64(&vm);
printf("Rust op result: %lld\n", sum);
#else
printf("=== Rust FFI demo (disabled; build with -DFUN_WITH_RUST=ON) ===\n");
#endif
vm_free(&vm);
bytecode_free(bc);
return 0;

View file

@ -235,6 +235,38 @@ static void fun_vm_exit(int code) {
/* Redirect exit inside this TU (affects included opcode handlers) */
#define exit(code) fun_vm_exit(code)
/* Forward decl for stack push used by vm_raise_error */
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. */
void vm_raise_error(VM *vm, const char *msg) {
if (!vm || vm->fp < 0) {
fprintf(stderr, "Runtime error: %s\n", msg ? msg : "<error>");
return;
}
Frame *f = &vm->frames[vm->fp];
if (f->try_sp >= 0) {
char buf[256];
if (msg) {
snprintf(buf, sizeof(buf), "Runtime error: %s", msg);
} else {
snprintf(buf, sizeof(buf), "Runtime error");
}
/* push error value and transfer control to handler target */
Value err = make_string(buf);
push_value(vm, err);
int try_idx = f->try_stack[f->try_sp--];
int target = f->fn->instructions[try_idx].operand;
f->ip = target;
return;
}
/* No handler: print annotated message and terminate VM */
fprintf(stderr, "Runtime error: %s\n", msg ? msg : "<error>");
vm->fp = -1; /* stop execution */
}
/*
Opcode case include index (vm_case_*.inc):
- Core/stack/frame:
@ -434,6 +466,28 @@ static Value pop_value(VM *vm) {
return vm->stack[vm->sp--]; /* caller owns returned Value */
}
/* --- C ABI helpers for Rust FFI --- */
int64_t vm_pop_i64(VM *vm) {
Value v = pop_value(vm);
int64_t out = 0;
if (v.type == VAL_INT) {
out = v.i;
} else if (v.type == VAL_FLOAT) {
out = (int64_t) v.d;
} else {
fprintf(stderr, "Runtime type error: expected int/float on stack, got %s\n", value_type_name(v.type));
free_value(v);
exit(1);
}
/* free any dynamic payload (no-op for int/float) */
free_value(v);
return out;
}
void vm_push_i64(VM *vm, int64_t v) {
push_value(vm, make_int(v));
}
static void frame_init(Frame *f) {
f->fn = NULL;
f->ip = 0;
@ -702,6 +756,9 @@ void vm_run(VM *vm, Bytecode *entry) {
#include "vm/math/isqrt.c"
#include "vm/math/sign.c"
/* Rust FFI demo opcode(s) */
#include "vm/rust/hello.c"
#include "vm/os/env.c"
#include "vm/os/env_all.c"
#include "vm/os/fun_version.c"

View file

@ -54,6 +54,9 @@ static const char *opcode_names[] = {
"SERIAL_OPEN","SERIAL_CONFIG","SERIAL_SEND","SERIAL_RECV","SERIAL_CLOSE",
"TK_EVAL","TK_RESULT","TK_LOOP","TK_WM_TITLE","TK_LABEL","TK_BUTTON","TK_PACK",
"TRY_PUSH","TRY_POP","THROW",
"FMIN","FMAX",
/* Rust FFI demo */
"RUST_HELLO",
/* Notcurses TUI (optional) */
"NC_INIT","NC_SHUTDOWN","NC_CLEAR","NC_DRAW_TEXT","NC_GETCH"
};
@ -122,6 +125,12 @@ void vm_dump_globals(VM *vm);
// run entry Bytecode (pushes first frame)
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. */
void vm_raise_error(VM *vm, const char *msg);
/* --- Debugger API --- */
void vm_debug_reset(VM *vm);
int vm_debug_add_breakpoint(VM *vm, const char *file, int line); // returns id >=0 or -1
@ -137,4 +146,16 @@ static inline int opcode_is_valid(int op) {
return op >= OP_NOP && op <= OP_NC_GETCH; // all current opcodes (including optional NC_*)
}
/* --- 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. */
int64_t vm_pop_i64(VM *vm);
/* Push an int64 onto VM stack. */
void vm_push_i64(VM *vm, int64_t v);
/* 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). */
const char *fun_rust_get_string(void);
#endif

View file

@ -40,8 +40,8 @@ case OP_DIV: {
double da = (a.type == VAL_FLOAT) ? a.d : (double)a.i;
double db = (b.type == VAL_FLOAT) ? b.d : (double)b.i;
if (db == 0.0) {
fprintf(stderr, "Runtime error: division by zero\n");
exit(1);
vm_raise_error(vm, "division by zero");
break;
}
Value res = make_float(da / db);
free_value(a);
@ -49,8 +49,8 @@ case OP_DIV: {
push_value(vm, res);
} else {
if (b.i == 0) {
fprintf(stderr, "Runtime error: division by zero\n");
exit(1);
vm_raise_error(vm, "division by zero");
break;
}
Value res = make_int(a.i / b.i);
free_value(a);

27
src/vm/rust/hello.c Normal file
View file

@ -0,0 +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: 2026-01-27
*/
/**
* Rust FFI demo opcode: OP_RUST_HELLO
* When executed, it pushes a hello string returned by Rust onto the VM stack.
*/
case OP_RUST_HELLO: {
#ifdef FUN_WITH_RUST
const char *s = fun_rust_get_string();
if (!s) s = "";
push_value(vm, make_string(s));
#else
vm_raise_error(vm, "RUST_HELLO requires FUN_WITH_RUST=ON at build time");
push_value(vm, make_nil());
#endif
break;
}