Compare commits
13 commits
21f437ebaf
...
83281bf2de
| Author | SHA1 | Date | |
|---|---|---|---|
| 83281bf2de | |||
| c8393251e5 | |||
| a27ff12b08 | |||
| 17c2b44c1a | |||
| 10b75596cd | |||
| aa8701a5a1 | |||
| 41cb23614e | |||
| e1890a5b89 | |||
| 06e4054e92 | |||
| 9ce6e8d08f | |||
| c20486a239 | |||
| 384a7e4502 | |||
| a1c545c6a2 |
38 changed files with 1009 additions and 10 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(fun VERSION 0.37.22 LANGUAGES C)
|
project(fun VERSION 0.37.32 LANGUAGES C)
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 99)
|
set(CMAKE_C_STANDARD 99)
|
||||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
|
@ -378,10 +378,12 @@ target_include_directories(fun_core PUBLIC
|
||||||
# Apply options to core
|
# Apply options to core
|
||||||
if(FUN_DEBUG)
|
if(FUN_DEBUG)
|
||||||
message(STATUS "FUN_DEBUG enabled: building with verbose debug logging")
|
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)
|
target_compile_definitions(fun_core PUBLIC FUN_DEBUG=1)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Provide default stdlib directory to the runtime
|
# 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}")
|
target_compile_definitions(fun_core PUBLIC DEFAULT_LIB_DIR="${DEFAULT_LIB_DIR}")
|
||||||
|
|
||||||
# PCSC include and link (if enabled)
|
# PCSC include and link (if enabled)
|
||||||
|
|
@ -472,7 +474,6 @@ add_executable(fun
|
||||||
src/fun.c
|
src/fun.c
|
||||||
)
|
)
|
||||||
# Provide version string to the CLI
|
# Provide version string to the CLI
|
||||||
target_compile_definitions(fun PRIVATE FUN_VERSION=\"${PROJECT_VERSION}\")
|
|
||||||
if(FUN_WITH_REPL)
|
if(FUN_WITH_REPL)
|
||||||
# Enable REPL compilation path and add the REPL source
|
# Enable REPL compilation path and add the REPL source
|
||||||
target_compile_definitions(fun PRIVATE FUN_WITH_REPL=1)
|
target_compile_definitions(fun PRIVATE FUN_WITH_REPL=1)
|
||||||
|
|
|
||||||
42
README.md
42
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
|
- if/else if/else
|
||||||
- try/catch/finally
|
- 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)
|
### Extensions (only Linux actually)
|
||||||
|
|
||||||
|
|
|
||||||
30
examples/class_test.fun
Executable file
30
examples/class_test.fun
Executable file
|
|
@ -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 <you@hanez.org>
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
10
examples/data/htdocs/index.html
Normal file
10
examples/data/htdocs/index.html
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Fun HTTP Server</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Welcome to the Fun HTTP Server!</h1>
|
||||||
|
<p>This page is served from <code>./examples/data/htdocs/index.html</code>.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
58
examples/data/htdocs/info.fun
Normal file
58
examples/data/htdocs/info.fun
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <strings.fun>
|
||||||
|
|
||||||
|
print("<!doctype html><html><body>")
|
||||||
|
print("<h1>Fun Environment Information</h1>")
|
||||||
|
print("<p><strong>Current Path:</strong> " + env("PATH") + "</p>")
|
||||||
|
print("<p><strong>Current User:</strong> " + env("USER") + "</p>")
|
||||||
|
print("<p><strong>Time:</strong> " + date_format(time_now_ms(), "%Y-%m-%d %H:%M:%S") + "</p>")
|
||||||
|
print("<p><strong>Version:</strong> " + to_string(fun_version()) + "</p>")
|
||||||
|
print("<h2>Full Environment:</h2>")
|
||||||
|
print("<ul>")
|
||||||
|
all_env = env_all()
|
||||||
|
all_keys = keys(all_env)
|
||||||
|
i = 0
|
||||||
|
while i < len(all_keys)
|
||||||
|
key = all_keys[i]
|
||||||
|
print("<li>" + key + "=" + all_env[key] + "</li>")
|
||||||
|
i = i + 1
|
||||||
|
print("</ul>")
|
||||||
|
print("<h2>GET Variables:</h2>")
|
||||||
|
print("<ul>")
|
||||||
|
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("<li>" + kv[0] + " = " + kv[1] + "</li>")
|
||||||
|
else
|
||||||
|
print("<li>" + params[i] + "</li>")
|
||||||
|
i = i + 1
|
||||||
|
print("</ul>")
|
||||||
|
print("<h2>POST Variables:</h2>")
|
||||||
|
print("<ul>")
|
||||||
|
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("<li>" + kv[0] + " = " + kv[1] + "</li>")
|
||||||
|
else
|
||||||
|
print("<li>" + params[i] + "</li>")
|
||||||
|
i = i + 1
|
||||||
|
print("</ul>")
|
||||||
|
print("</body></html>")
|
||||||
28
examples/env_all.fun
Normal file
28
examples/env_all.fun
Normal file
|
|
@ -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 <you@hanez.org>
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
22
examples/extra/http_server.fun
Executable file
22
examples/extra/http_server.fun
Executable file
|
|
@ -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 <you@hanez.org>
|
||||||
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*
|
||||||
|
* Added: 2025-12-28
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <net/http_server.fun>
|
||||||
|
|
||||||
|
port = 8080
|
||||||
|
htdocs = "./examples/data/htdocs"
|
||||||
|
|
||||||
|
server = HTTPServer(port)
|
||||||
|
server.set_htdocs(htdocs)
|
||||||
|
|
||||||
|
server.start()
|
||||||
32
examples/extra/http_server_test.fun
Executable file
32
examples/extra/http_server_test.fun
Executable file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#include <io/socket.fun>
|
||||||
|
|
||||||
|
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")
|
||||||
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
54
examples/serial_demo.fun
Executable file
54
examples/serial_demo.fun
Executable file
|
|
@ -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 <you@hanez.org>
|
||||||
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*
|
||||||
|
* Added: 2025-12-28
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <io/serial.fun>
|
||||||
|
|
||||||
|
// 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?)
|
||||||
|
*/
|
||||||
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
55
examples/test_serial.fun
Executable file
55
examples/test_serial.fun
Executable file
|
|
@ -0,0 +1,55 @@
|
||||||
|
#!/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()
|
||||||
|
|
||||||
|
/* 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)
|
||||||
|
*/
|
||||||
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
4
examples/version.fun
Executable file
4
examples/version.fun
Executable file
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env fun
|
||||||
|
|
||||||
|
print(to_string(fun_version()))
|
||||||
|
|
||||||
|
|
@ -204,7 +204,7 @@ class RIPEMD160()
|
||||||
B = T
|
B = T
|
||||||
|
|
||||||
// Right side step
|
// 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
|
Ar = Er
|
||||||
Er = Dr
|
Er = Dr
|
||||||
Dr = this.rol32(Cr, 10)
|
Dr = this.rol32(Cr, 10)
|
||||||
|
|
|
||||||
65
lib/io/serial.fun
Normal file
65
lib/io/serial.fun
Normal file
|
|
@ -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 <io/serial.fun>
|
||||||
|
* 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
|
||||||
108
lib/net/http_server.fun
Normal file
108
lib/net/http_server.fun
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <io/socket.fun>
|
||||||
|
#include <strings.fun>
|
||||||
|
|
||||||
|
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 = 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"
|
||||||
|
|
||||||
|
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"))
|
||||||
|
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(file_path)
|
||||||
|
|
||||||
|
if (len(content) > 0)
|
||||||
|
this.send_response(fd, 200, "OK", content)
|
||||||
|
else
|
||||||
|
this.send_response(fd, 404, "Not Found", "<h1>404 Not Found</h1>")
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
@ -93,7 +93,7 @@ fun str_replace_all(s, from, to)
|
||||||
out = []
|
out = []
|
||||||
number i = 0
|
number i = 0
|
||||||
while i < n
|
while i < n
|
||||||
if (i + lf <= n) && (substr(src, i, lf) == f)
|
if ((i + lf <= n) && (substr(src, i, lf) == f))
|
||||||
push(out, t)
|
push(out, t)
|
||||||
i = i + lf
|
i = i + lf
|
||||||
else
|
else
|
||||||
|
|
|
||||||
2
make
2
make
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# This file is part of the Fun programming language.
|
# This file is part of the Fun programming language.
|
||||||
# https://fun-lang.xyz/
|
# https://fun-lang.xyz/
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,8 @@ static const char *opcode_name(OpCode op) {
|
||||||
case OP_TIME_NOW_MS: return "TIME_NOW_MS";
|
case OP_TIME_NOW_MS: return "TIME_NOW_MS";
|
||||||
case OP_CLOCK_MONO_MS: return "CLOCK_MONO_MS";
|
case OP_CLOCK_MONO_MS: return "CLOCK_MONO_MS";
|
||||||
case OP_DATE_FORMAT: return "DATE_FORMAT";
|
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_SPAWN: return "THREAD_SPAWN";
|
||||||
case OP_THREAD_JOIN: return "THREAD_JOIN";
|
case OP_THREAD_JOIN: return "THREAD_JOIN";
|
||||||
case OP_SLEEP_MS: return "SLEEP_MS";
|
case OP_SLEEP_MS: return "SLEEP_MS";
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,8 @@ typedef enum {
|
||||||
OP_TIME_NOW_MS, // pushes current wall-clock time in milliseconds since Unix epoch
|
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_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_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
|
// Threads
|
||||||
OP_THREAD_SPAWN, // operand: 0=no args, 1=has args; pops [args?], fn; pushes thread id (int>0)
|
OP_THREAD_SPAWN, // operand: 0=no args, 1=has args; pops [args?], fn; pushes thread id (int>0)
|
||||||
|
|
@ -215,6 +217,13 @@ typedef enum {
|
||||||
// Tk additions
|
// Tk additions
|
||||||
OP_TK_BIND, // pops command, event, id; binds event to command
|
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
|
// Tk (Tcl/Tk) optional minimal API
|
||||||
OP_TK_EVAL, // pops script string; pushes int rc (0 = OK)
|
OP_TK_EVAL, // pops script string; pushes int rc (0 = OK)
|
||||||
OP_TK_RESULT, // pushes string: last Tcl result
|
OP_TK_RESULT, // pushes string: last Tcl result
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
#include "parser.h"
|
#include "parser.h"
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#ifdef FUN_WITH_REPL
|
#ifdef FUN_WITH_REPL
|
||||||
|
|
@ -42,6 +43,9 @@ static void print_usage(const char *prog) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
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 vm;
|
||||||
vm_init(&vm);
|
vm_init(&vm);
|
||||||
|
|
||||||
|
|
|
||||||
67
src/parser.c
67
src/parser.c
|
|
@ -757,6 +757,20 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
||||||
free(name);
|
free(name);
|
||||||
return 1;
|
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) {
|
if (strcmp(name, "os_list_dir") == 0) {
|
||||||
(*pos)++; /* '(' */
|
(*pos)++; /* '(' */
|
||||||
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "os_list_dir expects (path)"); free(name); return 0; }
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "os_list_dir expects (path)"); free(name); return 0; }
|
||||||
|
|
@ -1288,6 +1302,59 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
||||||
free(name);
|
free(name);
|
||||||
return 1;
|
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 */
|
/* string ops */
|
||||||
if (strcmp(name, "split") == 0) {
|
if (strcmp(name, "split") == 0) {
|
||||||
(*pos)++; /* '(' */
|
(*pos)++; /* '(' */
|
||||||
|
|
|
||||||
7
src/vm.c
7
src/vm.c
|
|
@ -667,6 +667,8 @@ void vm_run(VM *vm, Bytecode *entry) {
|
||||||
#include "vm/math/random_seed.c"
|
#include "vm/math/random_seed.c"
|
||||||
|
|
||||||
#include "vm/os/env.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/sleep_ms.c"
|
||||||
#include "vm/os/thread_join.c"
|
#include "vm/os/thread_join.c"
|
||||||
#include "vm/os/thread_spawn.c"
|
#include "vm/os/thread_spawn.c"
|
||||||
|
|
@ -676,6 +678,11 @@ void vm_run(VM *vm, Bytecode *entry) {
|
||||||
#include "vm/os/clock_mono_ms.c"
|
#include "vm/os/clock_mono_ms.c"
|
||||||
#include "vm/os/date_format.c"
|
#include "vm/os/date_format.c"
|
||||||
#include "vm/os/random_number.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 */
|
/* Socket ops */
|
||||||
#include "vm/os/socket_tcp_listen.c"
|
#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",
|
"EXIT",
|
||||||
"OS_LIST_DIR",
|
"OS_LIST_DIR",
|
||||||
"TK_BIND",
|
"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",
|
"TK_EVAL","TK_RESULT","TK_LOOP","TK_WM_TITLE","TK_LABEL","TK_BUTTON","TK_PACK",
|
||||||
"TRY_PUSH","TRY_POP","THROW"
|
"TRY_PUSH","TRY_POP","THROW"
|
||||||
};
|
};
|
||||||
|
|
|
||||||
35
src/vm/os/env_all.c
Normal file
35
src/vm/os/env_all.c
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
20
src/vm/os/fun_version.c
Normal file
20
src/vm/os/fun_version.c
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
@ -24,8 +24,11 @@
|
||||||
#if defined(_WIN32) || defined(_WIN64)
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <bcrypt.h>
|
#include <bcrypt.h>
|
||||||
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <sys/random.h>
|
||||||
|
/* On some BSDs, arc4random_buf might be hidden by _POSIX_C_SOURCE. */
|
||||||
|
void arc4random_buf(void *, size_t);
|
||||||
#elif defined(__unix__)
|
#elif defined(__unix__)
|
||||||
#if __has_include(<sys/random.h>)
|
#if __has_include(<sys/random.h>)
|
||||||
#include <sys/random.h>
|
#include <sys/random.h>
|
||||||
|
|
@ -85,7 +88,7 @@ case OP_RANDOM_NUMBER: {
|
||||||
NTSTATUS st = BCryptGenRandom(NULL, raw, (ULONG)len, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
NTSTATUS st = BCryptGenRandom(NULL, raw, (ULONG)len, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||||||
ok = (st == 0);
|
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);
|
arc4random_buf(raw, (size_t)len);
|
||||||
ok = 1;
|
ok = 1;
|
||||||
|
|
|
||||||
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;
|
||||||
|
}
|
||||||
142
src/vm/os/serial_open.c
Normal file
142
src/vm/os/serial_open.c
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
|
||||||
|
/* 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
|
||||||
|
#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: {
|
||||||
|
/* 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