Compare commits
17 commits
d5f4a51d12
...
6fd3497032
| Author | SHA1 | Date | |
|---|---|---|---|
| 6fd3497032 | |||
| da183962d4 | |||
| 149e135318 | |||
| 8aa150f8f7 | |||
| c164bb80ae | |||
| 08620d9359 | |||
| c95134669a | |||
| 592f6d2215 | |||
| 14c85c030f | |||
| 869d68b992 | |||
| 6648fa1e27 | |||
| 29db3bc63e | |||
| 97cfc74a01 | |||
| f073df040c | |||
| b513cf0463 | |||
| 8478c49240 | |||
| de50e11148 |
29 changed files with 2403 additions and 106 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -13,5 +13,7 @@ json.xml
|
|||
lib/*.so
|
||||
out/
|
||||
src/*.o
|
||||
src/rust/Cargo.lock
|
||||
src/rust/target
|
||||
*.swp
|
||||
tmp*
|
||||
|
|
|
|||
105
CMakeLists.txt
105
CMakeLists.txt
|
|
@ -1,5 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.10)
|
||||
project(fun VERSION 0.37.57 LANGUAGES C)
|
||||
project(fun VERSION 0.38.0 LANGUAGES C)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
|
@ -11,11 +11,114 @@ include(${CMAKE_SOURCE_DIR}/cmake/Dependencies.cmake)
|
|||
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/Extensions.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/Targets.cmake)
|
||||
|
||||
# --- Always show key build toggles ---
|
||||
# Normalize to ENABLED/DISABLED like the "Fun extension summary"
|
||||
if(FUN_DEBUG)
|
||||
set(_FUN_DEBUG_STATE "ENABLED")
|
||||
else()
|
||||
set(_FUN_DEBUG_STATE "DISABLED")
|
||||
endif()
|
||||
|
||||
if(FUN_WITH_RUST)
|
||||
set(_FUN_WITH_RUST_STATE "ENABLED")
|
||||
else()
|
||||
set(_FUN_WITH_RUST_STATE "DISABLED")
|
||||
endif()
|
||||
|
||||
message(STATUS "==== Fun build options ====")
|
||||
message(STATUS " FUN_DEBUG: ${_FUN_DEBUG_STATE}")
|
||||
message(STATUS " FUN_WITH_RUST: ${_FUN_WITH_RUST_STATE}")
|
||||
message(STATUS "===========================")
|
||||
|
||||
# Convenience aggregate target (like 'build' in Makefile)
|
||||
add_custom_target(build
|
||||
DEPENDS fun fun_test test_opcodes
|
||||
)
|
||||
|
||||
# --- Rust (Cargo) integration: optionally build and link a staticlib with opcode examples ---
|
||||
option(FUN_WITH_RUST "Build and link Rust opcode library" OFF)
|
||||
|
||||
if(FUN_WITH_RUST)
|
||||
find_program(CARGO_EXECUTABLE cargo)
|
||||
if(NOT CARGO_EXECUTABLE)
|
||||
message(FATAL_ERROR "FUN_WITH_RUST=ON but 'cargo' not found in PATH")
|
||||
endif()
|
||||
|
||||
set(RUST_CRATE_DIR ${CMAKE_SOURCE_DIR}/src/rust)
|
||||
|
||||
# Map CMake build type to Cargo profile and output path
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(RUST_PROFILE "debug")
|
||||
set(RUST_BUILD_ARGS)
|
||||
else()
|
||||
set(RUST_PROFILE "release")
|
||||
set(RUST_BUILD_ARGS --release)
|
||||
endif()
|
||||
|
||||
# Crate name as per Cargo.toml
|
||||
# Crate name on-disk replaces '-' with '_' in artifact names
|
||||
set(RUST_CRATE_NAME hello-c-world)
|
||||
string(REPLACE "-" "_" RUST_CRATE_BASENAME "${RUST_CRATE_NAME}")
|
||||
set(RUST_LIB_NAME lib${RUST_CRATE_BASENAME}.a)
|
||||
set(RUST_LIB_PATH ${RUST_CRATE_DIR}/target/${RUST_PROFILE}/${RUST_LIB_NAME})
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${RUST_LIB_PATH}
|
||||
COMMAND ${CARGO_EXECUTABLE} build ${RUST_BUILD_ARGS}
|
||||
WORKING_DIRECTORY ${RUST_CRATE_DIR}
|
||||
COMMENT "Building Rust static library (${RUST_PROFILE})"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
add_custom_target(rust_ops_build DEPENDS ${RUST_LIB_PATH})
|
||||
|
||||
add_library(fun_ops STATIC IMPORTED GLOBAL)
|
||||
set_target_properties(fun_ops PROPERTIES
|
||||
IMPORTED_LOCATION ${RUST_LIB_PATH}
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES C
|
||||
)
|
||||
add_dependencies(fun_ops rust_ops_build)
|
||||
|
||||
# Link Rust ops into the core so executables can call them
|
||||
if(TARGET fun_core)
|
||||
add_dependencies(fun_core rust_ops_build)
|
||||
target_link_libraries(fun_core PRIVATE fun_ops)
|
||||
target_compile_definitions(fun_core PRIVATE FUN_WITH_RUST)
|
||||
endif()
|
||||
|
||||
# Propagate define to test targets that might call Rust
|
||||
if(TARGET test_opcodes)
|
||||
add_dependencies(test_opcodes rust_ops_build)
|
||||
target_link_libraries(test_opcodes PRIVATE fun_ops)
|
||||
target_compile_definitions(test_opcodes PRIVATE FUN_WITH_RUST)
|
||||
endif()
|
||||
|
||||
if(TARGET fun)
|
||||
target_compile_definitions(fun PRIVATE FUN_WITH_RUST)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# --- Size optimization for final binaries (Release) ---
|
||||
# Enable function/data sectioning for better GC; safe for all configs.
|
||||
add_compile_options(-ffunction-sections -fdata-sections)
|
||||
|
||||
# Link-time garbage collection and stripping for the main executable in Release
|
||||
if(TARGET fun)
|
||||
# Garbage-collect unused sections; also strip symbols (-s) in Release
|
||||
target_link_options(fun PRIVATE
|
||||
$<$<CONFIG:Release>:-Wl,--gc-sections -s>
|
||||
)
|
||||
# Prefer enabling LTO/IPO for Release builds
|
||||
set_property(TARGET fun PROPERTY INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
|
||||
endif()
|
||||
|
||||
if(TARGET fun_core)
|
||||
# LTO/IPO for the core library helps the final link DCE more Rust/C glue
|
||||
set_property(TARGET fun_core PROPERTY INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
|
||||
# Ensure objects compiled with sectioning (added globally above) benefit from GC
|
||||
target_link_options(fun_core PRIVATE $<$<CONFIG:Release>:-Wl,--gc-sections>)
|
||||
endif()
|
||||
|
||||
# Convenience targets: repl, run, threads-demo, ops, examples
|
||||
set(FUN_RUN_SCRIPT "" CACHE STRING "Script to run with the 'run' target, e.g. -DFUN_RUN_SCRIPT=examples/strings_test.fun")
|
||||
|
||||
|
|
|
|||
49
README.md
49
README.md
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
## What is Fun?
|
||||
|
||||
Fun is a small, strict, and simple programming language that runs on a compact stack-based virtual machine. The C core is intentionally minimal; most functionality and standard libraries are implemented in Fun itself. The language emphasizes simplicity, consistency, and joy in coding.
|
||||
|
||||
Fun is an experiment, just for fun, but Fun works!
|
||||
|
||||
Fun is a highly strict programming language, but also highly simple. It looks like Python (My favorite language), but there are differences.
|
||||
|
|
@ -14,6 +16,8 @@ Fun is and will ever be 100% free under the terms of the [Apache-2.0 License](ht
|
|||
|
||||
- Simplicity
|
||||
- Consistency
|
||||
- Simple to extend
|
||||
- Hackable
|
||||
- Joy in coding
|
||||
- Fun!
|
||||
|
||||
|
|
@ -52,6 +56,7 @@ Fun is not about being the fastest or the most feature-rich. It’s about sharin
|
|||
- Respectful
|
||||
- Curious
|
||||
- Creative
|
||||
- Open for everyone
|
||||
|
||||
Like the name says: Fun Unites Nerds.
|
||||
|
||||
|
|
@ -77,47 +82,9 @@ Fun may not change the world — but it will make programming a little more fun.
|
|||
|
||||
### Lib (./lib/)
|
||||
|
||||
```
|
||||
arrays.fun
|
||||
crypt/
|
||||
crc32c.fun
|
||||
crc32.fun
|
||||
md5.fun
|
||||
md5_legacy.fun
|
||||
ripemd160.fun - Broken!
|
||||
sha1.fun
|
||||
sha256.fun
|
||||
sha384.fun
|
||||
sha512.fun
|
||||
encoding/
|
||||
base64.fun
|
||||
hello.fun
|
||||
hex.fun
|
||||
io/
|
||||
console.fun
|
||||
ini.fun
|
||||
json.fun
|
||||
pcsc2.fun
|
||||
pcsc.fun
|
||||
process.fun
|
||||
serial.fun
|
||||
socket.fun
|
||||
thread.fun
|
||||
xml.fun
|
||||
math.fun
|
||||
regex
|
||||
pcre2.fun
|
||||
regex.fun
|
||||
strings.fun
|
||||
ui/
|
||||
tk.fun
|
||||
utils/
|
||||
datetime.fun
|
||||
math.fun
|
||||
range.fun
|
||||
```
|
||||
See [./lib/](https://git.xw3.org/fun/fun/src/branch/main/lib) for what the standard library provides.
|
||||
|
||||
### Extensions (only Linux actually)
|
||||
### Optional extensions (build-time selectable / only testing this on Linux actually):
|
||||
|
||||
- [CGI](https://en.wikipedia.org/wiki/Common_Gateway_Interface) support builtin using [kcgi](https://kristaps.bsd.lv/kcgi/) (optional) ☐
|
||||
- [cURL](https://curl.se/) support builtin using [libcurl](https://curl.se/libcurl/) (optional) ☑
|
||||
|
|
@ -140,7 +107,7 @@ There are some libs written in Fun available in the [./lib/](https://git.xw3.org
|
|||
|
||||
This is actually a work in progress...
|
||||
|
||||
Current documentation is only found in the [Fun Handbook](https://git.xw3.org/fun/fun/src/branch/main/docs/handbook.md).
|
||||
Current documentation is only found in the [Fun Handbook](https://git.xw3.org/fun/fun/src/branch/main/docs/handbook.md) and the [specification](https://git.xw3.org/fun/fun/src/branch/main/spec/v0.3.md).
|
||||
|
||||
In the [./examples/](https://git.xw3.org/fun/fun/src/branch/main/examples) directory should be an example of most Fun features.
|
||||
|
||||
|
|
|
|||
283
docs/handbook.md
283
docs/handbook.md
|
|
@ -100,6 +100,8 @@ Pass all options as -DNAME=VALUE. The most relevant toggles are:
|
|||
- FUN_WITH_REPL=ON|OFF — enable the interactive REPL (default OFF)
|
||||
- FUN_WITH_SQLITE=ON|OFF — enable SQLite (sqlite3) support (default OFF)
|
||||
- FUN_WITH_TCLTK=ON|OFF — enable Tk (GUI via Tcl/Tk) support (default OFF)
|
||||
- FUN_WITH_INI=ON|OFF — enable INI (iniparser) support (default OFF)
|
||||
- FUN_WITH_NOTCURSES=ON|OFF — enable Notcurses TUI support (default OFF)
|
||||
|
||||
You can also set the default search path for the bundled stdlib with DEFAULT_LIB_DIR:
|
||||
|
||||
|
|
@ -321,20 +323,29 @@ Error handling and debugging:
|
|||
|
||||
Types:
|
||||
- number: signed integer (with helpers for unsigned behavior)
|
||||
- float: 64-bit IEEE-754 floating point
|
||||
- string: immutable bytes; len(s), join, split, substr, find
|
||||
- array: ordered list; len, push, apop, insert, remove, slice
|
||||
- map: associative dictionary typically keyed by strings
|
||||
- boolean: represented as 1 (true) or 0 (false); operators &&, ||, !
|
||||
- nil: absence of value
|
||||
|
||||
Numeric variants:
|
||||
- byte (uint8)
|
||||
- Fixed width ints: int8/16/32/64 and uint8/16/32/64
|
||||
|
||||
Control flow:
|
||||
- if/else, while; range helpers in utils.range
|
||||
- if/else, while; break, continue; range helpers in utils.range
|
||||
|
||||
Functions and classes:
|
||||
- Define a function: fun name(args) ...
|
||||
- Define a class: class Name(constructor params) with method definitions fun method(this, ...)
|
||||
- _construct acts as the constructor if present; methods use explicit this
|
||||
|
||||
Exceptions:
|
||||
- try { ... } catch (e) { ... } finally { ... }
|
||||
- Throwing/catching is defined by the spec; some runtimes may implement handling progressively. Examples: try_catch_finally.fun
|
||||
|
||||
Modules and includes:
|
||||
- #include <path/to/module.fun> for libs under FUN_LIB_DIR
|
||||
- #include "relative/path.fun" for local includes
|
||||
|
|
@ -357,6 +368,10 @@ Conversion and type:
|
|||
Math and random:
|
||||
- min(a,b), max(a,b), clamp(x, lo, hi), abs(x), pow(a,b), random_seed(seed), random_int(lo, hiExclusive)
|
||||
|
||||
Bitwise (uint32 operations):
|
||||
- band(a,b), bor(a,b), bxor(a,b), bnot(a)
|
||||
- shl(a, s), shr(a, s)
|
||||
|
||||
Regex (requires PCRE2 when enabled):
|
||||
- regex_match(text, pattern) -> 1/0
|
||||
- regex_search(text, pattern) -> map { match, start, end, groups }
|
||||
|
|
@ -491,20 +506,99 @@ Module lib/encoding/base64.fun: base64_encode(string), base64_decode(string)
|
|||
|
||||
### JSON (optional)
|
||||
|
||||
Build flag: -DFUN_WITH_JSON=ON; requires json-c. VM functions: json_parse, json_stringify, json_from_file, json_to_file. Stdlib class JSON wraps these with light ergonomics. Example: examples/json_showcase.fun.
|
||||
Build flag: -DFUN_WITH_JSON=ON; requires json-c.
|
||||
|
||||
VM API:
|
||||
- json_parse(text) -> value or nil
|
||||
- json_stringify(value, prettyFlag) -> string
|
||||
- json_from_file(path) -> value or nil
|
||||
- json_to_file(path, value, prettyFlag) -> 1/0
|
||||
|
||||
Stdlib wrapper:
|
||||
- class JSON (lib/io/json.fun) — convenience methods mirroring the VM API.
|
||||
|
||||
Example:
|
||||
```
|
||||
include <io/json.fun>
|
||||
|
||||
j = JSON()
|
||||
data = j.parse('{"name":"Fun","year":2026,"ok":true,"tags":["vm","lang"]}')
|
||||
print(typeof(data)) // map
|
||||
print(data["name"])
|
||||
|
||||
ok = j.to_file("./tmp/out.json", data, 1) // pretty = 1
|
||||
print("saved:", ok)
|
||||
```
|
||||
|
||||
Notes:
|
||||
- JSON types map to Fun types: object -> map, array -> array, string -> string, number -> number/float, true/false -> 1/0, null -> nil.
|
||||
- When writing, prettyFlag=1 enables pretty printing.
|
||||
|
||||
### CURL (optional)
|
||||
|
||||
Build flag: -DFUN_WITH_CURL=ON; requires libcurl. VM provides:
|
||||
- curl_get(url) -> string ("" on error)
|
||||
- curl_post(url, body) -> string ("" on error)
|
||||
Build flag: -DFUN_WITH_CURL=ON; requires libcurl.
|
||||
|
||||
VM API:
|
||||
- curl_get(url) -> string (empty string on error)
|
||||
- curl_post(url, body) -> string (empty string on error)
|
||||
- curl_download(url, path) -> 1/0
|
||||
|
||||
Examples: curl_get_json.fun, curl_post.fun, curl_download.fun
|
||||
Stdlib wrapper:
|
||||
- None (call built-ins directly). See examples in examples/extra/.
|
||||
|
||||
Example:
|
||||
```
|
||||
url = "https://httpbin.org/get"
|
||||
resp = curl_get(url)
|
||||
if (len(resp) == 0)
|
||||
print("GET failed")
|
||||
else
|
||||
print(substr(resp, 0, 60), "...")
|
||||
```
|
||||
|
||||
Examples:
|
||||
- curl_get_json.fun, curl_post.fun, curl_download.fun
|
||||
|
||||
### PCSC (optional)
|
||||
|
||||
Build flag: -DFUN_WITH_PCSC=ON; provides pcsc_* built-ins and a stdlib wrapper class PCSC. Example: pcsc_example.fun.
|
||||
Build flag: -DFUN_WITH_PCSC=ON; requires PC/SC (pcsc-lite).
|
||||
|
||||
VM API:
|
||||
- pcsc_establish() -> context id (>0) or 0
|
||||
- pcsc_list_readers(ctx) -> array of reader names or nil
|
||||
- pcsc_connect(ctx, readerName) -> handle id (>0) or 0
|
||||
- pcsc_disconnect(handle) -> 1/0
|
||||
- pcsc_transmit(handle, bytesArray) -> { data: array<byte>, sw1: number, sw2: number, code: number }
|
||||
|
||||
Stdlib wrapper:
|
||||
- class PCSC (lib/io/pcsc.fun) — higher-level helpers for listing readers, connecting, and APDU I/O.
|
||||
|
||||
Example:
|
||||
```
|
||||
include <io/pcsc.fun>
|
||||
|
||||
sc = PCSC()
|
||||
ctx = pcsc_establish()
|
||||
if (ctx == 0)
|
||||
print("PCSC not available")
|
||||
else
|
||||
readers = pcsc_list_readers(ctx)
|
||||
if (readers == nil || len(readers) == 0)
|
||||
print("No readers found")
|
||||
else
|
||||
h = pcsc_connect(ctx, readers[0])
|
||||
if (h)
|
||||
// Example APDU: GET RESPONSE (illustrative only)
|
||||
res = pcsc_transmit(h, [0x00, 0xC0, 0x00, 0x00, 0x00])
|
||||
print("SW:", res["sw1"], res["sw2"], "code:", res["code"])
|
||||
pcsc_disconnect(h)
|
||||
```
|
||||
|
||||
Examples:
|
||||
- examples/extra/pcsc_example.fun, examples/extra/pcsc_demo.fun
|
||||
|
||||
Notes:
|
||||
- Smart card operations depend on the reader, card, and drivers installed. Ensure pcscd/service is running.
|
||||
|
||||
### SQLite (optional)
|
||||
|
||||
|
|
@ -530,13 +624,174 @@ Example flow (examples/sqlite_example.fun):
|
|||
4) rows2 = sqlite_query(h, "SELECT count(*) AS cnt FROM tasks;")
|
||||
5) sqlite_close(h)
|
||||
|
||||
### INI (optional)
|
||||
|
||||
Build flag: -DFUN_WITH_INI=ON; requires iniparser (>= 4.2.6).
|
||||
|
||||
VM API:
|
||||
- ini_load(path) -> handle (>0) or 0 on error
|
||||
- ini_free(handle) -> 1/0
|
||||
- ini_get_string(handle, section, key, default) -> string
|
||||
- ini_get_int(handle, section, key, defaultNumber) -> number
|
||||
- ini_get_double(handle, section, key, defaultFloat) -> float
|
||||
- ini_get_bool(handle, section, key, defaultBool) -> 1/0
|
||||
- ini_set(handle, section, key, valueString) -> 1/0
|
||||
- ini_unset(handle, section, key) -> 1/0
|
||||
- ini_save(handle, path) -> 1/0
|
||||
|
||||
Notes:
|
||||
- Section/key may be looked up flexibly ("section:key" and "section.key").
|
||||
- Always free handles with ini_free when done.
|
||||
|
||||
Example:
|
||||
```
|
||||
h = ini_load("./examples/data/example.ini")
|
||||
if (h == 0)
|
||||
print("Failed to load INI")
|
||||
else
|
||||
host = ini_get_string(h, "server", "host", "localhost")
|
||||
port = ini_get_int(h, "server", "port", 8080)
|
||||
print("server:", host, port)
|
||||
ok = ini_set(h, "server", "host", "127.0.0.1")
|
||||
if (ok)
|
||||
ini_save(h, "./tmp/updated.ini")
|
||||
ini_free(h)
|
||||
```
|
||||
|
||||
### Notcurses (optional)
|
||||
|
||||
Build flag: -DFUN_WITH_NOTCURSES=ON; requires notcurses (pkg-config: notcurses or notcurses-core).
|
||||
|
||||
VM API:
|
||||
- nc_init() -> 1/0 (initialize TUI; call once)
|
||||
- nc_draw_text(x, y, text) -> 0 on success, -1 on error
|
||||
- nc_clear() -> 0 on success, -1 on error
|
||||
- nc_getch(timeout_ms) -> key code (int), -1 if timeout/no input
|
||||
- nc_shutdown() -> 1/0 (restore terminal)
|
||||
|
||||
Example:
|
||||
```
|
||||
if (nc_init())
|
||||
nc_draw_text(2, 1, "Fun + Notcurses")
|
||||
print("Press any key...")
|
||||
k = nc_getch(0) // 0 = blocking
|
||||
nc_shutdown()
|
||||
else
|
||||
print("Notcurses init failed")
|
||||
```
|
||||
|
||||
### XML (optional)
|
||||
|
||||
Build flag: -DFUN_WITH_XML2=ON; requires libxml2.
|
||||
|
||||
VM API:
|
||||
- xml_parse(text) -> doc_handle (>0) or 0 on error
|
||||
- xml_root(doc_handle) -> node_handle (>0) or 0 if missing
|
||||
- xml_name(node_handle) -> string
|
||||
- xml_text(node_handle) -> string
|
||||
|
||||
Stdlib wrapper:
|
||||
- class XML (lib/io/xml.fun)
|
||||
- parse(text): int
|
||||
- from_file(path): int
|
||||
- root(doc): int
|
||||
- name(node): string
|
||||
- text(node): string
|
||||
|
||||
Example:
|
||||
```
|
||||
include <io/xml.fun>
|
||||
|
||||
xml = XML()
|
||||
doc = xml.from_file("./examples/data/example.xml")
|
||||
if (doc == 0)
|
||||
print("Failed to load XML file")
|
||||
else
|
||||
root = xml.root(doc)
|
||||
print(xml.name(root))
|
||||
print(xml.text(root))
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Handles are integers managed by the VM; nodes belong to their document.
|
||||
|
||||
### libSQL (optional)
|
||||
|
||||
Build flag: -DFUN_WITH_LIBSQL=ON; requires libSQL client (compatible with sqlite3 C API).
|
||||
|
||||
VM API:
|
||||
- libsql_open(path_or_url) -> handle (>0) or 0 on error
|
||||
- libsql_close(handle) -> nil
|
||||
- libsql_exec(handle, sql) -> rc (0 on success)
|
||||
- libsql_query(handle, sql) -> array of row maps
|
||||
|
||||
Example flow:
|
||||
1) h = libsql_open("./database.sqlite")
|
||||
2) rows = libsql_query(h, "SELECT id, title FROM tasks;")
|
||||
3) rc = libsql_exec(h, "INSERT INTO tasks (title) VALUES ('Try libSQL');")
|
||||
4) libsql_close(h)
|
||||
|
||||
### PCRE2 / Regex (optional)
|
||||
|
||||
Build flag: -DFUN_WITH_PCRE2=ON; requires PCRE2 (8-bit API).
|
||||
|
||||
VM API:
|
||||
- regex_match(text, pattern) -> 1/0
|
||||
- regex_search(text, pattern) -> map { match, start, end, groups }
|
||||
- regex_replace(text, pattern, repl) -> string
|
||||
|
||||
Stdlib wrapper:
|
||||
- class Regex (lib/regex.fun) providing match/search/replace helpers.
|
||||
|
||||
Examples: regex_demo.fun, regex_procedural.fun
|
||||
|
||||
Notes:
|
||||
- Patterns use PCRE2 syntax.
|
||||
|
||||
### Tcl/Tk GUI (optional)
|
||||
|
||||
Build flag: -DFUN_WITH_TCLTK=ON; requires Tcl/Tk (8.6+).
|
||||
|
||||
VM API:
|
||||
- tk_title(title) -> rc
|
||||
- tk_label(id, text) -> rc
|
||||
- tk_button(id, text) -> rc
|
||||
- tk_pack(id) -> rc
|
||||
- tk_loop() -> nil (enters event loop)
|
||||
|
||||
Stdlib wrapper:
|
||||
- class TK (lib/ui/tk.fun) mirrors the VM API.
|
||||
|
||||
Example:
|
||||
```
|
||||
include <ui/tk.fun>
|
||||
|
||||
tk = TK()
|
||||
tk.title("Fun + Tk GUI")
|
||||
tk.label("hello", "Hello, world!")
|
||||
tk.pack("hello")
|
||||
tk.button("ok", "OK")
|
||||
tk.pack("ok")
|
||||
tk.loop()
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Ensure Tcl/Tk runtime libraries are available on your system.
|
||||
|
||||
### REPL (optional)
|
||||
|
||||
Build flag: -DFUN_WITH_REPL=ON.
|
||||
|
||||
Description:
|
||||
- Enables the interactive 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/<name>.fun
|
||||
FUN_LIB_DIR="$(pwd)/lib" ./build/fun examples/<path>.fun
|
||||
|
||||
Highlights (not exhaustive):
|
||||
- arrays.fun, arrays_advanced.fun, arrays_iter.fun — array operations
|
||||
|
|
@ -555,14 +810,14 @@ Highlights (not exhaustive):
|
|||
- have_fun.fun — quick sanity check
|
||||
- if_else_test.fun — branching
|
||||
- include_lib.fun, include_local.fun, include_namespace.fun — includes and namespacing
|
||||
- input_example.fun — console input
|
||||
- json_showcase.fun — JSON usage
|
||||
- curl_get_json.fun, curl_post.fun, curl_download.fun — HTTP via CURL
|
||||
- interactive/input_example.fun — console input
|
||||
- extra/json_showcase.fun — JSON usage
|
||||
- extra/curl_get_json.fun, extra/curl_post.fun, extra/curl_download.fun — HTTP via CURL
|
||||
- loops_break_continue.fun, nested_loops.fun, while_test.fun — loops
|
||||
- md5_demo.fun, sha1_demo.fun, sha256_demo.fun, sha256_str_demo.fun, sha384_example.fun, sha512_demo.fun, sha512_str_demo.fun — hashing
|
||||
- crypto/md5_demo.fun, crypto/sha1_demo.fun, crypto/sha256_demo.fun, crypto/sha256_str_demo.fun, crypto/sha384_example.fun, crypto/sha512_demo.fun, crypto/sha512_str_demo.fun — hashing
|
||||
- objects_basic.fun, objects_more.fun — map/object patterns
|
||||
- os_env.fun — environment variables
|
||||
- pcsc_example.fun — smart card demo
|
||||
- extra/pcsc_example.fun — smart card demo
|
||||
- process_example.fun — running external commands
|
||||
- regex_demo.fun, regex_procedural.fun — regex usage
|
||||
- stdlib_showcase.fun — tour through stdlib
|
||||
|
|
@ -574,7 +829,7 @@ Highlights (not exhaustive):
|
|||
- type_safety.fun, type_safety_fails.fun — type safety
|
||||
- types_integers.fun, signed_ints.fun, uint_types.fun — integers
|
||||
- unix_socket_echo.fun — UNIX domain sockets
|
||||
- sqlite_example.fun — SQLite usage
|
||||
- extra/sqlite_example.fun — SQLite usage
|
||||
|
||||
Notes:
|
||||
- Some examples rely on optional features (JSON, CURL, PCSC, SQLite) and degrade gracefully when disabled.
|
||||
|
|
|
|||
332
docs/internals.md
Normal file
332
docs/internals.md
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
# Fun Internals
|
||||
|
||||
This document describes how Fun is implemented under the hood: the bytecode format, the virtual machine (VM) execution model, how opcodes are organized, error handling and debugging, and how the parser translates source code into bytecode.
|
||||
|
||||
It complements the Handbook and Spec by focusing on implementation details in the C core, with pointers to the relevant source files.
|
||||
|
||||
- VM implementation: src/vm.c (and many small opcode handlers in src/vm/...)
|
||||
- Parser and compiler: src/parser.c
|
||||
- Bytecode and values: src/bytecode.h, src/value.h
|
||||
- VM API/structure: src/vm.h
|
||||
|
||||
## 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/<domain>/.
|
||||
|
||||
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__: <path>:<line>
|
||||
…included lines…
|
||||
// __include_end__: <path>:<line>
|
||||
|
||||
- map_expanded_line_to_include(path, line, out_path, out_line) uses these markers to map a line in the expanded text back to the original file:line that contributed it.
|
||||
|
||||
When stderr output is produced by the VM, fun_vm_vfprintf annotates messages with the mapped file and line if possible.
|
||||
|
||||
## Parser and compiler pipeline (src/parser.c)
|
||||
|
||||
The parser compiles directly to bytecode in a single pass with localized backpatching. The code is organized by precedence levels and statement/block parsing.
|
||||
|
||||
Key components:
|
||||
|
||||
- Namespaces and aliases: ns_aliases_scan detects alias directives at the top of the source (for module resolution) before parsing proper.
|
||||
- Symbol tables: sym_index for globals; LocalEnv tracks locals in the current function scope. local_find/local_add manage local slots.
|
||||
- Expression parser: a classic 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/<domain> when enabled by CMake options
|
||||
|
||||
Feature flags (CMake):
|
||||
- Many subsystems are guarded by -DFUN_WITH_… options (JSON, PCRE2, CURL, PCSC, SQLITE, LIBSQL, XML2, TCLTK, NOTCURSES, INI, REPL). See CMake options in the Handbook.
|
||||
|
||||
## Debugging and development tips
|
||||
|
||||
- Use the --trace flag (or VM.trace_enabled) to inspect execution 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.
|
||||
250
docs/opcodes.md
Normal file
250
docs/opcodes.md
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
# Fun VM Opcodes Overview
|
||||
|
||||
This document provides an overview of the available VM opcodes implemented under src/vm and its subdirectories. Opcodes are grouped by module. For each opcode, we briefly note its purpose and, where available, the expected stack arguments and return value.
|
||||
|
||||
## Notes
|
||||
|
||||
- Arguments are pushed on the stack before the opcode executes; results are pushed back on the stack.
|
||||
- Types are indicative; many opcodes accept integers and/or strings depending on context. See code for exact type checks and error behavior.
|
||||
|
||||
## Core
|
||||
|
||||
- OP_NOP: No operation.
|
||||
- OP_HALT: Stop the VM.
|
||||
- OP_EXIT: Terminate script with an exit code; pops int exitCode.
|
||||
- OP_LOAD_CONST: Push a constant (by index embedded in bytecode).
|
||||
- OP_LOAD_LOCAL: Push local variable value (by slot index).
|
||||
- OP_STORE_LOCAL: Pop value and store into local (by slot index).
|
||||
- OP_LOAD_GLOBAL: Push global variable value (by slot index/name id).
|
||||
- OP_STORE_GLOBAL: Pop value and store into global (by slot index/name id).
|
||||
- OP_CALL: Call a function; pops N args and a callable; pushes return value.
|
||||
- OP_RETURN: Return from current function; optionally pops return value and pushes it to caller frame.
|
||||
- OP_POP: Pop and discard the top of the stack.
|
||||
- OP_DUP: Duplicate the top stack value.
|
||||
- OP_SWAP: Swap the top two stack values.
|
||||
- OP_JUMP: Unconditional jump to bytecode offset.
|
||||
- OP_JUMP_IF_FALSE: Pop condition; jump if falsey.
|
||||
- OP_THROW: Pop error and raise; unwinds to nearest try.
|
||||
- OP_TRY_PUSH: Begin try handler (internal to exception handling).
|
||||
- OP_TRY_POP: End try handler (internal to exception handling).
|
||||
|
||||
## Arithmetic
|
||||
|
||||
- OP_ADD: Add two numbers or concatenate two strings; pops b, a; pushes a+b or a..b.
|
||||
- OP_SUB: Integer subtraction; pops b, a; pushes a-b.
|
||||
- OP_MUL: Integer multiplication; pops b, a; pushes a*b.
|
||||
- OP_DIV: Integer division; pops b, a; pushes a/b.
|
||||
- OP_MOD: Integer modulus; pops b, a; pushes a%b. (math/mod.c)
|
||||
|
||||
## Logic and Comparison
|
||||
|
||||
- OP_NOT: Logical NOT; pops v; pushes bool.
|
||||
- OP_AND: Logical AND; pops b, a; pushes bool.
|
||||
- OP_OR: Logical OR; pops b, a; pushes bool.
|
||||
- OP_EQ: Equality; pops b, a; pushes bool.
|
||||
- OP_NEQ: Inequality; pops b, a; pushes bool.
|
||||
- OP_LT: Less than; pops b, a; pushes bool.
|
||||
- OP_LTE: Less than or equal; pops b, a; pushes bool.
|
||||
- OP_GT: Greater than; pops b, a; pushes bool.
|
||||
- OP_GTE: Greater than or equal; pops b, a; pushes bool.
|
||||
|
||||
## Arrays and Indexing
|
||||
|
||||
- OP_MAKE_ARRAY: Create array from N values; pops N values (count encoded in bytecode); pushes array.
|
||||
- OP_INDEX_GET: Indexing into array or map; pops index/key, container; pushes value or Nil on missing.
|
||||
- OP_INDEX_SET: Assign into array or map; pops value, index/key, container; pushes 1/0 for success (see code for exact behavior).
|
||||
- OP_INSERT: Insert value at index in array; pops value, index, array; pushes 1/0.
|
||||
- OP_REMOVE: Remove element at index in array; pops index, array; pushes removed value or Nil.
|
||||
- OP_PUSH: Append element to end of array; pops value, array; pushes 1/0.
|
||||
- OP_APOP: Pop last element from array; pops array; pushes removed value or Nil.
|
||||
- OP_SLICE: Slice array; pops end, start, array; pushes new array slice.
|
||||
- OP_INDEX_OF: Find index of value in array; pops value, array; pushes index or -1.
|
||||
- OP_CONTAINS: Membership test for arrays; pops value, array; pushes bool.
|
||||
- OP_JOIN: Join array of strings; pops separator, array; pushes string.
|
||||
- OP_CLEAR: Clear all elements in array; pops array; pushes 1/0.
|
||||
- OP_ENUMERATE: Produce array of [index, value] pairs from array; pops array; pushes array of pairs.
|
||||
|
||||
## Maps
|
||||
|
||||
- OP_MAKE_MAP: Create a map from N key-value pairs; pops 2*N values (val, key ...); pushes map.
|
||||
- OP_HAS_KEY: Check if key exists; pops key, map; pushes bool.
|
||||
- OP_KEYS: Return array of keys; pops map; pushes array.
|
||||
- OP_VALUES: Return array of values; pops map; pushes array.
|
||||
|
||||
## Strings and Regex
|
||||
|
||||
- OP_SUBSTR: Substring; pops len, start, string; pushes substring.
|
||||
- OP_FIND: Find substring; pops needle, haystack; pushes index or -1.
|
||||
- OP_SPLIT: Split string; pops separator, string; pushes array of strings.
|
||||
- OP_REGEX_MATCH: Regex full match; pops pattern, string; pushes bool or captures (see strings/regex_match.c).
|
||||
- OP_REGEX_SEARCH: Regex search/find; pops pattern, string; pushes match details or -1.
|
||||
- OP_REGEX_REPLACE: Regex replace; pops replacement, pattern, string; pushes new string.
|
||||
|
||||
## Bitwise (uint32)
|
||||
|
||||
- OP_BAND: Bitwise AND; pops b, a; pushes a & b.
|
||||
- OP_BOR: Bitwise OR; pops b, a; pushes a | b.
|
||||
- OP_BXOR: Bitwise XOR; pops b, a; pushes a ^ b.
|
||||
- OP_BNOT: Bitwise NOT; pops a; pushes ~a.
|
||||
- OP_SHL: Logical left shift; pops shift, value; pushes value << shift.
|
||||
- OP_SHR: Logical right shift; pops shift, value; pushes value >> shift.
|
||||
- OP_ROTL: Rotate left; pops shift, value; pushes rotl(value, shift).
|
||||
- OP_ROTR: Rotate right; pops shift, value; pushes rotr(value, shift).
|
||||
|
||||
## Math
|
||||
|
||||
- OP_ABS: Absolute value; pops x; pushes |x|.
|
||||
- OP_CEIL: Ceiling; pops x; pushes ceil(x).
|
||||
- OP_FLOOR: Floor; pops x; pushes floor(x).
|
||||
- OP_ROUND: Round; pops x; pushes round(x).
|
||||
- OP_TRUNC: Truncate; pops x; pushes trunc(x).
|
||||
- OP_SIN: Sine; pops x; pushes sin(x).
|
||||
- OP_COS: Cosine; pops x; pushes cos(x).
|
||||
- OP_TAN: Tangent; pops x; pushes tan(x).
|
||||
- OP_SQRT: Square root; pops x; pushes sqrt(x) (float).
|
||||
- OP_ISQRT: Integer square root; pops x; pushes isqrt(x) (int).
|
||||
- OP_LOG: Natural logarithm; pops x; pushes log(x).
|
||||
- OP_LOG10: Base-10 logarithm; pops x; pushes log10(x).
|
||||
- OP_EXP: Exponential; pops x; pushes exp(x).
|
||||
- OP_MAX: Max of two ints; pops b, a; pushes max(a, b).
|
||||
- OP_MIN: Min of two ints; pops b, a; pushes min(a, b).
|
||||
- OP_FMAX: Max of two floats; pops b, a; pushes max(a, b).
|
||||
- OP_FMIN: Min of two floats; pops b, a; pushes min(a, b).
|
||||
- OP_CLAMP: Clamp; pops hi, lo, x; pushes clamp(x, lo, hi).
|
||||
- OP_SIGN: Sign; pops x; pushes -1, 0, or 1.
|
||||
- OP_POW: Power; pops exp, base; pushes base^exp.
|
||||
- OP_GCD: Greatest common divisor; pops b, a; pushes gcd(a, b).
|
||||
- OP_LCM: Least common multiple; pops b, a; pushes lcm(a, b).
|
||||
- OP_RANDOM_SEED: Seed RNG; pops int seed; pushes 1/0.
|
||||
- OP_RANDOM_INT: Random integer in [lo, hi]; pops hi, lo; pushes int.
|
||||
|
||||
## I/O
|
||||
|
||||
- OP_READ_FILE: Read file contents; pops path:string; pushes data:string or Nil.
|
||||
- OP_WRITE_FILE: Write data to file; pops data:string, path:string; pushes 1/0.
|
||||
- OP_INPUT_LINE: Read a line from stdin; optional prompt on stack; pushes string (may be empty) or Nil.
|
||||
|
||||
## JSON
|
||||
|
||||
- OP_JSON_PARSE: Parse JSON text; pops text:string; pushes value (map/array/number/string/bool/Nil) or Nil on error.
|
||||
- OP_JSON_STRINGIFY: Stringify a value; pops pretty:int(0/1), any; pushes json:string.
|
||||
- OP_JSON_TO_FILE: Write value as JSON to file; pops pretty:int(0/1), any, path; pushes 1/0.
|
||||
- OP_JSON_FROM_FILE: Read and parse JSON file; pops path; pushes value or Nil.
|
||||
|
||||
## XML
|
||||
|
||||
- OP_XML_PARSE: Parse XML text; pops text:string; pushes doc handle (>0) or 0 on error.
|
||||
- OP_XML_ROOT: Get root node; pops doc handle; pushes node handle (>0) or 0.
|
||||
- OP_XML_NAME: Get node name; pops node handle; pushes string.
|
||||
- OP_XML_TEXT: Get node text (concatenated); pops node handle; pushes string.
|
||||
|
||||
## INI
|
||||
|
||||
- OP_INI_LOAD: Load INI; pops path:string; pushes handle (>0) or 0.
|
||||
- OP_INI_FREE: Free INI handle; pops handle; pushes 1/0.
|
||||
- OP_INI_GET_STRING: Get string; pops default, key, section, handle; pushes string.
|
||||
- OP_INI_GET_INT: Get integer; pops default, key, section, handle; pushes int.
|
||||
- OP_INI_GET_DOUBLE: Get double; pops default, key, section, handle; pushes float.
|
||||
- OP_INI_GET_BOOL: Get bool; pops default, key, section, handle; pushes 1/0.
|
||||
- OP_INI_SET: Set value; pops value, key, section, handle; pushes 1/0.
|
||||
- OP_INI_UNSET: Remove key; pops key, section, handle; pushes 1/0.
|
||||
- OP_INI_SAVE: Save to file; pops path, handle; pushes 1/0.
|
||||
|
||||
## SQLite
|
||||
|
||||
- OP_SQLITE_OPEN: Open database; pops path:string; pushes handle (>0) or 0.
|
||||
- OP_SQLITE_CLOSE: Close database; pops handle; pushes Nil.
|
||||
- OP_SQLITE_EXEC: Execute statement; pops handle:int, sql:string; pushes rc:int (0=OK).
|
||||
- OP_SQLITE_QUERY: Run query; pops handle:int, sql:string; pushes array<map<string,any>>.
|
||||
|
||||
## libSQL
|
||||
|
||||
- OP_LIBSQL_OPEN: Open database (url or path); pops string; pushes handle (>0) or 0.
|
||||
- OP_LIBSQL_CLOSE: Close database; pops handle; pushes Nil.
|
||||
- OP_LIBSQL_EXEC: Execute statement; pops handle:int, sql:string; pushes rc:int (0=OK).
|
||||
- OP_LIBSQL_QUERY: Run query; pops handle:int, sql:string; pushes array<map<string,any>>.
|
||||
|
||||
## OS, Time, Processes, Threads, Sockets, Serial
|
||||
|
||||
- OP_ENV: Get environment variable; pops key:string; pushes value:string or Nil.
|
||||
- OP_ENV_ALL: Get all environment variables; pushes map<string,string>.
|
||||
- OP_FUN_VERSION: Push Fun version string; no args.
|
||||
- OP_SLEEP_MS: Sleep; pops ms:int; pushes Nil.
|
||||
- OP_TIME_NOW_MS: Current wall-clock time in ms since epoch; pushes int.
|
||||
- OP_CLOCK_MONO_MS: Monotonic clock in ms; pushes int.
|
||||
- OP_DATE_FORMAT: Format epoch ms with strftime; pops format:string, ms:int; pushes string.
|
||||
- OP_RANDOM_NUMBER: Random float in [0,1); optional lower/upper bound handling; see os/random_number.c.
|
||||
- OP_PROC_SYSTEM: Run command via system(); pops cmd:string; pushes exit code:int.
|
||||
- OP_PROC_RUN: Run command and capture stdout/stderr; pops cmd:string; pushes map or string (see os/proc_run.c).
|
||||
- OP_LIST_DIR / OP_OS_LIST_DIR: List directory; pops path; pushes array of file names.
|
||||
- Threads:
|
||||
- OP_THREAD_SPAWN: Spawn a thread to run a function; pops args (array or scalar), fn; pushes thread id.
|
||||
- OP_THREAD_JOIN: Join thread; pops thread id; pushes thread result.
|
||||
- Sockets (TCP/Unix):
|
||||
- OP_SOCK_TCP_LISTEN: Listen on TCP port; pops backlog:int, port:int; pushes fd:int or -1.
|
||||
- OP_SOCK_TCP_ACCEPT: Accept a connection; pops fd:int; pushes client fd:int or -1.
|
||||
- OP_SOCK_TCP_CONNECT: Connect to host:port; pops port:int, host:string; pushes fd:int or -1.
|
||||
- OP_SOCK_UNIX_LISTEN: Listen on Unix domain socket; pops backlog:int, path:string; pushes fd:int or -1.
|
||||
- OP_SOCK_UNIX_CONNECT: Connect to Unix domain socket; pops path:string; pushes fd:int or -1.
|
||||
- OP_SOCK_SEND: Send bytes; pops data:string, fd:int; pushes bytesSent:int or -1.
|
||||
- OP_SOCK_RECV: Receive bytes; pops max:int, fd:int; pushes data:string or Nil.
|
||||
- OP_SOCK_CLOSE: Close socket; pops fd:int; pushes 1/0.
|
||||
- Serial (TTY):
|
||||
- OP_SERIAL_OPEN: Open serial port; pops baud:int, path:string; pushes fd:int or -1.
|
||||
- OP_SERIAL_CONFIG: Configure port; pops flow_ctrl, stop_bits, parity, data_bits, fd; pushes 1/0.
|
||||
- OP_SERIAL_SEND: Send bytes; pops data:string, fd:int; pushes bytesSent:int or -1.
|
||||
- OP_SERIAL_RECV: Receive bytes; pops max:int, fd:int; pushes data:string or Nil.
|
||||
- OP_SERIAL_CLOSE: Close; pops fd:int; pushes 1/0.
|
||||
|
||||
## Curl (HTTP)
|
||||
|
||||
- OP_CURL_GET: HTTP GET; pops url:string; pushes body:string or Nil.
|
||||
- OP_CURL_POST: HTTP POST; pops body:string, url:string; pushes response:string or Nil.
|
||||
- OP_CURL_DOWNLOAD: Download URL to file; pops path:string, url:string; pushes 1/0.
|
||||
|
||||
## PCRE2 (Regex)
|
||||
|
||||
- OP_PCRE2_TEST: Test pattern; pops flags:int, text:string, pattern:string; pushes 1/0.
|
||||
- OP_PCRE2_MATCH: Match pattern; pops flags:int, text:string, pattern:string; pushes array/map with groups or 0.
|
||||
- OP_PCRE2_FINDALL: Find all matches; pops flags:int, text:string, pattern:string; pushes array of matches.
|
||||
|
||||
## PC/SC (Smart cards)
|
||||
|
||||
- OP_PCSC_ESTABLISH: Establish context; pushes ctx handle (>0) or 0.
|
||||
- OP_PCSC_LIST_READERS: List readers; pops scope/id; pushes array of strings.
|
||||
- OP_PCSC_CONNECT: Connect to reader; pops reader:string, ctx; pushes handle or 0.
|
||||
- OP_PCSC_TRANSMIT: Send APDU; pops apdu:array<byte>/string, handle; pushes response bytes or map incl. SW.
|
||||
- OP_PCSC_DISCONNECT: Disconnect; pops handle; pushes 1/0.
|
||||
- OP_PCSC_RELEASE: Release context; pops scope/id; pushes 1/0.
|
||||
|
||||
## Notcurses (Terminal UI)
|
||||
|
||||
- OP_NC_INIT: Initialize notcurses; pushes handle or 0.
|
||||
- OP_NC_SHUTDOWN: Shutdown; no args; pushes 1/0.
|
||||
- OP_NC_CLEAR: Clear screen; pushes 1/0.
|
||||
- OP_NC_DRAW_TEXT: Draw text at (x,y); pops text, x, y; pushes 1/0.
|
||||
- OP_NC_GETCH: Get key with timeout; pops timeout_ms:int; pushes int key or -1.
|
||||
|
||||
## SQLite-compatible lib (libSQL)
|
||||
|
||||
- See “libSQL” section above; identical opcode set with different backend.
|
||||
|
||||
## TK (Tcl/Tk UI)
|
||||
|
||||
- OP_TK_EVAL: Evaluate Tcl code; pops text:string; pushes result string or error.
|
||||
- OP_TK_LABEL: Create/update label; pops text, id; pushes 1/0.
|
||||
- OP_TK_BUTTON: Create/update button; pops text, id; pushes 1/0.
|
||||
- OP_TK_PACK: Pack widget; pops id; pushes 1/0.
|
||||
- OP_TK_BIND: Bind event; pops command, event, id; pushes 1/0.
|
||||
- OP_TK_WM_TITLE: Set window title; pops title; pushes 1/0.
|
||||
- OP_TK_LOOP: Enter main event loop; no args; blocks until exit.
|
||||
- OP_TK_RESULT: Retrieve last Tcl result; pushes string.
|
||||
|
||||
## Miscellaneous
|
||||
|
||||
- OP_KEYS / OP_VALUES: Map utilities (see Maps).
|
||||
- Additional opcodes may exist for modules under src/vm that are stubbed or platform-dependent. Refer to the corresponding C file for precise semantics and edge cases.
|
||||
|
||||
## How to explore
|
||||
|
||||
- Each opcode implementation lives in its own file and begins with a comment documenting its behavior and stack contract. Grep for “case OP_” or open files under src/vm/<module>/*.c to see details and error handling.
|
||||
241
docs/repl.md
Normal file
241
docs/repl.md
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
# 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 <file> 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 <file>
|
||||
Save the current buffer to a file.
|
||||
|
||||
- :load | :lo <file>
|
||||
Load a file into the buffer (does not run). Use :run or a blank line to execute afterward.
|
||||
|
||||
- :paste | :pa [run]
|
||||
Enter paste mode to insert multiple lines verbatim. Finish with a single dot line: `.`. If the optional argument `run` (or `exec`) is given, the REPL will run the pasted buffer immediately.
|
||||
|
||||
- :history | :hi [N]
|
||||
Show the last N lines of persistent history (default 50).
|
||||
|
||||
- :time | :ti on|off|toggle
|
||||
Toggle/enable/disable timing for subsequent runs.
|
||||
|
||||
- :env | :en [NAME[=VALUE]]
|
||||
Get or set an environment variable. With NAME only, prints NAME=value. With NAME=VALUE, sets the variable for the current process.
|
||||
|
||||
- :backtrace | :bt | :ba
|
||||
Show a backtrace of VM frames (most recent first), including function name, source file, IP, and line.
|
||||
|
||||
- :frame | :fr N
|
||||
Select a frame N (0..top) to target with :locals, :list, :disasm and value inspections. By default, the top frame is used.
|
||||
|
||||
- :list | :li [±K]
|
||||
Show K lines of source around the current 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 <file>]
|
||||
Dump a VM memory region. WHAT is one of: code | stack | globals | consts. Offset and length are optional; if omitted, a sensible default (up to 256 bytes) is used. With `raw`, write binary bytes. With `to <file>`, write output to a file; otherwise print to stdout as a formatted hexdump.
|
||||
|
||||
- :stack | :st [N]
|
||||
Show top N (or all) stack values, stringified.
|
||||
|
||||
- :top | :to
|
||||
Show the value at the top of the VM stack.
|
||||
|
||||
- :locals | :lc [FRAME]
|
||||
Show non-nil locals for the selected frame (or the provided frame index).
|
||||
|
||||
- :printv | :pv WHAT
|
||||
Print a specific value: `local[i]`, `stack[i]`, or `global[i]`.
|
||||
|
||||
- :break | :br [file:]line
|
||||
Set a breakpoint. If file is omitted, the current 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.
|
||||
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
// Arrays basics
|
||||
arr = [1, 2, 3]
|
||||
print(typeof(arr))
|
||||
print(arr) // -> [1, 2, 3]
|
||||
print(arr[0] + arr[1]) // -> 3
|
||||
|
||||
|
|
|
|||
240
examples/features.fun
Normal file
240
examples/features.fun
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
#!/usr/bin/env fun
|
||||
// features.fun - Showcase of Fun's neat features
|
||||
// Demonstrates modern language capabilities in pure Fun
|
||||
|
||||
print("=== Fun Language Feature Showcase ===")
|
||||
print("")
|
||||
|
||||
// ============================================
|
||||
// 1. Type System & Type Safety
|
||||
// ============================================
|
||||
print("1. Strong Type System:")
|
||||
string name = "Fun Language"
|
||||
float version = 0.3
|
||||
boolean is_awesome = true
|
||||
items = [1, 2, 3, 4, 5]
|
||||
config = {"debug": true, "port": 8080}
|
||||
|
||||
print(" Language: " + name + " v" + to_string(version))
|
||||
print(" Awesome: " + to_string(is_awesome))
|
||||
print("")
|
||||
|
||||
// ============================================
|
||||
// 2. Modern Array Operations
|
||||
// ============================================
|
||||
print("2. Array Operations:")
|
||||
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
print(" Original: " + to_string(numbers))
|
||||
|
||||
// Array helpers from spec: push, join, map, filter, reduce
|
||||
joined = join([10, 20, 30], ", ")
|
||||
print(" Joined: " + joined)
|
||||
|
||||
// Iterate arrays
|
||||
print(" Iteration:")
|
||||
for item in ["apple", "banana", "cherry"]
|
||||
print(" " + item)
|
||||
print("")
|
||||
|
||||
// ============================================
|
||||
// 3. Maps (Dictionaries)
|
||||
// ============================================
|
||||
print("3. Map Operations:")
|
||||
person = {"name": "Alice", "age": 30, "role": "Developer"}
|
||||
|
||||
print(" Person: " + to_string(person))
|
||||
print(" Has 'age' key: " + to_string(has(person, "age")))
|
||||
print(" Keys: " + to_string(keys(person)))
|
||||
print(" Values: " + to_string(values(person)))
|
||||
print("")
|
||||
|
||||
// ============================================
|
||||
// 4. Object-Oriented Programming
|
||||
// ============================================
|
||||
print("4. Classes & Objects:")
|
||||
|
||||
class Counter(number initial, string label)
|
||||
count = 0
|
||||
name = ""
|
||||
|
||||
fun _construct(this, initial, label)
|
||||
this.count = initial
|
||||
this.name = label
|
||||
|
||||
fun increment(this)
|
||||
this.count = this.count + 1
|
||||
|
||||
fun get_value(this)
|
||||
return this.count
|
||||
|
||||
fun display(this)
|
||||
print(" " + this.name + ": " + to_string(this.count))
|
||||
|
||||
counter = Counter(100, "Score")
|
||||
counter.display()
|
||||
counter.increment()
|
||||
counter.increment()
|
||||
counter.display()
|
||||
print("")
|
||||
|
||||
// ============================================
|
||||
// 5. Inheritance
|
||||
// ============================================
|
||||
print("5. Inheritance:")
|
||||
|
||||
class Animal(string type)
|
||||
species = ""
|
||||
|
||||
fun _construct(this, type)
|
||||
this.species = type
|
||||
|
||||
fun speak(this)
|
||||
return "Some sound"
|
||||
|
||||
class Dog(string dog_name) extends Animal
|
||||
name = ""
|
||||
|
||||
fun _construct(this, dog_name)
|
||||
this.species = "Canine"
|
||||
this.name = dog_name
|
||||
|
||||
fun speak(this)
|
||||
return "Woof! I'm " + this.name
|
||||
|
||||
dog = Dog("Buddy")
|
||||
print(" " + dog.speak())
|
||||
print(" Species: " + dog.species)
|
||||
print("")
|
||||
|
||||
// ============================================
|
||||
// 6. Error Handling
|
||||
// ============================================
|
||||
print("6. Exception Handling:")
|
||||
|
||||
try
|
||||
print(" Attempting risky operation...")
|
||||
result = 42 / 2
|
||||
print(" Result: " + to_string(result))
|
||||
catch err
|
||||
print(" Caught error! Handled gracefully.")
|
||||
finally
|
||||
print(" Cleanup completed.")
|
||||
print("")
|
||||
|
||||
// ============================================
|
||||
// 7. String Manipulation
|
||||
// ============================================
|
||||
print("7. String Features:")
|
||||
string text = "Hello, Fun Language!"
|
||||
print(" Original: " + text)
|
||||
// Note: Using stdlib functions (assumed to exist in utils modules)
|
||||
// len, substr, find, split would come from stdlib
|
||||
print("")
|
||||
|
||||
// ============================================
|
||||
// 8. Mathematical Operations
|
||||
// ============================================
|
||||
print("8. Math Functions:")
|
||||
float x = 16.7
|
||||
print(" x = " + to_string(x))
|
||||
// Note: Math functions like sqrt, floor, ceil, abs, gcd, lcm
|
||||
// would come from stdlib <utils/math.fun> or similar
|
||||
print("")
|
||||
|
||||
// ============================================
|
||||
// 9. Bitwise Operations
|
||||
// ============================================
|
||||
print("9. Bitwise Operations:")
|
||||
number bits1 = 12
|
||||
number bits2 = 10
|
||||
print(" 12 & 10 = " + to_string(band(bits1, bits2)))
|
||||
print(" 12 | 10 = " + to_string(bor(bits1, bits2)))
|
||||
print(" 12 ^ 10 = " + to_string(bxor(bits1, bits2)))
|
||||
print(" 12 << 2 = " + to_string(shl(bits1, 2)))
|
||||
print(" ~12 = " + to_string(bnot(bits1)))
|
||||
print("")
|
||||
|
||||
// ============================================
|
||||
// 10. Control Flow
|
||||
// ============================================
|
||||
print("10. Control Flow:")
|
||||
|
||||
// For loop with array
|
||||
print(" Countdown:")
|
||||
for i in [5, 4, 3, 2, 1]
|
||||
print(" " + to_string(i) + "...")
|
||||
print(" Liftoff!")
|
||||
|
||||
// While with break/continue
|
||||
print(" Skip evens:")
|
||||
number n = 0
|
||||
while n < 10
|
||||
n = n + 1
|
||||
if n % 2 == 0
|
||||
continue
|
||||
print(" " + to_string(n))
|
||||
if n >= 7
|
||||
break
|
||||
print("")
|
||||
|
||||
// ============================================
|
||||
// 11. Type Introspection
|
||||
// ============================================
|
||||
print("11. Type Introspection:")
|
||||
number check_int = 42
|
||||
string check_str = "hello"
|
||||
check_arr = [1, 2, 3]
|
||||
check_map = {"key": "value"}
|
||||
|
||||
print(" typeof(42) = " + typeof(check_int))
|
||||
print(" typeof(\"hello\") = " + typeof(check_str))
|
||||
print(" typeof([1,2,3]) = " + typeof(check_arr))
|
||||
print(" typeof(map) = " + typeof(check_map))
|
||||
print("")
|
||||
|
||||
// ============================================
|
||||
// 12. Functional Programming
|
||||
// ============================================
|
||||
print("12. Higher-Order Functions:")
|
||||
|
||||
fun double(n)
|
||||
return n * 2
|
||||
|
||||
fun apply_twice(x, func)
|
||||
return func(func(x))
|
||||
|
||||
result = apply_twice(5, double)
|
||||
print(" apply_twice(5, double) = " + to_string(result))
|
||||
print("")
|
||||
|
||||
// ============================================
|
||||
// 13. Array Operations with Spec Functions
|
||||
// ============================================
|
||||
print("13. Array Higher-Order Functions:")
|
||||
nums = [1, 2, 3, 4, 5]
|
||||
|
||||
fun square(x)
|
||||
return x * x
|
||||
|
||||
squared = map(nums, square)
|
||||
print(" Squared: " + to_string(squared))
|
||||
|
||||
fun is_even(x)
|
||||
return x % 2 == 0
|
||||
|
||||
evens = filter(nums, is_even)
|
||||
print(" Evens: " + to_string(evens))
|
||||
|
||||
fun sum(acc, x)
|
||||
return acc + x
|
||||
|
||||
total = reduce(nums, 0, sum)
|
||||
print(" Sum: " + to_string(total))
|
||||
print("")
|
||||
|
||||
// ============================================
|
||||
// Conclusion
|
||||
// ============================================
|
||||
print("=== Feature Showcase Complete! ===")
|
||||
print("Fun combines modern language features with simplicity.")
|
||||
print("Explore more examples in ./examples/ directory!")
|
||||
62
examples/maps.fun
Executable file
62
examples/maps.fun
Executable file
|
|
@ -0,0 +1,62 @@
|
|||
#!/usr/bin/env fun
|
||||
|
||||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2026-01-19
|
||||
*/
|
||||
|
||||
/*
|
||||
* Maps datatype — focused examples
|
||||
*
|
||||
* This script exclusively demonstrates the built-in maps datatype:
|
||||
* - Creating maps with literals and empty maps
|
||||
* - Reading and writing entries via [key]
|
||||
* - Checking for key existence with has(map, key)
|
||||
* - Getting keys(map) and values(map)
|
||||
* - Using nested maps
|
||||
*/
|
||||
|
||||
// Create an empty map and add entries
|
||||
user = {}
|
||||
user["name"] = "Ada"
|
||||
user["age"] = 37
|
||||
print(user) // -> {"name": "Ada", "age": 37}
|
||||
|
||||
// Read an entry by key
|
||||
print(user["name"]) // -> Ada
|
||||
|
||||
// Update an existing entry
|
||||
user["age"] = 38
|
||||
print(user["age"]) // -> 38
|
||||
|
||||
// Check if a key exists
|
||||
print(has(user, "age")) // -> 1 (true)
|
||||
print(has(user, "email")) // -> 0 (false)
|
||||
|
||||
// Keys and values (order may vary)
|
||||
print(keys(user)) // -> [name, age]
|
||||
print(values(user)) // -> ["Ada", 38]
|
||||
|
||||
// Nested maps
|
||||
address = { "city": "London", "zip": "E1" }
|
||||
user["address"] = address
|
||||
print(user["address"]) // -> {"city": "London", "zip": "E1"}
|
||||
print(user["address"]["city"]) // -> London
|
||||
|
||||
/* Expected output:
|
||||
{"name": Ada, "age": 37}
|
||||
Ada
|
||||
38
|
||||
1
|
||||
0
|
||||
[name, age]
|
||||
[Ada, 38]
|
||||
{"zip": E1, "city": London}
|
||||
London
|
||||
*/
|
||||
32
examples/rust_hello.fun
Normal file
32
examples/rust_hello.fun
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/env fun
|
||||
|
||||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2026-01-27
|
||||
*/
|
||||
|
||||
/*
|
||||
* Rust-backed opcode demo: rust_hello()
|
||||
*
|
||||
* Build instructions:
|
||||
* - Default builds disable Rust integration.
|
||||
* - Enable it via: cmake -S . -B build_debug -DFUN_WITH_RUST=ON
|
||||
* - Then: cmake --build build_debug --target fun
|
||||
*
|
||||
* Run:
|
||||
* build_debug/fun examples/rust_hello.fun
|
||||
*
|
||||
* Expected output (with FUN_WITH_RUST=ON):
|
||||
* Hello from Rust ops!
|
||||
*
|
||||
* If built without Rust, calling rust_hello() will raise a runtime error
|
||||
* explaining that Rust integration is disabled.
|
||||
*/
|
||||
|
||||
print(rust_hello())
|
||||
42
examples/sqlited/README.md
Normal file
42
examples/sqlited/README.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
Fun SQL TCP Demo (sqlited)
|
||||
|
||||
This example provides a minimal TCP server that executes SQL against a local SQLite database and a matching client.
|
||||
|
||||
Files
|
||||
- server.fun — TCP server daemon
|
||||
- client.fun — simple CLI client
|
||||
- protocol.md — wire protocol specification (line-based TSV)
|
||||
|
||||
Prerequisites
|
||||
- Build Fun with SQLite support enabled: configure with -DFUN_WITH_SQLITE=ON
|
||||
- Ensure the sqlite3 development headers and runtime are installed
|
||||
|
||||
Create a sample database
|
||||
- A schema is provided at examples/data/database.sql
|
||||
- Create ./database.sqlite at the repository root using the sqlite3 CLI:
|
||||
sqlite3 ./database.sqlite < ./examples/data/database.sql
|
||||
|
||||
Run the server
|
||||
- Set FUN_LIB_DIR to the 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
|
||||
142
examples/sqlited/client.fun
Executable file
142
examples/sqlited/client.fun
Executable file
|
|
@ -0,0 +1,142 @@
|
|||
#!/usr/bin/env fun
|
||||
|
||||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2026-01-19
|
||||
*/
|
||||
|
||||
// Simple TCP SQL client for Fun
|
||||
// Connects to host:port, sends a single-line SQL (from CLI args or default),
|
||||
// prints the server response, and exits.
|
||||
|
||||
// Run the server:
|
||||
// FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/sqlited/server.fun 127.0.0.1 5555
|
||||
|
||||
// Run the client:
|
||||
// FUN_LIB_DIR="$(pwd)/lib" ./build/fun --repl-on-error ./examples/sqlited/client.fun 127.0.0.1 5555 "SELECT * FROM tasks"
|
||||
|
||||
#include <cli.fun>
|
||||
|
||||
fun arg_or_default(args, i, d)
|
||||
if (len(args) > i)
|
||||
return args[i]
|
||||
else
|
||||
return d
|
||||
|
||||
fun read_all(fd)
|
||||
buf = ""
|
||||
while (true)
|
||||
chunk = sock_recv(fd, 1024)
|
||||
if (chunk == nil || len(chunk) == 0)
|
||||
break
|
||||
buf = buf + chunk
|
||||
return buf
|
||||
|
||||
fun main()
|
||||
args = argv()
|
||||
host = arg_or_default(args, 0, "127.0.0.1")
|
||||
port = to_number(arg_or_default(args, 1, 5555))
|
||||
sql = arg_or_default(args, 2, "SELECT 1 AS one;")
|
||||
|
||||
fd = tcp_connect(host, port)
|
||||
if (fd == 0)
|
||||
print("Connect failed to " + host + " " + to_string(port))
|
||||
return 1
|
||||
|
||||
// Ensure a single line terminated by \n
|
||||
if (len(sql) == 0 || substr(sql, len(sql)-1, 1) != "\n")
|
||||
sql = sql + "\n"
|
||||
|
||||
sent = sock_send(fd, sql)
|
||||
if (sent < 0)
|
||||
print("Send failed")
|
||||
sock_close(fd)
|
||||
return 1
|
||||
|
||||
resp = read_all(fd)
|
||||
sock_close(fd)
|
||||
if (resp == nil)
|
||||
resp = ""
|
||||
print(resp)
|
||||
|
||||
// Explicitly invoke main when the script is run
|
||||
main()
|
||||
|
||||
/* Possible result with 67 entries in the tasks table:
|
||||
RESULT
|
||||
value
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
{map n=4}
|
||||
END
|
||||
*/
|
||||
35
examples/sqlited/protocol.md
Normal file
35
examples/sqlited/protocol.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
Fun SQL TCP Demo Protocol (TSV, line-based)
|
||||
|
||||
- Client sends exactly one line with the SQL text terminated by a newline ("\n"). The server reads up to 64 KiB.
|
||||
|
||||
Responses
|
||||
|
||||
1) Query returning rows (e.g., SELECT):
|
||||
RESULT
|
||||
col1\tcol2\t...\n
|
||||
v11\tv12\t...\n
|
||||
...
|
||||
END
|
||||
|
||||
Notes:
|
||||
- First line is the literal word RESULT followed by a newline.
|
||||
- Second line is a header with column names separated by a single tab ("\t").
|
||||
- Each subsequent line is one row; fields are tab-separated. Nil/NULL are encoded as empty strings.
|
||||
- The block terminates with a line containing the literal END.
|
||||
|
||||
2) Exec/DDL (e.g., INSERT/UPDATE/CREATE):
|
||||
OK rc
|
||||
|
||||
Notes:
|
||||
- rc is the sqlite3 result code (0 indicates success).
|
||||
|
||||
3) Error:
|
||||
ERROR message
|
||||
|
||||
Notes:
|
||||
- The error message is human-readable and not machine-stable.
|
||||
|
||||
General
|
||||
- Newlines are Unix style ("\n").
|
||||
- Tabs and newlines in data are replaced with spaces for TSV safety.
|
||||
- The server closes the connection after sending the response.
|
||||
282
examples/sqlited/server.fun
Executable file
282
examples/sqlited/server.fun
Executable file
|
|
@ -0,0 +1,282 @@
|
|||
#!/usr/bin/env fun
|
||||
|
||||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2026-01-19
|
||||
*/
|
||||
|
||||
// Simple TCP SQL server for Fun
|
||||
// Listens on a TCP port, opens ./database.sqlite, executes one-line SQL per connection,
|
||||
// and returns results over the socket in a simple TSV protocol.
|
||||
|
||||
// Run the server:
|
||||
// FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/sqlited/server.fun 127.0.0.1 5555
|
||||
|
||||
// Run the client:
|
||||
// FUN_LIB_DIR="$(pwd)/lib" ./build/fun --repl-on-error ./examples/sqlited/client.fun 127.0.0.1 5555 "SELECT * FROM tasks"
|
||||
|
||||
// Protocol (per protocol.md):
|
||||
// - Client sends a single line of SQL ending with \n
|
||||
// - If query returns rows: respond with
|
||||
// RESULT\n
|
||||
// <col1>\t<col2>\t...\n
|
||||
// <v11>\t<v12>\t...\n
|
||||
// ...
|
||||
// END\n
|
||||
// - If exec/DDL: respond with
|
||||
// OK <rc>\n
|
||||
// - On error: respond with
|
||||
// ERROR <message>\n
|
||||
|
||||
// Helper: CLI args via stdlib
|
||||
#include <cli.fun>
|
||||
#include <strings.fun>
|
||||
|
||||
fun arg_or_default(args, i, d)
|
||||
if (len(args) > i)
|
||||
return args[i]
|
||||
else
|
||||
return d
|
||||
|
||||
// Helper: send a string (no newline added)
|
||||
fun send(fd, s)
|
||||
// sock_send returns bytes or -1
|
||||
return sock_send(fd, s)
|
||||
|
||||
// Helper: read a single line (up to max_len) ending with \n; returns string without trailing \r?\n or nil on EOF
|
||||
fun read_line(fd)
|
||||
max_len = 65536
|
||||
buf = ""
|
||||
while (len(buf) < max_len)
|
||||
chunk = sock_recv(fd, 256)
|
||||
if (chunk == nil || len(chunk) == 0)
|
||||
break
|
||||
buf = buf + chunk
|
||||
pos = find(buf, "\n")
|
||||
if (pos >= 0)
|
||||
line = substr(buf, 0, pos)
|
||||
// trim trailing \r if present
|
||||
if (len(line) > 0 && substr(line, len(line)-1, 1) == "\r")
|
||||
line = substr(line, 0, len(line)-1)
|
||||
return line
|
||||
if (len(buf) == 0)
|
||||
return nil
|
||||
// no newline; return whole buffer (trim any trailing CR)
|
||||
if (len(buf) > 0 && substr(buf, len(buf)-1, 1) == "\r")
|
||||
buf = substr(buf, 0, len(buf)-1)
|
||||
return buf
|
||||
|
||||
// Replace tab/newline with spaces for TSV safety
|
||||
fun sanitize_tsv(s)
|
||||
if (s == nil)
|
||||
return ""
|
||||
out = ""
|
||||
i = 0
|
||||
while (i < len(s))
|
||||
ch = substr(s, i, 1)
|
||||
if (ch == "\t" || ch == "\n" || ch == "\r")
|
||||
out = out + " "
|
||||
else
|
||||
out = out + ch
|
||||
i = i + 1
|
||||
return out
|
||||
|
||||
fun trim(s)
|
||||
// trim spaces and tabs
|
||||
i = 0
|
||||
j = len(s)
|
||||
while (i < j && (substr(s, i, 1) == " " || substr(s, i, 1) == "\t"))
|
||||
i = i + 1
|
||||
while (j > i && (substr(s, j-1, 1) == " " || substr(s, j-1, 1) == "\t" || substr(s, j-1, 1) == ";"))
|
||||
j = j - 1
|
||||
return substr(s, i, j - i)
|
||||
|
||||
fun split_on_comma(s)
|
||||
parts = []
|
||||
cur = ""
|
||||
i = 0
|
||||
while (i < len(s))
|
||||
ch = substr(s, i, 1)
|
||||
if (ch == ",")
|
||||
push(parts, trim(cur))
|
||||
cur = ""
|
||||
else
|
||||
cur = cur + ch
|
||||
i = i + 1
|
||||
push(parts, trim(cur))
|
||||
return parts
|
||||
|
||||
// Parse header from SQL SELECT list; for SELECT * tries PRAGMA table_info(table)
|
||||
fun parse_header_from_sql(sql, dbh)
|
||||
// Use stdlib helper for lowercase
|
||||
lower_sql = str_to_lower(sql)
|
||||
psel = find(lower_sql, "select ")
|
||||
pfrom = find(lower_sql, " from ")
|
||||
if (psel < 0 || pfrom < 0 || pfrom <= psel)
|
||||
return nil
|
||||
cols_str = substr(sql, psel + 7, pfrom - (psel + 7))
|
||||
cols_str = trim(cols_str)
|
||||
if (find(cols_str, "*") >= 0)
|
||||
// Attempt to detect table name after FROM
|
||||
rest = substr(sql, pfrom + 6, len(sql) - (pfrom + 6))
|
||||
rest = trim(rest)
|
||||
// table name is up to next space or semicolon
|
||||
sp = find(rest, " ")
|
||||
tname = rest
|
||||
if (sp > 0)
|
||||
tname = substr(rest, 0, sp)
|
||||
// remove trailing semicolon if any
|
||||
tname = trim(tname)
|
||||
if (len(tname) > 0)
|
||||
pragma_sql = "PRAGMA table_info(" + tname + ");"
|
||||
ti = sqlite_query(dbh, pragma_sql)
|
||||
if (ti != nil && len(ti) > 0)
|
||||
cols = []
|
||||
i = 0
|
||||
while (i < len(ti))
|
||||
nm = ti[i]["name"]
|
||||
if (nm != nil)
|
||||
push(cols, to_string(nm))
|
||||
i = i + 1
|
||||
if (len(cols) > 0)
|
||||
return cols
|
||||
// Parse explicit column list
|
||||
parts = split_on_comma(cols_str)
|
||||
cols = []
|
||||
i = 0
|
||||
while (i < len(parts))
|
||||
p = parts[i]
|
||||
pl = lower(p)
|
||||
// handle AS alias
|
||||
aspos = find(pl, " as ")
|
||||
if (aspos >= 0)
|
||||
alias = trim(substr(p, aspos + 4, len(p) - (aspos + 4)))
|
||||
push(cols, alias)
|
||||
else
|
||||
// take last token after dot
|
||||
dot = find(p, ".")
|
||||
if (dot >= 0)
|
||||
push(cols, trim(substr(p, dot + 1, len(p) - (dot + 1))))
|
||||
else
|
||||
push(cols, trim(p))
|
||||
i = i + 1
|
||||
if (len(cols) > 0)
|
||||
return cols
|
||||
return nil
|
||||
|
||||
// Attempt to build a deterministic header and row order using enumerate(row).
|
||||
// Falls back to attempting common column names if enumerate is unavailable.
|
||||
fun extract_header(row)
|
||||
// Build a header by probing a set of common keys present in many queries.
|
||||
// If none are present, fall back to a single synthetic column "value" and
|
||||
// the caller will print the entire row using to_string(row).
|
||||
hdr_candidates = [
|
||||
"id", "name", "title", "value", "count", "cnt",
|
||||
"done", "created_at", "updated_at", "rowid"
|
||||
]
|
||||
cols = []
|
||||
found = 0
|
||||
i = 0
|
||||
while (i < len(hdr_candidates))
|
||||
k = hdr_candidates[i]
|
||||
v = row[k]
|
||||
if (v != nil)
|
||||
push(cols, k)
|
||||
found = 1
|
||||
i = i + 1
|
||||
if (found == 1)
|
||||
return [cols, 0] // is_synthetic = 0
|
||||
else
|
||||
return [["value"], 1] // is_synthetic = 1
|
||||
|
||||
// Try to obtain map keys via enumerate(row). Returns [keys, is_synthetic]
|
||||
fun header_from_enumerate(row)
|
||||
keys = []
|
||||
pairs = enumerate(row)
|
||||
if (pairs == nil)
|
||||
return [["value"], 1]
|
||||
i = 0
|
||||
while (i < len(pairs))
|
||||
p = pairs[i]
|
||||
// Expect pair to be [key, value]
|
||||
if (p != nil && len(p) >= 1)
|
||||
push(keys, p[0])
|
||||
i = i + 1
|
||||
if (len(keys) == 0)
|
||||
return [["value"], 1]
|
||||
return [keys, 0]
|
||||
|
||||
fun handle_client(fd, dbh)
|
||||
print("[sqlited] client connected: fd=" + to_string(fd))
|
||||
sql = read_line(fd)
|
||||
print("[sqlited] received SQL: '" + (sql == nil ? "" : sql) + "'")
|
||||
if (sql == nil || len(sql) == 0)
|
||||
send(fd, "ERROR empty\n")
|
||||
sock_close(fd)
|
||||
return 0
|
||||
|
||||
// Try query first
|
||||
rows = sqlite_query(dbh, sql)
|
||||
if (rows != nil)
|
||||
print("[sqlited] query path; rows array obtained")
|
||||
// Build response in the stable synthetic format used in the 5558 build:
|
||||
// RESULT\n
|
||||
// value\n
|
||||
// {map n=...}\n (per row)
|
||||
resp = "RESULT\n"
|
||||
// Always emit single-column header 'value' for compatibility
|
||||
resp = resp + "value\n"
|
||||
// Emit rows
|
||||
r = 0
|
||||
while (r < len(rows))
|
||||
row = rows[r]
|
||||
print("[sqlited] sending row #" + to_string(r))
|
||||
resp = resp + sanitize_tsv(to_string(row)) + "\n"
|
||||
print("[sqlited] row #" + to_string(r) + " appended (synth)")
|
||||
r = r + 1
|
||||
// Terminate block
|
||||
print("[sqlited] finished building response; sending END and closing")
|
||||
resp = resp + "END\n"
|
||||
sb = send(fd, resp)
|
||||
print("[sqlited] total bytes sent=" + to_string(sb))
|
||||
sock_close(fd)
|
||||
return 1
|
||||
else
|
||||
// Exec path
|
||||
print("[sqlited] exec/DDL path")
|
||||
rc = sqlite_exec(dbh, sql)
|
||||
print("[sqlited] exec rc=" + to_string(rc))
|
||||
send(fd, "OK " + to_string(rc) + "\n")
|
||||
sock_close(fd)
|
||||
return 1
|
||||
|
||||
fun main()
|
||||
args = argv()
|
||||
host = arg_or_default(args, 0, "127.0.0.1")
|
||||
port = to_number(arg_or_default(args, 1, 5555))
|
||||
|
||||
dbh = sqlite_open("./database.sqlite")
|
||||
if (dbh == 0)
|
||||
print("Failed to open ./database.sqlite; create it first (sqlite3 ./database.sqlite < ./examples/data/database.sql)")
|
||||
return 1
|
||||
|
||||
lfd = tcp_listen(port, 16)
|
||||
if (lfd == 0)
|
||||
print("Failed to listen on port " + to_string(port))
|
||||
return 1
|
||||
|
||||
print("sqlited: listening on " + host + " " + to_string(port))
|
||||
while (true)
|
||||
cfd = tcp_accept(lfd)
|
||||
if (cfd > 0)
|
||||
// Handle sequentially to keep it simple for a demo
|
||||
handle_client(cfd, dbh)
|
||||
|
||||
// Explicitly invoke main when the script is run
|
||||
main()
|
||||
|
|
@ -71,7 +71,7 @@ class Person(number age, string name)
|
|||
// typeof(instance) will call this and return it
|
||||
return "Class"
|
||||
|
||||
Class p = Person(33, "Jo")
|
||||
p = Person(33, "Jo")
|
||||
print(typeof(p)) // -> "Class"
|
||||
// p = "nope" // Uncomment to see TypeError: expected Class
|
||||
|
||||
|
|
|
|||
73
play.fun
73
play.fun
|
|
@ -6,7 +6,10 @@
|
|||
* - Executes each example as a subprocess
|
||||
*/
|
||||
|
||||
#include <strings.fun>
|
||||
|
||||
#include <io/console.fun>
|
||||
#include <io/process.fun>
|
||||
|
||||
fun pick_fun_bin()
|
||||
// Prefer an explicit FUN_BIN override; otherwise rely on PATH
|
||||
|
|
@ -49,18 +52,42 @@ fun main()
|
|||
"examples/cast_demo.fun",
|
||||
"examples/class_constructor.fun",
|
||||
"examples/classes_demo.fun",
|
||||
"examples/crc32_example.fun",
|
||||
"examples/crc32c_example.fun",
|
||||
"examples/curl_download.fun",
|
||||
"examples/curl_get_json.fun",
|
||||
"examples/curl_post.fun",
|
||||
"examples/crypto/crc32_example.fun",
|
||||
"examples/crypto/crc32c_example.fun",
|
||||
"examples/crypto/md5_demo.fun",
|
||||
"examples/crypto/sha1_demo.fun",
|
||||
"examples/crypto/sha256_demo.fun",
|
||||
"examples/crypto/sha256_str_demo.fun",
|
||||
"examples/crypto/sha384_example.fun",
|
||||
"examples/crypto/sha512_demo.fun",
|
||||
"examples/crypto/sha512_str_demo.fun",
|
||||
"examples/datetime_basic.fun",
|
||||
"examples/datetime_extended.fun",
|
||||
"examples/datetime_timer.fun",
|
||||
"examples/debug_reporting.fun",
|
||||
"examples/echo_example.fun",
|
||||
"examples/exit_example.fun",
|
||||
"examples/error/debug_reporting.fun",
|
||||
"examples/error/exit_example.fun",
|
||||
"examples/error/repl_on_error.fun",
|
||||
"examples/expressions_test.fun",
|
||||
"examples/extra/curl_download.fun",
|
||||
"examples/extra/curl_get_json.fun",
|
||||
"examples/extra/curl_post.fun",
|
||||
"examples/extra/ini_class_demo.fun",
|
||||
"examples/extra/ini_complex.fun",
|
||||
"examples/extra/ini_demo.fun",
|
||||
"examples/extra/ini_subsections.fun",
|
||||
"examples/extra/json_showcase.fun",
|
||||
"examples/extra/libsql_example.fun",
|
||||
"examples/extra/pcre2_opcodes.fun",
|
||||
"examples/extra/pcre2_showcase.fun",
|
||||
"examples/extra/pcsc_example.fun",
|
||||
"examples/extra/sqlite_example.fun",
|
||||
"examples/extra/tk_hello.fun",
|
||||
"examples/extra/xml_access_catalog.fun",
|
||||
"examples/extra/xml_access_employees.fun",
|
||||
"examples/extra/xml_access_ns.fun",
|
||||
"examples/extra/xml_class_example.fun",
|
||||
"examples/extra/xml_minimal.fun",
|
||||
"examples/fail.fun",
|
||||
"examples/file_io.fun",
|
||||
"examples/file_print_for_file_line_by_line.fun",
|
||||
|
|
@ -75,59 +102,35 @@ fun main()
|
|||
"examples/include_local_util.fun",
|
||||
"examples/include_namespace.fun",
|
||||
"examples/inheritance_demo.fun",
|
||||
"examples/ini_class_demo.fun",
|
||||
"examples/ini_complex.fun",
|
||||
"examples/ini_demo.fun",
|
||||
"examples/ini_subsections.fun",
|
||||
"examples/input_example.fun",
|
||||
"examples/json_showcase.fun",
|
||||
"examples/libsql_example.fun",
|
||||
"examples/interactive/input_example.fun",
|
||||
"examples/loops_break_continue.fun",
|
||||
"examples/md5_demo.fun",
|
||||
"examples/namespaced_mod.fun",
|
||||
"examples/nested_loops.fun",
|
||||
"examples/objects_basic.fun",
|
||||
"examples/objects_more.fun",
|
||||
"examples/os_env.fun",
|
||||
"examples/pcre2_opcodes.fun",
|
||||
"examples/pcre2_showcase.fun",
|
||||
"examples/pcsc_example.fun",
|
||||
"examples/process_example.fun",
|
||||
"examples/regex_demo.fun",
|
||||
"examples/regex_procedural.fun",
|
||||
"examples/repl_on_error.fun",
|
||||
"examples/sha1_demo.fun",
|
||||
"examples/sha256_demo.fun",
|
||||
"examples/sha256_str_demo.fun",
|
||||
"examples/sha384_example.fun",
|
||||
"examples/sha512_demo.fun",
|
||||
"examples/sha512_str_demo.fun",
|
||||
"examples/short_circuit_test.fun",
|
||||
"examples/signed_ints.fun",
|
||||
"examples/sqlite_example.fun",
|
||||
"examples/stdlib_showcase.fun",
|
||||
"examples/strings_test.fun",
|
||||
"examples/tcp_http_get.fun",
|
||||
"examples/tcp_http_get_class.fun",
|
||||
"examples/thread_class_example.fun",
|
||||
"examples/threads_demo.fun",
|
||||
"examples/tk_hello.fun",
|
||||
"examples/try_catch_finally.fun",
|
||||
"examples/try_catch_with_error.fun",
|
||||
"examples/typeof.fun",
|
||||
"examples/typeof_features.fun",
|
||||
"examples/type_safety.fun",
|
||||
"examples/type_safety_fails.fun",
|
||||
"examples/types_integers.fun",
|
||||
"examples/types_overview.fun",
|
||||
"examples/typeof.fun",
|
||||
"examples/typeof_features.fun",
|
||||
"examples/uint_types.fun",
|
||||
"examples/unix_socket_echo.fun",
|
||||
"examples/while_test.fun",
|
||||
"examples/xml_access_catalog.fun",
|
||||
"examples/xml_access_employees.fun",
|
||||
"examples/xml_access_ns.fun",
|
||||
"examples/xml_class_example.fun",
|
||||
"examples/xml_minimal.fun"
|
||||
"examples/while_test.fun"
|
||||
]
|
||||
|
||||
failures = []
|
||||
|
|
|
|||
|
|
@ -206,6 +206,7 @@ static const char *opcode_name(OpCode op) {
|
|||
case OP_SIGN: return "SIGN";
|
||||
case OP_FMIN: return "FMIN";
|
||||
case OP_FMAX: return "FMAX";
|
||||
case OP_RUST_HELLO: return "RUST_HELLO";
|
||||
default: return "???";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -263,6 +263,9 @@ typedef enum {
|
|||
OP_FMIN, // pops b, a (int/float); pushes fmin(a,b) (NaN handling per C99)
|
||||
OP_FMAX, // pops b, a (int/float); pushes fmax(a,b) (NaN handling per C99)
|
||||
|
||||
// Rust FFI demo opcode(s)
|
||||
OP_RUST_HELLO, // pushes string returned from Rust (hello world)
|
||||
|
||||
/* Notcurses TUI (optional) */
|
||||
OP_NC_INIT, // initializes Notcurses; returns 1 on success, 0 on failure
|
||||
OP_NC_SHUTDOWN, // shuts down Notcurses; returns 0
|
||||
|
|
|
|||
64
src/parser.c
64
src/parser.c
|
|
@ -73,6 +73,7 @@ static int g_temp_counter = 0;
|
|||
#define TYPE_META_NIL 10003
|
||||
#define TYPE_META_CLASS 10004
|
||||
#define TYPE_META_FLOAT 10005
|
||||
#define TYPE_META_ARRAY 10006
|
||||
|
||||
static void parser_fail(size_t pos, const char *fmt, ...) {
|
||||
g_has_error = 1;
|
||||
|
|
@ -785,6 +786,13 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
|||
free(name);
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(name, "rust_hello") == 0) {
|
||||
(*pos)++; /* '(' */
|
||||
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "rust_hello expects ()"); free(name); return 0; }
|
||||
bytecode_add_instruction(bc, OP_RUST_HELLO, 0);
|
||||
free(name);
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(name, "os_list_dir") == 0) {
|
||||
(*pos)++; /* '(' */
|
||||
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "os_list_dir expects (path)"); free(name); return 0; }
|
||||
|
|
@ -2845,7 +2853,8 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
|
|||
Note: 'number' maps to signed 64-bit here. 'byte' is an alias of unsigned 8-bit. 'Class' restricts to class instances.
|
||||
*/
|
||||
if (strcmp(name, "number") == 0 || strcmp(name, "string") == 0 || strcmp(name, "boolean") == 0 || strcmp(name, "nil") == 0
|
||||
|| strcmp(name, "Class") == 0 || strcmp(name, "float") == 0
|
||||
|| strcmp(name, "class") == 0 || strcmp(name, "float") == 0
|
||||
|| strcmp(name, "array") == 0
|
||||
|| strcmp(name, "byte") == 0
|
||||
|| strcmp(name, "uint8") == 0 || strcmp(name, "uint16") == 0 || strcmp(name, "uint32") == 0 || strcmp(name, "uint64") == 0
|
||||
|| strcmp(name, "int8") == 0 || strcmp(name, "int16") == 0 || strcmp(name, "int32") == 0 || strcmp(name, "int64") == 0) {
|
||||
|
|
@ -2853,8 +2862,9 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
|
|||
int is_string = (strcmp(name, "string") == 0);
|
||||
int is_boolean = (strcmp(name, "boolean") == 0);
|
||||
int is_nil = (strcmp(name, "nil") == 0);
|
||||
int is_class_tkn = (strcmp(name, "Class") == 0);
|
||||
int is_float_tkn = (strcmp(name, "float") == 0);
|
||||
int is_class = (strcmp(name, "class") == 0);
|
||||
int is_float = (strcmp(name, "float") == 0);
|
||||
int is_array = (strcmp(name, "array") == 0);
|
||||
int is_byte = (strcmp(name, "byte") == 0);
|
||||
int is_u8 = (strcmp(name, "uint8") == 0) || is_byte;
|
||||
int is_u16 = (strcmp(name, "uint16") == 0);
|
||||
|
|
@ -2865,12 +2875,12 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
|
|||
int is_s32 = (strcmp(name, "int32") == 0);
|
||||
int is_s64 = (strcmp(name, "int64") == 0) || is_number; /* number maps to int64 (signed) */
|
||||
int decl_bits = is_u8 ? 8 : is_u16 ? 16 : is_u32 ? 32 : is_u64 ? 64
|
||||
: is_s8 ? 8 : is_s16 ? 16 : is_s32 ? 32 : is_s64 ? 64 : 0;
|
||||
: is_s8 ? 8 : is_s16 ? 16 : is_s32 ? 32 : is_s64 ? 64 : 0;
|
||||
int decl_signed = (is_s8 || is_s16 || is_s32 || is_s64) ? 1 : 0;
|
||||
/* store decl bits with sign encoded: negative means signed (number is signed 64-bit) */
|
||||
/* store decl bits with sign encoded: negative means signed (number is signed 64-bit) */
|
||||
if (decl_signed) decl_bits = -decl_bits;
|
||||
|
||||
/* declared type metadata: integers use decl_bits; string/boolean/nil/Class/float use special markers */
|
||||
/* declared type metadata: integers use decl_bits; string/boolean/nil/Class/float/array use special markers */
|
||||
int decl_meta = decl_bits;
|
||||
if (is_string) {
|
||||
decl_meta = TYPE_META_STRING;
|
||||
|
|
@ -2878,10 +2888,12 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
|
|||
decl_meta = TYPE_META_BOOLEAN;
|
||||
} else if (is_nil) {
|
||||
decl_meta = TYPE_META_NIL;
|
||||
} else if (is_class_tkn) {
|
||||
} else if (is_class) {
|
||||
decl_meta = TYPE_META_CLASS;
|
||||
} else if (is_float_tkn) {
|
||||
} else if (is_float) {
|
||||
decl_meta = TYPE_META_FLOAT;
|
||||
} else if (is_array) {
|
||||
decl_meta = TYPE_META_ARRAY;
|
||||
}
|
||||
|
||||
free(name);
|
||||
|
|
@ -2989,6 +3001,23 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
|
|||
bytecode_add_instruction(bc, OP_HALT, 0);
|
||||
}
|
||||
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
||||
} else if (decl_meta == TYPE_META_ARRAY) {
|
||||
/* expect Array */
|
||||
bytecode_add_instruction(bc, OP_DUP, 0);
|
||||
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
||||
int ciArr = bytecode_add_constant(bc, make_string("Array"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciArr);
|
||||
bytecode_add_instruction(bc, OP_EQ, 0);
|
||||
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
||||
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
|
||||
bytecode_set_operand(bc, j_to_error, bc->instr_count);
|
||||
{
|
||||
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Array"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
||||
bytecode_add_instruction(bc, OP_PRINT, 0);
|
||||
bytecode_add_instruction(bc, OP_HALT, 0);
|
||||
}
|
||||
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
||||
} else if (decl_meta == TYPE_META_BOOLEAN) {
|
||||
/* accept Boolean literal or Number; if Number, clamp to 0/1 */
|
||||
/* check if value is Boolean */
|
||||
|
|
@ -3077,7 +3106,7 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
|
|||
ci = bytecode_add_constant(bc, make_string(""));
|
||||
} else if (is_nil) {
|
||||
ci = bytecode_add_constant(bc, make_nil());
|
||||
} else if (is_class_tkn) {
|
||||
} else if (is_class) {
|
||||
/* Class-typed variable defaults to Nil until assigned an instance */
|
||||
ci = bytecode_add_constant(bc, make_nil());
|
||||
} else if (is_boolean) {
|
||||
|
|
@ -3349,6 +3378,23 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
|
|||
bytecode_add_instruction(bc, OP_HALT, 0);
|
||||
}
|
||||
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
||||
} else if (meta == TYPE_META_ARRAY) {
|
||||
/* expect Array */
|
||||
bytecode_add_instruction(bc, OP_DUP, 0);
|
||||
bytecode_add_instruction(bc, OP_TYPEOF, 0);
|
||||
int ciArr = bytecode_add_constant(bc, make_string("Array"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciArr);
|
||||
bytecode_add_instruction(bc, OP_EQ, 0);
|
||||
int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
|
||||
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
|
||||
bytecode_set_operand(bc, j_to_error, bc->instr_count);
|
||||
{
|
||||
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Array"));
|
||||
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
|
||||
bytecode_add_instruction(bc, OP_PRINT, 0);
|
||||
bytecode_add_instruction(bc, OP_HALT, 0);
|
||||
}
|
||||
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
|
||||
} else if (meta == TYPE_META_BOOLEAN) {
|
||||
/* expect Number then clamp to 1 bit (unsigned) */
|
||||
bytecode_add_instruction(bc, OP_DUP, 0);
|
||||
|
|
|
|||
16
src/rust/Cargo.toml
Normal file
16
src/rust/Cargo.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[package]
|
||||
name = "hello-c-world"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
# Optimize for minimal size
|
||||
opt-level = "z"
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
# If Cargo is new enough, this strips symbols from Rust objects
|
||||
strip = "symbols"
|
||||
44
src/rust/src/lib.rs
Normal file
44
src/rust/src/lib.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2026-01-27
|
||||
*/
|
||||
|
||||
#![no_std]
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Vm;
|
||||
|
||||
extern "C" {
|
||||
fn vm_pop_i64(vm: *mut Vm) -> i64;
|
||||
fn vm_push_i64(vm: *mut Vm, v: i64);
|
||||
}
|
||||
|
||||
// Submodule with additional Rust VM math ops (exported via C ABI)
|
||||
pub mod vm;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fun_op_radd(vm: *mut Vm) -> i32 {
|
||||
unsafe {
|
||||
let b = vm_pop_i64(vm);
|
||||
let a = vm_pop_i64(vm);
|
||||
vm_push_i64(vm, a + b);
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fun_rust_get_string() -> *const core::ffi::c_char {
|
||||
b"Hello from Rust ops!\0".as_ptr() as *const _
|
||||
}
|
||||
|
||||
// Minimal panic handler for no_std; abort behavior requested via Cargo profile
|
||||
#[panic_handler]
|
||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
70
src/rust/src/vm/mod.rs
Normal file
70
src/rust/src/vm/mod.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2026-01-27
|
||||
*/
|
||||
|
||||
//! Rust VM helpers and opcode handlers exposed via C ABI.
|
||||
//!
|
||||
//! Functions in this module are compiled into the Rust static library and can
|
||||
//! be called from the C VM. They operate on the VM stack using the minimal C
|
||||
//! ABI helpers declared on the C side (`vm_pop_i64`, `vm_push_i64`).
|
||||
|
||||
use super::Vm;
|
||||
|
||||
extern "C" {
|
||||
fn vm_pop_i64(vm: *mut Vm) -> i64;
|
||||
fn vm_push_i64(vm: *mut Vm, v: i64);
|
||||
}
|
||||
|
||||
/// Multiply the top two integers on the VM stack.
|
||||
///
|
||||
/// Before: [..., a, b]
|
||||
/// After: [..., a*b]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fun_op_rmul(vm: *mut Vm) -> i32 {
|
||||
unsafe {
|
||||
let b = vm_pop_i64(vm);
|
||||
let a = vm_pop_i64(vm);
|
||||
vm_push_i64(vm, a.saturating_mul(b));
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
/// Subtract the top two integers on the VM stack.
|
||||
///
|
||||
/// Before: [..., a, b]
|
||||
/// After: [..., a-b]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fun_op_rsub(vm: *mut Vm) -> i32 {
|
||||
unsafe {
|
||||
let b = vm_pop_i64(vm);
|
||||
let a = vm_pop_i64(vm);
|
||||
vm_push_i64(vm, a.wrapping_sub(b));
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
/// Integer division of the top two integers on the VM stack (a / b).
|
||||
/// Pushes 0 and returns 1 when dividing by zero.
|
||||
///
|
||||
/// Before: [..., a, b]
|
||||
/// After: [..., a/b]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fun_op_rdiv(vm: *mut Vm) -> i32 {
|
||||
unsafe {
|
||||
let b = vm_pop_i64(vm);
|
||||
let a = vm_pop_i64(vm);
|
||||
if b == 0 {
|
||||
vm_push_i64(vm, 0);
|
||||
return 1; // indicate error (div by zero)
|
||||
}
|
||||
vm_push_i64(vm, a / b);
|
||||
}
|
||||
0
|
||||
}
|
||||
|
|
@ -46,6 +46,29 @@ int main() {
|
|||
}
|
||||
|
||||
vm_clear_output(&vm);
|
||||
|
||||
/* --- Rust FFI demo: call a Rust opcode and string function --- */
|
||||
#ifdef FUN_WITH_RUST
|
||||
extern int fun_op_radd(VM *vm);
|
||||
extern const char *fun_rust_get_string(void);
|
||||
|
||||
printf("=== Rust FFI demo ===\n");
|
||||
const char *rs = fun_rust_get_string();
|
||||
if (rs) {
|
||||
printf("Rust says: %s\n", rs);
|
||||
}
|
||||
|
||||
/* prepare stack: push 10 and 32, then call Rust add -> expect 42 */
|
||||
vm_push_i64(&vm, 10);
|
||||
vm_push_i64(&vm, 32);
|
||||
int rc = fun_op_radd(&vm);
|
||||
printf("fun_op_radd rc=%d\n", rc);
|
||||
long long sum = (long long)vm_pop_i64(&vm);
|
||||
printf("Rust op result: %lld\n", sum);
|
||||
#else
|
||||
printf("=== Rust FFI demo (disabled; build with -DFUN_WITH_RUST=ON) ===\n");
|
||||
#endif
|
||||
|
||||
vm_free(&vm);
|
||||
bytecode_free(bc);
|
||||
return 0;
|
||||
|
|
|
|||
57
src/vm.c
57
src/vm.c
|
|
@ -235,6 +235,38 @@ static void fun_vm_exit(int code) {
|
|||
/* Redirect exit inside this TU (affects included opcode handlers) */
|
||||
#define exit(code) fun_vm_exit(code)
|
||||
|
||||
/* Forward decl for stack push used by vm_raise_error */
|
||||
static void push_value(VM *vm, Value v);
|
||||
|
||||
/* Raise a runtime error that respects try/catch/finally semantics.
|
||||
* If a handler is installed for the current frame, jump to it and push
|
||||
* an error string for the catch clause. Otherwise, print and stop VM. */
|
||||
void vm_raise_error(VM *vm, const char *msg) {
|
||||
if (!vm || vm->fp < 0) {
|
||||
fprintf(stderr, "Runtime error: %s\n", msg ? msg : "<error>");
|
||||
return;
|
||||
}
|
||||
Frame *f = &vm->frames[vm->fp];
|
||||
if (f->try_sp >= 0) {
|
||||
char buf[256];
|
||||
if (msg) {
|
||||
snprintf(buf, sizeof(buf), "Runtime error: %s", msg);
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "Runtime error");
|
||||
}
|
||||
/* push error value and transfer control to handler target */
|
||||
Value err = make_string(buf);
|
||||
push_value(vm, err);
|
||||
int try_idx = f->try_stack[f->try_sp--];
|
||||
int target = f->fn->instructions[try_idx].operand;
|
||||
f->ip = target;
|
||||
return;
|
||||
}
|
||||
/* No handler: print annotated message and terminate VM */
|
||||
fprintf(stderr, "Runtime error: %s\n", msg ? msg : "<error>");
|
||||
vm->fp = -1; /* stop execution */
|
||||
}
|
||||
|
||||
/*
|
||||
Opcode case include index (vm_case_*.inc):
|
||||
- Core/stack/frame:
|
||||
|
|
@ -434,6 +466,28 @@ static Value pop_value(VM *vm) {
|
|||
return vm->stack[vm->sp--]; /* caller owns returned Value */
|
||||
}
|
||||
|
||||
/* --- C ABI helpers for Rust FFI --- */
|
||||
int64_t vm_pop_i64(VM *vm) {
|
||||
Value v = pop_value(vm);
|
||||
int64_t out = 0;
|
||||
if (v.type == VAL_INT) {
|
||||
out = v.i;
|
||||
} else if (v.type == VAL_FLOAT) {
|
||||
out = (int64_t) v.d;
|
||||
} else {
|
||||
fprintf(stderr, "Runtime type error: expected int/float on stack, got %s\n", value_type_name(v.type));
|
||||
free_value(v);
|
||||
exit(1);
|
||||
}
|
||||
/* free any dynamic payload (no-op for int/float) */
|
||||
free_value(v);
|
||||
return out;
|
||||
}
|
||||
|
||||
void vm_push_i64(VM *vm, int64_t v) {
|
||||
push_value(vm, make_int(v));
|
||||
}
|
||||
|
||||
static void frame_init(Frame *f) {
|
||||
f->fn = NULL;
|
||||
f->ip = 0;
|
||||
|
|
@ -702,6 +756,9 @@ void vm_run(VM *vm, Bytecode *entry) {
|
|||
#include "vm/math/isqrt.c"
|
||||
#include "vm/math/sign.c"
|
||||
|
||||
/* Rust FFI demo opcode(s) */
|
||||
#include "vm/rust/hello.c"
|
||||
|
||||
#include "vm/os/env.c"
|
||||
#include "vm/os/env_all.c"
|
||||
#include "vm/os/fun_version.c"
|
||||
|
|
|
|||
21
src/vm.h
21
src/vm.h
|
|
@ -54,6 +54,9 @@ static const char *opcode_names[] = {
|
|||
"SERIAL_OPEN","SERIAL_CONFIG","SERIAL_SEND","SERIAL_RECV","SERIAL_CLOSE",
|
||||
"TK_EVAL","TK_RESULT","TK_LOOP","TK_WM_TITLE","TK_LABEL","TK_BUTTON","TK_PACK",
|
||||
"TRY_PUSH","TRY_POP","THROW",
|
||||
"FMIN","FMAX",
|
||||
/* Rust FFI demo */
|
||||
"RUST_HELLO",
|
||||
/* Notcurses TUI (optional) */
|
||||
"NC_INIT","NC_SHUTDOWN","NC_CLEAR","NC_DRAW_TEXT","NC_GETCH"
|
||||
};
|
||||
|
|
@ -122,6 +125,12 @@ void vm_dump_globals(VM *vm);
|
|||
// run entry Bytecode (pushes first frame)
|
||||
void vm_run(VM *vm, Bytecode *entry);
|
||||
|
||||
/* Raise a runtime error that respects try/catch/finally.
|
||||
* If a try handler is active in the current frame, control jumps to it
|
||||
* with an error string pushed on the stack. Otherwise, prints the error
|
||||
* (annotated with location) and terminates execution. */
|
||||
void vm_raise_error(VM *vm, const char *msg);
|
||||
|
||||
/* --- Debugger API --- */
|
||||
void vm_debug_reset(VM *vm);
|
||||
int vm_debug_add_breakpoint(VM *vm, const char *file, int line); // returns id >=0 or -1
|
||||
|
|
@ -137,4 +146,16 @@ static inline int opcode_is_valid(int op) {
|
|||
return op >= OP_NOP && op <= OP_NC_GETCH; // all current opcodes (including optional NC_*)
|
||||
}
|
||||
|
||||
/* --- Minimal C ABI helpers for FFI (Rust opcode experiments) --- */
|
||||
/* Pop an int64 from VM stack (errors if not an int/float); returns integer-converted value. */
|
||||
int64_t vm_pop_i64(VM *vm);
|
||||
/* Push an int64 onto VM stack. */
|
||||
void vm_push_i64(VM *vm, int64_t v);
|
||||
|
||||
/* Example Rust-implemented opcode (adds top two ints on stack) */
|
||||
int fun_op_radd(VM *vm);
|
||||
|
||||
/* Example Rust function returning a demo C string (null-terminated). */
|
||||
const char *fun_rust_get_string(void);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ case OP_DIV: {
|
|||
double da = (a.type == VAL_FLOAT) ? a.d : (double)a.i;
|
||||
double db = (b.type == VAL_FLOAT) ? b.d : (double)b.i;
|
||||
if (db == 0.0) {
|
||||
fprintf(stderr, "Runtime error: division by zero\n");
|
||||
exit(1);
|
||||
vm_raise_error(vm, "division by zero");
|
||||
break;
|
||||
}
|
||||
Value res = make_float(da / db);
|
||||
free_value(a);
|
||||
|
|
@ -49,8 +49,8 @@ case OP_DIV: {
|
|||
push_value(vm, res);
|
||||
} else {
|
||||
if (b.i == 0) {
|
||||
fprintf(stderr, "Runtime error: division by zero\n");
|
||||
exit(1);
|
||||
vm_raise_error(vm, "division by zero");
|
||||
break;
|
||||
}
|
||||
Value res = make_int(a.i / b.i);
|
||||
free_value(a);
|
||||
|
|
|
|||
27
src/vm/rust/hello.c
Normal file
27
src/vm/rust/hello.c
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2026-01-27
|
||||
*/
|
||||
|
||||
/**
|
||||
* Rust FFI demo opcode: OP_RUST_HELLO
|
||||
* When executed, it pushes a hello string returned by Rust onto the VM stack.
|
||||
*/
|
||||
|
||||
case OP_RUST_HELLO: {
|
||||
#ifdef FUN_WITH_RUST
|
||||
const char *s = fun_rust_get_string();
|
||||
if (!s) s = "";
|
||||
push_value(vm, make_string(s));
|
||||
#else
|
||||
vm_raise_error(vm, "RUST_HELLO requires FUN_WITH_RUST=ON at build time");
|
||||
push_value(vm, make_nil());
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue