Added serial device support. (0.37.23)
This commit is contained in:
parent
21f437ebaf
commit
a1c545c6a2
19 changed files with 403 additions and 1 deletions
|
|
@ -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)
|
||||
|
|
|
|||
0
examples/ripemd160_debug.fun
Normal file → Executable file
0
examples/ripemd160_debug.fun
Normal file → Executable file
0
examples/ripemd160_demo.fun
Normal file → Executable file
0
examples/ripemd160_demo.fun
Normal file → Executable file
0
examples/ripemd160_test.fun
Normal file → Executable file
0
examples/ripemd160_test.fun
Normal file → Executable file
0
examples/test_bits.fun
Normal file → Executable file
0
examples/test_bits.fun
Normal file → Executable file
0
examples/test_rol.fun
Normal file → Executable file
0
examples/test_rol.fun
Normal file → Executable file
50
examples/test_serial.fun
Executable file
50
examples/test_serial.fun
Executable file
|
|
@ -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 <you@hanez.org>
|
||||
* 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()
|
||||
0
examples/test_shl.fun
Normal file → Executable file
0
examples/test_shl.fun
Normal file → Executable file
0
examples/test_types.fun
Normal file → Executable file
0
examples/test_types.fun
Normal file → Executable file
0
examples/test_xor.fun
Normal file → Executable file
0
examples/test_xor.fun
Normal file → Executable file
|
|
@ -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
|
||||
|
|
|
|||
53
src/parser.c
53
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)++; /* '(' */
|
||||
|
|
|
|||
5
src/vm.c
5
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"
|
||||
|
|
|
|||
1
src/vm.h
1
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"
|
||||
};
|
||||
|
|
|
|||
31
src/vm/os/serial_close.c
Normal file
31
src/vm/os/serial_close.c
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2025-12-28
|
||||
*/
|
||||
|
||||
#ifdef __unix__
|
||||
#include <unistd.h>
|
||||
#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;
|
||||
}
|
||||
90
src/vm/os/serial_config.c
Normal file
90
src/vm/os/serial_config.c
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2025-12-28
|
||||
*/
|
||||
|
||||
#ifdef __unix__
|
||||
#include <termios.h>
|
||||
#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;
|
||||
}
|
||||
82
src/vm/os/serial_open.c
Normal file
82
src/vm/os/serial_open.c
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
/**
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2025-12-28
|
||||
*/
|
||||
|
||||
#ifdef __unix__
|
||||
#include <fcntl.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#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;
|
||||
}
|
||||
47
src/vm/os/serial_recv.c
Normal file
47
src/vm/os/serial_recv.c
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2025-12-28
|
||||
*/
|
||||
|
||||
#ifdef __unix__
|
||||
#include <unistd.h>
|
||||
#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;
|
||||
}
|
||||
36
src/vm/os/serial_send.c
Normal file
36
src/vm/os/serial_send.c
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2025-12-28
|
||||
*/
|
||||
|
||||
#ifdef __unix__
|
||||
#include <unistd.h>
|
||||
#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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue