From d546632cd47540e52b6e09d5a28b3ac2c19efa4b Mon Sep 17 00:00:00 2001 From: hanez Date: Sat, 4 Oct 2025 02:52:30 +0200 Subject: [PATCH] Basic sockets (0.21.0) --- CMakeLists.txt | 2 +- examples/datetime_basic.fun | 0 examples/regex_demo.fun | 0 examples/regex_procedural.fun | 0 examples/tcp_http_get.fun | 34 +++++++++++ examples/tcp_http_get_class.fun | 30 ++++++++++ lib/io/socket.fun | 99 +++++++++++++++++++++++++++++++++ src/bytecode.h | 12 +++- src/parser.c | 75 +++++++++++++++++++++++++ src/vm.c | 10 ++++ src/vm.h | 5 +- src/vm/os/socket_close.c | 33 +++++++++++ src/vm/os/socket_recv.c | 53 ++++++++++++++++++ src/vm/os/socket_send.c | 43 ++++++++++++++ src/vm/os/socket_tcp_accept.c | 36 ++++++++++++ src/vm/os/socket_tcp_connect.c | 60 ++++++++++++++++++++ src/vm/os/socket_tcp_listen.c | 64 +++++++++++++++++++++ src/vm/os/socket_unix_connect.c | 53 ++++++++++++++++++ src/vm/os/socket_unix_listen.c | 62 +++++++++++++++++++++ 19 files changed, 667 insertions(+), 4 deletions(-) mode change 100644 => 100755 examples/datetime_basic.fun mode change 100644 => 100755 examples/regex_demo.fun mode change 100644 => 100755 examples/regex_procedural.fun create mode 100755 examples/tcp_http_get.fun create mode 100755 examples/tcp_http_get_class.fun create mode 100644 lib/io/socket.fun create mode 100644 src/vm/os/socket_close.c create mode 100644 src/vm/os/socket_recv.c create mode 100644 src/vm/os/socket_send.c create mode 100644 src/vm/os/socket_tcp_accept.c create mode 100644 src/vm/os/socket_tcp_connect.c create mode 100644 src/vm/os/socket_tcp_listen.c create mode 100644 src/vm/os/socket_unix_connect.c create mode 100644 src/vm/os/socket_unix_listen.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 2af9cbf..d60ed03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(fun VERSION 0.20.0 LANGUAGES C) +project(fun VERSION 0.21.0 LANGUAGES C) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/examples/datetime_basic.fun b/examples/datetime_basic.fun old mode 100644 new mode 100755 diff --git a/examples/regex_demo.fun b/examples/regex_demo.fun old mode 100644 new mode 100755 diff --git a/examples/regex_procedural.fun b/examples/regex_procedural.fun old mode 100644 new mode 100755 diff --git a/examples/tcp_http_get.fun b/examples/tcp_http_get.fun new file mode 100755 index 0000000..d084002 --- /dev/null +++ b/examples/tcp_http_get.fun @@ -0,0 +1,34 @@ +/** + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-04 + */ + +/* + * Simple TCP client in Fun: fetches HTTP/1.0 from example.org + * Requires network access. Demonstrates tcp_connect, sock_send, sock_recv, sock_close. + */ + +host = "example.org" +port = 80 + +fd = tcp_connect(host, port) +if (fd <= 0) + print("connect failed") +else + req = join([ + "GET / HTTP/1.0\r\n", + "Host: ", host, "\r\n", + "User-Agent: fun/0.20\r\n", + "Connection: close\r\n\r\n" + ], "") + sent = sock_send(fd, req) + // Read up to 8192 bytes (first chunk) + data = sock_recv(fd, 8192) + print(data) + sock_close(fd) diff --git a/examples/tcp_http_get_class.fun b/examples/tcp_http_get_class.fun new file mode 100755 index 0000000..9cfd10b --- /dev/null +++ b/examples/tcp_http_get_class.fun @@ -0,0 +1,30 @@ +/* + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-04 + */ + +#include + +// Simple HTTP GET using TcpClient class from stdlib + +c = TcpClient() +if (!c.connect("example.org", 80)) + print("connect failed") +else + req = join([ + "GET / HTTP/1.0\r\n", + "Host: example.org\r\n", + "User-Agent: fun/0.21\r\n", + "Connection: close\r\n", + "\r\n" + ], "") + c.send(req) + resp = c.recv_all(8192) + print(resp) + c.close() diff --git a/lib/io/socket.fun b/lib/io/socket.fun new file mode 100644 index 0000000..cd6f769 --- /dev/null +++ b/lib/io/socket.fun @@ -0,0 +1,99 @@ +/* + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-04 + */ + +/* + * Sockets convenience classes for Fun stdlib. + * + * Provides: + * - TcpClient: simple TCP client wrapper over built-ins: tcp_connect, sock_send, sock_recv, sock_close + * - UnixClient: simple UNIX-domain client wrapper over built-ins: unix_connect, sock_send, sock_recv, sock_close + * + * Usage: + * #include + * c = TcpClient() + * if (c.connect("example.org", 80)) + * c.send("GET / HTTP/1.0\r\nHost: example.org\r\n\r\n") + * resp = c.recv_all(8192) + * print(resp) + * c.close() + */ + +class TcpClient() + // file descriptor or 0 if not connected + fd = 0 + + fun connect(this, host, port) + // host: string, port: int + this.fd = tcp_connect(host, port) + return this.fd > 0 + + fun is_connected(this) + return this.fd > 0 + + fun send(this, data) + if (!this.is_connected()) + return -1 + // returns bytes sent or -1 + return sock_send(this.fd, to_string(data)) + + fun recv(this, maxlen) + if (!this.is_connected()) + return "" + // returns string (empty on EOF/error) + return sock_recv(this.fd, to_number(maxlen)) + + // Receive until EOF or up to max_total bytes (0 => unlimited, but capped inside recv) + fun recv_all(this, chunk_size) + cs = to_number(chunk_size) + if (cs <= 0) + cs = 4096 + buf = "" + while this.is_connected() + part = this.recv(cs) + if (len(part) == 0) + break + buf = join([buf, part], "") + // crude heuristic: stop if last chunk was smaller than requested + if (len(part) < cs) + break + return buf + + fun close(this) + if (this.is_connected()) + sock_close(this.fd) + this.fd = 0 + return 1 + +class UnixClient() + fd = 0 + + fun connect(this, path) + this.fd = unix_connect(to_string(path)) + return this.fd > 0 + + fun is_connected(this) + return this.fd > 0 + + fun send(this, data) + if (!this.is_connected()) + return -1 + return sock_send(this.fd, to_string(data)) + + fun recv(this, maxlen) + if (!this.is_connected()) + return "" + return sock_recv(this.fd, to_number(maxlen)) + + fun close(this) + if (this.is_connected()) + sock_close(this.fd) + this.fd = 0 + return 1 diff --git a/src/bytecode.h b/src/bytecode.h index 63a6059..30a7e3f 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -147,7 +147,17 @@ typedef enum { OP_PCSC_LIST_READERS, // pops ctx id; returns array of reader names (possibly empty) OP_PCSC_CONNECT, // pops reader, ctx id; returns handle id (>0) or 0 OP_PCSC_DISCONNECT, // pops handle id; returns 1/0 - OP_PCSC_TRANSMIT // pops apdu array, handle id; returns map {"data":[],"sw1":n,"sw2":n,"code":n} + OP_PCSC_TRANSMIT, // pops apdu array, handle id; returns map {"data":[],"sw1":n,"sw2":n,"code":n} + + // Sockets (UNIX platforms) + OP_SOCK_TCP_LISTEN, // pops backlog, port; returns listen fd (>0) or 0 + OP_SOCK_TCP_ACCEPT, // pops listen fd; returns client fd (>0) or 0 + OP_SOCK_TCP_CONNECT, // pops port, host; returns fd (>0) or 0 + OP_SOCK_SEND, // pops data string, fd; returns bytes sent (>=0) or -1 + OP_SOCK_RECV, // pops maxlen, fd; returns data string ("" on EOF/error) + OP_SOCK_CLOSE, // pops fd; returns 1/0 + OP_SOCK_UNIX_LISTEN, // pops backlog, path; returns listen fd (>0) or 0 + OP_SOCK_UNIX_CONNECT // pops path; returns fd (>0) or 0 } OpCode; typedef struct { diff --git a/src/parser.c b/src/parser.c index cb8e537..f3edbf5 100644 --- a/src/parser.c +++ b/src/parser.c @@ -757,6 +757,81 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) free(name); return 1; } + /* Socket builtins */ + if (strcmp(name, "tcp_listen") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tcp_listen expects (port, backlog)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "tcp_listen expects (port, backlog)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tcp_listen expects (port, backlog)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after tcp_listen args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_SOCK_TCP_LISTEN, 0); + free(name); + return 1; + } + if (strcmp(name, "tcp_accept") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tcp_accept expects (listen_fd)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after tcp_accept arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_SOCK_TCP_ACCEPT, 0); + free(name); + return 1; + } + if (strcmp(name, "tcp_connect") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tcp_connect expects (host, port)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "tcp_connect expects (host, port)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tcp_connect expects (host, port)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after tcp_connect args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_SOCK_TCP_CONNECT, 0); + free(name); + return 1; + } + if (strcmp(name, "sock_send") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "sock_send expects (fd, data)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "sock_send expects (fd, data)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "sock_send expects (fd, data)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after sock_send args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_SOCK_SEND, 0); + free(name); + return 1; + } + if (strcmp(name, "sock_recv") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "sock_recv expects (fd, maxlen)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "sock_recv expects (fd, maxlen)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "sock_recv expects (fd, maxlen)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after sock_recv args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_SOCK_RECV, 0); + free(name); + return 1; + } + if (strcmp(name, "sock_close") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "sock_close expects (fd)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after sock_close arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_SOCK_CLOSE, 0); + free(name); + return 1; + } + if (strcmp(name, "unix_listen") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "unix_listen expects (path, backlog)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "unix_listen expects (path, backlog)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "unix_listen expects (path, backlog)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after unix_listen args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_SOCK_UNIX_LISTEN, 0); + free(name); + return 1; + } + if (strcmp(name, "unix_connect") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "unix_connect expects (path)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after unix_connect arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_SOCK_UNIX_CONNECT, 0); + free(name); + return 1; + } /* string ops */ if (strcmp(name, "split") == 0) { (*pos)++; /* '(' */ diff --git a/src/vm.c b/src/vm.c index 11495a8..b805a9d 100644 --- a/src/vm.c +++ b/src/vm.c @@ -329,6 +329,16 @@ void vm_run(VM *vm, Bytecode *entry) { #include "vm/os/time_now_ms.c" #include "vm/os/clock_mono_ms.c" #include "vm/os/date_format.c" + + /* Socket ops */ + #include "vm/os/socket_tcp_listen.c" + #include "vm/os/socket_tcp_accept.c" + #include "vm/os/socket_tcp_connect.c" + #include "vm/os/socket_send.c" + #include "vm/os/socket_recv.c" + #include "vm/os/socket_close.c" + #include "vm/os/socket_unix_listen.c" + #include "vm/os/socket_unix_connect.c" #include "vm/pcsc/establish.c" #include "vm/pcsc/release.c" diff --git a/src/vm.h b/src/vm.h index 246f158..952761a 100644 --- a/src/vm.h +++ b/src/vm.h @@ -38,7 +38,8 @@ static const char *opcode_names[] = { "TIME_NOW_MS","CLOCK_MONO_MS","DATE_FORMAT", "THREAD_SPAWN","THREAD_JOIN","SLEEP_MS", "BAND","BOR","BXOR","BNOT","SHL","SHR","ROTL","ROTR", - "PCSC_ESTABLISH","PCSC_RELEASE","PCSC_LIST_READERS","PCSC_CONNECT","PCSC_DISCONNECT","PCSC_TRANSMIT" + "PCSC_ESTABLISH","PCSC_RELEASE","PCSC_LIST_READERS","PCSC_CONNECT","PCSC_DISCONNECT","PCSC_TRANSMIT", + "SOCK_TCP_LISTEN","SOCK_TCP_ACCEPT","SOCK_TCP_CONNECT","SOCK_SEND","SOCK_RECV","SOCK_CLOSE","SOCK_UNIX_LISTEN","SOCK_UNIX_CONNECT" }; typedef struct { @@ -81,7 +82,7 @@ void vm_dump_globals(VM *vm); void vm_run(VM *vm, Bytecode *entry); static inline int opcode_is_valid(int op) { - return op >= OP_NOP && op <= OP_PCSC_TRANSMIT; // all current opcodes + return op >= OP_NOP && op <= OP_SOCK_UNIX_CONNECT; // all current opcodes } #endif diff --git a/src/vm/os/socket_close.c b/src/vm/os/socket_close.c new file mode 100644 index 0000000..0d8a2ee --- /dev/null +++ b/src/vm/os/socket_close.c @@ -0,0 +1,33 @@ +/** + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-04 + */ + +#ifdef __unix__ +#include +#endif + +case OP_SOCK_CLOSE: { + /* Pops fd; returns 1/0 */ + Value fdv = pop_value(vm); + int ok = 0; +#ifdef __unix__ + if (fdv.type != VAL_INT) { + fprintf(stderr, "Runtime type error: sock_close expects (int fd)\n"); + free_value(fdv); + push_value(vm, make_int(0)); + break; + } + int fd = (int)fdv.i; + ok = (close(fd) == 0) ? 1 : 0; +#endif + free_value(fdv); + push_value(vm, make_int(ok)); + break; +} diff --git a/src/vm/os/socket_recv.c b/src/vm/os/socket_recv.c new file mode 100644 index 0000000..9d72d5c --- /dev/null +++ b/src/vm/os/socket_recv.c @@ -0,0 +1,53 @@ +/** + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-04 + */ + +#include + +#ifdef __unix__ +#include +#include +#include +#endif + +case OP_SOCK_RECV: { + /* Pops maxlen, fd; pushes data string ("" on EOF/error) */ + 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: sock_recv expects (int fd, int maxlen)\n"); + free_value(maxv); + free_value(fdv); + push_value(vm, make_string("")); + break; + } + 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 = recv(fd, out, (size_t)maxlen, 0); + 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/socket_send.c b/src/vm/os/socket_send.c new file mode 100644 index 0000000..efa859f --- /dev/null +++ b/src/vm/os/socket_send.c @@ -0,0 +1,43 @@ +/** + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-04 + */ + +#include + +#ifdef __unix__ +#include +#include +#include +#endif + +case OP_SOCK_SEND: { + /* Pops data string, fd; pushes bytes sent (>=0) or -1 */ + 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: sock_send expects (int fd, string data)\n"); + free_value(datav); + free_value(fdv); + push_value(vm, make_int(-1)); + break; + } + int fd = (int)fdv.i; + const char *buf = datav.s ? datav.s : ""; + size_t len = strlen(buf); + ssize_t n = send(fd, buf, len, 0); + if (n >= 0) sent = (int)n; else sent = -1; +#endif + free_value(datav); + free_value(fdv); + push_value(vm, make_int(sent)); + break; +} diff --git a/src/vm/os/socket_tcp_accept.c b/src/vm/os/socket_tcp_accept.c new file mode 100644 index 0000000..7dadef4 --- /dev/null +++ b/src/vm/os/socket_tcp_accept.c @@ -0,0 +1,36 @@ +/** + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-04 + */ + +#ifdef __unix__ +#include +#include +#include +#endif + +case OP_SOCK_TCP_ACCEPT: { + /* Pops listen fd; pushes client fd (>0) or 0 */ + Value fdv = pop_value(vm); + int client = 0; +#ifdef __unix__ + if (fdv.type != VAL_INT) { + fprintf(stderr, "Runtime type error: tcp_accept expects (int listen_fd)\n"); + free_value(fdv); + push_value(vm, make_int(0)); + break; + } + int s = (int)fdv.i; + int c = accept(s, NULL, NULL); + if (c >= 0) client = c; +#endif + free_value(fdv); + push_value(vm, make_int(client > 0 ? client : 0)); + break; +} diff --git a/src/vm/os/socket_tcp_connect.c b/src/vm/os/socket_tcp_connect.c new file mode 100644 index 0000000..d388903 --- /dev/null +++ b/src/vm/os/socket_tcp_connect.c @@ -0,0 +1,60 @@ +/** + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-04 + */ + +#include +#include + +#ifdef __unix__ +#include +#include +#include +#include +#endif + +case OP_SOCK_TCP_CONNECT: { + /* Pops port, host; pushes fd (>0) or 0 */ + Value portv = pop_value(vm); + Value hostv = pop_value(vm); + int fd = 0; +#ifdef __unix__ + if (hostv.type != VAL_STRING || portv.type != VAL_INT) { + fprintf(stderr, "Runtime type error: tcp_connect expects (string host, int port)\n"); + free_value(portv); + free_value(hostv); + push_value(vm, make_int(0)); + break; + } + char *host = value_to_string_alloc(&hostv); + int port = (int)portv.i; + if (host) { + char portstr[16]; + snprintf(portstr, sizeof(portstr), "%d", port); + struct addrinfo hints, *res = NULL, *rp; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + if (getaddrinfo(host, portstr, &hints, &res) == 0) { + for (rp = res; rp != NULL; rp = rp->ai_next) { + int s = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (s < 0) continue; + if (connect(s, rp->ai_addr, rp->ai_addrlen) == 0) { fd = s; break; } + close(s); + } + if (res) freeaddrinfo(res); + } + free(host); + } +#endif + free_value(portv); + free_value(hostv); + push_value(vm, make_int(fd > 0 ? fd : 0)); + break; +} diff --git a/src/vm/os/socket_tcp_listen.c b/src/vm/os/socket_tcp_listen.c new file mode 100644 index 0000000..c5fcc52 --- /dev/null +++ b/src/vm/os/socket_tcp_listen.c @@ -0,0 +1,64 @@ +/** + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-04 + */ + +#include +#include +#include + +#ifdef __unix__ +#include +#include +#include +#include +#endif + +case OP_SOCK_TCP_LISTEN: { + /* Pops backlog, port; pushes listen fd (>0) or 0 */ + Value backlogv = pop_value(vm); + Value portv = pop_value(vm); + int fd = 0; +#ifdef __unix__ + if (portv.type != VAL_INT || backlogv.type != VAL_INT) { + fprintf(stderr, "Runtime type error: tcp_listen expects (int port, int backlog)\n"); + free_value(backlogv); + free_value(portv); + push_value(vm, make_int(0)); + break; + } + int port = (int)portv.i; + int backlog = (int)backlogv.i; + int s = socket(AF_INET, SOCK_STREAM, 0); + if (s >= 0) { + int yes = 1; + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons((uint16_t)port); + if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) == 0) { + if (listen(s, backlog > 0 ? backlog : 1) == 0) { + fd = s; + } else { + close(s); + } + } else { + close(s); + } + } +#else + (void)fd; +#endif + free_value(backlogv); + free_value(portv); + push_value(vm, make_int(fd > 0 ? fd : 0)); + break; +} diff --git a/src/vm/os/socket_unix_connect.c b/src/vm/os/socket_unix_connect.c new file mode 100644 index 0000000..7490b2b --- /dev/null +++ b/src/vm/os/socket_unix_connect.c @@ -0,0 +1,53 @@ +/** +* This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-04 + */ + +#include +#ifdef __unix__ +#include +#include +#include +#include +#endif + +case OP_SOCK_UNIX_CONNECT: { + /* Pops path; returns fd (>0) or 0 */ + Value pathv = pop_value(vm); + int fd = 0; +#ifdef __unix__ + if (pathv.type != VAL_STRING) { + fprintf(stderr, "Runtime type error: unix_connect expects (string path)\n"); + free_value(pathv); + push_value(vm, make_int(0)); + break; + } + char *path = value_to_string_alloc(&pathv); + if (path) { + int s = socket(AF_UNIX, SOCK_STREAM, 0); + if (s >= 0) { + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + size_t maxlen = sizeof(addr.sun_path) - 1; + strncpy(addr.sun_path, path, maxlen); + addr.sun_path[maxlen] = '\0'; + if (connect(s, (struct sockaddr*)&addr, sizeof(addr)) == 0) { + fd = s; + } else { + close(s); + } + } + free(path); + } +#endif + free_value(pathv); + push_value(vm, make_int(fd > 0 ? fd : 0)); + break; +} diff --git a/src/vm/os/socket_unix_listen.c b/src/vm/os/socket_unix_listen.c new file mode 100644 index 0000000..dd4c2cd --- /dev/null +++ b/src/vm/os/socket_unix_listen.c @@ -0,0 +1,62 @@ +/** + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-04 + */ + +#include +#ifdef __unix__ +#include +#include +#include +#include +#endif + +case OP_SOCK_UNIX_LISTEN: { + /* Pops backlog, path; returns listen fd (>0) or 0 */ + Value backlogv = pop_value(vm); + Value pathv = pop_value(vm); + int fd = 0; +#ifdef __unix__ + if (pathv.type != VAL_STRING || backlogv.type != VAL_INT) { + fprintf(stderr, "Runtime type error: unix_listen expects (string path, int backlog)\n"); + free_value(backlogv); + free_value(pathv); + push_value(vm, make_int(0)); + break; + } + char *path = value_to_string_alloc(&pathv); + int backlog = (int)backlogv.i; + if (path) { + int s = socket(AF_UNIX, SOCK_STREAM, 0); + if (s >= 0) { + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + size_t maxlen = sizeof(addr.sun_path) - 1; + strncpy(addr.sun_path, path, maxlen); + addr.sun_path[maxlen] = '\0'; + unlink(addr.sun_path); /* best effort */ + if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) == 0) { + if (listen(s, backlog > 0 ? backlog : 1) == 0) { + fd = s; + } else { + close(s); + } + } else { + close(s); + } + } + free(path); + } +#endif + free_value(backlogv); + free_value(pathv); + push_value(vm, make_int(fd > 0 ? fd : 0)); + break; +}