Skip to content

Commit 8892a8f

Browse files
Re-port TCP to the flat platform
Bring TCP back online under the new flat platform architecture, mirroring the SQLite port: - platform/Tcp.roc: opaque `Stream :: Box(U64)` handle, 5 `host_*!` FFI declarations, and ergonomic wrappers (connect!/read_up_to!/read_exactly!/ read_until!/read_line!/write!/write_utf8!) plus ConnectErr/StreamErr unions and parse_*/*_to_str helpers. - src/lib.rs: 5 hosted_tcp_* host fns over a boxed BufReader<TcpStream>, using the same box helpers proven for SQLite (false for payload_contains_refcounted), with errors marshalled as Str. - platform/main.roc: expose Tcp, import it, and append the hosted_tcp_* entries at the end of the hosted block to avoid renumbering glue types. - src/roc_platform_abi.rs: regenerated (no existing aliases shifted). - examples/tcp-client.roc: migrated from .todoroc to new-compiler syntax. - ci/expect_scripts/tcp-client.exp + tcp_echo_server.py: self-contained expect test using a python3 echo server (no ncat dependency); wired into EXPECT_EXAMPLES in ci/all_tests.sh. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 49a072d commit 8892a8f

9 files changed

Lines changed: 843 additions & 99 deletions

File tree

ci/all_tests.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ EXPECT_EXAMPLES=(
130130
"dir"
131131
"env-var"
132132
"sqlite-basic"
133+
"tcp-client"
133134
)
134135

135136
for roc_file in "${EXAMPLES_DIR}"*.roc; do

ci/expect_scripts/tcp-client.exp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/expect
2+
3+
# uncomment line below for debugging
4+
# exp_internal 1
5+
6+
set timeout 7
7+
8+
# Resolve the directory this script lives in so we can find the echo server.
9+
set script_dir [file dirname [info script]]
10+
11+
# Start a self-contained echo server (avoids depending on ncat/nc being present).
12+
spawn python3 "$script_dir/tcp_echo_server.py"
13+
set server_id $spawn_id
14+
sleep 1
15+
16+
spawn $env(EXAMPLES_DIR)tcp-client
17+
set client_id $spawn_id
18+
19+
expect {
20+
-i $client_id "Connected!\r\n" {
21+
expect -i $client_id "> " {
22+
send -i $client_id -- "Hi\r"
23+
expect -i $client_id "< Hi\r\n" {
24+
exit 0
25+
}
26+
}
27+
}
28+
}
29+
30+
puts stderr "\nExpect script failed: output was different from expected value. uncomment `exp_internal 1` to debug."
31+
exit 1
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/usr/bin/env python3
2+
"""Minimal single-connection TCP echo server used by tcp-client.exp.
3+
4+
Listens on 127.0.0.1:8085, accepts one client, and echoes everything it
5+
receives straight back until the client disconnects. This keeps the TCP
6+
example's expect test self-contained (no dependency on `ncat`/`nc`).
7+
"""
8+
import socket
9+
10+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
11+
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
12+
server.bind(("127.0.0.1", 8085))
13+
server.listen(1)
14+
conn, _ = server.accept()
15+
with conn:
16+
while True:
17+
data = conn.recv(1024)
18+
if not data:
19+
break
20+
conn.sendall(data)

examples/tcp-client.roc

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
app [main!] { pf: platform "../platform/main.roc" }
2+
3+
import pf.Tcp
4+
import pf.Stdout
5+
import pf.Stdin
6+
import pf.Stderr
7+
8+
# Simple TCP client in Roc.
9+
#
10+
# Connects to a server on localhost:8085, reads user input from stdin, sends it
11+
# to the server, and prints the server's response — looping until end-of-input.
12+
#
13+
# To try it interactively, start an echo server in another terminal first:
14+
#
15+
# $ ncat -e $(which cat) -l 8085
16+
#
17+
# then run this example.
18+
main! : List(Str) => Try({}, [Exit(I32), ..])
19+
main! = |_args|
20+
match Tcp.connect!("127.0.0.1", 8085) {
21+
Ok(stream) => {
22+
_ = Stdout.line!("Connected!")
23+
run!(stream)
24+
}
25+
Err(connect_err) => report_connect_err!(connect_err)
26+
}
27+
28+
## Read a line from stdin, send it to the server, print the response, repeat.
29+
run! : Tcp.Stream => Try({}, [Exit(I32), ..])
30+
run! = |stream| {
31+
_ = Stdout.write!("> ")
32+
match Stdin.line!({}) {
33+
# No more input — exit cleanly.
34+
Err(EndOfFile) => Ok({})
35+
Err(StdinErr(_)) => Ok({})
36+
Ok(out_msg) =>
37+
match Tcp.write_utf8!(stream, "${out_msg}\n") {
38+
Err(TcpWriteErr(err)) => report_stream_err!("writing", err)
39+
Ok({}) =>
40+
match Tcp.read_line!(stream) {
41+
Err(read_err) => report_read_err!(read_err)
42+
Ok(in_msg) => {
43+
_ = Stdout.line!("< ${in_msg}")
44+
run!(stream)
45+
}
46+
}
47+
}
48+
}
49+
}
50+
51+
report_connect_err! : Tcp.ConnectErr => Try({}, [Exit(I32), ..])
52+
report_connect_err! = |err| {
53+
err_str = Tcp.connect_err_to_str(err)
54+
_ = Stderr.line!(
55+
\\Failed to connect: ${err_str}
56+
\\
57+
\\If you don't have anything listening on port 8085, run:
58+
\\ $ nc -l 8085
59+
\\
60+
\\If you want an echo server you can run:
61+
\\ $ ncat -e $(which cat) -l 8085
62+
)
63+
Ok({})
64+
}
65+
66+
report_read_err! : [TcpReadErr(Tcp.StreamErr), TcpReadBadUtf8(_)] => Try({}, [Exit(I32), ..])
67+
report_read_err! = |err|
68+
match err {
69+
TcpReadErr(stream_err) => report_stream_err!("reading", stream_err)
70+
TcpReadBadUtf8(_) => {
71+
_ = Stderr.line!("Received invalid UTF-8 data")
72+
Ok({})
73+
}
74+
}
75+
76+
report_stream_err! : Str, Tcp.StreamErr => Try({}, [Exit(I32), ..])
77+
report_stream_err! = |action, err| {
78+
err_str = Tcp.stream_err_to_str(err)
79+
_ = Stderr.line!("Error while ${action}: ${err_str}")
80+
Ok({})
81+
}

examples/tcp-client.todoroc

Lines changed: 0 additions & 82 deletions
This file was deleted.

0 commit comments

Comments
 (0)