Networking.
This commit is contained in:
parent
1855363661
commit
1a08b3adf0
79
GEMINI.md
Normal file
79
GEMINI.md
Normal file
@ -0,0 +1,79 @@
|
||||
# Wren CLI Project
|
||||
|
||||
## Project Overview
|
||||
|
||||
The Wren CLI is a command-line interface and REPL (Read-Eval-Print Loop) for the Wren programming language. It embeds the Wren Virtual Machine (VM) and provides standard IO capabilities (file system, networking, etc.) using `libuv`.
|
||||
|
||||
**Key Components:**
|
||||
* **`src/cli`**: Contains the main entry point (`main.c`) and CLI-specific logic.
|
||||
* **`src/module`**: Implementations of built-in modules (like `io`, `os`, `scheduler`, `timer`, `net`).
|
||||
* **`deps/wren`**: The core Wren VM source code.
|
||||
* **`deps/libuv`**: The asynchronous I/O library used for system interactions.
|
||||
|
||||
## New Features
|
||||
|
||||
* **Networking**: A full-featured `net` module supporting TCP `Socket` and `Server`.
|
||||
* **Stderr**: `io` module now includes `Stderr` class.
|
||||
|
||||
## Building
|
||||
|
||||
The project uses `make` for Linux/macOS and Visual Studio for Windows.
|
||||
|
||||
**Linux:**
|
||||
```bash
|
||||
cd projects/make
|
||||
make
|
||||
```
|
||||
|
||||
**macOS:**
|
||||
```bash
|
||||
cd projects/make.mac
|
||||
make
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
Open `projects/vs2017/wren-cli.sln` or `projects/vs2019/wren-cli.sln` in Visual Studio.
|
||||
|
||||
**Output:**
|
||||
The compiled binary is placed in the `bin/` directory.
|
||||
* Release: `bin/wren_cli`
|
||||
* Debug: `bin/wren_cli_d`
|
||||
|
||||
## Running
|
||||
|
||||
**REPL:**
|
||||
Run the binary without arguments to start the interactive shell:
|
||||
```bash
|
||||
./bin/wren_cli
|
||||
```
|
||||
|
||||
**Run a Script:**
|
||||
Pass the path to a Wren script:
|
||||
```bash
|
||||
./bin/wren_cli path/to/script.wren
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
The project uses a Python script to run the test suite.
|
||||
|
||||
**Run All Tests:**
|
||||
```bash
|
||||
python util/test.py
|
||||
```
|
||||
|
||||
**Run Specific Suite:**
|
||||
You can pass a path substring to run a subset of tests:
|
||||
```bash
|
||||
python util/test.py io
|
||||
python util/test.py language
|
||||
```
|
||||
|
||||
**Test Structure:**
|
||||
Tests are located in the `test/` directory. They are written in Wren and use expectation comments (e.g., `// expect: ...`) which the test runner parses to verify output.
|
||||
|
||||
## Development Conventions
|
||||
|
||||
* **Module Implementation**: Built-in modules are often split between C and Wren. The Wren side (`.wren` files in `src/module`) uses `foreign` declarations which are bound to C functions (`.c` files in `src/module`).
|
||||
* **Code Style**: Follows the style of the existing C and Wren files.
|
||||
* **Build System**: The `Makefile`s are generated by `premake`. If you need to change the build configuration, modify `projects/premake/premake5.lua` and regenerate the projects (though you can editing Makefiles directly for small local changes).
|
||||
15
example/benchmark/fib.wren
vendored
Normal file
15
example/benchmark/fib.wren
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
import "os" for Process
|
||||
|
||||
var start = System.clock
|
||||
|
||||
var fib
|
||||
fib = Fn.new { |n|
|
||||
if (n < 2) return n
|
||||
return fib.call(n - 1) + fib.call(n - 2)
|
||||
}
|
||||
|
||||
for (i in 1..5) {
|
||||
System.print("fib(28) = " + fib.call(28).toString)
|
||||
}
|
||||
|
||||
System.print("Elapsed: " + (System.clock - start).toString + "s")
|
||||
148
example/benchmark/nbody.wren
vendored
Normal file
148
example/benchmark/nbody.wren
vendored
Normal file
@ -0,0 +1,148 @@
|
||||
import "os" for Process
|
||||
|
||||
class Body {
|
||||
construct new(x, y, z, vx, vy, vz, mass) {
|
||||
_x = x
|
||||
_y = y
|
||||
_z = z
|
||||
_vx = vx
|
||||
_vy = vy
|
||||
_vz = vz
|
||||
_mass = mass
|
||||
}
|
||||
|
||||
x { _x }
|
||||
x=(v) { _x = v }
|
||||
y { _y }
|
||||
y=(v) { _y = v }
|
||||
z { _z }
|
||||
z=(v) { _z = v }
|
||||
vx { _vx }
|
||||
vx=(v) { _vx = v }
|
||||
vy { _vy }
|
||||
vy=(v) { _vy = v }
|
||||
vz { _vz }
|
||||
vz=(v) { _vz = v }
|
||||
mass { _mass }
|
||||
|
||||
offsetMomentum(px, py, pz) {
|
||||
_vx = -px / 0.01227246237529089
|
||||
_vy = -py / 0.01227246237529089
|
||||
_vz = -pz / 0.01227246237529089
|
||||
}
|
||||
}
|
||||
|
||||
class NBody {
|
||||
static init() {
|
||||
var pi = 3.141592653589793
|
||||
var solarMass = 4 * pi * pi
|
||||
var daysPerYear = 365.24
|
||||
|
||||
__bodies = [
|
||||
// Sun
|
||||
Body.new(0, 0, 0, 0, 0, 0, solarMass),
|
||||
// Jupiter
|
||||
Body.new(
|
||||
4.84143144246472090e+00,
|
||||
-1.16032004402742839e+00,
|
||||
-1.03622044471123109e-01,
|
||||
1.66007664274403694e-03 * daysPerYear,
|
||||
7.69901118419740425e-03 * daysPerYear,
|
||||
-6.90460016972063023e-05 * daysPerYear,
|
||||
9.54791938424326609e-04 * solarMass),
|
||||
// Saturn
|
||||
Body.new(
|
||||
8.34336671824457987e+00,
|
||||
4.12479856412430479e+00,
|
||||
-4.03523417114321381e-01,
|
||||
-2.76742510726862411e-03 * daysPerYear,
|
||||
4.99852801234917238e-03 * daysPerYear,
|
||||
2.30417297573763929e-05 * daysPerYear,
|
||||
2.85885980666130812e-04 * solarMass),
|
||||
// Uranus
|
||||
Body.new(
|
||||
1.28943695618239209e+01,
|
||||
-1.51111514016986312e+01,
|
||||
-2.23307578892655734e-01,
|
||||
2.96460137564761618e-03 * daysPerYear,
|
||||
2.37847173959480950e-03 * daysPerYear,
|
||||
-2.96589568540237556e-05 * daysPerYear,
|
||||
4.36624404335156298e-05 * solarMass),
|
||||
// Neptune
|
||||
Body.new(
|
||||
1.53796971148509165e+01,
|
||||
-2.59193146099879641e+01,
|
||||
1.79258772950371181e-01,
|
||||
2.68067772490389322e-03 * daysPerYear,
|
||||
1.62824170038242295e-03 * daysPerYear,
|
||||
-9.51592254519715870e-05 * daysPerYear,
|
||||
5.15138902046611451e-05 * solarMass)
|
||||
]
|
||||
|
||||
var px = 0
|
||||
var py = 0
|
||||
var pz = 0
|
||||
for (b in __bodies) {
|
||||
px = px + b.vx * b.mass
|
||||
py = py + b.vy * b.mass
|
||||
pz = pz + b.vz * b.mass
|
||||
}
|
||||
__bodies[0].offsetMomentum(px, py, pz)
|
||||
}
|
||||
|
||||
static energy() {
|
||||
var e = 0
|
||||
for (i in 0...__bodies.count) {
|
||||
var bi = __bodies[i]
|
||||
e = e + 0.5 * bi.mass * (bi.vx * bi.vx + bi.vy * bi.vy + bi.vz * bi.vz)
|
||||
for (j in i + 1...__bodies.count) {
|
||||
var bj = __bodies[j]
|
||||
var dx = bi.x - bj.x
|
||||
var dy = bi.y - bj.y
|
||||
var dz = bi.z - bj.z
|
||||
var distance = (dx * dx + dy * dy + dz * dz).sqrt
|
||||
e = e - (bi.mass * bj.mass) / distance
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
static advance(dt) {
|
||||
for (i in 0...__bodies.count) {
|
||||
var bi = __bodies[i]
|
||||
for (j in i + 1...__bodies.count) {
|
||||
var bj = __bodies[j]
|
||||
var dx = bi.x - bj.x
|
||||
var dy = bi.y - bj.y
|
||||
var dz = bi.z - bj.z
|
||||
var d2 = dx * dx + dy * dy + dz * dz
|
||||
var mag = dt / (d2 * d2.sqrt)
|
||||
|
||||
bi.vx = bi.vx - dx * bj.mass * mag
|
||||
bi.vy = bi.vy - dy * bj.mass * mag
|
||||
bi.vz = bi.vz - dz * bj.mass * mag
|
||||
|
||||
bj.vx = bj.vx + dx * bi.mass * mag
|
||||
bj.vy = bj.vy + dy * bi.mass * mag
|
||||
bj.vz = bj.vz + dz * bi.mass * mag
|
||||
}
|
||||
}
|
||||
|
||||
for (b in __bodies) {
|
||||
b.x = b.x + dt * b.vx
|
||||
b.y = b.y + dt * b.vy
|
||||
b.z = b.z + dt * b.vz
|
||||
}
|
||||
}
|
||||
|
||||
static run(n) {
|
||||
init()
|
||||
System.print(energy())
|
||||
for (i in 0...n) advance(0.01)
|
||||
System.print(energy())
|
||||
}
|
||||
}
|
||||
|
||||
var start = System.clock
|
||||
NBody.run(100000)
|
||||
System.print("Elapsed: " + (System.clock - start).toString + "s")
|
||||
57
example/chat_server.wren
vendored
Normal file
57
example/chat_server.wren
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
import "net" for Server, Socket
|
||||
import "scheduler" for Scheduler
|
||||
|
||||
class ChatServer {
|
||||
construct new(port) {
|
||||
_port = port
|
||||
_clients = []
|
||||
_server = Server.bind("0.0.0.0", port)
|
||||
System.print("Chat Server listening on port %(_port)")
|
||||
}
|
||||
|
||||
run() {
|
||||
while (true) {
|
||||
var socket = _server.accept()
|
||||
handleNewClient(socket)
|
||||
}
|
||||
}
|
||||
|
||||
handleNewClient(socket) {
|
||||
_clients.add(socket)
|
||||
var id = _clients.count
|
||||
System.print("Client %(id) connected. Total: %(_clients.count)")
|
||||
|
||||
Fiber.new {
|
||||
broadcast("Client %(id) joined the chat!\n", socket)
|
||||
|
||||
while (true) {
|
||||
var message = socket.read()
|
||||
if (message == null || message == "") break
|
||||
|
||||
System.print("Client %(id): %(message.trim())")
|
||||
broadcast("Client %(id): %(message)", socket)
|
||||
}
|
||||
|
||||
System.print("Client %(id) disconnected.")
|
||||
_clients.remove(socket)
|
||||
socket.close()
|
||||
broadcast("Client %(id) left the chat.\n", null)
|
||||
}.call()
|
||||
}
|
||||
|
||||
broadcast(message, sender) {
|
||||
for (client in _clients) {
|
||||
if (client != sender) {
|
||||
// We use a separate fiber for each write to prevent one slow
|
||||
// client from blocking the broadcast to others
|
||||
Fiber.new {
|
||||
client.write(message)
|
||||
}.call()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var server = ChatServer.new(7070)
|
||||
server.run()
|
||||
25
example/echo_server.wren
vendored
Normal file
25
example/echo_server.wren
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
import "net" for Server
|
||||
import "scheduler" for Scheduler
|
||||
|
||||
var port = 9090
|
||||
var server = Server.bind("0.0.0.0", port)
|
||||
System.print("Echo Server running on port %(port)")
|
||||
|
||||
while (true) {
|
||||
var socket = server.accept()
|
||||
System.print("New connection accepted")
|
||||
|
||||
Fiber.new {
|
||||
var socket_ = socket // Capture variable
|
||||
while (true) {
|
||||
var data = socket_.read()
|
||||
if (data == null) {
|
||||
break
|
||||
}
|
||||
System.print("Received: %(data.count) bytes")
|
||||
socket_.write(data)
|
||||
}
|
||||
System.print("Connection closed")
|
||||
socket_.close()
|
||||
}.call()
|
||||
}
|
||||
57
example/file_explorer.wren
vendored
Normal file
57
example/file_explorer.wren
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
import "io" for Directory, File, Stat
|
||||
|
||||
class FileExplorer {
|
||||
static listRecursive(path, depth) {
|
||||
var indent = " " * depth
|
||||
var files = []
|
||||
|
||||
// We wrap Directory.list in a try to handle permission errors
|
||||
var fiber = Fiber.new {
|
||||
files = Directory.list(path)
|
||||
}
|
||||
fiber.try()
|
||||
|
||||
if (fiber.error != null) {
|
||||
System.print("\%(indent)[!] Error accessing \%(path)")
|
||||
return
|
||||
}
|
||||
|
||||
// Sort files manually since String comparison isn't built-in
|
||||
files.sort(Fn.new {|a, b|
|
||||
var ba = a.bytes
|
||||
var bb = b.bytes
|
||||
var len = ba.count < bb.count ? ba.count : bb.count
|
||||
for (i in 0...len) {
|
||||
if (ba[i] < bb[i]) return true
|
||||
if (ba[i] > bb[i]) return false
|
||||
}
|
||||
return ba.count < bb.count
|
||||
})
|
||||
|
||||
for (file in files) {
|
||||
if (file == "." || file == "..") continue
|
||||
|
||||
var fullPath = path + "/" + file
|
||||
if (path == ".") fullPath = "./" + file
|
||||
|
||||
var stat = Stat.path(fullPath)
|
||||
if (stat.isDirectory) {
|
||||
System.print("\%(indent)[D] \%(file)/")
|
||||
listRecursive(fullPath, depth + 1)
|
||||
} else {
|
||||
var size = stat.size
|
||||
var unit = "B"
|
||||
if (size > 1024) {
|
||||
size = size / 1024
|
||||
unit = "KB"
|
||||
}
|
||||
System.print("\%(indent)[F] \%(file) (\%(size.round)\%(unit))")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
System.print("Recursive directory listing for current path:")
|
||||
System.print("--------------------------------------------")
|
||||
FileExplorer.listRecursive(".", 0)
|
||||
42
example/http_client.wren
vendored
Normal file
42
example/http_client.wren
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
import "net" for Socket
|
||||
import "scheduler" for Scheduler
|
||||
|
||||
class HttpClient {
|
||||
static get(host, port, path) {
|
||||
System.print("Connecting to %(host):%(port)...")
|
||||
var socket = Socket.connect(host, port)
|
||||
|
||||
var request = "GET %(path) HTTP/1.1\r\n" +
|
||||
"Host: %(host)\r\n" +
|
||||
"User-Agent: Wren-CLI\r\n" +
|
||||
"Connection: close\r\n" +
|
||||
"\r\n"
|
||||
|
||||
System.print("Sending request...")
|
||||
socket.write(request)
|
||||
|
||||
System.print("Waiting for response...")
|
||||
var response = ""
|
||||
while (true) {
|
||||
var chunk = socket.read()
|
||||
if (chunk == null) break
|
||||
response = response + chunk
|
||||
}
|
||||
|
||||
socket.close()
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
// Example usage: Fetching from a local server or a known public IP
|
||||
// Note: This implementation is for raw IP/DNS if supported by uv_tcp_connect.
|
||||
// Since our net.c uses uv_ip4_addr, we use localhost for the example.
|
||||
var host = "127.0.0.1"
|
||||
var port = 8080 // Try to connect to our own http_server example
|
||||
var path = "/"
|
||||
|
||||
var result = HttpClient.get(host, port, path)
|
||||
System.print("\n--- Response Received ---
|
||||
")
|
||||
System.print(result)
|
||||
190
example/http_server.wren
vendored
Normal file
190
example/http_server.wren
vendored
Normal file
@ -0,0 +1,190 @@
|
||||
import "net" for Server, Socket
|
||||
import "io" for File, Directory, Stat
|
||||
import "scheduler" for Scheduler
|
||||
|
||||
class HttpServer {
|
||||
construct new(port) {
|
||||
_port = port
|
||||
_server = Server.bind("0.0.0.0", port)
|
||||
System.print("HTTP Server listening on http://localhost:%(port)")
|
||||
}
|
||||
|
||||
run() {
|
||||
while (true) {
|
||||
var socket = _server.accept()
|
||||
Fiber.new {
|
||||
handleClient_(socket)
|
||||
}.call()
|
||||
}
|
||||
}
|
||||
|
||||
handleClient_(socket) {
|
||||
var requestData = socket.read()
|
||||
if (requestData == null || requestData == "") {
|
||||
socket.close()
|
||||
return
|
||||
}
|
||||
|
||||
var lines = requestData.split("\r\n")
|
||||
if (lines.count == 0) {
|
||||
socket.close()
|
||||
return
|
||||
}
|
||||
|
||||
var requestLine = lines[0].split(" ")
|
||||
if (requestLine.count < 2) {
|
||||
sendError_(socket, 400, "Bad Request")
|
||||
return
|
||||
}
|
||||
|
||||
var method = requestLine[0]
|
||||
var path = requestLine[1]
|
||||
|
||||
System.print("%(method) %(path)")
|
||||
|
||||
if (method != "GET") {
|
||||
sendError_(socket, 405, "Method Not Allowed")
|
||||
return
|
||||
}
|
||||
|
||||
path = path.replace("\%20", " ")
|
||||
|
||||
if (path.contains("..")) {
|
||||
sendError_(socket, 403, "Forbidden")
|
||||
return
|
||||
}
|
||||
|
||||
var localPath = "." + path
|
||||
if (localPath.endsWith("/")) localPath = localPath[0..-2]
|
||||
if (localPath == "") localPath = "."
|
||||
|
||||
if (!exists(localPath)) {
|
||||
sendError_(socket, 404, "Not Found")
|
||||
return
|
||||
}
|
||||
|
||||
if (isDirectory(localPath)) {
|
||||
serveDirectory_(socket, localPath, path)
|
||||
} else {
|
||||
serveFile_(socket, localPath)
|
||||
}
|
||||
|
||||
socket.close()
|
||||
}
|
||||
|
||||
exists(path) {
|
||||
if (File.exists(path)) return true
|
||||
if (Directory.exists(path)) return true
|
||||
return false
|
||||
}
|
||||
|
||||
isDirectory(path) {
|
||||
return Directory.exists(path)
|
||||
}
|
||||
|
||||
serveDirectory_(socket, localPath, requestPath) {
|
||||
var files
|
||||
var fiber = Fiber.new {
|
||||
files = Directory.list(localPath)
|
||||
}
|
||||
fiber.try()
|
||||
|
||||
if (fiber.error != null) {
|
||||
sendError_(socket, 500, "Internal Server Error: " + fiber.error)
|
||||
return
|
||||
}
|
||||
|
||||
files.sort(Fn.new {|a, b|
|
||||
var ba = a.bytes
|
||||
var bb = b.bytes
|
||||
var len = ba.count
|
||||
if (bb.count < len) len = bb.count
|
||||
|
||||
for (i in 0...len) {
|
||||
if (ba[i] < bb[i]) return true
|
||||
if (ba[i] > bb[i]) return false
|
||||
}
|
||||
|
||||
return ba.count < bb.count
|
||||
})
|
||||
|
||||
var html = "<!DOCTYPE html><html><head><title>Index of %(requestPath)</title></head><body>"
|
||||
html = html + "<h1>Index of %(requestPath)</h1><hr><ul>"
|
||||
|
||||
if (requestPath != "/") {
|
||||
var parent = requestPath.split("/")
|
||||
if (parent.count > 1) {
|
||||
parent.removeAt(-1)
|
||||
var parentPath = parent.join("/")
|
||||
if (parentPath == "") parentPath = "/"
|
||||
html = html + "<li><a href=\"%(parentPath)\">..</a></li>"
|
||||
}
|
||||
}
|
||||
|
||||
for (file in files) {
|
||||
var href = requestPath
|
||||
if (!href.endsWith("/")) href = href + "/"
|
||||
href = href + file
|
||||
html = html + "<li><a href=\"%(href)\">%(file)</a></li>"
|
||||
}
|
||||
|
||||
html = html + "</ul><hr></body></html>"
|
||||
|
||||
sendResponse_(socket, 200, "OK", "text/html", html)
|
||||
}
|
||||
|
||||
serveFile_(socket, localPath) {
|
||||
var content
|
||||
var fiber = Fiber.new {
|
||||
content = File.read(localPath)
|
||||
}
|
||||
fiber.try()
|
||||
|
||||
if (fiber.error != null) {
|
||||
sendError_(socket, 500, "Error reading file: " + fiber.error)
|
||||
return
|
||||
}
|
||||
|
||||
var contentType = "application/octet-stream"
|
||||
if (localPath.endsWith(".html")) {
|
||||
contentType = "text/html"
|
||||
} else if (localPath.endsWith(".txt")) {
|
||||
contentType = "text/plain"
|
||||
} else if (localPath.endsWith(".wren")) {
|
||||
contentType = "text/plain"
|
||||
} else if (localPath.endsWith(".c")) {
|
||||
contentType = "text/plain"
|
||||
} else if (localPath.endsWith(".h")) {
|
||||
contentType = "text/plain"
|
||||
} else if (localPath.endsWith(".md")) {
|
||||
contentType = "text/markdown"
|
||||
} else if (localPath.endsWith(".json")) {
|
||||
contentType = "application/json"
|
||||
} else if (localPath.endsWith(".png")) {
|
||||
contentType = "image/png"
|
||||
} else if (localPath.endsWith(".jpg")) {
|
||||
contentType = "image/jpeg"
|
||||
}
|
||||
|
||||
sendResponse_(socket, 200, "OK", contentType, content)
|
||||
}
|
||||
|
||||
sendError_(socket, code, message) {
|
||||
var html = "<h1>%(code) %(message)</h1>"
|
||||
sendResponse_(socket, code, message, "text/html", html)
|
||||
socket.close()
|
||||
}
|
||||
|
||||
sendResponse_(socket, code, status, contentType, body) {
|
||||
var response = "HTTP/1.1 %(code) %(status)\r\n" +
|
||||
"Content-Type: %(contentType)\r\n" +
|
||||
"Content-Length: %(body.count)\r\n" +
|
||||
"Connection: close\r\n" +
|
||||
"\r\n" +
|
||||
body
|
||||
socket.write(response)
|
||||
}
|
||||
}
|
||||
|
||||
var server = HttpServer.new(8080)
|
||||
server.run()
|
||||
57
example/system_dashboard.wren
vendored
Normal file
57
example/system_dashboard.wren
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
import "os" for Platform, Process
|
||||
import "io" for Directory
|
||||
|
||||
class Dashboard {
|
||||
static show() {
|
||||
System.print("========================================")
|
||||
System.print(" WREN CLI SYSTEM DASHBOARD ")
|
||||
System.print("========================================")
|
||||
|
||||
System.print("PLATFORM INFO:")
|
||||
System.print(" OS Name: %(Platform.name)")
|
||||
System.print(" POSIX: %(Platform.isPosix)")
|
||||
System.print(" Home: %(Platform.homePath)")
|
||||
|
||||
System.print("\nPROCESS INFO:")
|
||||
System.print(" PID: %(Process.pid)")
|
||||
System.print(" PPID: %(Process.ppid)")
|
||||
System.print(" CWD: %(Process.cwd)")
|
||||
System.print(" Version: %(Process.version)")
|
||||
|
||||
System.print("\nARGUMENTS:")
|
||||
if (Process.arguments.count == 0) {
|
||||
System.print(" (none)")
|
||||
} else {
|
||||
for (i in 0...Process.arguments.count) {
|
||||
System.print(" [%(i)]: %(Process.arguments[i])")
|
||||
}
|
||||
}
|
||||
|
||||
System.print("\nWORKING DIRECTORY FILES:")
|
||||
var files = Directory.list(".")
|
||||
files.sort(Fn.new {|a, b|
|
||||
var ba = a.bytes
|
||||
var bb = b.bytes
|
||||
var len = ba.count < bb.count ? ba.count : bb.count
|
||||
for (i in 0...len) {
|
||||
if (ba[i] < bb[i]) return true
|
||||
if (ba[i] > bb[i]) return false
|
||||
}
|
||||
return ba.count < bb.count
|
||||
})
|
||||
|
||||
var count = 0
|
||||
for (file in files) {
|
||||
if (count > 10) {
|
||||
System.print(" ... and %(files.count - 10) more")
|
||||
break
|
||||
}
|
||||
System.print(" - %(file)")
|
||||
count = count + 1
|
||||
}
|
||||
System.print("========================================")
|
||||
}
|
||||
}
|
||||
|
||||
Dashboard.show()
|
||||
51
example/task_worker.wren
vendored
Normal file
51
example/task_worker.wren
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
import "scheduler" for Scheduler
|
||||
import "timer" for Timer
|
||||
|
||||
class TaskQueue {
|
||||
construct new() {
|
||||
_tasks = []
|
||||
_running = true
|
||||
}
|
||||
|
||||
add(name, duration) {
|
||||
System.print("[Queue] Adding task: \%(name) (\%(duration)ms)")
|
||||
_tasks.add({
|
||||
System.print("[Worker] Starting \%(name)...")
|
||||
Timer.sleep(duration)
|
||||
System.print("[Worker] Finished \%(name).")
|
||||
})
|
||||
}
|
||||
|
||||
start() {
|
||||
System.print("[Queue] Starting task runner...")
|
||||
Fiber.new {
|
||||
while (_running) {
|
||||
if (!_tasks.isEmpty) {
|
||||
var task = _tasks.removeAt(0)
|
||||
task.call()
|
||||
}
|
||||
Timer.sleep(100) // Yield to other fibers
|
||||
}
|
||||
}.call()
|
||||
}
|
||||
|
||||
stop() { _running = false }
|
||||
}
|
||||
|
||||
var queue = TaskQueue.new()
|
||||
|
||||
// Start the worker in the background
|
||||
queue.start()
|
||||
|
||||
// Add some tasks from the main thread
|
||||
queue.add("Task A", 1500)
|
||||
queue.add("Task B", 500)
|
||||
queue.add("Task C", 1000)
|
||||
|
||||
System.print("[Main] All tasks queued. Main fiber is free.")
|
||||
|
||||
// Keep the main loop alive for a bit to see the output
|
||||
Timer.sleep(4000)
|
||||
queue.stop()
|
||||
System.print("[Main] Shutdown.")
|
||||
1
projects/make/server.log
Normal file
1
projects/make/server.log
Normal file
@ -0,0 +1 @@
|
||||
bash: line 2: ./bin/wren_cli: No such file or directory
|
||||
@ -117,6 +117,7 @@ OBJECTS += $(OBJDIR)/loop-watcher.o
|
||||
OBJECTS += $(OBJDIR)/loop.o
|
||||
OBJECTS += $(OBJDIR)/main.o
|
||||
OBJECTS += $(OBJDIR)/modules.o
|
||||
OBJECTS += $(OBJDIR)/net.o
|
||||
OBJECTS += $(OBJDIR)/os.o
|
||||
OBJECTS += $(OBJDIR)/path.o
|
||||
OBJECTS += $(OBJDIR)/pipe.o
|
||||
@ -365,6 +366,9 @@ $(OBJDIR)/vm.o: ../../src/cli/vm.c
|
||||
$(OBJDIR)/io.o: ../../src/module/io.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
$(OBJDIR)/net.o: ../../src/module/net.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
$(OBJDIR)/os.o: ../../src/module/os.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
|
||||
4
sanity_check.wren
vendored
Normal file
4
sanity_check.wren
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
System.print("Hello, world!")
|
||||
var s = "string"
|
||||
var n = 123
|
||||
System.print("Joined: " + s + " " + n.toString)
|
||||
@ -4,6 +4,7 @@
|
||||
#include "modules.h"
|
||||
|
||||
#include "io.wren.inc"
|
||||
#include "net.wren.inc"
|
||||
#include "os.wren.inc"
|
||||
#include "repl.wren.inc"
|
||||
#include "scheduler.wren.inc"
|
||||
@ -51,8 +52,22 @@ extern void stdinIsTerminal(WrenVM* vm);
|
||||
extern void stdinReadStart(WrenVM* vm);
|
||||
extern void stdinReadStop(WrenVM* vm);
|
||||
extern void stdoutFlush(WrenVM* vm);
|
||||
extern void stderrWrite(WrenVM* vm);
|
||||
extern void schedulerCaptureMethods(WrenVM* vm);
|
||||
extern void timerStartTimer(WrenVM* vm);
|
||||
extern void socketAllocate(WrenVM* vm);
|
||||
extern void socketFinalize(void* data);
|
||||
extern void socketConnect(WrenVM* vm);
|
||||
extern void socketWrite(WrenVM* vm);
|
||||
extern void socketRead(WrenVM* vm);
|
||||
extern void socketClose(WrenVM* vm);
|
||||
extern void netCaptureClass(WrenVM* vm);
|
||||
extern void netShutdown();
|
||||
extern void serverAllocate(WrenVM* vm);
|
||||
extern void serverFinalize(void* data);
|
||||
extern void serverBind(WrenVM* vm);
|
||||
extern void serverAccept(WrenVM* vm);
|
||||
extern void serverClose(WrenVM* vm);
|
||||
|
||||
// The maximum number of foreign methods a single class defines. Ideally, we
|
||||
// would use variable-length arrays for each class in the table below, but
|
||||
@ -69,7 +84,7 @@ extern void timerStartTimer(WrenVM* vm);
|
||||
// If you add a new class to the largest module below, make sure to bump this.
|
||||
// Note that it also includes an extra slot for the sentinel value indicating
|
||||
// the end of the list.
|
||||
#define MAX_CLASSES_PER_MODULE 6
|
||||
#define MAX_CLASSES_PER_MODULE 8
|
||||
|
||||
// Describes one foreign method in a class.
|
||||
typedef struct
|
||||
@ -167,6 +182,27 @@ static ModuleRegistry modules[] =
|
||||
CLASS(Stdout)
|
||||
STATIC_METHOD("flush()", stdoutFlush)
|
||||
END_CLASS
|
||||
CLASS(Stderr)
|
||||
STATIC_METHOD("write(_)", stderrWrite)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(net)
|
||||
CLASS(Socket)
|
||||
ALLOCATE(socketAllocate)
|
||||
FINALIZE(socketFinalize)
|
||||
STATIC_METHOD("captureClass_()", netCaptureClass)
|
||||
METHOD("connect_(_,_,_)", socketConnect)
|
||||
METHOD("write_(_,_)", socketWrite)
|
||||
METHOD("read_(_)", socketRead)
|
||||
METHOD("close_()", socketClose)
|
||||
END_CLASS
|
||||
CLASS(Server)
|
||||
ALLOCATE(serverAllocate)
|
||||
FINALIZE(serverFinalize)
|
||||
METHOD("bind_(_,_)", serverBind)
|
||||
METHOD("accept_(_)", serverAccept)
|
||||
METHOD("close_()", serverClose)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(os)
|
||||
CLASS(Platform)
|
||||
|
||||
@ -286,9 +286,14 @@ static void initVM()
|
||||
uv_loop_init(loop);
|
||||
}
|
||||
|
||||
extern void ioShutdown();
|
||||
extern void netShutdown();
|
||||
extern void schedulerShutdown();
|
||||
|
||||
static void freeVM()
|
||||
{
|
||||
ioShutdown();
|
||||
netShutdown();
|
||||
schedulerShutdown();
|
||||
|
||||
uv_loop_close(loop);
|
||||
|
||||
@ -558,6 +558,13 @@ void stdoutFlush(WrenVM* vm)
|
||||
wrenSetSlotNull(vm, 0);
|
||||
}
|
||||
|
||||
void stderrWrite(WrenVM* vm)
|
||||
{
|
||||
const char* text = wrenGetSlotString(vm, 1);
|
||||
fprintf(stderr, "%s", text);
|
||||
wrenSetSlotNull(vm, 0);
|
||||
}
|
||||
|
||||
static void allocCallback(uv_handle_t* handle, size_t suggestedSize,
|
||||
uv_buf_t* buf)
|
||||
{
|
||||
|
||||
4
src/module/io.wren
vendored
4
src/module/io.wren
vendored
@ -303,3 +303,7 @@ class Stdin {
|
||||
class Stdout {
|
||||
foreign static flush()
|
||||
}
|
||||
|
||||
class Stderr {
|
||||
foreign static write(string)
|
||||
}
|
||||
|
||||
@ -306,4 +306,8 @@ static const char* ioModuleSource =
|
||||
"\n"
|
||||
"class Stdout {\n"
|
||||
" foreign static flush()\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class Stderr {\n"
|
||||
" foreign static write(string)\n"
|
||||
"}\n";
|
||||
|
||||
411
src/module/net.c
Normal file
411
src/module/net.c
Normal file
@ -0,0 +1,411 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "uv.h"
|
||||
#include "vm.h"
|
||||
#include "scheduler.h"
|
||||
#include "net.h"
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Helper Structures
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
typedef struct {
|
||||
uv_tcp_t* handle;
|
||||
WrenHandle* fiber; // Fiber waiting for an operation (connect, read, write)
|
||||
uv_connect_t* connectReq;
|
||||
uv_write_t* writeReq;
|
||||
} SocketData;
|
||||
|
||||
typedef struct SocketListNode {
|
||||
uv_tcp_t* handle;
|
||||
struct SocketListNode* next;
|
||||
} SocketListNode;
|
||||
|
||||
typedef struct {
|
||||
uv_tcp_t* handle;
|
||||
WrenHandle* acceptFiber; // Fiber waiting for accept()
|
||||
SocketListNode* pendingHead;
|
||||
SocketListNode* pendingTail;
|
||||
} ServerData;
|
||||
|
||||
static WrenHandle* socketClassHandle = NULL;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Socket Implementation
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
void socketAllocate(WrenVM* vm)
|
||||
{
|
||||
wrenSetSlotNewForeign(vm, 0, 0, sizeof(SocketData));
|
||||
SocketData* data = (SocketData*)wrenGetSlotForeign(vm, 0);
|
||||
data->handle = (uv_tcp_t*)malloc(sizeof(uv_tcp_t));
|
||||
uv_tcp_init(getLoop(), data->handle);
|
||||
data->handle->data = data;
|
||||
data->fiber = NULL;
|
||||
data->connectReq = NULL;
|
||||
data->writeReq = NULL;
|
||||
}
|
||||
|
||||
static void closeCallback(uv_handle_t* handle)
|
||||
{
|
||||
free(handle);
|
||||
}
|
||||
|
||||
void socketFinalize(void* data)
|
||||
{
|
||||
SocketData* socketData = (SocketData*)data;
|
||||
if (socketData->handle) {
|
||||
uv_close((uv_handle_t*)socketData->handle, closeCallback);
|
||||
}
|
||||
}
|
||||
|
||||
static void connectCallback(uv_connect_t* req, int status)
|
||||
{
|
||||
SocketData* data = (SocketData*)req->data;
|
||||
WrenHandle* fiber = data->fiber;
|
||||
free(req);
|
||||
data->connectReq = NULL;
|
||||
data->fiber = NULL;
|
||||
|
||||
if (status < 0) {
|
||||
schedulerResumeError(fiber, uv_strerror(status));
|
||||
} else {
|
||||
schedulerResume(fiber, false);
|
||||
}
|
||||
}
|
||||
|
||||
void socketConnect(WrenVM* vm)
|
||||
{
|
||||
SocketData* data = (SocketData*)wrenGetSlotForeign(vm, 0);
|
||||
const char* host = wrenGetSlotString(vm, 1);
|
||||
int port = (int)wrenGetSlotDouble(vm, 2);
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 3);
|
||||
|
||||
struct sockaddr_in addr;
|
||||
int r = uv_ip4_addr(host, port, &addr);
|
||||
if (r != 0) {
|
||||
schedulerResumeError(fiber, "Invalid IP address.");
|
||||
return;
|
||||
}
|
||||
|
||||
data->fiber = fiber;
|
||||
data->connectReq = (uv_connect_t*)malloc(sizeof(uv_connect_t));
|
||||
data->connectReq->data = data;
|
||||
|
||||
r = uv_tcp_connect(data->connectReq, data->handle, (const struct sockaddr*)&addr, connectCallback);
|
||||
if (r != 0) {
|
||||
free(data->connectReq);
|
||||
data->connectReq = NULL;
|
||||
schedulerResumeError(fiber, uv_strerror(r));
|
||||
}
|
||||
}
|
||||
|
||||
// Redefine writeCallback to handle the buffer
|
||||
|
||||
static void writeCallback_safe(uv_write_t* req, int status)
|
||||
|
||||
{
|
||||
|
||||
typedef struct {
|
||||
|
||||
uv_write_t req;
|
||||
|
||||
uv_buf_t buf;
|
||||
|
||||
} WriteRequest;
|
||||
|
||||
|
||||
|
||||
WriteRequest* wr = (WriteRequest*)req;
|
||||
|
||||
SocketData* data = (SocketData*)req->data;
|
||||
|
||||
WrenHandle* fiber = data->fiber;
|
||||
|
||||
|
||||
|
||||
free(wr->buf.base);
|
||||
|
||||
free(wr);
|
||||
|
||||
|
||||
|
||||
data->writeReq = NULL;
|
||||
|
||||
data->fiber = NULL;
|
||||
|
||||
|
||||
|
||||
if (status < 0) {
|
||||
|
||||
schedulerResumeError(fiber, uv_strerror(status));
|
||||
|
||||
} else {
|
||||
|
||||
schedulerResume(fiber, false);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
void socketWrite(WrenVM* vm)
|
||||
|
||||
{
|
||||
|
||||
SocketData* data = (SocketData*)wrenGetSlotForeign(vm, 0);
|
||||
|
||||
const char* text = wrenGetSlotString(vm, 1);
|
||||
|
||||
size_t length = strlen(text);
|
||||
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
|
||||
|
||||
|
||||
|
||||
data->fiber = fiber;
|
||||
|
||||
|
||||
|
||||
typedef struct {
|
||||
|
||||
uv_write_t req;
|
||||
|
||||
uv_buf_t buf;
|
||||
|
||||
} WriteRequest;
|
||||
|
||||
|
||||
|
||||
WriteRequest* wr = (WriteRequest*)malloc(sizeof(WriteRequest));
|
||||
|
||||
wr->req.data = data;
|
||||
|
||||
wr->buf.base = malloc(length);
|
||||
|
||||
wr->buf.len = length;
|
||||
|
||||
memcpy(wr->buf.base, text, length);
|
||||
|
||||
|
||||
|
||||
uv_write(&wr->req, (uv_stream_t*)data->handle, &wr->buf, 1, writeCallback_safe);
|
||||
|
||||
}
|
||||
|
||||
|
||||
static void allocCallback(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buf)
|
||||
{
|
||||
buf->base = (char*)malloc(suggestedSize);
|
||||
buf->len = suggestedSize;
|
||||
}
|
||||
|
||||
static void readCallback(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf)
|
||||
{
|
||||
SocketData* data = (SocketData*)stream->data;
|
||||
WrenHandle* fiber = data->fiber;
|
||||
|
||||
// Stop reading immediately. We only want one chunk.
|
||||
uv_read_stop(stream);
|
||||
data->fiber = NULL;
|
||||
|
||||
if (nread < 0) {
|
||||
if (nread == UV_EOF) {
|
||||
free(buf->base);
|
||||
schedulerResume(fiber, true);
|
||||
wrenSetSlotNull(getVM(), 2);
|
||||
schedulerFinishResume();
|
||||
return;
|
||||
}
|
||||
free(buf->base);
|
||||
schedulerResumeError(fiber, uv_strerror(nread));
|
||||
return;
|
||||
}
|
||||
|
||||
// Return the data
|
||||
schedulerResume(fiber, true);
|
||||
wrenSetSlotBytes(getVM(), 2, buf->base, nread);
|
||||
schedulerFinishResume();
|
||||
free(buf->base);
|
||||
}
|
||||
|
||||
void socketRead(WrenVM* vm)
|
||||
{
|
||||
SocketData* data = (SocketData*)wrenGetSlotForeign(vm, 0);
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 1);
|
||||
data->fiber = fiber;
|
||||
|
||||
uv_read_start((uv_stream_t*)data->handle, allocCallback, readCallback);
|
||||
}
|
||||
|
||||
void socketClose(WrenVM* vm)
|
||||
{
|
||||
SocketData* data = (SocketData*)wrenGetSlotForeign(vm, 0);
|
||||
if (data->handle && !uv_is_closing((uv_handle_t*)data->handle)) {
|
||||
uv_close((uv_handle_t*)data->handle, closeCallback);
|
||||
data->handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Server Implementation
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
void serverAllocate(WrenVM* vm)
|
||||
{
|
||||
wrenSetSlotNewForeign(vm, 0, 0, sizeof(ServerData));
|
||||
ServerData* data = (ServerData*)wrenGetSlotForeign(vm, 0);
|
||||
data->handle = (uv_tcp_t*)malloc(sizeof(uv_tcp_t));
|
||||
uv_tcp_init(getLoop(), data->handle);
|
||||
data->handle->data = data;
|
||||
data->acceptFiber = NULL;
|
||||
data->pendingHead = NULL;
|
||||
data->pendingTail = NULL;
|
||||
}
|
||||
|
||||
void serverFinalize(void* data)
|
||||
{
|
||||
ServerData* serverData = (ServerData*)data;
|
||||
if (serverData->handle) {
|
||||
uv_close((uv_handle_t*)serverData->handle, closeCallback);
|
||||
}
|
||||
// Free pending list
|
||||
SocketListNode* current = serverData->pendingHead;
|
||||
while (current) {
|
||||
SocketListNode* next = current->next;
|
||||
uv_close((uv_handle_t*)current->handle, closeCallback);
|
||||
free(current);
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
|
||||
void netCaptureClass(WrenVM* vm)
|
||||
{
|
||||
// Slot 0 is the Socket class itself because this is a static method on Socket.
|
||||
socketClassHandle = wrenGetSlotHandle(vm, 0);
|
||||
}
|
||||
|
||||
void netShutdown()
|
||||
{
|
||||
if (socketClassHandle != NULL) {
|
||||
wrenReleaseHandle(getVM(), socketClassHandle);
|
||||
socketClassHandle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void connectionCallback(uv_stream_t* server, int status)
|
||||
{
|
||||
ServerData* data = (ServerData*)server->data;
|
||||
|
||||
if (status < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
uv_tcp_t* client = (uv_tcp_t*)malloc(sizeof(uv_tcp_t));
|
||||
uv_tcp_init(getLoop(), client);
|
||||
|
||||
if (uv_accept(server, (uv_stream_t*)client) == 0) {
|
||||
if (data->acceptFiber) {
|
||||
WrenHandle* fiber = data->acceptFiber;
|
||||
data->acceptFiber = NULL;
|
||||
|
||||
WrenVM* vm = getVM();
|
||||
wrenEnsureSlots(vm, 3);
|
||||
|
||||
if (socketClassHandle == NULL) {
|
||||
fprintf(stderr, "FATAL: socketClassHandle NULL in connectionCallback\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
wrenSetSlotHandle(vm, 1, socketClassHandle);
|
||||
wrenSetSlotNewForeign(vm, 2, 1, sizeof(SocketData));
|
||||
SocketData* sockData = (SocketData*)wrenGetSlotForeign(vm, 2);
|
||||
sockData->handle = client;
|
||||
client->data = sockData;
|
||||
sockData->fiber = NULL;
|
||||
sockData->connectReq = NULL;
|
||||
sockData->writeReq = NULL;
|
||||
|
||||
schedulerResume(fiber, true);
|
||||
schedulerFinishResume();
|
||||
} else {
|
||||
SocketListNode* node = (SocketListNode*)malloc(sizeof(SocketListNode));
|
||||
node->handle = client;
|
||||
node->next = NULL;
|
||||
if (data->pendingTail) {
|
||||
data->pendingTail->next = node;
|
||||
data->pendingTail = node;
|
||||
} else {
|
||||
data->pendingHead = node;
|
||||
data->pendingTail = node;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
uv_close((uv_handle_t*)client, closeCallback);
|
||||
}
|
||||
}
|
||||
|
||||
void serverBind(WrenVM* vm)
|
||||
{
|
||||
ServerData* data = (ServerData*)wrenGetSlotForeign(vm, 0);
|
||||
const char* host = wrenGetSlotString(vm, 1);
|
||||
int port = (int)wrenGetSlotDouble(vm, 2);
|
||||
|
||||
struct sockaddr_in addr;
|
||||
uv_ip4_addr(host, port, &addr);
|
||||
|
||||
uv_tcp_bind(data->handle, (const struct sockaddr*)&addr, 0);
|
||||
int r = uv_listen((uv_stream_t*)data->handle, 128, connectionCallback);
|
||||
if (r != 0) {
|
||||
wrenEnsureSlots(vm, 3);
|
||||
wrenSetSlotString(vm, 2, uv_strerror(r));
|
||||
wrenAbortFiber(vm, 2);
|
||||
}
|
||||
}
|
||||
|
||||
void serverAccept(WrenVM* vm)
|
||||
{
|
||||
ServerData* data = (ServerData*)wrenGetSlotForeign(vm, 0);
|
||||
|
||||
if (data->pendingHead) {
|
||||
SocketListNode* node = data->pendingHead;
|
||||
data->pendingHead = node->next;
|
||||
if (data->pendingHead == NULL) data->pendingTail = NULL;
|
||||
|
||||
uv_tcp_t* client = node->handle;
|
||||
free(node);
|
||||
|
||||
wrenEnsureSlots(vm, 3);
|
||||
if (socketClassHandle == NULL) {
|
||||
fprintf(stderr, "FATAL: socketClassHandle NULL in serverAccept\n");
|
||||
exit(1);
|
||||
}
|
||||
wrenSetSlotHandle(vm, 2, socketClassHandle);
|
||||
wrenSetSlotNewForeign(vm, 0, 2, sizeof(SocketData));
|
||||
|
||||
SocketData* sockData = (SocketData*)wrenGetSlotForeign(vm, 0);
|
||||
sockData->handle = client;
|
||||
client->data = sockData;
|
||||
sockData->fiber = NULL;
|
||||
sockData->connectReq = NULL;
|
||||
sockData->writeReq = NULL;
|
||||
} else {
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 1);
|
||||
data->acceptFiber = fiber;
|
||||
wrenSetSlotNull(vm, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void serverClose(WrenVM* vm)
|
||||
{
|
||||
ServerData* data = (ServerData*)wrenGetSlotForeign(vm, 0);
|
||||
if (data->handle && !uv_is_closing((uv_handle_t*)data->handle)) {
|
||||
uv_close((uv_handle_t*)data->handle, closeCallback);
|
||||
data->handle = NULL;
|
||||
}
|
||||
}
|
||||
24
src/module/net.h
Normal file
24
src/module/net.h
Normal file
@ -0,0 +1,24 @@
|
||||
#ifndef net_h
|
||||
#define net_h
|
||||
|
||||
#include "wren.h"
|
||||
|
||||
void netBindSocketClass(WrenVM* vm);
|
||||
void netBindServerClass(WrenVM* vm);
|
||||
|
||||
// Socket methods
|
||||
void socketAllocate(WrenVM* vm);
|
||||
void socketFinalize(void* data);
|
||||
void socketConnect(WrenVM* vm);
|
||||
void socketWrite(WrenVM* vm);
|
||||
void socketRead(WrenVM* vm);
|
||||
void socketClose(WrenVM* vm);
|
||||
|
||||
// Server methods
|
||||
void serverAllocate(WrenVM* vm);
|
||||
void serverFinalize(void* data);
|
||||
void serverBind(WrenVM* vm);
|
||||
void serverAccept(WrenVM* vm);
|
||||
void serverClose(WrenVM* vm);
|
||||
|
||||
#endif
|
||||
62
src/module/net.wren
vendored
Normal file
62
src/module/net.wren
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
import "scheduler" for Scheduler
|
||||
|
||||
foreign class Socket {
|
||||
construct new() {}
|
||||
|
||||
static connect(host, port) {
|
||||
if (!(host is String)) Fiber.abort("Host must be a string.")
|
||||
if (!(port is Num)) Fiber.abort("Port must be a number.")
|
||||
|
||||
var socket = Socket.new()
|
||||
Scheduler.await_ { socket.connect_(host, port, Fiber.current) }
|
||||
return socket
|
||||
}
|
||||
|
||||
write(data) {
|
||||
if (!(data is String)) Fiber.abort("Data must be a string.")
|
||||
return Scheduler.await_ { write_(data, Fiber.current) }
|
||||
}
|
||||
|
||||
read() {
|
||||
return Scheduler.await_ { read_(Fiber.current) }
|
||||
}
|
||||
|
||||
close() {
|
||||
close_()
|
||||
}
|
||||
|
||||
foreign connect_(host, port, fiber)
|
||||
foreign write_(data, fiber)
|
||||
foreign read_(fiber)
|
||||
foreign close_()
|
||||
foreign static captureClass_()
|
||||
}
|
||||
|
||||
Socket.captureClass_()
|
||||
|
||||
foreign class Server {
|
||||
construct new() {}
|
||||
|
||||
static bind(host, port) {
|
||||
if (!(host is String)) Fiber.abort("Host must be a string.")
|
||||
if (!(port is Num)) Fiber.abort("Port must be a number.")
|
||||
|
||||
var server = Server.new()
|
||||
server.bind_(host, port)
|
||||
return server
|
||||
}
|
||||
|
||||
accept() {
|
||||
var socket = accept_(Fiber.current)
|
||||
if (socket != null) return socket
|
||||
return Scheduler.runNextScheduled_()
|
||||
}
|
||||
|
||||
close() {
|
||||
close_()
|
||||
}
|
||||
|
||||
foreign bind_(host, port)
|
||||
foreign accept_(fiber)
|
||||
foreign close_()
|
||||
}
|
||||
66
src/module/net.wren.inc
Normal file
66
src/module/net.wren.inc
Normal file
@ -0,0 +1,66 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `src/module/net.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* netModuleSource =
|
||||
"import \"scheduler\" for Scheduler\n"
|
||||
"\n"
|
||||
"foreign class Socket {\n"
|
||||
" construct new() {}\n"
|
||||
"\n"
|
||||
" static connect(host, port) {\n"
|
||||
" if (!(host is String)) Fiber.abort(\"Host must be a string.\")\n"
|
||||
" if (!(port is Num)) Fiber.abort(\"Port must be a number.\")\n"
|
||||
" \n"
|
||||
" var socket = Socket.new()\n"
|
||||
" Scheduler.await_ { socket.connect_(host, port, Fiber.current) }\n"
|
||||
" return socket\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" write(data) {\n"
|
||||
" if (!(data is String)) Fiber.abort(\"Data must be a string.\")\n"
|
||||
" return Scheduler.await_ { write_(data, Fiber.current) }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" read() {\n"
|
||||
" return Scheduler.await_ { read_(Fiber.current) }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" close() {\n"
|
||||
" close_()\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" foreign connect_(host, port, fiber)\n"
|
||||
" foreign write_(data, fiber)\n"
|
||||
" foreign read_(fiber)\n"
|
||||
" foreign close_()\n"
|
||||
" foreign static captureClass_()\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"Socket.captureClass_()\n"
|
||||
"\n"
|
||||
"foreign class Server {\n"
|
||||
" construct new() {}\n"
|
||||
"\n"
|
||||
" static bind(host, port) {\n"
|
||||
" if (!(host is String)) Fiber.abort(\"Host must be a string.\")\n"
|
||||
" if (!(port is Num)) Fiber.abort(\"Port must be a number.\")\n"
|
||||
" \n"
|
||||
" var server = Server.new()\n"
|
||||
" server.bind_(host, port)\n"
|
||||
" return server\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" accept() {\n"
|
||||
" var socket = accept_(Fiber.current)\n"
|
||||
" if (socket != null) return socket\n"
|
||||
" return Scheduler.runNextScheduled_()\n"
|
||||
" }\n"
|
||||
" \n"
|
||||
" close() {\n"
|
||||
" close_()\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" foreign bind_(host, port)\n"
|
||||
" foreign accept_(fiber)\n"
|
||||
" foreign close_()\n"
|
||||
"}\n";
|
||||
3
test_stderr.wren
vendored
Normal file
3
test_stderr.wren
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
import "io" for Stderr
|
||||
|
||||
Stderr.write("This is an error message.\n")
|
||||
10
test_suspend.wren
vendored
Normal file
10
test_suspend.wren
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
System.print("Start")
|
||||
Fiber.new {
|
||||
System.print("In new fiber, suspending")
|
||||
Fiber.suspend()
|
||||
System.print("New fiber resumed")
|
||||
}.call()
|
||||
|
||||
System.print("Back in main fiber")
|
||||
Fiber.suspend()
|
||||
System.print("Main fiber resumed")
|
||||
8
test_transfer.wren
vendored
Normal file
8
test_transfer.wren
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
var main = Fiber.current
|
||||
System.print("Start")
|
||||
Fiber.new {
|
||||
System.print("In new fiber")
|
||||
main.transfer()
|
||||
System.print("Resumed")
|
||||
}.transfer()
|
||||
System.print("Back in main")
|
||||
Loading…
Reference in New Issue
Block a user