From a1c545c6a27ae732805bb60c7a164b15f6c1b761 Mon Sep 17 00:00:00 2001 From: hanez Date: Sun, 28 Dec 2025 02:23:44 +0100 Subject: [PATCH 01/13] Added serial device support. (0.37.23) --- CMakeLists.txt | 2 +- examples/ripemd160_debug.fun | 0 examples/ripemd160_demo.fun | 0 examples/ripemd160_test.fun | 0 examples/test_bits.fun | 0 examples/test_rol.fun | 0 examples/test_serial.fun | 50 ++++++++++++++++++++ examples/test_shl.fun | 0 examples/test_types.fun | 0 examples/test_xor.fun | 0 src/bytecode.h | 7 +++ src/parser.c | 53 +++++++++++++++++++++ src/vm.c | 5 ++ src/vm.h | 1 + src/vm/os/serial_close.c | 31 +++++++++++++ src/vm/os/serial_config.c | 90 ++++++++++++++++++++++++++++++++++++ src/vm/os/serial_open.c | 82 ++++++++++++++++++++++++++++++++ src/vm/os/serial_recv.c | 47 +++++++++++++++++++ src/vm/os/serial_send.c | 36 +++++++++++++++ 19 files changed, 403 insertions(+), 1 deletion(-) mode change 100644 => 100755 examples/ripemd160_debug.fun mode change 100644 => 100755 examples/ripemd160_demo.fun mode change 100644 => 100755 examples/ripemd160_test.fun mode change 100644 => 100755 examples/test_bits.fun mode change 100644 => 100755 examples/test_rol.fun create mode 100755 examples/test_serial.fun mode change 100644 => 100755 examples/test_shl.fun mode change 100644 => 100755 examples/test_types.fun mode change 100644 => 100755 examples/test_xor.fun create mode 100644 src/vm/os/serial_close.c create mode 100644 src/vm/os/serial_config.c create mode 100644 src/vm/os/serial_open.c create mode 100644 src/vm/os/serial_recv.c create mode 100644 src/vm/os/serial_send.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 473099d..d8bc295 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.37.22 LANGUAGES C) +project(fun VERSION 0.37.23 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 old mode 100644 new mode 100755 diff --git a/examples/ripemd160_demo.fun b/examples/ripemd160_demo.fun old mode 100644 new mode 100755 diff --git a/examples/ripemd160_test.fun b/examples/ripemd160_test.fun old mode 100644 new mode 100755 diff --git a/examples/test_bits.fun b/examples/test_bits.fun old mode 100644 new mode 100755 diff --git a/examples/test_rol.fun b/examples/test_rol.fun old mode 100644 new mode 100755 diff --git a/examples/test_serial.fun b/examples/test_serial.fun new file mode 100755 index 0000000..22c64ed --- /dev/null +++ b/examples/test_serial.fun @@ -0,0 +1,50 @@ +#!/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-28 + */ + +// Test script for serial communication +// This script attempts to open a virtual serial port if it exists, +// or just demonstrates the syntax if not. + +fun test_serial() + number fd = 0 + number baud = 9600 + string path = "/dev/ttyUSB0" + + print("Attempting to open serial port: " + path) + fd = serial_open(path, baud) + + if fd > 0 + print("Successfully opened serial port. FD: " + to_string(fd)) + + // Config: fd, data_bits, parity, stop_bits, flow_control + // Parity: 0=None, 1=Odd, 2=Even + // Flow: 0=None, 1=Hardware + number ok = serial_config(fd, 8, 0, 1, 0) + if ok + print("Configured serial port: 8N1, no flow control") + + number sent = serial_send(fd, "HELLO SERIAL\n") + print("Sent " + to_string(sent) + " bytes") + + // recv is blocking in this implementation + // data = serial_recv(fd, 100) + // print "Received: " + data + else + print("Failed to configure serial port") + + serial_close(fd) + print("Closed serial port") + else + print("Could not open serial port (this is expected if /dev/ttyUSB0 doesn't exist or no permission)") + +test_serial() diff --git a/examples/test_shl.fun b/examples/test_shl.fun old mode 100644 new mode 100755 diff --git a/examples/test_types.fun b/examples/test_types.fun old mode 100644 new mode 100755 diff --git a/examples/test_xor.fun b/examples/test_xor.fun old mode 100644 new mode 100755 diff --git a/src/bytecode.h b/src/bytecode.h index 79a457d..25158e4 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -215,6 +215,13 @@ typedef enum { // Tk additions OP_TK_BIND, // pops command, event, id; binds event to command + // Serial communication (termios) + OP_SERIAL_OPEN, // pops baud_rate (int), path (string); returns fd (int) or 0 + OP_SERIAL_CONFIG, // pops flow_control, stop_bits, parity, data_bits, fd; returns 1/0 + OP_SERIAL_SEND, // pops data (string), fd; returns bytes sent (int) + OP_SERIAL_RECV, // pops maxlen (int), fd; returns data (string) + OP_SERIAL_CLOSE, // pops fd; returns 1/0 + // 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 diff --git a/src/parser.c b/src/parser.c index 6788539..717ca89 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1288,6 +1288,59 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) free(name); return 1; } + /* Serial builtins */ + if (strcmp(name, "serial_open") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "serial_open expects (path, baud)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "serial_open expects (path, baud)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "serial_open expects (path, baud)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after serial_open args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_SERIAL_OPEN, 0); + free(name); + return 1; + } + if (strcmp(name, "serial_config") == 0) { + (*pos)++; /* '(' */ + // fd, data_bits, parity, stop_bits, flow_control + for (int i = 0; i < 5; ++i) { + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "serial_config expects 5 arguments"); free(name); return 0; } + if (i < 4) { + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "serial_config expects 5 arguments"); free(name); return 0; } + } + } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after serial_config args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_SERIAL_CONFIG, 0); + free(name); + return 1; + } + if (strcmp(name, "serial_send") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "serial_send expects (fd, data)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "serial_send expects (fd, data)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "serial_send expects (fd, data)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after serial_send args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_SERIAL_SEND, 0); + free(name); + return 1; + } + if (strcmp(name, "serial_recv") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "serial_recv expects (fd, maxlen)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "serial_recv expects (fd, maxlen)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "serial_recv expects (fd, maxlen)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after serial_recv args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_SERIAL_RECV, 0); + free(name); + return 1; + } + if (strcmp(name, "serial_close") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "serial_close expects (fd)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after serial_close arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_SERIAL_CLOSE, 0); + free(name); + return 1; + } /* string ops */ if (strcmp(name, "split") == 0) { (*pos)++; /* '(' */ diff --git a/src/vm.c b/src/vm.c index 3c714c6..5ad2021 100644 --- a/src/vm.c +++ b/src/vm.c @@ -676,6 +676,11 @@ void vm_run(VM *vm, Bytecode *entry) { #include "vm/os/clock_mono_ms.c" #include "vm/os/date_format.c" #include "vm/os/random_number.c" + #include "vm/os/serial_open.c" + #include "vm/os/serial_config.c" + #include "vm/os/serial_send.c" + #include "vm/os/serial_recv.c" + #include "vm/os/serial_close.c" /* Socket ops */ #include "vm/os/socket_tcp_listen.c" diff --git a/src/vm.h b/src/vm.h index 4fd2281..bff9bfb 100644 --- a/src/vm.h +++ b/src/vm.h @@ -51,6 +51,7 @@ static const char *opcode_names[] = { "EXIT", "OS_LIST_DIR", "TK_BIND", + "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" }; diff --git a/src/vm/os/serial_close.c b/src/vm/os/serial_close.c new file mode 100644 index 0000000..b03290d --- /dev/null +++ b/src/vm/os/serial_close.c @@ -0,0 +1,31 @@ +/** + * 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-28 + */ + +#ifdef __unix__ +#include +#endif + +case OP_SERIAL_CLOSE: { + /* Pops fd (int); returns 1/0 */ + Value fdv = pop_value(vm); + int ok = 0; +#ifdef __unix__ + if (fdv.type != VAL_INT) { + fprintf(stderr, "Runtime type error: serial_close expects (int fd)\n"); + } else { + int fd = (int)fdv.i; + if (close(fd) == 0) ok = 1; + } +#endif + free_value(fdv); + push_value(vm, make_int(ok)); + break; +} diff --git a/src/vm/os/serial_config.c b/src/vm/os/serial_config.c new file mode 100644 index 0000000..04994b9 --- /dev/null +++ b/src/vm/os/serial_config.c @@ -0,0 +1,90 @@ +/** + * 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-28 + */ + +#ifdef __unix__ +#include +#endif + +case OP_SERIAL_CONFIG: { + /* Pops flow_control (int), stop_bits (int), parity (int), data_bits (int), fd (int); returns 1/0 */ + Value flowv = pop_value(vm); + Value stopv = pop_value(vm); + Value parityv = pop_value(vm); + Value datav = pop_value(vm); + Value fdv = pop_value(vm); + int ok = 0; +#ifdef __unix__ + if (flowv.type != VAL_INT || stopv.type != VAL_INT || parityv.type != VAL_INT || + datav.type != VAL_INT || fdv.type != VAL_INT) { + fprintf(stderr, "Runtime type error: serial_config expects (int fd, int data_bits, int parity, int stop_bits, int flow_control)\n"); + } else { + int fd = (int)fdv.i; + int data_bits = (int)datav.i; + int parity = (int)parityv.i; + int stop_bits = (int)stopv.i; + int flow = (int)flowv.i; + + struct termios options; + if (tcgetattr(fd, &options) == 0) { + // Data bits + options.c_cflag &= ~CSIZE; + switch (data_bits) { + case 5: options.c_cflag |= CS5; break; + case 6: options.c_cflag |= CS6; break; + case 7: options.c_cflag |= CS7; break; + case 8: default: options.c_cflag |= CS8; break; + } + + // Parity + switch (parity) { + case 0: // None + options.c_cflag &= ~PARENB; + break; + case 1: // Odd + options.c_cflag |= PARENB; + options.c_cflag |= PARODD; + break; + case 2: // Even + options.c_cflag |= PARENB; + options.c_cflag &= ~PARODD; + break; + } + + // Stop bits + if (stop_bits == 2) { + options.c_cflag |= CSTOPB; + } else { + options.c_cflag &= ~CSTOPB; + } + + // Flow control +#ifdef CRTSCTS + if (flow == 1) { // Hardware (RTS/CTS) + options.c_cflag |= CRTSCTS; + } else { + options.c_cflag &= ~CRTSCTS; + } +#endif + + if (tcsetattr(fd, TCSANOW, &options) == 0) { + ok = 1; + } + } + } +#endif + free_value(flowv); + free_value(stopv); + free_value(parityv); + free_value(datav); + free_value(fdv); + push_value(vm, make_int(ok)); + break; +} diff --git a/src/vm/os/serial_open.c b/src/vm/os/serial_open.c new file mode 100644 index 0000000..02c8d73 --- /dev/null +++ b/src/vm/os/serial_open.c @@ -0,0 +1,82 @@ +/** + * 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-28 + */ + +#ifdef __unix__ +#include +#include +#include +#endif + +case OP_SERIAL_OPEN: { + /* Pops baud_rate (int), path (string); returns fd (int) or 0 */ + Value baudv = pop_value(vm); + Value pathv = pop_value(vm); + int fd = 0; +#ifdef __unix__ + if (baudv.type != VAL_INT || pathv.type != VAL_STRING) { + fprintf(stderr, "Runtime type error: serial_open expects (string path, int baud_rate)\n"); + free_value(baudv); + free_value(pathv); + push_value(vm, make_int(0)); + break; + } + + const char *path = pathv.s ? pathv.s : ""; + int baud = (int)baudv.i; + speed_t speed; + + switch (baud) { + case 50: speed = B50; break; + case 75: speed = B75; break; + case 110: speed = B110; break; + case 134: speed = B134; break; + case 150: speed = B150; break; + case 200: speed = B200; break; + case 300: speed = B300; break; + case 600: speed = B600; break; + case 1200: speed = B1200; break; + case 1800: speed = B1800; break; + case 2400: speed = B2400; break; + case 4800: speed = B4800; break; + case 9600: speed = B9600; break; + case 19200: speed = B19200; break; + case 38400: speed = B38400; break; + case 57600: speed = B57600; break; + case 115200: speed = B115200; break; + case 230400: speed = B230400; break; + default: speed = B9600; break; + } + + fd = open(path, O_RDWR | O_NOCTTY | O_NDELAY); + if (fd != -1) { + struct termios options; + tcgetattr(fd, &options); + cfsetispeed(&options, speed); + cfsetospeed(&options, speed); + options.c_cflag |= (CLOCAL | CREAD); + options.c_cflag &= ~PARENB; + options.c_cflag &= ~CSTOPB; + options.c_cflag &= ~CSIZE; + options.c_cflag |= CS8; + options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + options.c_iflag &= ~(IXON | IXOFF | IXANY); + options.c_oflag &= ~OPOST; + tcsetattr(fd, TCSANOW, &options); + fcntl(fd, F_SETFL, 0); // block on read + } else { + fd = 0; + } +#endif + free_value(baudv); + free_value(pathv); + push_value(vm, make_int(fd > 0 ? fd : 0)); + break; +} diff --git a/src/vm/os/serial_recv.c b/src/vm/os/serial_recv.c new file mode 100644 index 0000000..520bed3 --- /dev/null +++ b/src/vm/os/serial_recv.c @@ -0,0 +1,47 @@ +/** + * 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-28 + */ + +#ifdef __unix__ +#include +#endif + +case OP_SERIAL_RECV: { + /* Pops maxlen (int), fd (int); returns data (string) */ + Value maxv = pop_value(vm); + Value fdv = pop_value(vm); + char *out = NULL; +#ifdef __unix__ + if (fdv.type != VAL_INT || maxv.type != VAL_INT) { + fprintf(stderr, "Runtime type error: serial_recv expects (int fd, int maxlen)\n"); + } else { + int fd = (int)fdv.i; + int maxlen = (int)maxv.i; + if (maxlen <= 0) maxlen = 4096; + if (maxlen > 1<<20) maxlen = 1<<20; /* cap at 1MB */ + out = (char*)malloc((size_t)maxlen + 1); + if (out) { + ssize_t n = read(fd, out, (size_t)maxlen); + if (n <= 0) { + free(out); + out = NULL; + } else { + out[n] = '\0'; + } + } + } +#endif + free_value(maxv); + free_value(fdv); + Value s = make_string(out ? out : ""); + if (out) free(out); + push_value(vm, s); + break; +} diff --git a/src/vm/os/serial_send.c b/src/vm/os/serial_send.c new file mode 100644 index 0000000..c679fcc --- /dev/null +++ b/src/vm/os/serial_send.c @@ -0,0 +1,36 @@ +/** + * 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-28 + */ + +#ifdef __unix__ +#include +#endif + +case OP_SERIAL_SEND: { + /* Pops data (string), fd (int); returns bytes sent (int) */ + Value datav = pop_value(vm); + Value fdv = pop_value(vm); + int sent = -1; +#ifdef __unix__ + if (fdv.type != VAL_INT || datav.type != VAL_STRING) { + fprintf(stderr, "Runtime type error: serial_send expects (int fd, string data)\n"); + } else { + int fd = (int)fdv.i; + const char *buf = datav.s ? datav.s : ""; + size_t len = strlen(buf); + ssize_t n = write(fd, buf, len); + if (n >= 0) sent = (int)n; + } +#endif + free_value(datav); + free_value(fdv); + push_value(vm, make_int(sent)); + break; +} From 384a7e4502994cf9a22fea84f3cd10c57c7e1723 Mon Sep 17 00:00:00 2001 From: hanez Date: Sun, 28 Dec 2025 03:00:58 +0100 Subject: [PATCH 02/13] Some more serial stuff. (0.37.24) --- CMakeLists.txt | 2 +- examples/serial_demo.fun | 54 +++++++++++++++++++++++++++++++++ examples/test_serial.fun | 5 ++++ lib/io/serial.fun | 65 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 examples/serial_demo.fun create mode 100644 lib/io/serial.fun diff --git a/CMakeLists.txt b/CMakeLists.txt index d8bc295..73aeabc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.37.23 LANGUAGES C) +project(fun VERSION 0.37.24 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/examples/serial_demo.fun b/examples/serial_demo.fun new file mode 100644 index 0000000..7b2156c --- /dev/null +++ b/examples/serial_demo.fun @@ -0,0 +1,54 @@ +#!/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-28 + */ + +#include + +// This is an example of how to use the Serial class from the stdlib. +// Note: This requires a serial device to be present at the specified path. + +path = "/dev/ttyUSB0" +baud = 115200 + +print("Opening serial port " + path + " at " + to_string(baud) + " baud...") +s = Serial(path, baud) + +if (s.open()) + print("Serial port opened successfully.") + + // Configure: 8 data bits, no parity (0), 1 stop bit, no flow control (0) + if (s.config(8, 0, 1, 0)) + print("Configured to 8N1.") + + sent = s.send("AT\r\n") + print("Sent " + to_string(sent) + " bytes.") + + // Wait a bit for response if needed (mocked by sleep if available) + // sleep_ms(100) + + resp = s.recv(64) + if (len(resp) > 0) + print("Received: " + resp) + else + print("No response received.") + else + print("Failed to configure serial port.") + + s.close() + print("Serial port closed.") +else + print("Failed to open serial port. (Do you have permissions?)") + +/* Possible output: +Opening serial port /dev/ttyUSB0 at 115200 baud... +Failed to open serial port. (Do you have permissions?) +*/ diff --git a/examples/test_serial.fun b/examples/test_serial.fun index 22c64ed..d1c6fa0 100755 --- a/examples/test_serial.fun +++ b/examples/test_serial.fun @@ -48,3 +48,8 @@ fun test_serial() print("Could not open serial port (this is expected if /dev/ttyUSB0 doesn't exist or no permission)") test_serial() + +/* Possible output: +Attempting to open serial port: /dev/ttyUSB0 +Could not open serial port (this is expected if /dev/ttyUSB0 doesn't exist or no permission) +*/ diff --git a/lib/io/serial.fun b/lib/io/serial.fun new file mode 100644 index 0000000..756d3ee --- /dev/null +++ b/lib/io/serial.fun @@ -0,0 +1,65 @@ +/* + * 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-28 + */ + +/* + * Serial communication class for Fun stdlib. + * + * Provides: + * - Serial: wrapper over built-ins: serial_open, serial_config, serial_send, serial_recv, serial_close + * + * Usage: + * #include + * s = Serial("/dev/ttyUSB0", 115200) + * if (s.open()) + * s.config(8, 0, 1, 0) // 8N1, no flow control + * s.send("Hello Serial!") + * resp = s.recv(64) + * print("Received: " + resp) + * s.close() + */ + +class Serial(string path, number baud_rate) + + fun _construct(this, path, baud_rate) + this.path = path + this.baud_rate = baud_rate + this.fd = 0 + + fun open(this) + this.fd = serial_open(this.path, this.baud_rate) + return this.fd > 0 + + fun is_open(this) + return this.fd > 0 + + fun config(this, data_bits, parity, stop_bits, flow_control) + if (!this.is_open()) + return 0 + // parity: 0=None, 1=Odd, 2=Even + // flow_control: 0=None, 1=Hardware (RTS/CTS) + return serial_config(this.fd, data_bits, parity, stop_bits, flow_control) + + fun send(this, data) + if (!this.is_open()) + return -1 + return serial_send(this.fd, to_string(data)) + + fun recv(this, maxlen) + if (!this.is_open()) + return "" + return serial_recv(this.fd, to_number(maxlen)) + + fun close(this) + if (this.is_open()) + res = serial_close(this.fd) + this.fd = 0 + return res + return 0 From c20486a239cba1f76625f2a57a46739be8801519 Mon Sep 17 00:00:00 2001 From: hanez Date: Sun, 28 Dec 2025 19:08:15 +0100 Subject: [PATCH 03/13] README update. No Code changes. (0.37.24) --- README.md | 42 ++++++++++++++++++++++++++++++++++++++-- examples/serial_demo.fun | 0 2 files changed, 40 insertions(+), 2 deletions(-) mode change 100644 => 100755 examples/serial_demo.fun diff --git a/README.md b/README.md index 8e2a0d6..aa11d35 100644 --- a/README.md +++ b/README.md @@ -75,9 +75,47 @@ Fun may not change the world — but it will make programming a little more fun. - if/else if/else - try/catch/finally -### Lib +### 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 +``` ### Extensions (only Linux actually) diff --git a/examples/serial_demo.fun b/examples/serial_demo.fun old mode 100644 new mode 100755 From 9ce6e8d08fd3ff96c39a8b92162e27a2565cb6d7 Mon Sep 17 00:00:00 2001 From: hanez Date: Sun, 28 Dec 2025 19:09:26 +0100 Subject: [PATCH 04/13] README update. No Code changes. (0.37.24) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa11d35..0c26be1 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ crypt/ sha384.fun sha512.fun encoding/ - base64.fun + base64.fun hello.fun hex.fun io/ From 06e4054e92e9da2d0db660cf9c19ff8781db5e4f Mon Sep 17 00:00:00 2001 From: hanez Date: Sun, 28 Dec 2025 19:16:34 +0100 Subject: [PATCH 05/13] Small make script fix. (0.37.24) --- make | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/make b/make index 171c7f6..914b699 100755 --- a/make +++ b/make @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # This file is part of the Fun programming language. # https://fun-lang.xyz/ From e1890a5b898b3d2cb11e54f9786945c2c0ffb0d7 Mon Sep 17 00:00:00 2001 From: hanez Date: Sun, 28 Dec 2025 19:24:00 +0100 Subject: [PATCH 06/13] Small fix to compile on FreeBSD. (0.37.25) --- CMakeLists.txt | 2 +- src/vm/os/random_number.c | 5 +++-- src/vm/os/serial_open.c | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 73aeabc..5befba2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.37.24 LANGUAGES C) +project(fun VERSION 0.37.25 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/src/vm/os/random_number.c b/src/vm/os/random_number.c index 36e3dab..a84244d 100644 --- a/src/vm/os/random_number.c +++ b/src/vm/os/random_number.c @@ -24,8 +24,9 @@ #if defined(_WIN32) || defined(_WIN64) #include #include -#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) +#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) #include + #include #elif defined(__unix__) #if __has_include() #include @@ -85,7 +86,7 @@ case OP_RANDOM_NUMBER: { NTSTATUS st = BCryptGenRandom(NULL, raw, (ULONG)len, BCRYPT_USE_SYSTEM_PREFERRED_RNG); ok = (st == 0); } -#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) +#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) { arc4random_buf(raw, (size_t)len); ok = 1; diff --git a/src/vm/os/serial_open.c b/src/vm/os/serial_open.c index 02c8d73..772776a 100644 --- a/src/vm/os/serial_open.c +++ b/src/vm/os/serial_open.c @@ -13,6 +13,21 @@ #include #include #include + +/* Fallbacks for baud rates that might be missing on some systems */ +#ifndef B57600 +#define B57600 0010001 +#endif +#ifndef B115200 +#define B115200 0010002 +#endif +#ifndef B230400 +#define B230400 0010003 +#endif + +#ifndef O_NDELAY +#define O_NDELAY O_NONBLOCK +#endif #endif case OP_SERIAL_OPEN: { From 41cb23614e0436a93c7691879ed0981cf9f95b57 Mon Sep 17 00:00:00 2001 From: hanez Date: Sun, 28 Dec 2025 19:26:57 +0100 Subject: [PATCH 07/13] Small fix to remove a warning at compile time on FreeBSD. (0.37.26) --- CMakeLists.txt | 2 +- src/vm/os/random_number.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5befba2..cf50de9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.37.25 LANGUAGES C) +project(fun VERSION 0.37.26 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/src/vm/os/random_number.c b/src/vm/os/random_number.c index a84244d..adc81f1 100644 --- a/src/vm/os/random_number.c +++ b/src/vm/os/random_number.c @@ -27,6 +27,8 @@ #elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) #include #include + /* On some BSDs, arc4random_buf might be hidden by _POSIX_C_SOURCE. */ + void arc4random_buf(void *, size_t); #elif defined(__unix__) #if __has_include() #include From aa8701a5a1c012baa7c4f7571923f55ded4b3b76 Mon Sep 17 00:00:00 2001 From: hanez Date: Sun, 28 Dec 2025 20:37:51 +0100 Subject: [PATCH 08/13] Added more baud rates as constants to serial_open.c. (0.37.27) --- CMakeLists.txt | 2 +- lib/crypt/ripemd160.fun | 2 +- src/vm/os/serial_open.c | 45 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cf50de9..3320603 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.37.26 LANGUAGES C) +project(fun VERSION 0.37.27 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/lib/crypt/ripemd160.fun b/lib/crypt/ripemd160.fun index bb9abd6..f866ab1 100644 --- a/lib/crypt/ripemd160.fun +++ b/lib/crypt/ripemd160.fun @@ -204,7 +204,7 @@ class RIPEMD160() 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) + 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_r])), this.SR[j]), Er) Ar = Er Er = Dr Dr = this.rol32(Cr, 10) diff --git a/src/vm/os/serial_open.c b/src/vm/os/serial_open.c index 772776a..1694df3 100644 --- a/src/vm/os/serial_open.c +++ b/src/vm/os/serial_open.c @@ -15,6 +15,51 @@ #include /* Fallbacks for baud rates that might be missing on some systems */ +#ifndef B50 +#define B50 0000001 +#endif +#ifndef B75 +#define B75 0000002 +#endif +#ifndef B110 +#define B110 0000003 +#endif +#ifndef B134 +#define B134 0000004 +#endif +#ifndef B150 +#define B150 0000005 +#endif +#ifndef B200 +#define B200 0000006 +#endif +#ifndef B300 +#define B300 0000007 +#endif +#ifndef B600 +#define B600 0000010 +#endif +#ifndef B1200 +#define B1200 0000011 +#endif +#ifndef B1800 +#define B1800 0000012 +#endif +#ifndef B2400 +#define B2400 0000013 +#endif +#ifndef B4800 +#define B4800 0000014 +#endif +#ifndef B9600 +#define B9600 0000015 +#endif +#ifndef B19200 +#define B19200 0000016 +#endif +#ifndef B38400 +#define B38400 0000017 +#endif #ifndef B57600 #define B57600 0010001 #endif From 10b75596cd0316110e835d6e003ddd395a523e14 Mon Sep 17 00:00:00 2001 From: hanez Date: Sun, 28 Dec 2025 21:21:34 +0100 Subject: [PATCH 09/13] Added HTTP server class to stdlib and an example server written in 100% pure Fun. (0.37.28) --- CMakeLists.txt | 2 +- examples/class_test.fun | 30 +++++++++++++ examples/data/htdocs/index.html | 10 +++++ examples/extra/http_server.fun | 22 ++++++++++ lib/net/http_server.fun | 78 +++++++++++++++++++++++++++++++++ lib/strings.fun | 2 +- 6 files changed, 142 insertions(+), 2 deletions(-) create mode 100755 examples/class_test.fun create mode 100644 examples/data/htdocs/index.html create mode 100755 examples/extra/http_server.fun create mode 100644 lib/net/http_server.fun diff --git a/CMakeLists.txt b/CMakeLists.txt index 3320603..7d11c81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.37.27 LANGUAGES C) +project(fun VERSION 0.37.28 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/examples/class_test.fun b/examples/class_test.fun new file mode 100755 index 0000000..6be0286 --- /dev/null +++ b/examples/class_test.fun @@ -0,0 +1,30 @@ +#!/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-28 + */ + +class Other(number val) + fun _construct(this, val) + this.v = val + +class Test() + fun _construct(this) + val = Other(123) + this.a = val + print("a set") + +t = Test() +print(t.a.v) + +/* Expected output: +a set +123 +*/ diff --git a/examples/data/htdocs/index.html b/examples/data/htdocs/index.html new file mode 100644 index 0000000..df339a0 --- /dev/null +++ b/examples/data/htdocs/index.html @@ -0,0 +1,10 @@ + + + + Fun HTTP Server + + +

Welcome to the Fun HTTP Server!

+

This page is served from ./examples/data/htdocs/index.html.

+ + diff --git a/examples/extra/http_server.fun b/examples/extra/http_server.fun new file mode 100755 index 0000000..0335919 --- /dev/null +++ b/examples/extra/http_server.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-28 + */ + +#include + +port = 8080 +htdocs = "./examples/data/htdocs" + +server = HTTPServer(port) +server.set_htdocs(htdocs) + +server.start() diff --git a/lib/net/http_server.fun b/lib/net/http_server.fun new file mode 100644 index 0000000..cfc49ef --- /dev/null +++ b/lib/net/http_server.fun @@ -0,0 +1,78 @@ +/* + * 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-28 + */ + +#include +#include + +class HTTPServer(number port) + fun _construct(this, port) + this.port = port + srv = TcpServer(port, 10) + this.server = srv + this.htdocs = "./" + + fun set_htdocs(this, path) + this.htdocs = to_string(path) + + fun start(this) + if (this.server.listen() <= 0) + print("HTTPServer: failed to listen on port " + to_string(this.port)) + return 0 + print("HTTPServer: serving " + this.htdocs + " on port " + to_string(this.port)) + while true + client_fd = this.server.accept() + if (client_fd > 0) + this.handle_client(client_fd) + return 1 + + fun handle_client(this, fd) + request = sock_recv(fd, 4096) + if (len(request) == 0) + sock_close(fd) + return 0 + + // Basic request parsing + lines = str_split(request, "\n") + if (len(lines) == 0) + sock_close(fd) + return 0 + + first_line = lines[0] + parts = str_split(first_line, " ") + if (len(parts) < 2) + sock_close(fd) + return 0 + + method = parts[0] + path = parts[1] + + if (path == "/") + path = "/index.html" + + full_path = this.htdocs + path + content = read_file(full_path) + + if (len(content) > 0) + this.send_response(fd, 200, "OK", content) + else + this.send_response(fd, 404, "Not Found", "

404 Not Found

") + + sock_close(fd) + return 1 + + fun send_response(this, fd, status_code, status_text, body) + resp = "HTTP/1.1 " + to_string(status_code) + " " + status_text + "\r\n" + resp = resp + "Content-Type: text/html\r\n" + resp = resp + "Content-Length: " + to_string(len(body)) + "\r\n" + resp = resp + "Connection: close\r\n" + resp = resp + "\r\n" + resp = resp + body + sock_send(fd, resp) diff --git a/lib/strings.fun b/lib/strings.fun index 47c803a..2ab3e28 100644 --- a/lib/strings.fun +++ b/lib/strings.fun @@ -93,7 +93,7 @@ fun str_replace_all(s, from, to) out = [] number i = 0 while i < n - if (i + lf <= n) && (substr(src, i, lf) == f) + if ((i + lf <= n) && (substr(src, i, lf) == f)) push(out, t) i = i + lf else From 17c2b44c1a6bd29a56c6b8e460a4228b18024ad8 Mon Sep 17 00:00:00 2001 From: hanez Date: Sun, 28 Dec 2025 22:11:19 +0100 Subject: [PATCH 10/13] Added basic script execution to the HTTP server class in stdlib. (0.37.29) --- CMakeLists.txt | 2 +- examples/data/htdocs/info.fun | 23 +++++++++++++++++++++++ lib/net/http_server.fun | 9 ++++++++- 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 examples/data/htdocs/info.fun diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d11c81..6e8bd14 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.37.28 LANGUAGES C) +project(fun VERSION 0.37.29 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/examples/data/htdocs/info.fun b/examples/data/htdocs/info.fun new file mode 100644 index 0000000..1e48bae --- /dev/null +++ b/examples/data/htdocs/info.fun @@ -0,0 +1,23 @@ +#!/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-28 + */ + +result = proc_run("fun -V") +content = result["out"] + +print("") +print("

Fun Environment Information

") +print("

Current Path: " + env("PATH") + "

") +print("

Current User: " + env("USER") + "

") +print("

Time: " + date_format(time_now_ms(), "%Y-%m-%d %H:%M:%S") + "

") +print("

Version: " + to_string(content) + "

") +print("") diff --git a/lib/net/http_server.fun b/lib/net/http_server.fun index cfc49ef..cf430d2 100644 --- a/lib/net/http_server.fun +++ b/lib/net/http_server.fun @@ -58,7 +58,14 @@ class HTTPServer(number port) path = "/index.html" full_path = this.htdocs + path - content = read_file(full_path) + + content = "" + if (str_ends_with(path, ".fun")) + res = proc_run("fun " + full_path) + content = res["out"] + //#include <\"full_path\"> + else + content = read_file(full_path) if (len(content) > 0) this.send_response(fd, 200, "OK", content) From a27ff12b08ffc027374c2d50a80280dbb4da59b4 Mon Sep 17 00:00:00 2001 From: hanez Date: Sun, 28 Dec 2025 23:36:37 +0100 Subject: [PATCH 11/13] Added fun_version and env_all as opcodes and some fixes in httpserver. (0.37.30) --- CMakeLists.txt | 5 +++-- examples/data/htdocs/info.fun | 13 +++++++++++-- examples/env_all.fun | 28 ++++++++++++++++++++++++++++ examples/version.fun | 4 ++++ lib/net/http_server.fun | 3 +-- src/bytecode.c | 2 ++ src/bytecode.h | 2 ++ src/fun.c | 4 ++++ src/parser.c | 14 ++++++++++++++ src/vm.c | 2 ++ src/vm/os/env_all.c | 35 +++++++++++++++++++++++++++++++++++ src/vm/os/fun_version.c | 20 ++++++++++++++++++++ 12 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 examples/env_all.fun create mode 100755 examples/version.fun create mode 100644 src/vm/os/env_all.c create mode 100644 src/vm/os/fun_version.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e8bd14..65ff1d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.37.29 LANGUAGES C) +project(fun VERSION 0.37.30 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) @@ -378,10 +378,12 @@ target_include_directories(fun_core PUBLIC # Apply options to core if(FUN_DEBUG) message(STATUS "FUN_DEBUG enabled: building with verbose debug logging") + target_compile_definitions(fun_core PUBLIC FUN_VERSION="${PROJECT_VERSION}") target_compile_definitions(fun_core PUBLIC FUN_DEBUG=1) endif() # Provide default stdlib directory to the runtime +target_compile_definitions(fun_core PUBLIC FUN_VERSION="${PROJECT_VERSION}") target_compile_definitions(fun_core PUBLIC DEFAULT_LIB_DIR="${DEFAULT_LIB_DIR}") # PCSC include and link (if enabled) @@ -472,7 +474,6 @@ add_executable(fun src/fun.c ) # Provide version string to the CLI -target_compile_definitions(fun PRIVATE FUN_VERSION=\"${PROJECT_VERSION}\") if(FUN_WITH_REPL) # Enable REPL compilation path and add the REPL source target_compile_definitions(fun PRIVATE FUN_WITH_REPL=1) diff --git a/examples/data/htdocs/info.fun b/examples/data/htdocs/info.fun index 1e48bae..93c3665 100644 --- a/examples/data/htdocs/info.fun +++ b/examples/data/htdocs/info.fun @@ -11,8 +11,7 @@ * Added: 2025-12-28 */ -result = proc_run("fun -V") -content = result["out"] +content = fun_version() print("") print("

Fun Environment Information

") @@ -20,4 +19,14 @@ print("

Current Path: " + env("PATH") + "

") print("

Current User: " + env("USER") + "

") print("

Time: " + date_format(time_now_ms(), "%Y-%m-%d %H:%M:%S") + "

") print("

Version: " + to_string(content) + "

") +print("

Full Environment:

") +print("
    ") +all_env = env_all() +all_keys = keys(all_env) +i = 0 +while i < len(all_keys) + key = all_keys[i] + print("
  • " + key + "=" + all_env[key] + "
  • ") + i = i + 1 +print("
") print("") diff --git a/examples/env_all.fun b/examples/env_all.fun new file mode 100644 index 0000000..ccbd93b --- /dev/null +++ b/examples/env_all.fun @@ -0,0 +1,28 @@ +#!/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-28 + */ + +all_env = env_all() +keys = keys(all_env) + +print("Number of environment variables: " + to_string(len(keys))) +print("--------------------------------------------------") + +i = 0 +while i < len(keys) + key = keys[i] + print(key + "=" + all_env[key]) + i = i + 1 + +/* Possible output: +Depends on your environment. +*/ diff --git a/examples/version.fun b/examples/version.fun new file mode 100755 index 0000000..84d0456 --- /dev/null +++ b/examples/version.fun @@ -0,0 +1,4 @@ +#!/usr/bin/env fun + +print(to_string(fun_version())) + diff --git a/lib/net/http_server.fun b/lib/net/http_server.fun index cf430d2..3d36b03 100644 --- a/lib/net/http_server.fun +++ b/lib/net/http_server.fun @@ -61,9 +61,8 @@ class HTTPServer(number port) content = "" if (str_ends_with(path, ".fun")) - res = proc_run("fun " + full_path) + res = proc_run("fun" + " " + full_path) content = res["out"] - //#include <\"full_path\"> else content = read_file(full_path) diff --git a/src/bytecode.c b/src/bytecode.c index 6bf4c6e..f1af0e2 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -131,6 +131,8 @@ static const char *opcode_name(OpCode op) { case OP_TIME_NOW_MS: return "TIME_NOW_MS"; case OP_CLOCK_MONO_MS: return "CLOCK_MONO_MS"; case OP_DATE_FORMAT: return "DATE_FORMAT"; + case OP_ENV_ALL: return "ENV_ALL"; + case OP_FUN_VERSION: return "FUN_VERSION"; case OP_THREAD_SPAWN: return "THREAD_SPAWN"; case OP_THREAD_JOIN: return "THREAD_JOIN"; case OP_SLEEP_MS: return "SLEEP_MS"; diff --git a/src/bytecode.h b/src/bytecode.h index 25158e4..0c0e971 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -126,6 +126,8 @@ typedef enum { OP_TIME_NOW_MS, // pushes current wall-clock time in milliseconds since Unix epoch OP_CLOCK_MONO_MS, // pushes monotonic clock in milliseconds (not wall time) OP_DATE_FORMAT, // pops fmt string, ms epoch (int); pushes formatted date string using strftime + OP_ENV_ALL, // pushes map of all environment variables + OP_FUN_VERSION, // pushes version string // Threads OP_THREAD_SPAWN, // operand: 0=no args, 1=has args; pops [args?], fn; pushes thread id (int>0) diff --git a/src/fun.c b/src/fun.c index afd7305..041e306 100644 --- a/src/fun.c +++ b/src/fun.c @@ -9,6 +9,7 @@ #include "parser.h" #include #include +#include #include #ifdef FUN_WITH_REPL @@ -42,6 +43,9 @@ static void print_usage(const char *prog) { } int main(int argc, char **argv) { + /* Set FUN_EXECUTABLE environment variable to the path of this binary */ + setenv("FUN_EXECUTABLE", argv[0], 1); + VM vm; vm_init(&vm); diff --git a/src/parser.c b/src/parser.c index 717ca89..f5b69d9 100644 --- a/src/parser.c +++ b/src/parser.c @@ -757,6 +757,20 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) free(name); return 1; } + if (strcmp(name, "env_all") == 0) { + (*pos)++; /* '(' */ + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "env_all expects ()"); free(name); return 0; } + bytecode_add_instruction(bc, OP_ENV_ALL, 0); + free(name); + return 1; + } + if (strcmp(name, "fun_version") == 0) { + (*pos)++; /* '(' */ + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "fun_version expects ()"); free(name); return 0; } + bytecode_add_instruction(bc, OP_FUN_VERSION, 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; } diff --git a/src/vm.c b/src/vm.c index 5ad2021..f2a4470 100644 --- a/src/vm.c +++ b/src/vm.c @@ -667,6 +667,8 @@ void vm_run(VM *vm, Bytecode *entry) { #include "vm/math/random_seed.c" #include "vm/os/env.c" + #include "vm/os/env_all.c" + #include "vm/os/fun_version.c" #include "vm/os/sleep_ms.c" #include "vm/os/thread_join.c" #include "vm/os/thread_spawn.c" diff --git a/src/vm/os/env_all.c b/src/vm/os/env_all.c new file mode 100644 index 0000000..61b76cc --- /dev/null +++ b/src/vm/os/env_all.c @@ -0,0 +1,35 @@ +/** + * 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-28 + */ + +// Get all environment variables of the operation system and push them as a map. + +case OP_ENV_ALL: { + extern char **environ; + Value m = make_map_empty(); + if (environ) { + for (char **env = environ; *env; ++env) { + char *entry = *env; + char *equals = strchr(entry, '='); + if (equals) { + size_t key_len = equals - entry; + char *key = malloc(key_len + 1); + if (key) { + memcpy(key, entry, key_len); + key[key_len] = '\0'; + map_set(&m, key, make_string(equals + 1)); + free(key); + } + } + } + } + push_value(vm, m); + break; +} diff --git a/src/vm/os/fun_version.c b/src/vm/os/fun_version.c new file mode 100644 index 0000000..f2ca4b6 --- /dev/null +++ b/src/vm/os/fun_version.c @@ -0,0 +1,20 @@ +/** + * 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-28 + */ + +// Pushes the current Fun version string onto the stack. + +case OP_FUN_VERSION: { +#ifndef FUN_VERSION +#define FUN_VERSION "0.0.0-dev" +#endif + push_value(vm, make_string(FUN_VERSION)); + break; +} From c8393251e56ed79d4ccf3bd8dccaaead856a4245 Mon Sep 17 00:00:00 2001 From: hanez Date: Mon, 29 Dec 2025 13:07:20 +0100 Subject: [PATCH 12/13] Small HTML fix and optimizations in htdocs/info.fun. (0.37.31) --- CMakeLists.txt | 2 +- examples/data/htdocs/info.fun | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 65ff1d3..473dd62 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.37.30 LANGUAGES C) +project(fun VERSION 0.37.31 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/examples/data/htdocs/info.fun b/examples/data/htdocs/info.fun index 93c3665..4473ec0 100644 --- a/examples/data/htdocs/info.fun +++ b/examples/data/htdocs/info.fun @@ -1,5 +1,3 @@ -#!/usr/bin/env fun - /* * This file is part of the Fun programming language. * https://fun-lang.xyz/ @@ -11,14 +9,12 @@ * Added: 2025-12-28 */ -content = fun_version() - -print("") +print("") print("

Fun Environment Information

") print("

Current Path: " + env("PATH") + "

") print("

Current User: " + env("USER") + "

") print("

Time: " + date_format(time_now_ms(), "%Y-%m-%d %H:%M:%S") + "

") -print("

Version: " + to_string(content) + "

") +print("

Version: " + to_string(fun_version()) + "

") print("

Full Environment:

") print("
    ") all_env = env_all() From 83281bf2de4adcea16b8281857fbfaf02dbab2bb Mon Sep 17 00:00:00 2001 From: hanez Date: Mon, 29 Dec 2025 17:24:08 +0100 Subject: [PATCH 13/13] Added GET and POST support to HTTPServer class. (0.37.32) --- CMakeLists.txt | 2 +- examples/data/htdocs/info.fun | 30 +++++++++++++++++++++++++ examples/extra/http_server_test.fun | 32 +++++++++++++++++++++++++++ lib/net/http_server.fun | 34 ++++++++++++++++++++++++----- 4 files changed, 92 insertions(+), 6 deletions(-) create mode 100755 examples/extra/http_server_test.fun diff --git a/CMakeLists.txt b/CMakeLists.txt index 473dd62..dd7dbf9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.37.31 LANGUAGES C) +project(fun VERSION 0.37.32 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/examples/data/htdocs/info.fun b/examples/data/htdocs/info.fun index 4473ec0..65e64cb 100644 --- a/examples/data/htdocs/info.fun +++ b/examples/data/htdocs/info.fun @@ -9,6 +9,8 @@ * Added: 2025-12-28 */ +#include + print("") print("

    Fun Environment Information

    ") print("

    Current Path: " + env("PATH") + "

    ") @@ -25,4 +27,32 @@ while i < len(all_keys) print("
  • " + key + "=" + all_env[key] + "
  • ") i = i + 1 print("
") +print("

GET Variables:

") +print("
    ") +qs = env("QUERY_STRING") +if (len(qs) > 0) + params = str_split(qs, "&") + i = 0 + while i < len(params) + kv = str_split(params[i], "=") + if (len(kv) == 2) + print("
  • " + kv[0] + " = " + kv[1] + "
  • ") + else + print("
  • " + params[i] + "
  • ") + i = i + 1 +print("
") +print("

POST Variables:

") +print("
    ") +pd = env("POST_DATA") +if (len(pd) > 0) + params = str_split(pd, "&") + i = 0 + while i < len(params) + kv = str_split(params[i], "=") + if (len(kv) == 2) + print("
  • " + kv[0] + " = " + kv[1] + "
  • ") + else + print("
  • " + params[i] + "
  • ") + i = i + 1 +print("
") print("") diff --git a/examples/extra/http_server_test.fun b/examples/extra/http_server_test.fun new file mode 100755 index 0000000..ad3ab64 --- /dev/null +++ b/examples/extra/http_server_test.fun @@ -0,0 +1,32 @@ +#include + +c = TcpClient() +if (c.connect("127.0.0.1", 8080)) + print("Connecting to server...") + // Test GET + print("Testing GET...") + c.send("GET /info.fun?foo=bar&baz=qux HTTP/1.1\r\nHost: localhost\r\n\r\n") + resp = c.recv_all(4096) + print("GET Response contains foo=bar: " + to_string(find(resp, "foo = bar") >= 0)) + print("GET Response contains baz=qux: " + to_string(find(resp, "baz = qux") >= 0)) + + c.close() +else + print("Failed to connect to server") + +if (c.connect("127.0.0.1", 8080)) + // Test POST + print("Testing POST...") + body = "postfoo=postbar&postbaz=postqux" + req = "POST /info.fun HTTP/1.1\r\n" + req = req + "Host: localhost\r\n" + req = req + "Content-Length: " + to_string(len(body)) + "\r\n" + req = req + "\r\n" + req = req + body + c.send(req) + resp = c.recv_all(4096) + print("POST Response contains postfoo=postbar: " + to_string(find(resp, "postfoo = postbar") >= 0)) + print("POST Response contains postbaz=postqux: " + to_string(find(resp, "postbaz = postqux") >= 0)) + c.close() +else + print("Failed to connect to server for POST") diff --git a/lib/net/http_server.fun b/lib/net/http_server.fun index 3d36b03..dc78dac 100644 --- a/lib/net/http_server.fun +++ b/lib/net/http_server.fun @@ -51,20 +51,44 @@ class HTTPServer(number port) sock_close(fd) return 0 - method = parts[0] - path = parts[1] + method = str_trim(parts[0]) + full_path = str_trim(parts[1]) + + path = full_path + query_string = "" + q_pos = find(full_path, "?") + if (q_pos >= 0) + path = substr(full_path, 0, q_pos) + query_string = substr(full_path, q_pos + 1, len(full_path) - q_pos - 1) if (path == "/") path = "/index.html" - full_path = this.htdocs + path + file_path = this.htdocs + path + + post_data = "" + if (method == "POST") + // Find the end of headers (marked by \r\n\r\n or \n\n) + header_end = find(request, "\r\n\r\n") + if (header_end >= 0) + post_data = substr(request, header_end + 4, len(request) - header_end - 4) + else + header_end = find(request, "\n\n") + if (header_end >= 0) + post_data = substr(request, header_end + 2, len(request) - header_end - 2) content = "" if (str_ends_with(path, ".fun")) - res = proc_run("fun" + " " + full_path) + env_vars = "" + if (len(query_string) > 0) + env_vars = "QUERY_STRING='" + query_string + "' " + if (len(post_data) > 0) + env_vars = env_vars + "POST_DATA='" + post_data + "' " + + res = proc_run(env_vars + " " + "fun" + " " + file_path) content = res["out"] else - content = read_file(full_path) + content = read_file(file_path) if (len(content) > 0) this.send_response(fd, 200, "OK", content)