From 689a716499e96bc512ee02b6d75a2f6128061365 Mon Sep 17 00:00:00 2001 From: hanez Date: Tue, 23 Dec 2025 23:19:20 +0100 Subject: [PATCH 1/5] Some more Tk fun. (0.37.18) --- CMakeLists.txt | 2 +- examples/test_dec_to_hex.fun | 0 examples/test_include.fun | 1 + examples/tk_file_manager.fun | 59 ++++++++++++++++++++++++++++++++++++ examples/tk_hello.fun | 1 - examples/tk_testing.fun | 38 +++++++++++++++++++++++ lib/ui/tk.fun | 24 +++++++++++++++ src/bytecode.h | 26 ++++++++++------ src/parser.c | 35 +++++++++++++++++++++ src/vm.c | 2 ++ src/vm.h | 3 ++ src/vm/os/list_dir.c | 49 ++++++++++++++++++++++++++++++ src/vm/tk/bind.c | 54 +++++++++++++++++++++++++++++++++ 13 files changed, 282 insertions(+), 12 deletions(-) mode change 100644 => 100755 examples/test_dec_to_hex.fun mode change 100644 => 100755 examples/test_include.fun create mode 100755 examples/tk_file_manager.fun create mode 100755 examples/tk_testing.fun create mode 100644 src/vm/os/list_dir.c create mode 100644 src/vm/tk/bind.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 328dea4..8118674 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.37.17 LANGUAGES C) +project(fun VERSION 0.37.18 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/examples/test_dec_to_hex.fun b/examples/test_dec_to_hex.fun old mode 100644 new mode 100755 diff --git a/examples/test_include.fun b/examples/test_include.fun old mode 100644 new mode 100755 index bf218b7..a5acd8b --- a/examples/test_include.fun +++ b/examples/test_include.fun @@ -12,6 +12,7 @@ */ #include + print(dec_to_hex(255)) /* Expected output: diff --git a/examples/tk_file_manager.fun b/examples/tk_file_manager.fun new file mode 100755 index 0000000..1614c45 --- /dev/null +++ b/examples/tk_file_manager.fun @@ -0,0 +1,59 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-12-23 + */ + +#include + +print("Initializing Fun File Manager...") +tk = TK() +tk.title("Fun File Manager") + +// Current directory +dir = env("PWD") +if (dir == "") + dir = "." +print("Current directory: " + dir) + +tk.label("path", "Current Dir: " + dir) +tk.pack("path") + +// Files listbox +tk.listbox("files") +tk.pack("files") + +// Populate listbox +fun refresh(tk, dir) + print("Refreshing file list for: " + dir) + tk.clear("files") + files = os_list_dir(dir) + print("Found " + to_string(len(files)) + " entries.") + for f in files + tk.insert("files", "end", f) + +refresh(tk, dir) + +// Refresh button +// Using tk.eval for button because tk.button currently exits the app. +tk.eval("button .refresh -text {Refresh} -command {puts {Refresh requested}}") +tk.pack("refresh") + +// Exit button +tk.button("exit", "Exit") +tk.pack("exit") + +print("Entering Tk loop...") +tk.loop() +print("Tk loop exited.") + +/* Expected output: +A GUI... ;) +*/ diff --git a/examples/tk_hello.fun b/examples/tk_hello.fun index e4a3fe2..88d2129 100755 --- a/examples/tk_hello.fun +++ b/examples/tk_hello.fun @@ -31,4 +31,3 @@ tk.loop() /* Expected output: A GUI... ;) */ - diff --git a/examples/tk_testing.fun b/examples/tk_testing.fun new file mode 100755 index 0000000..9bff362 --- /dev/null +++ b/examples/tk_testing.fun @@ -0,0 +1,38 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-12-23 + */ + +#include + +print("Testing os_list_dir...") +files = os_list_dir(".") +print("Found " + to_string(len(files)) + " files.") +if len(files) > 0 + print("First file: " + files[0]) + +print("Testing tk_bind parsing...") +// We can't easily test Tk without an X server, but we can see if it crashes. +// If built with FUN_WITH_TCLTK, it should at least initialize. +// We use tk_eval to avoid full loop. +rc = tk_eval("set x 1") +print("tk_eval rc: " + to_string(rc)) +if rc == 0 + print("Tcl Result: " + tk_result()) + +/* Expected output: +Testing os_list_dir... +Found 23 files. +First file: build +Testing tk_bind parsing... +tk_eval rc: 0 +Tcl Result: 1 +*/ diff --git a/lib/ui/tk.fun b/lib/ui/tk.fun index e39368d..ca062b5 100644 --- a/lib/ui/tk.fun +++ b/lib/ui/tk.fun @@ -31,6 +31,30 @@ class TK() fun pack(this, id) return tk_pack(to_string(id)) + // Create a listbox widget + fun listbox(this, id) + return tk_eval("listbox ." + to_string(id)) + + // Insert text into a widget (like listbox) at index + fun insert(this, id, index, text) + return tk_eval("." + to_string(id) + " insert " + to_string(index) + " {" + to_string(text) + "}") + + // Clear a widget (like listbox) + fun clear(this, id) + return tk_eval("." + to_string(id) + " delete 0 end") + + // Bind an event to a command + fun bind(this, id, event, cmd) + return tk_bind(to_string(id), to_string(event), to_string(cmd)) + + // Evaluate raw Tcl script + fun eval(this, script) + return tk_eval(to_string(script)) + + // Get last Tcl result + fun result(this) + return tk_result() + // Enter the Tk event loop (blocks until windows are closed) fun loop(this) return tk_loop() diff --git a/src/bytecode.h b/src/bytecode.h index 7a14da3..79a457d 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -35,7 +35,7 @@ typedef enum { OP_EQ, // a == b -> push 1/0 OP_NEQ, // a != b -> push 1/0 - OP_POP, // dApache-2.0ard top of stack + OP_POP, // discard top of stack OP_JUMP, // unconditional jump OP_JUMP_IF_FALSE, // jump if top of stack is false (0) @@ -196,15 +196,6 @@ typedef enum { OP_XML_NAME, // pops node handle; pushes string (node name) OP_XML_TEXT, // pops node handle; pushes string (node text) - // Tk (Tcl/Tk) optional minimal API - OP_TK_EVAL, // pops script string; pushes int rc (0 = OK) - OP_TK_RESULT, // pushes string: last Tcl result - OP_TK_LOOP, // enters Tk 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 - // Sockets (UNIX platforms) OP_SOCK_TCP_LISTEN, // pops backlog, port; returns listen fd (>0) or 0 OP_SOCK_TCP_ACCEPT, // pops listen fd; returns client fd (>0) or 0 @@ -218,6 +209,21 @@ typedef enum { // process control OP_EXIT, // pops code (or uses operand) and terminates script with exit code + // OS additions + OP_OS_LIST_DIR, // pops path string; pushes array of strings + + // Tk additions + OP_TK_BIND, // pops command, event, id; binds event to command + + // Tk (Tcl/Tk) optional minimal API + OP_TK_EVAL, // pops script string; pushes int rc (0 = OK) + OP_TK_RESULT, // pushes string: last Tcl result + OP_TK_LOOP, // enters Tk 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 + // exceptions (minimal) OP_TRY_PUSH, // operand = handler ip; push handler onto try-stack OP_TRY_POP, // pop current handler diff --git a/src/parser.c b/src/parser.c index fea4e2f..6788539 100644 --- a/src/parser.c +++ b/src/parser.c @@ -757,6 +757,14 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) 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; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after os_list_dir arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_OS_LIST_DIR, 0); + free(name); + return 1; + } /* JSON builtins */ if (strcmp(name, "json_parse") == 0) { (*pos)++; /* '(' */ @@ -853,6 +861,33 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) free(name); return 1; } + if (strcmp(name, "tk_bind") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tk_bind expects (id, event, cmd)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "tk_bind expects 3 args"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tk_bind expects 3 args"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "tk_bind expects 3 args"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tk_bind expects 3 args"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after tk_bind args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_TK_BIND, 0); + free(name); + return 1; + } + if (strcmp(name, "tk_eval") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tk_eval expects (script)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after tk_eval arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_TK_EVAL, 0); + free(name); + return 1; + } + if (strcmp(name, "tk_result") == 0) { + (*pos)++; /* '(' */ + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "tk_result expects ()"); free(name); return 0; } + bytecode_add_instruction(bc, OP_TK_RESULT, 0); + free(name); + return 1; + } if (strcmp(name, "json_from_file") == 0) { (*pos)++; /* '(' */ if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "json_from_file expects (path)"); free(name); return 0; } diff --git a/src/vm.c b/src/vm.c index dd96216..3e90d12 100644 --- a/src/vm.c +++ b/src/vm.c @@ -741,6 +741,7 @@ void vm_run(VM *vm, Bytecode *entry) { #include "vm/tk/label.c" #include "vm/tk/button.c" #include "vm/tk/pack.c" + #include "vm/tk/bind.c" #endif /* SQLite ops */ @@ -783,6 +784,7 @@ void vm_run(VM *vm, Bytecode *entry) { #include "vm/typeof.c" #include "vm/uclamp.c" #include "vm/sclamp.c" + #include "vm/os/list_dir.c" default: if (!opcode_is_valid(inst.op)) { diff --git a/src/vm.h b/src/vm.h index 6ee2f7f..4fd2281 100644 --- a/src/vm.h +++ b/src/vm.h @@ -49,6 +49,9 @@ static const char *opcode_names[] = { "XML_PARSE","XML_ROOT","XML_NAME","XML_TEXT", "SOCK_TCP_LISTEN","SOCK_TCP_ACCEPT","SOCK_TCP_CONNECT","SOCK_SEND","SOCK_RECV","SOCK_CLOSE","SOCK_UNIX_LISTEN","SOCK_UNIX_CONNECT", "EXIT", + "OS_LIST_DIR", + "TK_BIND", + "TK_EVAL","TK_RESULT","TK_LOOP","TK_WM_TITLE","TK_LABEL","TK_BUTTON","TK_PACK", "TRY_PUSH","TRY_POP","THROW" }; diff --git a/src/vm/os/list_dir.c b/src/vm/os/list_dir.c new file mode 100644 index 0000000..5976c60 --- /dev/null +++ b/src/vm/os/list_dir.c @@ -0,0 +1,49 @@ +/** + * 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: 2025-12-23 + */ + +case OP_OS_LIST_DIR: { + /* pops path string; pushes array of strings */ + Value pathv = pop_value(vm); + char *path = value_to_string_alloc(&pathv); + free_value(pathv); + + Value arr = make_array_from_values(NULL, 0); + if (path) { + /* + * Using 'ls -1' as a fallback to avoid dirent.h conflicts on some systems. + * We escape the path minimally for the shell. + */ + size_t slen = strlen(path) + 16; + char *cmd = (char*)malloc(slen); + if (cmd) { + snprintf(cmd, slen, "ls -1 \"%s\"", path); + FILE *fp = popen(cmd, "r"); + if (fp) { + char line[1024]; + while (fgets(line, sizeof(line), fp)) { + /* Strip trailing newline */ + size_t l = strlen(line); + if (l > 0 && line[l-1] == '\n') line[l-1] = '\0'; + if (l > 1 && line[l-2] == '\r') line[l-2] = '\0'; + + if (line[0] != '\0') { + array_push(&arr, make_string(line)); + } + } + pclose(fp); + } + free(cmd); + } + free(path); + } + push_value(vm, arr); + break; +} diff --git a/src/vm/tk/bind.c b/src/vm/tk/bind.c new file mode 100644 index 0000000..78fb732 --- /dev/null +++ b/src/vm/tk/bind.c @@ -0,0 +1,54 @@ +/** + * 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: 2025-12-23 + */ + +/* TK_BIND */ +case OP_TK_BIND: { + /* stack: ..., id, event, command -> rc */ + Value cmdv = pop_value(vm); + Value eventv = pop_value(vm); + Value idv = pop_value(vm); + char *cmd = value_to_string_alloc(&cmdv); + char *event = value_to_string_alloc(&eventv); + char *id = value_to_string_alloc(&idv); + free_value(cmdv); + free_value(eventv); + free_value(idv); + + if (!id || !event || !cmd) { + if (id) free(id); + if (event) free(event); + if (cmd) free(cmd); + push_value(vm, make_int(-1)); + break; + } + + /* + * Construct: bind .id {command} + * Note: for now, command is just raw Tcl as well, + * but could be extended to call Fun functions if we had a callback mechanism. + */ + size_t slen = strlen(id) + strlen(event) + strlen(cmd) + 32; + char *script = (char*)malloc(slen); + if (!script) { + free(id); free(event); free(cmd); + push_value(vm, make_int(-1)); + break; + } + + snprintf(script, slen, "bind .%s %s {%s}", id, event, cmd); + int rc = fun_tk_eval_script(script); + free(script); + free(id); + free(event); + free(cmd); + push_value(vm, make_int(rc)); + break; +} From bfe1926fe6252a281c6c961a105f6475edfb3871 Mon Sep 17 00:00:00 2001 From: hanez Date: Wed, 24 Dec 2025 00:49:25 +0100 Subject: [PATCH 2/5] Some more error debugging for indentation fun. (0.37.19) --- CMakeLists.txt | 2 +- examples/error/test_indent.fun | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 examples/error/test_indent.fun diff --git a/CMakeLists.txt b/CMakeLists.txt index 8118674..1f21240 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.37.18 LANGUAGES C) +project(fun VERSION 0.37.19 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/examples/error/test_indent.fun b/examples/error/test_indent.fun new file mode 100644 index 0000000..a052832 --- /dev/null +++ b/examples/error/test_indent.fun @@ -0,0 +1,21 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-12-24 + */ + +if (true) + print("This should fail if 4 spaces are not allowed") +else + print("This is 2 spaces") + +/* Expected output: +This should fail if 4 spaces are not allowed +*/ From 3e64bfb8f935415c4f3f1f9c7db8c7704bda12fe Mon Sep 17 00:00:00 2001 From: hanez Date: Wed, 24 Dec 2025 01:07:07 +0100 Subject: [PATCH 3/5] Some fixed fail on last commit. (0.37.20) --- CMakeLists.txt | 2 +- examples/error/test_indent.fun | 0 2 files changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 examples/error/test_indent.fun diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f21240..b18c403 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.37.19 LANGUAGES C) +project(fun VERSION 0.37.20 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/examples/error/test_indent.fun b/examples/error/test_indent.fun old mode 100644 new mode 100755 From 033bf53889f5b4e3fcfae3403a173d4f21734a0c Mon Sep 17 00:00:00 2001 From: hanez Date: Sat, 27 Dec 2025 12:20:22 +0100 Subject: [PATCH 4/5] Some refactoring. (0.37.21) --- CMakeLists.txt | 2 +- src/{ext => external}/curl.c | 0 src/{ext => external}/ini.c | 0 src/{ext => external}/json.c | 0 src/{ext => external}/libsql.c | 0 src/{ext => external}/pcre2.c | 0 src/{ext => external}/pcsc.c | 0 src/{ext => external}/sqlite.c | 0 src/{ext => external}/tcltk.c | 0 src/{ext => external}/xml2.c | 0 src/vm.c | 18 +++++++++--------- 11 files changed, 10 insertions(+), 10 deletions(-) rename src/{ext => external}/curl.c (100%) rename src/{ext => external}/ini.c (100%) rename src/{ext => external}/json.c (100%) rename src/{ext => external}/libsql.c (100%) rename src/{ext => external}/pcre2.c (100%) rename src/{ext => external}/pcsc.c (100%) rename src/{ext => external}/sqlite.c (100%) rename src/{ext => external}/tcltk.c (100%) rename src/{ext => external}/xml2.c (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index b18c403..f741f91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.37.20 LANGUAGES C) +project(fun VERSION 0.37.21 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/src/ext/curl.c b/src/external/curl.c similarity index 100% rename from src/ext/curl.c rename to src/external/curl.c diff --git a/src/ext/ini.c b/src/external/ini.c similarity index 100% rename from src/ext/ini.c rename to src/external/ini.c diff --git a/src/ext/json.c b/src/external/json.c similarity index 100% rename from src/ext/json.c rename to src/external/json.c diff --git a/src/ext/libsql.c b/src/external/libsql.c similarity index 100% rename from src/ext/libsql.c rename to src/external/libsql.c diff --git a/src/ext/pcre2.c b/src/external/pcre2.c similarity index 100% rename from src/ext/pcre2.c rename to src/external/pcre2.c diff --git a/src/ext/pcsc.c b/src/external/pcsc.c similarity index 100% rename from src/ext/pcsc.c rename to src/external/pcsc.c diff --git a/src/ext/sqlite.c b/src/external/sqlite.c similarity index 100% rename from src/ext/sqlite.c rename to src/external/sqlite.c diff --git a/src/ext/tcltk.c b/src/external/tcltk.c similarity index 100% rename from src/ext/tcltk.c rename to src/external/tcltk.c diff --git a/src/ext/xml2.c b/src/external/xml2.c similarity index 100% rename from src/ext/xml2.c rename to src/external/xml2.c diff --git a/src/vm.c b/src/vm.c index 3e90d12..3c714c6 100644 --- a/src/vm.c +++ b/src/vm.c @@ -40,15 +40,15 @@ #include "vm.h" // Optional by extensions commonly used code. #ifdef's are in each single file. -#include "ext/curl.c" -#include "ext/ini.c" -#include "ext/json.c" -#include "ext/libsql.c" -#include "ext/pcsc.c" -#include "ext/pcre2.c" -#include "ext/sqlite.c" -#include "ext/tcltk.c" -#include "ext/xml2.c" +#include "external/curl.c" +#include "external/ini.c" +#include "external/json.c" +#include "external/libsql.c" +#include "external/pcsc.c" +#include "external/pcre2.c" +#include "external/sqlite.c" +#include "external/tcltk.c" +#include "external/xml2.c" /* forward declarations for include mapping used in error reporting */ extern char *preprocess_includes(const char *src); From 21f437ebaf70d522b1249a530242a4edaa8053eb Mon Sep 17 00:00:00 2001 From: hanez Date: Sun, 28 Dec 2025 00:54:55 +0100 Subject: [PATCH 5/5] Added RIPEMD-160 support to stdlib in lib/crypt/ripemd160.fun (BROKEN) and some more test scripts to examples/. (0.37.22) --- CMakeLists.txt | 2 +- examples/ripemd160_debug.fun | 154 ++++++++++++++++++++ examples/ripemd160_demo.fun | 58 ++++++++ examples/ripemd160_test.fun | 19 +++ examples/test_bits.fun | 26 ++++ examples/test_rol.fun | 22 +++ examples/test_shl.fun | 20 +++ examples/test_types.fun | 24 ++++ examples/test_xor.fun | 22 +++ lib/crypt/ripemd160.fun | 272 +++++++++++++++++++++++++++++++++++ 10 files changed, 618 insertions(+), 1 deletion(-) create mode 100644 examples/ripemd160_debug.fun create mode 100644 examples/ripemd160_demo.fun create mode 100644 examples/ripemd160_test.fun create mode 100644 examples/test_bits.fun create mode 100644 examples/test_rol.fun create mode 100644 examples/test_shl.fun create mode 100644 examples/test_types.fun create mode 100644 examples/test_xor.fun create mode 100644 lib/crypt/ripemd160.fun diff --git a/CMakeLists.txt b/CMakeLists.txt index f741f91..473099d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.37.21 LANGUAGES C) +project(fun VERSION 0.37.22 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/examples/ripemd160_debug.fun b/examples/ripemd160_debug.fun new file mode 100644 index 0000000..0c40593 --- /dev/null +++ b/examples/ripemd160_debug.fun @@ -0,0 +1,154 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-12-27 + */ + +#include + +fun to_hex_32(v) + d = ["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"] + out = [] + i = 0 + while i < 4 + b = band(shr(v, (3 - i) * 8), 0xFF) + push(out, d[shr(b, 4)]) + push(out, d[band(b, 0xF)]) + i = i + 1 + return join(out, "") + +rmd = RIPEMD160() +state = [1732584193, 4023233417, 2562383102, 271733878, 3285377520] +block = [] +push(block, 128) // 0x80 +i = 1 +while i < 64 + push(block, 0) + i = i + 1 + +print("Initial state:") +for v in state + print(" " + to_hex_32(v)) + +// Manual process_block logic with prints +M = [] +i = 0 +while i < 16 + j = i * 4 + push(M, rmd.word32_le(block[j], block[j+1], block[j+2], block[j+3])) + i = i + 1 + +al = state[0] +bl = state[1] +cl = state[2] +dl = state[3] +el = state[4] +ar = state[0] +br = state[1] +cr = state[2] +dr = state[3] +er = state[4] + +j = 0 +while j < 80 + if j < 16 + stage = 0 + stage_r = 4 + else if j < 32 + stage = 1 + stage_r = 3 + else if j < 48 + stage = 2 + stage_r = 2 + else if j < 64 + stage = 3 + stage_r = 1 + else + stage = 4 + stage_r = 0 + + f = rmd.F(stage, bl, cl, dl) + TL1 = rmd.add32(al, f) + TL2 = rmd.add32(M[rmd.RL[j]], rmd.KL[stage]) + TL3 = rmd.add32(TL1, TL2) + TL4 = rmd.rol32(TL3, rmd.SL[j]) + TL = rmd.add32(TL4, el) + if j < 2 + print("j=" + to_string(j) + " L: al=" + to_hex_32(al) + " f=" + to_hex_32(f) + " TL3=" + to_hex_32(TL3) + " TL4=" + to_hex_32(TL4) + " TL=" + to_hex_32(TL)) + al = el + el = dl + dl = rmd.rol32(cl, 10) + cl = bl + bl = TL + + fr = rmd.F(stage_r, br, cr, dr) + TR1 = rmd.add32(ar, fr) + TR2 = rmd.add32(M[rmd.RR[j]], rmd.KR[stage]) + TR3 = rmd.add32(TR1, TR2) + TR4 = rmd.rol32(TR3, rmd.SR[j]) + TR = rmd.add32(TR4, er) + if j < 2 + print("j=" + to_string(j) + " R: ar=" + to_hex_32(ar) + " fr=" + to_hex_32(fr) + " TR3=" + to_hex_32(TR3) + " TR4=" + to_hex_32(TR4) + " TR=" + to_hex_32(TR)) + ar = er + er = dr + dr = rmd.rol32(cr, 10) + cr = br + br = TR + + if j == 0 + print("After j=0:") + print(" L: al=" + to_hex_32(al) + " bl=" + to_hex_32(bl) + " cl=" + to_hex_32(cl) + " dl=" + to_hex_32(dl) + " el=" + to_hex_32(el)) + print(" R: ar=" + to_hex_32(ar) + " br=" + to_hex_32(br) + " cr=" + to_hex_32(cr) + " dr=" + to_hex_32(dr) + " er=" + to_hex_32(er)) + + j = j + 1 + +print("Registers after 80 steps:") +print(" L: A=" + to_hex_32(al) + " B=" + to_hex_32(bl) + " C=" + to_hex_32(cl) + " D=" + to_hex_32(dl) + " E=" + to_hex_32(el)) +print(" R: Ar=" + to_hex_32(ar) + " Br=" + to_hex_32(br) + " Cr=" + to_hex_32(cr) + " Dr=" + to_hex_32(dr) + " Er=" + to_hex_32(er)) + +h0 = rmd.add32(state[1], rmd.add32(cl, dr)) +h1 = rmd.add32(state[2], rmd.add32(dl, er)) +h2 = rmd.add32(state[3], rmd.add32(el, ar)) +h3 = rmd.add32(state[4], rmd.add32(al, br)) +h4 = rmd.add32(state[0], rmd.add32(bl, cr)) +state[0] = h0 +state[1] = h1 +state[2] = h2 +state[3] = h3 +state[4] = h4 + +print("Final state:") +for v in state + print(" " + to_hex_32(v)) + +/* Possible output: +Initial state: + 67452301 + efcdab89 + 98badcfe + 10325476 + c3d2e1f0 +j=0 L: al=67452301 f=67452301 TL3=ce8a4682 TL4=52341674 TL=1606f864 +j=0 R: ar=67452301 fr=10325476 TR3=c81a035d TR4=1a035dc8 TR=ddd63fb8 +After j=0: + L: al=c3d2e1f0 bl=1606f864 cl=efcdab89 dl=eb73fa62 el=10325476 + R: ar=c3d2e1f0 br=ddd63fb8 cr=efcdab89 dr=eb73fa62 er=10325476 +j=1 L: al=c3d2e1f0 f=12b8a98f TL3=d68b8b7f TL4=e2dff5a2 TL=f3124a18 +j=1 R: ar=c3d2e1f0 fr=221b9025 TR3=3690fdfb TR4=21fbf66d TR=322e4ae3 +Registers after 80 steps: + L: A=594637a7 B=215d97b3 C=eba027eb D=b99cdc7a E=ec4dcdff + R: Ar=e7eb2e49 Br=85bff1f4 Cr=1d9bc99d Dr=a7ffa90b Er=ffcec761 +Final state: + 836d7c7f + 522680d9 + e46b50be + a2d90b8b + a63e8451 +*/ diff --git a/examples/ripemd160_demo.fun b/examples/ripemd160_demo.fun new file mode 100644 index 0000000..9f7d06c --- /dev/null +++ b/examples/ripemd160_demo.fun @@ -0,0 +1,58 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-12-27 + */ + +// THIS IS BROKEN ACTUALLY! IT DOES NOT CALCULATE THE EXPECTED HASHES! + +// Example usage of the RIPEMD-160 hash algorithm. + +#include + +rmd = RIPEMD160() + +// Test case 1: Empty string +s1 = "" +h1 = rmd.ripemd160_str(s1) +print("RIPEMD-160(\"" + s1 + "\") = " + h1) +// Expected: 9c1185a5c5e9fc54612808977ee8f548b2258d31 + +// Test case 2: "abc" +s2 = "abc" +h2 = rmd.ripemd160_str(s2) +print("RIPEMD-160(\"" + s2 + "\") = " + h2) +// Expected: 8eb208f7e05d987a9b044a8e98c6b087f15a0bfc + +// Test case 3: "message digest" +s3 = "message digest" +h3 = rmd.ripemd160_str(s3) +print("RIPEMD-160(\"" + s3 + "\") = " + h3) +// Expected: 5d0689ef49d2fae572b881b123a85ffa21595f36 + +// Example of hashing from hex string +hex_input = "616263" // "abc" in hex +h4 = rmd.ripemd160_hex(hex_input) +print("RIPEMD-160(hex: " + hex_input + ") = " + h4) +// Expected: 8eb208f7e05d987a9b044a8e98c6b087f15a0bfc + +/* Expected output: +RIPEMD-160("") = 9c1185a5c5e9fc54612808977ee8f548b2258d31 +RIPEMD-160("abc") = 8eb208f7e05d987a9b044a8e98c6b087f15a0bfc +RIPEMD-160("message digest") = 5d0689ef49d2fae572b881b123a85ffa21595f36 +RIPEMD-160(hex: 616263) = 8eb208f7e05d987a9b044a8e98c6b087f15a0bfc +*/ + +/* Actual output: +RIPEMD-160("") = d90cc72ef4ad5a2c51f89dc938de0b989c4c4b46 +RIPEMD-160("abc") = 7f339b642aaa074a849c6c1cd860cb049cc792b3 +RIPEMD-160("message digest") = a4f9cd0270a341d05a713df4bf49a3ce8aabde64 +RIPEMD-160(hex: 616263) = 7f339b642aaa074a849c6c1cd860cb049cc792b3 +*/ diff --git a/examples/ripemd160_test.fun b/examples/ripemd160_test.fun new file mode 100644 index 0000000..8c2dc27 --- /dev/null +++ b/examples/ripemd160_test.fun @@ -0,0 +1,19 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-12-27 + */ + +#include +print("Include OK") + +/* Expected output: +Include OK +*/ diff --git a/examples/test_bits.fun b/examples/test_bits.fun new file mode 100644 index 0000000..ab810b2 --- /dev/null +++ b/examples/test_bits.fun @@ -0,0 +1,26 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-12-27 + */ + +print(bxor(0x80000000, 0x00000001)) +print(bor(0x80000000, 0x00000001)) +print(band(0x80000000, 0x80000000)) +print(bnot(0x80000000)) +print(bnot(0x7FFFFFFF)) + +/* Expected output: +2147483649 +2147483649 +2147483648 +2147483647 +2147483648 +*/ diff --git a/examples/test_rol.fun b/examples/test_rol.fun new file mode 100644 index 0000000..46c87f1 --- /dev/null +++ b/examples/test_rol.fun @@ -0,0 +1,22 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-12-27 + */ + +print(rol(0x12345678, 4)) // Should be 0x23456781 +print(rol(0x80000000, 1)) // Should be 0x00000001 +print(rol(0x00000001, 31)) // Should be 0x80000000 + +/* Expected output: +591751041 +1 +2147483648 +*/ diff --git a/examples/test_shl.fun b/examples/test_shl.fun new file mode 100644 index 0000000..1024225 --- /dev/null +++ b/examples/test_shl.fun @@ -0,0 +1,20 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-12-27 + */ + +print(shl(0x80, 24)) +print(128 * 16777216) + +/* Expected output: +2147483648 +2147483648 +*/ diff --git a/examples/test_types.fun b/examples/test_types.fun new file mode 100644 index 0000000..120774d --- /dev/null +++ b/examples/test_types.fun @@ -0,0 +1,24 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-12-27 + */ + +print(typeof(128 * 16777216)) +print(128 * 16777216) +print(typeof(0x80000000)) +print(0x80000000) + +/* Expected output: +Number +2147483648 +Number +2147483648 +*/ diff --git a/examples/test_xor.fun b/examples/test_xor.fun new file mode 100644 index 0000000..d1caccf --- /dev/null +++ b/examples/test_xor.fun @@ -0,0 +1,22 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-12-27 + */ + +print(bxor(0xefcdab89, 0x98badcfe)) +print(bxor(0x77767677, 0x10325476)) +print(bxor(bxor(0xefcdab89, 0x98badcfe), 0x10325476)) + +/* Expected output: +2004318071 +1732518401 +1732584193 +*/ diff --git a/lib/crypt/ripemd160.fun b/lib/crypt/ripemd160.fun new file mode 100644 index 0000000..bb9abd6 --- /dev/null +++ b/lib/crypt/ripemd160.fun @@ -0,0 +1,272 @@ +/* + * 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: 2025-12-27 + */ + +// THIS IS BROKEN ACTUALLY! IT DOES NOT CALCULATE THE EXPECTED HASHES! + +#include + +class RIPEMD160() + KL = [0, 1518500249, 1859775393, 2400959708, 2840853838] + KR = [1352829926, 1548603684, 1836062451, 2054012649, 0] + + RL = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13 + ] + + RR = [ + 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11 + ] + + SL = [ + 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 + ] + + SR = [ + 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 + ] + + fun u32(this, x) + m = 4294967296 + while x < 0 + x = x + m + while x >= m + x = x - m + return x + + fun rol32(this, x, s) + return rol(this.u32(x), s) + + fun add32(this, a, b) + return this.u32(a + b) + + fun F(this, stage, x, y, z) + if stage == 0 + return bxor(bxor(x, y), z) + else if stage == 1 + return bor(band(x, y), band(bnot(x), z)) + else if stage == 2 + return bxor(bor(x, bnot(y)), z) + else if stage == 3 + return bor(band(x, z), band(y, bnot(z))) + else + return bxor(x, bor(y, bnot(z))) + + fun hex_val(this, ch) + if ch == "0" + return 0 + else if ch == "1" + return 1 + else if ch == "2" + return 2 + else if ch == "3" + return 3 + else if ch == "4" + return 4 + else if ch == "5" + return 5 + else if ch == "6" + return 6 + else if ch == "7" + return 7 + else if ch == "8" + return 8 + else if ch == "9" + return 9 + else if ch == "a" || ch == "A" + return 10 + else if ch == "b" || ch == "B" + return 11 + else if ch == "c" || ch == "C" + return 12 + else if ch == "d" || ch == "D" + return 13 + else if ch == "e" || ch == "E" + return 14 + else if ch == "f" || ch == "F" + return 15 + else + return 0 + + fun byte_from_hex_pair(this, hh) + hi = this.hex_val(substr(hh, 0, 1)) + lo = this.hex_val(substr(hh, 1, 1)) + return hi * 16 + lo + + fun from_hex(this, hex) + arr = [] + i = 0 + n = len(hex) + while i + 1 < n + push(arr, this.byte_from_hex_pair(substr(hex, i, 2))) + i = i + 2 + return arr + + fun two_hex(this, n) + n = band(n, 0xFF) + d = ["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"] + return join([d[shr(n, 4)], d[band(n, 0xF)]], "") + + fun bytes_to_hex(this, arr) + i = 0 + out = [] + while i < len(arr) + push(out, this.two_hex(arr[i])) + i = i + 1 + return join(out, "") + + fun pad_bytes(this, bytes) + L = len(bytes) + out = [] + i = 0 + while i < L + push(out, bytes[i]) + i = i + 1 + push(out, 128) + while (len(out) % 64) != 56 + push(out, 0) + len_bits = L * 8 + j = 0 + while j < 8 + push(out, band(shr(len_bits, 8 * j), 0xFF)) + j = j + 1 + return out + + fun word32_le(this, b0, b1, b2, b3) + return bor(bor(bor(b0, shl(b1, 8)), shl(b2, 16)), shl(b3, 24)) + + fun process_block(this, state, block) + M = [] + i = 0 + while i < 16 + j = i * 4 + push(M, this.word32_le(block[j], block[j+1], block[j+2], block[j+3])) + i = i + 1 + + A = state[0] + B = state[1] + C = state[2] + D = state[3] + E = state[4] + Ar = state[0] + Br = state[1] + Cr = state[2] + Dr = state[3] + Er = state[4] + + j = 0 + while j < 80 + if j < 16 + stage = 0 + stage_r = 4 + else if j < 32 + stage = 1 + stage_r = 3 + else if j < 48 + stage = 2 + stage_r = 2 + else if j < 64 + stage = 3 + stage_r = 1 + else + stage = 4 + stage_r = 0 + + // Left side step + T = this.add32(this.rol32(this.add32(this.add32(A, this.F(stage, B, C, D)), this.add32(M[this.RL[j]], this.KL[stage])), this.SL[j]), E) + A = E + E = D + D = this.rol32(C, 10) + C = B + B = T + + // Right side step + Tr = this.add32(this.rol32(this.add32(this.add32(Ar, this.F(stage_r, Br, Cr, Dr)), this.add32(M[this.RR[j]], this.KR[stage])), this.SR[j]), Er) + Ar = Er + Er = Dr + Dr = this.rol32(Cr, 10) + Cr = Br + Br = Tr + + j = j + 1 + + al = A + bl = B + cl = C + dl = D + el = E + ar = Ar + br = Br + cr = Cr + dr = Dr + er = Er + + h0 = state[0] + h1 = state[1] + h2 = state[2] + h3 = state[3] + h4 = state[4] + + state[0] = this.add32(h1, this.add32(B, Cr)) + state[1] = this.add32(h2, this.add32(C, Dr)) + state[2] = this.add32(h3, this.add32(D, Er)) + state[3] = this.add32(h4, this.add32(E, Ar)) + state[4] = this.add32(h0, this.add32(A, Br)) + return state + + fun ripemd160_bytes(this, bytes) + state = [1732584193, 4023233417, 2562383102, 271733878, 3285377520] + data = this.pad_bytes(bytes) + N = len(data) + off = 0 + while off < N + block = [] + i = 0 + while i < 64 + push(block, data[off + i]) + i = i + 1 + state = this.process_block(state, block) + off = off + 64 + out = [] + j = 0 + while j < 5 + v = state[j] + push(out, band(v, 0xFF)) + push(out, band(shr(v, 8), 0xFF)) + push(out, band(shr(v, 16), 0xFF)) + push(out, band(shr(v, 24), 0xFF)) + j = j + 1 + return out + + fun ripemd160_hex(this, hexStr) + bytes = this.from_hex(hexStr) + digest = this.ripemd160_bytes(bytes) + return this.bytes_to_hex(digest) + + fun ripemd160_str(this, str) + bytes = string_to_bytes_ascii(str) + digest = this.ripemd160_bytes(bytes) + return this.bytes_to_hex(digest)