diff --git a/.gitignore b/.gitignore index 48a69a6..ceee666 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,5 @@ json.xml lib/*.so out/ src/*.o -src/rust/Cargo.lock -src/rust/target *.swp tmp* diff --git a/CMakeLists.txt b/CMakeLists.txt index fbfca89..a8847f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.38.0 LANGUAGES C) +project(fun VERSION 0.37.57 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) @@ -11,114 +11,11 @@ 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 - $<$:-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 $<$:-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") diff --git a/README.md b/README.md index bd30bf4..0c26be1 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ ## 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. @@ -16,8 +14,6 @@ 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! @@ -56,7 +52,6 @@ Fun is not about being the fastest or the most feature-rich. It’s about sharin - Respectful - Curious - Creative -- Open for everyone Like the name says: Fun Unites Nerds. @@ -82,9 +77,47 @@ Fun may not change the world — but it will make programming a little more fun. ### Lib (./lib/) -See [./lib/](https://git.xw3.org/fun/fun/src/branch/main/lib) for what the standard library provides. +``` +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 +``` -### Optional extensions (build-time selectable / only testing this on Linux actually): +### Extensions (only Linux actually) - [CGI](https://en.wikipedia.org/wiki/Common_Gateway_Interface) support builtin using [kcgi](https://kristaps.bsd.lv/kcgi/) (optional) ☐ - [cURL](https://curl.se/) support builtin using [libcurl](https://curl.se/libcurl/) (optional) ☑ @@ -107,7 +140,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) and the [specification](https://git.xw3.org/fun/fun/src/branch/main/spec/v0.3.md). +Current documentation is only found in the [Fun Handbook](https://git.xw3.org/fun/fun/src/branch/main/docs/handbook.md). In the [./examples/](https://git.xw3.org/fun/fun/src/branch/main/examples) directory should be an example of most Fun features. diff --git a/docs/handbook.md b/docs/handbook.md index 006d60a..f78f4d8 100644 --- a/docs/handbook.md +++ b/docs/handbook.md @@ -100,8 +100,6 @@ 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: @@ -323,29 +321,20 @@ 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; break, continue; range helpers in utils.range +- if/else, while; 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 for libs under FUN_LIB_DIR - #include "relative/path.fun" for local includes @@ -368,10 +357,6 @@ 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 } @@ -506,99 +491,20 @@ Module lib/encoding/base64.fun: base64_encode(string), base64_decode(string) ### JSON (optional) -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 - -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. +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. ### CURL (optional) -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) +Build flag: -DFUN_WITH_CURL=ON; requires libcurl. VM provides: +- curl_get(url) -> string ("" on error) +- curl_post(url, body) -> string ("" on error) - curl_download(url, path) -> 1/0 -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 +Examples: curl_get_json.fun, curl_post.fun, curl_download.fun ### PCSC (optional) -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, 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 - -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. +Build flag: -DFUN_WITH_PCSC=ON; provides pcsc_* built-ins and a stdlib wrapper class PCSC. Example: pcsc_example.fun. ### SQLite (optional) @@ -624,174 +530,13 @@ 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 - -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 - -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 Read–Eval–Print 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/.fun + FUN_LIB_DIR="$(pwd)/lib" ./build/fun examples/.fun Highlights (not exhaustive): - arrays.fun, arrays_advanced.fun, arrays_iter.fun — array operations @@ -810,14 +555,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 -- 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 +- input_example.fun — console input +- json_showcase.fun — JSON usage +- curl_get_json.fun, curl_post.fun, curl_download.fun — HTTP via CURL - loops_break_continue.fun, nested_loops.fun, while_test.fun — loops -- 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 +- md5_demo.fun, sha1_demo.fun, sha256_demo.fun, sha256_str_demo.fun, sha384_example.fun, sha512_demo.fun, sha512_str_demo.fun — hashing - objects_basic.fun, objects_more.fun — map/object patterns - os_env.fun — environment variables -- extra/pcsc_example.fun — smart card demo +- 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 @@ -829,7 +574,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 -- extra/sqlite_example.fun — SQLite usage +- sqlite_example.fun — SQLite usage Notes: - Some examples rely on optional features (JSON, CURL, PCSC, SQLite) and degrade gracefully when disabled. diff --git a/docs/internals.md b/docs/internals.md deleted file mode 100644 index 9995968..0000000 --- a/docs/internals.md +++ /dev/null @@ -1,332 +0,0 @@ -# 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 - -## High‑level 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 VM’s main interpreter loop dispatches these opcodes and performs type‑aware 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: per‑frame 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 REPL‑on‑error 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*): tear‑down 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 single‑purpose 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//. - -Dispatch naming and visibility: -- Human‑readable 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 (right‑to‑left) and push a result. - -Call frames: -- vm_push_frame sets up a new frame for a function call (OP_CALL), transferring arguments into the callee’s 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 VM‑wide 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 float‑aware 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 1‑based 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 per‑frame 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 VM‑global unwind state. - -## REPL‑on‑error and debugger - -Runtime error path: -- vm_raise_error consults the current frame’s 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__: : - …included lines… - // __include_end__: : - -- 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 precedence‑climbing/recursive‑descent 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 indentation‑based 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. - -- Control‑flow 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 left‑to‑right; the callee consumes them from the stack into local slots as per the compiler’s 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. - -Front‑end 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 built‑ins 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/ 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 step‑by‑step. -- Use OP_LINE markers (visible via bytecode_dump) to correlate bytecode with source lines. -- vm_dump_globals helps inspect non‑nil 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 Python’s GIL). Each VM has its own heap, scheduler, and GC. There are no cross‑VM pointers. Concurrency and data exchange happen via message passing and a few carefully scoped shared‑memory primitives for high‑throughput use cases. This keeps the C API simple, predictable, and safe to embed in multi‑threaded 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. -- Intra‑VM concurrency: User‑space tasks (green threads/fibers) scheduled by the VM. Within a single VM, you get structured concurrency; the VM is single‑owner from the host’s perspective. -- Inter‑VM concurrency: Use message passing (channels/ports) and zero‑copy shared buffers where explicitly opted in. - -### Memory and sharing - -- Per‑VM 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, GC‑safe serialization format. -- Zero‑copy fast path (opt‑in): For large payloads, hosts can create `fun_shared_buffer` objects which are reference‑counted, immutable within VMs, and can be shared across VMs without copying. Mutating a shared buffer requires creating a new buffer (copy‑on‑write style), preserving safety. - -### Thread‑safety 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 VM’s 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 cross‑VM 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 doesn’t need to mix threading libraries. These do not participate in GC and are outside VM heaps. - -### Scheduling and GC - -- Per‑VM scheduler: Green threads are multiplexed within a VM. Preemption is cooperative with periodic safe points; an optional time‑slice can yield between bytecode instruction groups when `FUN_DEBUG` or tracing is enabled. -- Per‑VM GC: Stop‑the‑world, per‑VM. No global stop‑the‑world 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 CPU‑bound work and penalizes embedded hosts with existing thread pools. Our isolate + message‑passing 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 (stop‑the‑world, per‑VM). There is no global stop‑the‑world and no global lock; a GC pause in one VM does not affect others. diff --git a/docs/opcodes.md b/docs/opcodes.md deleted file mode 100644 index 15c742d..0000000 --- a/docs/opcodes.md +++ /dev/null @@ -1,250 +0,0 @@ -# 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>. - -## 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>. - -## 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. -- 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/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//*.c to see details and error handling. diff --git a/docs/repl.md b/docs/repl.md deleted file mode 100644 index e918406..0000000 --- a/docs/repl.md +++ /dev/null @@ -1,241 +0,0 @@ -# Fun REPL Guide - -This document describes the interactive Read–Eval–Print 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 to execute a file’s 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 - Save the current buffer to a file. - -- :load | :lo - 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 frame’s line (default 5). The current line is marked with `>`. - -- :disasm | :di [±N] - Disassemble around current frame’s instruction pointer (default 5 on each side). Shows index, opcode name, and operand. - -- :mdump | :md WHAT [offset [len]] [raw] [to ] - 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 `, 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 frame’s 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 VM’s 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. diff --git a/examples/arrays.fun b/examples/arrays.fun index c6fd414..ba28939 100755 --- a/examples/arrays.fun +++ b/examples/arrays.fun @@ -22,7 +22,6 @@ // Arrays basics arr = [1, 2, 3] -print(typeof(arr)) print(arr) // -> [1, 2, 3] print(arr[0] + arr[1]) // -> 3 diff --git a/examples/blocking/http_server.fun b/examples/extra/http_server.fun similarity index 100% rename from examples/blocking/http_server.fun rename to examples/extra/http_server.fun diff --git a/examples/features.fun b/examples/features.fun deleted file mode 100644 index 483219e..0000000 --- a/examples/features.fun +++ /dev/null @@ -1,240 +0,0 @@ -#!/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 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!") diff --git a/examples/maps.fun b/examples/maps.fun deleted file mode 100755 index c8425a4..0000000 --- a/examples/maps.fun +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env fun - -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2026 Johannes Findeisen - * 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 -*/ diff --git a/examples/rust_hello.fun b/examples/rust_hello.fun deleted file mode 100644 index 5cd23d2..0000000 --- a/examples/rust_hello.fun +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env fun - -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * 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()) diff --git a/examples/sqlited/README.md b/examples/sqlited/README.md deleted file mode 100644 index 4be87fc..0000000 --- a/examples/sqlited/README.md +++ /dev/null @@ -1,42 +0,0 @@ -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 repo’s 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 diff --git a/examples/sqlited/client.fun b/examples/sqlited/client.fun deleted file mode 100755 index d524bc9..0000000 --- a/examples/sqlited/client.fun +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/env fun - -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2026 Johannes Findeisen - * 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 - -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 -*/ diff --git a/examples/sqlited/protocol.md b/examples/sqlited/protocol.md deleted file mode 100644 index c7075c1..0000000 --- a/examples/sqlited/protocol.md +++ /dev/null @@ -1,35 +0,0 @@ -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. diff --git a/examples/sqlited/server.fun b/examples/sqlited/server.fun deleted file mode 100755 index 316aafc..0000000 --- a/examples/sqlited/server.fun +++ /dev/null @@ -1,282 +0,0 @@ -#!/usr/bin/env fun - -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2026 Johannes Findeisen - * 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 -// \t\t...\n -// \t\t...\n -// ... -// END\n -// - If exec/DDL: respond with -// OK \n -// - On error: respond with -// ERROR \n - -// Helper: CLI args via stdlib -#include -#include - -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() diff --git a/examples/types_overview.fun b/examples/types_overview.fun index ff02f12..859f64d 100755 --- a/examples/types_overview.fun +++ b/examples/types_overview.fun @@ -71,7 +71,7 @@ class Person(number age, string name) // typeof(instance) will call this and return it return "Class" -p = Person(33, "Jo") +Class p = Person(33, "Jo") print(typeof(p)) // -> "Class" // p = "nope" // Uncomment to see TypeError: expected Class diff --git a/play.fun b/play.fun index 3801b78..c8153e3 100755 --- a/play.fun +++ b/play.fun @@ -6,10 +6,7 @@ * - Executes each example as a subprocess */ -#include - #include -#include fun pick_fun_bin() // Prefer an explicit FUN_BIN override; otherwise rely on PATH @@ -52,42 +49,18 @@ fun main() "examples/cast_demo.fun", "examples/class_constructor.fun", "examples/classes_demo.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/crc32_example.fun", + "examples/crc32c_example.fun", + "examples/curl_download.fun", + "examples/curl_get_json.fun", + "examples/curl_post.fun", "examples/datetime_basic.fun", "examples/datetime_extended.fun", "examples/datetime_timer.fun", + "examples/debug_reporting.fun", "examples/echo_example.fun", - "examples/error/debug_reporting.fun", - "examples/error/exit_example.fun", - "examples/error/repl_on_error.fun", + "examples/exit_example.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", @@ -102,35 +75,59 @@ fun main() "examples/include_local_util.fun", "examples/include_namespace.fun", "examples/inheritance_demo.fun", - "examples/interactive/input_example.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/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/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" ] failures = [] diff --git a/src/bytecode.c b/src/bytecode.c index 58b62a8..80a4ce7 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -206,7 +206,6 @@ 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 "???"; } } diff --git a/src/bytecode.h b/src/bytecode.h index 0cb4908..c1b9434 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -263,9 +263,6 @@ 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 diff --git a/src/parser.c b/src/parser.c index 78da293..eae29c6 100644 --- a/src/parser.c +++ b/src/parser.c @@ -73,7 +73,6 @@ 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; @@ -786,13 +785,6 @@ 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; } @@ -2853,8 +2845,7 @@ 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, "array") == 0 + || strcmp(name, "Class") == 0 || strcmp(name, "float") == 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) { @@ -2862,9 +2853,8 @@ 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 = (strcmp(name, "class") == 0); - int is_float = (strcmp(name, "float") == 0); - int is_array = (strcmp(name, "array") == 0); + int is_class_tkn = (strcmp(name, "Class") == 0); + int is_float_tkn = (strcmp(name, "float") == 0); int is_byte = (strcmp(name, "byte") == 0); int is_u8 = (strcmp(name, "uint8") == 0) || is_byte; int is_u16 = (strcmp(name, "uint16") == 0); @@ -2875,12 +2865,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/array use special markers */ + /* declared type metadata: integers use decl_bits; string/boolean/nil/Class/float use special markers */ int decl_meta = decl_bits; if (is_string) { decl_meta = TYPE_META_STRING; @@ -2888,12 +2878,10 @@ 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) { + } else if (is_class_tkn) { decl_meta = TYPE_META_CLASS; - } else if (is_float) { + } else if (is_float_tkn) { decl_meta = TYPE_META_FLOAT; - } else if (is_array) { - decl_meta = TYPE_META_ARRAY; } free(name); @@ -3001,23 +2989,6 @@ 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 */ @@ -3106,7 +3077,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) { + } else if (is_class_tkn) { /* Class-typed variable defaults to Nil until assigned an instance */ ci = bytecode_add_constant(bc, make_nil()); } else if (is_boolean) { @@ -3378,23 +3349,6 @@ 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); diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml deleted file mode 100644 index a6c7500..0000000 --- a/src/rust/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[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" diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs deleted file mode 100644 index 12121ed..0000000 --- a/src/rust/src/lib.rs +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * 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 {} -} diff --git a/src/rust/src/vm/mod.rs b/src/rust/src/vm/mod.rs deleted file mode 100644 index aec43a4..0000000 --- a/src/rust/src/vm/mod.rs +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * 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 -} diff --git a/src/test_opcodes.c b/src/test_opcodes.c index db79283..c6c6e3a 100644 --- a/src/test_opcodes.c +++ b/src/test_opcodes.c @@ -46,29 +46,6 @@ 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; diff --git a/src/vm.c b/src/vm.c index 4c78db9..01864f8 100644 --- a/src/vm.c +++ b/src/vm.c @@ -235,38 +235,6 @@ 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 : ""); - 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 : ""); - vm->fp = -1; /* stop execution */ -} - /* Opcode case include index (vm_case_*.inc): - Core/stack/frame: @@ -466,28 +434,6 @@ 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; @@ -756,9 +702,6 @@ 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" diff --git a/src/vm.h b/src/vm.h index 0e1d528..13ad548 100644 --- a/src/vm.h +++ b/src/vm.h @@ -54,9 +54,6 @@ 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" }; @@ -125,12 +122,6 @@ 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 @@ -146,16 +137,4 @@ 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 diff --git a/src/vm/arithmetic/div.c b/src/vm/arithmetic/div.c index fa37615..8c821ef 100644 --- a/src/vm/arithmetic/div.c +++ b/src/vm/arithmetic/div.c @@ -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) { - vm_raise_error(vm, "division by zero"); - break; + fprintf(stderr, "Runtime error: division by zero\n"); + exit(1); } 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) { - vm_raise_error(vm, "division by zero"); - break; + fprintf(stderr, "Runtime error: division by zero\n"); + exit(1); } Value res = make_int(a.i / b.i); free_value(a); diff --git a/src/vm/rust/hello.c b/src/vm/rust/hello.c deleted file mode 100644 index 709d8c6..0000000 --- a/src/vm/rust/hello.c +++ /dev/null @@ -1,27 +0,0 @@ -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * 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; -}