Added some basic asyncio stuff. (0.39.12)
This commit is contained in:
parent
6a16e5fb45
commit
cb3eb0a498
9 changed files with 328 additions and 1 deletions
|
|
@ -1,5 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.10)
|
||||
project(fun VERSION 0.39.11 LANGUAGES C)
|
||||
project(fun VERSION 0.39.12 LANGUAGES C)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
|
|
|||
91
examples/io/async_http_client.fun
Executable file
91
examples/io/async_http_client.fun
Executable file
|
|
@ -0,0 +1,91 @@
|
|||
#!/usr/bin/env fun
|
||||
|
||||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2026-03-26
|
||||
*/
|
||||
|
||||
/*
|
||||
* Async-ish HTTP GET using new FD helper opcodes
|
||||
*
|
||||
* Demonstrates:
|
||||
* - tcp_connect(host, port)
|
||||
* - fd_set_nonblock(fd, on)
|
||||
* - fd_poll_write(fd, timeout_ms) and fd_poll_read(fd, timeout_ms)
|
||||
* - non-blocking send/recv loop
|
||||
*/
|
||||
|
||||
host = "example.org"
|
||||
port = 80
|
||||
|
||||
fd = tcp_connect(host, port)
|
||||
if (fd == 0)
|
||||
print("connect failed")
|
||||
exit(1)
|
||||
|
||||
ok = fd_set_nonblock(fd, 1)
|
||||
if (ok == 0)
|
||||
print("failed to set nonblocking")
|
||||
sock_close(fd)
|
||||
exit(1)
|
||||
|
||||
req = "GET / HTTP/1.1\r\nHost: " + host + "\r\nConnection: close\r\n\r\n"
|
||||
to_send = len(req)
|
||||
sent_total = 0
|
||||
|
||||
// Write request in a non-blocking fashion
|
||||
while (sent_total < to_send)
|
||||
// Wait until socket is writable (timeout 1000ms)
|
||||
wr = fd_poll_write(fd, 1000)
|
||||
if (wr < 0)
|
||||
print("poll write error")
|
||||
sock_close(fd)
|
||||
exit(1)
|
||||
if (wr == 0)
|
||||
// timeout, loop again
|
||||
continue
|
||||
|
||||
chunk = substr(req, sent_total, to_send - sent_total)
|
||||
n = sock_send(fd, chunk)
|
||||
if (n < 0)
|
||||
print("send error")
|
||||
sock_close(fd)
|
||||
exit(1)
|
||||
sent_total = sent_total + n
|
||||
|
||||
// Read response non-blocking until peer closes
|
||||
buf = ""
|
||||
while (true)
|
||||
rd = fd_poll_read(fd, 2000) // wait up to 2s for data
|
||||
if (rd < 0)
|
||||
print("poll read error")
|
||||
break
|
||||
if (rd == 0)
|
||||
// timeout or EOF; try a final read to see if closed
|
||||
data = sock_recv(fd, 4096)
|
||||
if (len(data) == 0)
|
||||
// assume connection closed
|
||||
break
|
||||
buf = buf + data
|
||||
continue
|
||||
|
||||
data = sock_recv(fd, 4096)
|
||||
if (len(data) == 0)
|
||||
// closed
|
||||
break
|
||||
buf = buf + data
|
||||
|
||||
sock_close(fd)
|
||||
|
||||
// Print the first lines of the response to show it's working
|
||||
print(substr(buf, 0, 200))
|
||||
|
||||
/*
|
||||
Expected: prints the beginning of an HTTP response from example.org
|
||||
*/
|
||||
|
|
@ -220,6 +220,11 @@ typedef enum {
|
|||
OP_SOCK_UNIX_LISTEN, // pops backlog, path; returns listen fd (>0) or 0
|
||||
OP_SOCK_UNIX_CONNECT, // pops path; returns fd (>0) or 0
|
||||
|
||||
// Async-friendly FD helpers (UNIX platforms)
|
||||
OP_FD_SET_NONBLOCK, // pops on:int (0/1), fd:int; returns 1 on success, 0 on error/unsupported
|
||||
OP_FD_POLL_READ, // pops timeout_ms:int, fd:int; returns 1 if readable, 0 if timeout/EOF, -1 on error
|
||||
OP_FD_POLL_WRITE, // pops timeout_ms:int, fd:int; returns 1 if writable, 0 if timeout, -1 on error
|
||||
|
||||
// process control
|
||||
OP_EXIT, // pops code (or uses operand) and terminates script with exit code
|
||||
|
||||
|
|
|
|||
82
src/parser.c
82
src/parser.c
|
|
@ -2853,6 +2853,88 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
|||
free(name);
|
||||
return 1;
|
||||
}
|
||||
/* Async-friendly FD helpers */
|
||||
if (strcmp(name, "fd_set_nonblock") == 0) {
|
||||
(*pos)++; /* '(' */
|
||||
/* Expect (fd, on) -> push fd then on so VM pops on first */
|
||||
if (!emit_expression(bc, src, len, pos)) {
|
||||
parser_fail(*pos, "fd_set_nonblock expects (fd, on)");
|
||||
free(name);
|
||||
return 0;
|
||||
}
|
||||
if (!consume_char(src, len, pos, ',')) {
|
||||
parser_fail(*pos, "fd_set_nonblock expects (fd, on)");
|
||||
free(name);
|
||||
return 0;
|
||||
}
|
||||
if (!emit_expression(bc, src, len, pos)) {
|
||||
parser_fail(*pos, "fd_set_nonblock expects (fd, on)");
|
||||
free(name);
|
||||
return 0;
|
||||
}
|
||||
if (!consume_char(src, len, pos, ')')) {
|
||||
parser_fail(*pos, "Expected ')' after fd_set_nonblock args");
|
||||
free(name);
|
||||
return 0;
|
||||
}
|
||||
bytecode_add_instruction(bc, OP_FD_SET_NONBLOCK, 0);
|
||||
free(name);
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(name, "fd_poll_read") == 0) {
|
||||
(*pos)++; /* '(' */
|
||||
/* Expect (fd, timeout_ms) -> push fd then timeout so VM pops timeout first */
|
||||
if (!emit_expression(bc, src, len, pos)) {
|
||||
parser_fail(*pos, "fd_poll_read expects (fd, timeout_ms)");
|
||||
free(name);
|
||||
return 0;
|
||||
}
|
||||
if (!consume_char(src, len, pos, ',')) {
|
||||
parser_fail(*pos, "fd_poll_read expects (fd, timeout_ms)");
|
||||
free(name);
|
||||
return 0;
|
||||
}
|
||||
if (!emit_expression(bc, src, len, pos)) {
|
||||
parser_fail(*pos, "fd_poll_read expects (fd, timeout_ms)");
|
||||
free(name);
|
||||
return 0;
|
||||
}
|
||||
if (!consume_char(src, len, pos, ')')) {
|
||||
parser_fail(*pos, "Expected ')' after fd_poll_read args");
|
||||
free(name);
|
||||
return 0;
|
||||
}
|
||||
bytecode_add_instruction(bc, OP_FD_POLL_READ, 0);
|
||||
free(name);
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(name, "fd_poll_write") == 0) {
|
||||
(*pos)++; /* '(' */
|
||||
/* Expect (fd, timeout_ms) */
|
||||
if (!emit_expression(bc, src, len, pos)) {
|
||||
parser_fail(*pos, "fd_poll_write expects (fd, timeout_ms)");
|
||||
free(name);
|
||||
return 0;
|
||||
}
|
||||
if (!consume_char(src, len, pos, ',')) {
|
||||
parser_fail(*pos, "fd_poll_write expects (fd, timeout_ms)");
|
||||
free(name);
|
||||
return 0;
|
||||
}
|
||||
if (!emit_expression(bc, src, len, pos)) {
|
||||
parser_fail(*pos, "fd_poll_write expects (fd, timeout_ms)");
|
||||
free(name);
|
||||
return 0;
|
||||
}
|
||||
if (!consume_char(src, len, pos, ')')) {
|
||||
parser_fail(*pos, "Expected ')' after fd_poll_write args");
|
||||
free(name);
|
||||
return 0;
|
||||
}
|
||||
bytecode_add_instruction(bc, OP_FD_POLL_WRITE, 0);
|
||||
free(name);
|
||||
return 1;
|
||||
}
|
||||
/* Serial builtins */
|
||||
if (strcmp(name, "serial_open") == 0) {
|
||||
(*pos)++; /* '(' */
|
||||
|
|
|
|||
8
src/vm.c
8
src/vm.c
|
|
@ -36,6 +36,9 @@
|
|||
#include <unistd.h>
|
||||
/* For hidden input (password) handling in OP_INPUT_LINE */
|
||||
#include <termios.h>
|
||||
/* For non-blocking sockets and polling */
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
// #include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
|
|
@ -857,6 +860,11 @@ void vm_run(VM *vm, Bytecode *entry) {
|
|||
#include "vm/os/socket_unix_connect.c"
|
||||
#include "vm/os/socket_unix_listen.c"
|
||||
|
||||
/* Async-friendly FD helpers (UNIX) */
|
||||
#include "vm/os/fd_set_nonblock.c"
|
||||
#include "vm/os/fd_poll_read.c"
|
||||
#include "vm/os/fd_poll_write.c"
|
||||
|
||||
#ifdef FUN_WITH_PCSC
|
||||
#include "vm/pcsc/connect.c"
|
||||
#include "vm/pcsc/disconnect.c"
|
||||
|
|
|
|||
1
src/vm.h
1
src/vm.h
|
|
@ -49,6 +49,7 @@ static const char *opcode_names[] = {
|
|||
"INI_LOAD", "INI_FREE", "INI_GET_STRING", "INI_GET_INT", "INI_GET_DOUBLE", "INI_GET_BOOL", "INI_SET", "INI_UNSET", "INI_SAVE",
|
||||
"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",
|
||||
"FD_SET_NONBLOCK", "FD_POLL_READ", "FD_POLL_WRITE",
|
||||
"EXIT",
|
||||
"OS_LIST_DIR",
|
||||
"TK_BIND",
|
||||
|
|
|
|||
50
src/vm/os/fd_poll_read.c
Normal file
50
src/vm/os/fd_poll_read.c
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2026-03-26
|
||||
*/
|
||||
|
||||
case OP_FD_POLL_READ: {
|
||||
/* Pops timeout_ms:int, fd:int; pushes 1 if readable, 0 on timeout/EOF, -1 on error */
|
||||
Value to = pop_value(vm);
|
||||
Value fdv = pop_value(vm);
|
||||
int rc = -1;
|
||||
#ifdef __unix__
|
||||
if (fdv.type != VAL_INT || to.type != VAL_INT) {
|
||||
fprintf(stderr, "Runtime type error: fd_poll_read expects (int fd, int timeout_ms)\n");
|
||||
rc = -1;
|
||||
} else {
|
||||
int fd = (int)fdv.i;
|
||||
int timeout_ms = (int)to.i;
|
||||
struct pollfd pfd;
|
||||
pfd.fd = fd;
|
||||
pfd.events = POLLIN;
|
||||
pfd.revents = 0;
|
||||
int pr = poll(&pfd, 1, timeout_ms);
|
||||
if (pr > 0) {
|
||||
if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
|
||||
rc = 0; /* treat as not readable / peer closed */
|
||||
} else if (pfd.revents & POLLIN) {
|
||||
rc = 1;
|
||||
} else {
|
||||
rc = 0;
|
||||
}
|
||||
} else if (pr == 0) {
|
||||
rc = 0; /* timeout */
|
||||
} else {
|
||||
rc = -1; /* error */
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)to; (void)fdv;
|
||||
#endif
|
||||
free_value(to);
|
||||
free_value(fdv);
|
||||
push_value(vm, make_int(rc));
|
||||
break;
|
||||
}
|
||||
50
src/vm/os/fd_poll_write.c
Normal file
50
src/vm/os/fd_poll_write.c
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2026-03-26
|
||||
*/
|
||||
|
||||
case OP_FD_POLL_WRITE: {
|
||||
/* Pops timeout_ms:int, fd:int; pushes 1 if writable, 0 on timeout, -1 on error */
|
||||
Value to = pop_value(vm);
|
||||
Value fdv = pop_value(vm);
|
||||
int rc = -1;
|
||||
#ifdef __unix__
|
||||
if (fdv.type != VAL_INT || to.type != VAL_INT) {
|
||||
fprintf(stderr, "Runtime type error: fd_poll_write expects (int fd, int timeout_ms)\n");
|
||||
rc = -1;
|
||||
} else {
|
||||
int fd = (int)fdv.i;
|
||||
int timeout_ms = (int)to.i;
|
||||
struct pollfd pfd;
|
||||
pfd.fd = fd;
|
||||
pfd.events = POLLOUT;
|
||||
pfd.revents = 0;
|
||||
int pr = poll(&pfd, 1, timeout_ms);
|
||||
if (pr > 0) {
|
||||
if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
|
||||
rc = 0; /* treat as not writable */
|
||||
} else if (pfd.revents & POLLOUT) {
|
||||
rc = 1;
|
||||
} else {
|
||||
rc = 0;
|
||||
}
|
||||
} else if (pr == 0) {
|
||||
rc = 0; /* timeout */
|
||||
} else {
|
||||
rc = -1; /* error */
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)to; (void)fdv;
|
||||
#endif
|
||||
free_value(to);
|
||||
free_value(fdv);
|
||||
push_value(vm, make_int(rc));
|
||||
break;
|
||||
}
|
||||
40
src/vm/os/fd_set_nonblock.c
Normal file
40
src/vm/os/fd_set_nonblock.c
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2026-03-26
|
||||
*/
|
||||
|
||||
case OP_FD_SET_NONBLOCK: {
|
||||
/* Pops on:int (0/1), fd:int; pushes 1 on success, 0 on error/unsupported */
|
||||
Value onv = pop_value(vm);
|
||||
Value fdv = pop_value(vm);
|
||||
int ok = 0;
|
||||
#ifdef __unix__
|
||||
if (fdv.type != VAL_INT || onv.type != VAL_INT) {
|
||||
fprintf(stderr, "Runtime type error: fd_set_nonblock expects (int fd, int on)\n");
|
||||
ok = 0;
|
||||
} else {
|
||||
int fd = (int)fdv.i;
|
||||
int on = (int)onv.i;
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
if (flags >= 0) {
|
||||
if (on)
|
||||
flags |= O_NONBLOCK;
|
||||
else
|
||||
flags &= ~O_NONBLOCK;
|
||||
if (fcntl(fd, F_SETFL, flags) == 0) ok = 1;
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)onv; (void)fdv;
|
||||
#endif
|
||||
free_value(onv);
|
||||
free_value(fdv);
|
||||
push_value(vm, make_int(ok ? 1 : 0));
|
||||
break;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue