diff --git a/CMakeLists.txt b/CMakeLists.txt index fd663a3..a5a8aba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.38.12 LANGUAGES C) +project(fun VERSION 0.38.13 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/README.md b/README.md index 402a86d..dcfc6fd 100644 --- a/README.md +++ b/README.md @@ -87,16 +87,16 @@ See [./lib/](https://git.xw3.org/fun/fun/src/branch/main/lib) for what the stand ### 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) ☑ -- [INI](https://en.wikipedia.org/wiki/INI_file) support builtin using [iniparser](https://gitlab.com/iniparser/iniparser/) (optional) ☑ -- [JSON](https://www.json.org/) support builtin using [json-c](https://github.com/json-c/json-c) (optional) ☑ -- [libSQL](https://github.com/tursodatabase/libsql) support builtin as a compatible alternative to SQLite (optional) ☑ -- [PCRE2](https://pcre2project.github.io/pcre2/) support builtin for Perl-Compatible Regular Expressions (optional) ☑ -- [PCSC](https://pcscworkgroup.com/) smart card support builtin using [PCSC lite](https://pcsclite.apdu.fr/) (optional) ☑ -- [OpenSSL](https://www.openssl.org/) integration (currently MD5 helper) (optional) ☑ -- [SQLite](https://sqlite.org/) support builtin (optional) ☑ -- [Tk](https://www.tcl-lang.org/) support builtin for GUI application development (optional) ☑ -- [XML](https://www.w3.org/XML/) support builtin using [libxml2](https://gitlab.gnome.org/GNOME/libxml2/-/wikis/home) (optional) ☑ +- [cURL (libcurl)](./docs/external/curl.md) (optional) ☑ +- [INI (iniparser)](./docs/external/ini.md) (optional) ☑ +- [JSON (json-c)](./docs/external/json.md) (optional) ☑ +- [libSQL](./docs/external/libsql.md) (optional) ☑ +- [PCRE2](./docs/external/pcre2.md) (optional) ☑ +- [PCSC (smart cards)](./docs/external/pcsc.md) (optional) ☑ +- [OpenSSL](./docs/external/openssl.md) (optional) ☑ +- [SQLite](./docs/external/sqlite.md) (optional) ☑ +- [Tk (Tcl/Tk GUI)](./docs/external/tcltk.md) (optional) ☑ +- [XML (libxml2)](./docs/external/xml2.md) (optional) ☑ ☑ = Done / ☐ = Planned or in progress. @@ -106,17 +106,7 @@ There are some libs written in Fun available in the [./lib/](https://git.xw3.org ### OpenSSL quickstart (MD5) -The OpenSSL integration is optional and currently provides an `openssl_md5(data)` helper. - -- Build with OpenSSL enabled: - - `cmake -S . -B build_debug -DFUN_WITH_OPENSSL=ON` - - `cmake --build build_debug --target fun` -- Run the example: - - `./build_debug/fun examples/crypto/openssl_md5.fun` - -If OpenSSL is disabled, the function returns an empty string (consistent with other optional extensions). - -Note: On OpenSSL 3.x the legacy `MD5_*` APIs are deprecated; you may see warnings during build. +See the dedicated page: ./docs/external/openssl.md ## Documentation diff --git a/docs/README.md b/docs/README.md index 2c4cccc..ab2ea78 100644 --- a/docs/README.md +++ b/docs/README.md @@ -35,6 +35,13 @@ This file serves as an index of the documents in this directory. Links are relat - [bytecode-format.md](./bytecode-format.md) - Reference for the bytecode format (split out from internals for convenience). - [roadmap.md](./roadmap.md) - High-level direction, planned features, and pointers to issues. +## External extensions + +Documentation for optional, build-time selectable integrations lives in [external/](./external/): + +- [Index of extensions](./external/README.md) + - Highlights: [cURL](./external/curl.md), [INI](./external/ini.md), [JSON](./external/json.md), [XML (libxml2)](./external/xml2.md), [SQLite](./external/sqlite.md), [libSQL](./external/libsql.md), [PCRE2](./external/pcre2.md), [PC/SC](./external/pcsc.md), [Notcurses](./external/notcurses.md), [Tcl/Tk](./external/tcltk.md), [OpenSSL](./external/openssl.md) + ## Tips - When building from the repo without installing, set `FUN_LIB_DIR` to the local `./lib` directory so examples and the REPL can locate the stdlib. diff --git a/docs/cli.md b/docs/cli.md index 0874569..46f8a32 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -29,10 +29,10 @@ See also: [includes.md](./includes.md) for namespaced includes and search order. ## Examples Run a script: ``` -FUN_LIB_DIR=./lib ./build_debug/fun examples/hello.fun +FUN_LIB_DIR=./lib ./build/fun examples/hello.fun ``` Start the REPL: ``` -./build_debug/fun -i +./build/fun -i ``` diff --git a/docs/examples.md b/docs/examples.md index 5571db7..abd6614 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -44,7 +44,7 @@ FUN_LIB_DIR="$(pwd)/lib" fun examples/crypto/openssl_md5.fun Browse the `examples/` tree for areas of interest: -- crypto — crypto demonstrations (e.g., OpenSSL MD5 helper; requires build with `-DFUN_WITH_OPENSSL=ON`) +- crypto — crypto demonstrations (e.g., OpenSSL MD5/SHA-256/SHA-512 helpers; requires build with `-DFUN_WITH_OPENSSL=ON`) - blocking / interactive — I/O or user-interactive patterns - error / broken — negative tests and error showcases - math — numeric operations diff --git a/docs/external/README.md b/docs/external/README.md new file mode 100644 index 0000000..8f3391e --- /dev/null +++ b/docs/external/README.md @@ -0,0 +1,25 @@ +# External integrations (optional extensions) + +This section documents Fun’s optional, build‑time selectable extensions. Each page covers: +- How to enable the extension via CMake option (FUN_WITH_*) +- Build requirements and detection notes +- Available opcodes and/or helper functions +- Minimal usage examples and links to example scripts + +Extensions: + +- [cURL (libcurl)](./curl.md) +- [INI (iniparser)](./ini.md) +- [JSON (json-c)](./json.md) +- [libxml2 (XML)](./xml2.md) +- [SQLite](./sqlite.md) +- [libSQL (SQLite-compatible)](./libsql.md) +- [PCRE2 (Perl-compatible regex)](./pcre2.md) +- [PC/SC (Smart cards)](./pcsc.md) +- [Notcurses (TUI)](./notcurses.md) +- [Tcl/Tk (GUI)](./tcltk.md) +- [OpenSSL (MD5 helper)](./openssl.md) + +Notes: +- These integrations are optional; the VM compiles without them. +- When disabled, related builtins usually return empty strings/neutral values rather than fail hard, mirroring existing optionality patterns. diff --git a/docs/external/curl.md b/docs/external/curl.md new file mode 100644 index 0000000..b47ddc3 --- /dev/null +++ b/docs/external/curl.md @@ -0,0 +1,14 @@ +# cURL (libcurl) extension (optional) + +- CMake option: FUN_WITH_CURL=ON +- Purpose: HTTP helpers using libcurl. +- Homepage: https://curl.se/libcurl/ + +Opcodes: +- OP_CURL_GET: GET; pops url:string; pushes body:string (empty on error/disabled) +- OP_CURL_POST: POST; pops body:string, url:string; pushes response:string +- OP_CURL_DOWNLOAD: Download to file; pops path:string, url:string; pushes 1/0 + +Notes: +- Requires libcurl development headers/libs. +- When disabled, helpers return empty strings/0 to match optional behavior. diff --git a/docs/external/ini.md b/docs/external/ini.md new file mode 100644 index 0000000..2975313 --- /dev/null +++ b/docs/external/ini.md @@ -0,0 +1,20 @@ +# INI (iniparser) extension (optional) + +- CMake option: FUN_WITH_INI=ON +- Purpose: Read/write simple INI configuration files. +- Homepage: https://github.com/ndevilla/iniparser + +Opcodes: +- OP_INI_LOAD: pops path; pushes handle (>0) or 0 +- OP_INI_FREE: pops handle; pushes 1/0 +- OP_INI_GET_STRING: pops def, key, section, handle; pushes string +- OP_INI_GET_INT: pops def, key, section, handle; pushes int +- OP_INI_GET_DOUBLE: pops def, key, section, handle; pushes float +- OP_INI_GET_BOOL: pops def, key, section, handle; pushes 0/1 +- OP_INI_SET: pops value, key, section, handle; pushes 1/0 +- OP_INI_UNSET: pops key, section, handle; pushes 1/0 +- OP_INI_SAVE: pops path, handle; pushes 1/0 + +Notes: +- Requires iniparser development headers/libs. +- When disabled, helpers return neutral values (0/empty strings) like other optional extensions. diff --git a/docs/external/json.md b/docs/external/json.md new file mode 100644 index 0000000..60475d1 --- /dev/null +++ b/docs/external/json.md @@ -0,0 +1,15 @@ +# JSON (json-c) extension (optional) + +- CMake option: FUN_WITH_JSON=ON +- Purpose: JSON parse/stringify and file helpers via json-c. +- Homepage: https://json-c.github.io/json-c/ + +Opcodes: +- OP_JSON_PARSE: pops text; pushes value or Nil on error +- OP_JSON_STRINGIFY: pops pretty:int(0/1), value; pushes string +- OP_JSON_FROM_FILE: pops path; pushes value or Nil +- OP_JSON_TO_FILE: pops pretty:int(0/1), value, path; pushes 1/0 + +Notes: +- Requires json-c development headers/libs. +- When disabled, functions push empty/neutral values similar to other optional modules. diff --git a/docs/external/libsql.md b/docs/external/libsql.md new file mode 100644 index 0000000..c2df1f9 --- /dev/null +++ b/docs/external/libsql.md @@ -0,0 +1,15 @@ +# libSQL extension (optional) + +- CMake option: FUN_WITH_LIBSQL=ON +- Purpose: SQLite-compatible client using the libSQL (Turso) library. +- Homepage: https://libsql.org/ + +Opcodes: +- OP_LIBSQL_OPEN: pops url_or_path; pushes handle (>0) or 0 +- OP_LIBSQL_CLOSE: pops handle; pushes Nil +- OP_LIBSQL_EXEC: pops sql, handle; pushes rc:int (0=OK) +- OP_LIBSQL_QUERY: pops sql, handle; pushes array + +Notes: +- Uses a SQLite-compatible C API provided by libSQL; behavior is similar to the SQLite backend. +- This module is independent from the SQLite extension; you may enable either or both. diff --git a/docs/external/notcurses.md b/docs/external/notcurses.md new file mode 100644 index 0000000..c596981 --- /dev/null +++ b/docs/external/notcurses.md @@ -0,0 +1,16 @@ +# Notcurses (TUI) extension (optional) + +- CMake option: FUN_WITH_NOTCURSES=ON +- Purpose: Terminal UI capabilities via the Notcurses library. +- Homepage: https://notcurses.com/ + +Opcodes: +- OP_NC_INIT: initialize Notcurses; returns 1 on success, 0 on failure +- OP_NC_SHUTDOWN: shutdown; returns 0 +- OP_NC_CLEAR: clear screen/plane; returns 0 +- OP_NC_DRAW_TEXT: pops text, x, y; draws; returns 0 +- OP_NC_GETCH: pops timeout_ms; returns codepoint or -1 on timeout/error + +Notes: +- Requires Notcurses development headers/libs. +- Behavior may vary across terminals; see the implementation for details. diff --git a/docs/external/openssl.md b/docs/external/openssl.md new file mode 100644 index 0000000..c0a743b --- /dev/null +++ b/docs/external/openssl.md @@ -0,0 +1,31 @@ +# OpenSSL extension (optional) + +- CMake option: FUN_WITH_OPENSSL=ON +- Purpose: provide small crypto helpers backed by OpenSSL. Includes md5, sha256, sha512 helpers. +- Homepage: https://www.openssl.org/ + +Build notes: +- Requires system OpenSSL development headers and libraries. +- On OpenSSL 3.x, legacy MD5_* APIs are deprecated; you may see warnings during build. + +Provided helper/opcodes: +- Function: openssl_md5(data:string) -> string (lowercase hex). Falls back to empty string when the extension is disabled, mirroring other optional modules. +- Function: openssl_sha256(data:string) -> string (lowercase hex). +- Function: openssl_sha512(data:string) -> string (lowercase hex). +- Opcodes: OP_OPENSSL_MD5, OP_OPENSSL_SHA256, OP_OPENSSL_SHA512 (internal mappings for the functions above). + +Quickstart: +- Configure: cmake -S . -B build -DFUN_WITH_OPENSSL=ON +- Build: cmake --build build --target fun +- Run examples: + - ./build/fun examples/crypto/openssl_md5.fun + - ./build/fun examples/crypto/openssl_sha256.fun + - ./build/fun examples/crypto/openssl_sha512.fun + +Example output: +- md5(abc) = 900150983cd24fb0d6963f7d28e17f72 +- md5("") = d41d8cd98f00b204e9800998ecf8427e +- sha256(abc) = ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad +- sha256("") = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +- sha512(abc) = ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f +- sha512("") = cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e diff --git a/docs/external/pcre2.md b/docs/external/pcre2.md new file mode 100644 index 0000000..9e4d9f2 --- /dev/null +++ b/docs/external/pcre2.md @@ -0,0 +1,14 @@ +# PCRE2 (Perl-Compatible Regex) extension (optional) + +- CMake option: FUN_WITH_PCRE2=ON +- Purpose: Advanced regular expressions via PCRE2. +- Homepage: https://www.pcre.org/ + +Opcodes: +- OP_PCRE2_TEST: pops flags, text, pattern; pushes 1/0 +- OP_PCRE2_MATCH: pops flags, text, pattern; pushes match map or Nil +- OP_PCRE2_FINDALL: pops flags, text, pattern; pushes array of match maps + +Notes: +- Requires PCRE2 development headers/libs. +- Flags are backend-specific; see implementation for supported bits. diff --git a/docs/external/pcsc.md b/docs/external/pcsc.md new file mode 100644 index 0000000..c00b171 --- /dev/null +++ b/docs/external/pcsc.md @@ -0,0 +1,17 @@ +# PC/SC (smart cards) extension (optional) + +- CMake option: FUN_WITH_PCSC=ON +- Purpose: Access smart card readers/cards via PC/SC (pcsclite). +- Homepage: https://pcsclite.apdu.fr/ + +Opcodes: +- OP_PCSC_ESTABLISH: returns context id (>0) or 0 +- OP_PCSC_RELEASE: pops ctx id; returns 1/0 +- OP_PCSC_LIST_READERS: pops ctx id; returns array of reader names +- OP_PCSC_CONNECT: pops reader, ctx id; returns handle (>0) or 0 +- OP_PCSC_DISCONNECT: pops handle id; returns 1/0 +- OP_PCSC_TRANSMIT: pops apdu, handle id; returns map with data/SW/rc + +Notes: +- Requires PC/SC lite development headers/libs. +- Behavior and availability depend on platform and reader drivers. diff --git a/docs/external/sqlite.md b/docs/external/sqlite.md new file mode 100644 index 0000000..e246a76 --- /dev/null +++ b/docs/external/sqlite.md @@ -0,0 +1,15 @@ +# SQLite extension (optional) + +- CMake option: FUN_WITH_SQLITE=ON +- Purpose: Access SQLite databases via the native C API. +- Homepage: https://www.sqlite.org/ + +Opcodes: +- OP_SQLITE_OPEN: pops path; pushes handle (>0) or 0 +- OP_SQLITE_CLOSE: pops handle; pushes Nil +- OP_SQLITE_EXEC: pops sql, handle; pushes rc:int (0=OK) +- OP_SQLITE_QUERY: pops sql, handle; pushes array + +Notes: +- Requires SQLite development headers/libs. +- See also: `libSQL` for a compatible alternative backend. diff --git a/docs/external/tcltk.md b/docs/external/tcltk.md new file mode 100644 index 0000000..97cc429 --- /dev/null +++ b/docs/external/tcltk.md @@ -0,0 +1,16 @@ +# Tcl/Tk (GUI) extension (optional) +- CMake option: FUN_WITH_TCLTK=ON +- Purpose: Basic GUI functionality via Tcl/Tk. +- Homepage: https://www.tcl.tk/ +Opcodes: +- OP_TK_EVAL: pops script string; pushes rc (0=OK) +- OP_TK_RESULT: pushes last Tcl result string +- OP_TK_LOOP: enters main event loop; pushes Nil when done +- OP_TK_WM_TITLE: pops title string; sets window title; pushes rc +- OP_TK_LABEL: pops text, id; creates/updates label .id; pushes rc +- OP_TK_BUTTON: pops text, id; creates/updates button .id; pushes rc +- OP_TK_PACK: pops id; packs .id; pushes rc +- OP_TK_BIND: pops command, event, id; binds; pushes rc +Notes: +- Requires Tcl/Tk development headers/libs. +- GUI behavior depends on your desktop environment/window manager. diff --git a/docs/external/xml2.md b/docs/external/xml2.md new file mode 100644 index 0000000..3a86071 --- /dev/null +++ b/docs/external/xml2.md @@ -0,0 +1,15 @@ +# XML (libxml2) extension (optional) + +- CMake option: FUN_WITH_XML2=ON +- Purpose: Minimal XML parsing helpers using libxml2. +- Homepage: http://xmlsoft.org/ + +Opcodes: +- OP_XML_PARSE: pops text; pushes doc handle (>0) or 0 +- OP_XML_ROOT: pops doc handle; pushes node handle (>0) or 0 +- OP_XML_NAME: pops node handle; pushes string (node name) +- OP_XML_TEXT: pops node handle; pushes string (concatenated text) + +Notes: +- Requires libxml2 development headers/libs. +- On many systems, the include path is `/usr/include/libxml2`. diff --git a/docs/faq.md b/docs/faq.md index 5e98acf..1fbc565 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -5,7 +5,7 @@ Answers to common questions. ## I built Fun but includes aren't found Set `FUN_LIB_DIR` to the repository's `./lib` directory when running without installation: ``` -FUN_LIB_DIR=./lib ./build_debug/fun examples/hello.fun +FUN_LIB_DIR=./lib ./build/fun examples/hello.fun ``` See [includes.md](./includes.md). diff --git a/docs/opcodes.md b/docs/opcodes.md index d1d9dbc..5ea6c86 100644 --- a/docs/opcodes.md +++ b/docs/opcodes.md @@ -205,7 +205,9 @@ This document provides an overview of the available VM opcodes implemented under ## OpenSSL (optional) - OP_OPENSSL_MD5: Compute MD5 digest and return lowercase hex string; pops data:string; pushes md5:string. - - Requires building with `-DFUN_WITH_OPENSSL=ON`. When disabled, this opcode still exists but returns an empty string to match other optional extensions’ fallback behavior. +- OP_OPENSSL_SHA256: Compute SHA‑256 digest and return lowercase hex string; pops data:string; pushes sha256:string. +- OP_OPENSSL_SHA512: Compute SHA‑512 digest and return lowercase hex string; pops data:string; pushes sha512:string. + - Requires building with `-DFUN_WITH_OPENSSL=ON`. When disabled, these opcodes still exist but return an empty string to match other optional extensions’ fallback behavior. ## PCRE2 (Regex) diff --git a/docs/rust.md b/docs/rust.md index 71ca53b..9145916 100644 --- a/docs/rust.md +++ b/docs/rust.md @@ -27,8 +27,8 @@ Rust integration is optional and gated by a CMake flag. Default builds usually h Enable it for a configured profile (Debug or Release): - Debug example: - cmake -S . -B build_debug -DFUN_WITH_RUST=ON - cmake --build build_debug --target fun + cmake -S . -B build -DFUN_WITH_RUST=ON + cmake --build build --target fun - Release example: cmake -S . -B build_release -DFUN_WITH_RUST=ON @@ -137,11 +137,11 @@ Once wired, expose the opcode via a builtin function or directly in bytecode. Th Run the example: 1) Build with Rust enabled (Debug): - cmake -S . -B build_debug -DFUN_WITH_RUST=ON - cmake --build build_debug --target fun + cmake -S . -B build -DFUN_WITH_RUST=ON + cmake --build build --target fun 2) Execute the script: - build_debug/fun examples/rust_hello.fun + build/fun examples/rust_hello.fun Expected output: Hello from Rust ops! diff --git a/docs/testing.md b/docs/testing.md index 410e18a..acb140b 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -18,7 +18,7 @@ To list targets with CMake directly, consult your IDE or run the build system’ Debug profile example: ``` -cmake --build build_debug --target test_opcodes && ./build/test_opcodes +cmake --build build --target test_opcodes && ./build/test_opcodes ``` Release profile example: @@ -30,14 +30,14 @@ cmake --build build_release --target test_opcodes && ./build/test_opcodes If `fun_test` exists in your configuration: ``` -cmake --build build_debug --target fun_test && ./build/fun_test +cmake --build build --target fun_test && ./build/fun_test ``` You can also invoke CTest to run any tests registered with `add_test()`: ``` -cmake --build build_debug --target test -ctest --test-dir build_debug -j +cmake --build build --target test +ctest --test-dir build -j ``` ## Adding new tests diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index e8dfec0..95fb6cf 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -40,8 +40,8 @@ Fix: - Build with `-DFUN_WITH_REPL=ON` and rebuild the `fun` target. Then launch without arguments: ``` -cmake -S . -B build_debug -DFUN_WITH_REPL=ON -cmake --build build_debug --target fun +cmake -S . -B build -DFUN_WITH_REPL=ON +cmake --build build --target fun FUN_LIB_DIR="$(pwd)/lib" ./build/fun ``` diff --git a/examples/crypto/openssl_sha256.fun b/examples/crypto/openssl_sha256.fun new file mode 100644 index 0000000..f6f564b --- /dev/null +++ b/examples/crypto/openssl_sha256.fun @@ -0,0 +1,28 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-02-19 + */ + +// OpenSSL SHA-256 example +// Enable with -DFUN_WITH_OPENSSL=ON during build for real hashing. + +s = "abc" +d = openssl_sha256(s) +print("sha256(abc) = " + d) + +// Another quick check (empty string) +e = "" +print("sha256(\"\") = " + openssl_sha256(e)) + +/* Expected output: +sha256(abc) = ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad +sha256("") = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +*/ diff --git a/examples/crypto/openssl_sha512.fun b/examples/crypto/openssl_sha512.fun new file mode 100644 index 0000000..f6f8eb7 --- /dev/null +++ b/examples/crypto/openssl_sha512.fun @@ -0,0 +1,28 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-02-19 + */ + +// OpenSSL SHA-512 example +// Enable with -DFUN_WITH_OPENSSL=ON during build for real hashing. + +s = "abc" +d = openssl_sha512(s) +print("sha512(abc) = " + d) + +// Another quick check (empty string) +e = "" +print("sha512(\"\") = " + openssl_sha512(e)) + +/* Expected output: +sha512(abc) = ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f +sha512("") = cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e +*/ diff --git a/make b/make index 4f7dd8d..01e2fbd 100755 --- a/make +++ b/make @@ -32,6 +32,7 @@ if [ "$target" = "all" ]; then -DFUN_WITH_INI=ON \ -DFUN_WITH_NOTCURSES=ON \ -DFUN_WITH_CPP=ON \ + -DFUN_WITH_OPENSSL=ON \ && cmake --build build --target fun elif [ "$target" = "all_debug" ]; then rm -rf build \ @@ -48,6 +49,7 @@ elif [ "$target" = "all_debug" ]; then -DFUN_WITH_INI=ON \ -DFUN_WITH_NOTCURSES=ON \ -DFUN_WITH_CPP=ON \ + -DFUN_WITH_OPENSSL=ON \ -DFUN_DEBUG=ON \ && cmake --build build --target fun elif [ "$target" = "alpine" ]; then @@ -62,6 +64,7 @@ elif [ "$target" = "alpine" ]; then -DFUN_WITH_JSON=ON \ -DFUN_WITH_INI=ON \ -DFUN_WITH_CPP=ON \ + -DFUN_WITH_OPENSSL=ON \ && cmake --build build --target fun elif [ "$target" = "cpp_all" ]; then rm -rf build \ @@ -78,6 +81,7 @@ elif [ "$target" = "cpp_all" ]; then -DFUN_WITH_INI=ON \ -DFUN_WITH_NOTCURSES=ON \ -DFUN_WITH_CPP=ON \ + -DFUN_WITH_OPENSSL=ON \ && cmake --build build --target fun elif [ "$target" = "cpp_minimal" ]; then rm -rf build \ @@ -133,6 +137,7 @@ elif [ "$target" = "rust_all" ]; then -DFUN_WITH_INI=ON \ -DFUN_WITH_RUST=ON \ -DFUN_WITH_CPP=ON \ + -DFUN_WITH_OPENSSL=ON \ -DFUN_DEBUG=OFF \ && cmake --build build --target fun elif [ "$target" = "rust_minimal" ]; then diff --git a/src/bytecode.c b/src/bytecode.c index 13e2ce9..caf6029 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -170,6 +170,8 @@ static const char *opcode_name(OpCode op) { case OP_PCRE2_MATCH: return "PCRE2_MATCH"; case OP_PCRE2_FINDALL: return "PCRE2_FINDALL"; case OP_OPENSSL_MD5: return "OPENSSL_MD5"; + case OP_OPENSSL_SHA256: return "OPENSSL_SHA256"; + case OP_OPENSSL_SHA512: return "OPENSSL_SHA512"; case OP_INI_LOAD: return "INI_LOAD"; case OP_INI_FREE: return "INI_FREE"; case OP_INI_GET_STRING: return "INI_GET_STRING"; diff --git a/src/bytecode.h b/src/bytecode.h index 92f9867..446dd21 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -183,6 +183,8 @@ typedef enum { // OpenSSL (optional) OP_OPENSSL_MD5, // pops data string; pushes md5 hex string + OP_OPENSSL_SHA256, // pops data string; pushes sha256 hex string + OP_OPENSSL_SHA512, // pops data string; pushes sha512 hex string // INI (iniparser 4.2.6) optional OP_INI_LOAD, // pops path; pushes handle (>0) or 0 diff --git a/src/external/openssl.c b/src/external/openssl.c new file mode 100644 index 0000000..cbec06e --- /dev/null +++ b/src/external/openssl.c @@ -0,0 +1,130 @@ +/* +* This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-02-19 + */ + + /* + * OpenSSL integration helpers (MD5, SHA-256, SHA-512) + */ + +#ifdef FUN_WITH_OPENSSL +# include +#endif +#include + +/* Compute MD5 hex of input buffer; returns malloc'ed C string (lowercase hex). + * Caller must free(). Returns NULL on failure. When OpenSSL is disabled, + * returns an allocated empty string ("") to keep behavior consistent with + * other optional extensions. */ +static char *fun_openssl_md5_hex(const unsigned char *data, size_t len) { + static const char hexdig[] = "0123456789abcdef"; + if (!data && len != 0) return NULL; +#ifdef FUN_WITH_OPENSSL + const EVP_MD *md = EVP_md5(); + if (!md) return NULL; + int dlen = EVP_MD_get_size(md); + if (dlen <= 0) return NULL; + unsigned char *digest = (unsigned char*)malloc((size_t)dlen); + if (!digest) return NULL; + unsigned int out_len = 0; + int ok; + if (len == 0) { + // EVP_Digest handles zero-length fine as well + ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); + } else { + ok = EVP_Digest(data, len, digest, &out_len, md, NULL); + } + if (ok != 1 || (int)out_len != dlen) { free(digest); return NULL; } + char *hex = (char*)malloc((size_t)dlen * 2 + 1); + if (!hex) { free(digest); return NULL; } + for (int i = 0; i < dlen; ++i) { + hex[2*i] = hexdig[(digest[i] >> 4) & 0xF]; + hex[2*i+1] = hexdig[digest[i] & 0xF]; + } + hex[dlen * 2] = '\0'; + free(digest); + return hex; +#else + char *hex = (char*)malloc(1); + if (hex) hex[0] = '\0'; + return hex; +#endif +} + +/* Compute SHA-256 hex of input buffer; returns malloc'ed C string (lowercase hex). + * Fallback when OpenSSL disabled: empty string. */ +static char *fun_openssl_sha256_hex(const unsigned char *data, size_t len) { + static const char hexdig[] = "0123456789abcdef"; + if (!data && len != 0) return NULL; +#ifdef FUN_WITH_OPENSSL + const EVP_MD *md = EVP_sha256(); + if (!md) return NULL; + int dlen = EVP_MD_get_size(md); + if (dlen <= 0) return NULL; + unsigned char *digest = (unsigned char*)malloc((size_t)dlen); + if (!digest) return NULL; + unsigned int out_len = 0; + int ok; + if (len == 0) { + ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); + } else { + ok = EVP_Digest(data, len, digest, &out_len, md, NULL); + } + if (ok != 1 || (int)out_len != dlen) { free(digest); return NULL; } + char *hex = (char*)malloc((size_t)dlen * 2 + 1); + if (!hex) { free(digest); return NULL; } + for (int i = 0; i < dlen; ++i) { + hex[2*i] = hexdig[(digest[i] >> 4) & 0xF]; + hex[2*i+1] = hexdig[digest[i] & 0xF]; + } + hex[dlen * 2] = '\0'; + free(digest); + return hex; +#else + char *hex = (char*)malloc(1); + if (hex) hex[0] = '\0'; + return hex; +#endif +} + +/* Compute SHA-512 hex of input buffer; returns malloc'ed C string (lowercase hex). + * Fallback when OpenSSL disabled: empty string. */ +static char *fun_openssl_sha512_hex(const unsigned char *data, size_t len) { + static const char hexdig[] = "0123456789abcdef"; + if (!data && len != 0) return NULL; +#ifdef FUN_WITH_OPENSSL + const EVP_MD *md = EVP_sha512(); + if (!md) return NULL; + int dlen = EVP_MD_get_size(md); + if (dlen <= 0) return NULL; + unsigned char *digest = (unsigned char*)malloc((size_t)dlen); + if (!digest) return NULL; + unsigned int out_len = 0; + int ok; + if (len == 0) { + ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); + } else { + ok = EVP_Digest(data, len, digest, &out_len, md, NULL); + } + if (ok != 1 || (int)out_len != dlen) { free(digest); return NULL; } + char *hex = (char*)malloc((size_t)dlen * 2 + 1); + if (!hex) { free(digest); return NULL; } + for (int i = 0; i < dlen; ++i) { + hex[2*i] = hexdig[(digest[i] >> 4) & 0xF]; + hex[2*i+1] = hexdig[digest[i] & 0xF]; + } + hex[dlen * 2] = '\0'; + free(digest); + return hex; +#else + char *hex = (char*)malloc(1); + if (hex) hex[0] = '\0'; + return hex; +#endif +} diff --git a/src/external/opensssl.c b/src/external/opensssl.c deleted file mode 100644 index a4fcf26..0000000 --- a/src/external/opensssl.c +++ /dev/null @@ -1,47 +0,0 @@ -/* -* This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - * - * Added: 2026-02-19 - */ - - /* - * OpenSSL integration helpers (currently MD5 only) - */ - -#ifdef FUN_WITH_OPENSSL -# include -#endif -#include - -/* Compute MD5 hex of input buffer; returns malloc'ed C string (lowercase hex). - * Caller must free(). Returns NULL on failure. When OpenSSL is disabled, - * returns an allocated empty string ("") to keep behavior consistent with - * other optional extensions. */ -static char *fun_openssl_md5_hex(const unsigned char *data, size_t len) { - static const char hexdig[] = "0123456789abcdef"; - if (!data && len != 0) return NULL; -#ifdef FUN_WITH_OPENSSL - unsigned char digest[MD5_DIGEST_LENGTH]; - MD5_CTX ctx; - if (MD5_Init(&ctx) != 1) return NULL; - if (len && MD5_Update(&ctx, data, len) != 1) return NULL; - if (MD5_Final(digest, &ctx) != 1) return NULL; - char *hex = (char*)malloc(MD5_DIGEST_LENGTH * 2 + 1); - if (!hex) return NULL; - for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) { - hex[2*i] = hexdig[(digest[i] >> 4) & 0xF]; - hex[2*i+1] = hexdig[digest[i] & 0xF]; - } - hex[MD5_DIGEST_LENGTH * 2] = '\0'; - return hex; -#else - char *hex = (char*)malloc(1); - if (hex) hex[0] = '\0'; - return hex; -#endif -} diff --git a/src/parser.c b/src/parser.c index aa16896..b27055e 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1197,7 +1197,7 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) free(name); return 1; } - /* OpenSSL (md5) */ + /* OpenSSL (md5/sha256/sha512) */ if (strcmp(name, "openssl_md5") == 0) { (*pos)++; /* '(' */ if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "openssl_md5 expects (data)"); free(name); return 0; } @@ -1206,6 +1206,22 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) free(name); return 1; } + if (strcmp(name, "openssl_sha256") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "openssl_sha256 expects (data)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after openssl_sha256 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_OPENSSL_SHA256, 0); + free(name); + return 1; + } + if (strcmp(name, "openssl_sha512") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "openssl_sha512 expects (data)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after openssl_sha512 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_OPENSSL_SHA512, 0); + free(name); + return 1; + } /* PCSC builtins */ if (strcmp(name, "pcsc_establish") == 0) { (*pos)++; /* '(' */ diff --git a/src/vm.c b/src/vm.c index a64f80c..19d9181 100644 --- a/src/vm.c +++ b/src/vm.c @@ -73,7 +73,7 @@ #include "external/sqlite.c" #include "external/tcltk.c" #include "external/xml2.c" -#include "external/opensssl.c" +#include "external/openssl.c" /* forward declarations for include mapping used in error reporting */ extern char *preprocess_includes(const char *src); @@ -869,8 +869,10 @@ void vm_run(VM *vm, Bytecode *entry) { #include "vm/curl/download.c" #endif - /* OpenSSL ops (md5) */ + /* OpenSSL ops (md5/sha256/sha512) */ #include "vm/openssl/md5.c" + #include "vm/openssl/sha256.c" + #include "vm/openssl/sha512.c" /* Tk (Tcl/Tk) ops */ #ifdef FUN_WITH_TCLTK diff --git a/src/vm/openssl/sha256.c b/src/vm/openssl/sha256.c new file mode 100644 index 0000000..bed86f0 --- /dev/null +++ b/src/vm/openssl/sha256.c @@ -0,0 +1,16 @@ +/** + * OpenSSL SHA-256 builtin + */ +case OP_OPENSSL_SHA256: { + Value vdata = pop_value(vm); + char *s = value_to_string_alloc(&vdata); + free_value(vdata); + if (!s) { push_value(vm, make_string("")); break; } + char *hex = fun_openssl_sha256_hex((const unsigned char*)s, strlen(s)); + free(s); + if (!hex) { push_value(vm, make_string("")); break; } + Value out = make_string(hex); + free(hex); + push_value(vm, out); + break; +} diff --git a/src/vm/openssl/sha512.c b/src/vm/openssl/sha512.c new file mode 100644 index 0000000..2e091b1 --- /dev/null +++ b/src/vm/openssl/sha512.c @@ -0,0 +1,16 @@ +/** + * OpenSSL SHA-512 builtin + */ +case OP_OPENSSL_SHA512: { + Value vdata = pop_value(vm); + char *s = value_to_string_alloc(&vdata); + free_value(vdata); + if (!s) { push_value(vm, make_string("")); break; } + char *hex = fun_openssl_sha512_hex((const unsigned char*)s, strlen(s)); + free(s); + if (!hex) { push_value(vm, make_string("")); break; } + Value out = make_string(hex); + free(hex); + push_value(vm, out); + break; +}