From a1c545c6a27ae732805bb60c7a164b15f6c1b761 Mon Sep 17 00:00:00 2001 From: hanez Date: Sun, 28 Dec 2025 02:23:44 +0100 Subject: [PATCH] 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; +}