Release 1.0.0-beta.6 networking foundation

This commit is contained in:
sanjin 2026-05-22 17:36:02 +02:00
parent 9956b1d874
commit 2726ec4915
32 changed files with 2008 additions and 73 deletions

View File

@ -0,0 +1,66 @@
# 1.0.0-beta.6 Networking Foundation Target
Status: released as `1.0.0-beta.6` on 2026-05-22.
`1.0.0-beta.6` targets a deliberately narrow networking foundation after the
resource-handle and host-error policy introduced in `1.0.0-beta.2`. The goal
is blocking loopback TCP only, enough for local client/server fixtures and
small request/response examples without committing to a full networking stack.
## Slovo Source Surface
The staged source facade is `lib/std/net.slo`, importable explicitly as
`std.net`.
Exported helpers:
- `tcp_connect_loopback_result : (i32) -> (result i32 i32)`
- `tcp_listen_loopback_result : (i32) -> (result i32 i32)`
- `tcp_bound_port_result : (i32) -> (result i32 i32)`
- `tcp_accept_result : (i32) -> (result i32 i32)`
- `tcp_read_all_result : (i32) -> (result string i32)`
- `tcp_write_text_result : (i32, string) -> (result i32 i32)`
- `tcp_close_result : (i32) -> (result i32 i32)`
- `tcp_write_text_ok : (i32, string) -> bool`
- `tcp_close_ok : (i32) -> bool`
The `i32` values returned by successful connect/listen/accept operations are
opaque process-local handles. They are not host file descriptors, stable ABI
values, transferable capabilities, or ownership-checked affine resources.
## Runtime Calls
The facade wraps these compiler-known runtime calls:
- `std.net.tcp_connect_loopback_result(port i32) -> (result i32 i32)`
- `std.net.tcp_listen_loopback_result(port i32) -> (result i32 i32)`
- `std.net.tcp_bound_port_result(handle i32) -> (result i32 i32)`
- `std.net.tcp_accept_result(listener i32) -> (result i32 i32)`
- `std.net.tcp_read_all_result(handle i32) -> (result string i32)`
- `std.net.tcp_write_text_result(handle i32, text string) -> (result i32 i32)`
- `std.net.tcp_close_result(handle i32) -> (result i32 i32)`
Ordinary host failures return `err 1`. Successful status-returning operations
return `ok 0`. Successful handle-returning operations return `ok handle`.
Successful `tcp_bound_port_result` returns the bound loopback TCP port.
## Fixtures
- `examples/projects/std-import-net/` exercises explicit `std.net` source
import.
- `examples/projects/std-layout-local-net/` mirrors the facade as a local
module fixture and keeps the source-search contract explicit.
The source-side fixtures use invalid ports and handles for deterministic
result-shape checks. Positive loopback client/server behavior is covered by
the matching compiler/runtime tests when the local sandbox allows loopback
sockets.
## Deferrals
This scope does not add DNS, TLS, UDP, Unix-domain sockets, non-loopback
binding, async IO, event loops, readiness polling, timeouts, buffering policy,
HTTP frameworks, socket options beyond the implementation minimum,
platform-specific error codes, rich host-error ADTs, stable runtime helper
symbols, stable ABI/layout/ownership guarantees, automatic cleanup, or a
stable standard-library API freeze.

View File

@ -6,7 +6,7 @@ This repository is the canonical public monorepo for the language design,
standard library source, compiler, runtime, examples, benchmarks, and technical
documents.
Current release: `1.0.0-beta.5`.
Current release: `1.0.0-beta.6`.
## Repository Layout
@ -24,13 +24,13 @@ scripts/ local release and document tooling
## Beta Scope
`1.0.0-beta.5` keeps the `1.0.0-beta` language baseline, includes the
`1.0.0-beta.6` keeps the `1.0.0-beta` language baseline, includes the
`1.0.0-beta.1` tooling/install hardening slice, the `1.0.0-beta.2`
runtime/resource foundation bundle, the `1.0.0-beta.3` standard-library
stabilization bundle, the `1.0.0-beta.4` language-usability diagnostics
bundle, and the `1.0.0-beta.5` local package/workspace discipline bundle. The
language baseline supports practical local command-line programs and libraries
with:
bundle, the `1.0.0-beta.5` local package/workspace discipline bundle, and the
`1.0.0-beta.6` loopback networking foundation. The language baseline supports
practical local command-line programs and libraries with:
- modules, explicit imports, packages, and local workspaces
- `new`, `check`, `fmt`, `test`, `doc`, and `build`
@ -39,11 +39,12 @@ with:
current `match`
- explicit `std/*.slo` imports from `lib/std`, installed `share/slovo/std`, or
`SLOVO_STD_PATH`
- beta-scoped loopback TCP handles through `std.net`
- hosted native builds through LLVM IR, Clang, and `runtime/runtime.c`
Still deferred before stable: generics, maps/sets, broad package registry
semantics, networking/async, LSP/watch/debug-adapter guarantees, stable ABI and
layout, and a stable standard-library compatibility freeze.
semantics, DNS/TLS/async networking, LSP/watch/debug-adapter guarantees,
stable ABI and layout, and a stable standard-library compatibility freeze.
## Build And Test
@ -163,6 +164,19 @@ package/dependency summary, new workspace templates declare
local-package rules. Remote registries, lockfiles, semantic-version solving,
package publishing, and stable package ABI/layout remain deferred.
## 1.0.0-beta.6 Networking Foundation
The `1.0.0-beta.6` release adds a narrow blocking loopback TCP foundation:
- compiler-known `std.net.tcp_*_result` calls for connect, listen,
bound-port lookup, accept, read-all, write-text, and close
- `lib/std/net.slo` source facades and explicit std/local example projects
- opaque beta-scoped `i32` socket handles with concrete `result` values
This is not a general networking stack. DNS, TLS, UDP, non-loopback binding,
async IO, HTTP frameworks, rich host-error ADTs, stable socket ABI/layout, and
automatic resource ownership remain deferred.
## Documentation
- [Language Manifest](docs/language/MANIFEST.md)

2
compiler/Cargo.lock generated
View File

@ -4,4 +4,4 @@ version = 3
[[package]]
name = "glagol"
version = "1.0.0-beta.5"
version = "1.0.0-beta.6"

View File

@ -1,6 +1,6 @@
[package]
name = "glagol"
version = "1.0.0-beta.5"
version = "1.0.0-beta.6"
edition = "2021"
description = "Glagol, the first compiler for the Slovo language"
license = "MIT OR Apache-2.0"

View File

@ -57,6 +57,13 @@ pub fn emit(_file: &str, program: &CheckedProgram) -> Result<String, Vec<Diagnos
out.push_str("declare i64 @__glagol_fs_open_text_read_result(ptr)\n\n");
out.push_str("declare ptr @__glagol_fs_read_open_text_result(i32)\n\n");
out.push_str("declare i32 @__glagol_fs_close_result(i32)\n\n");
out.push_str("declare i64 @__glagol_net_tcp_connect_loopback_result(i32)\n\n");
out.push_str("declare i64 @__glagol_net_tcp_listen_loopback_result(i32)\n\n");
out.push_str("declare i64 @__glagol_net_tcp_bound_port_result(i32)\n\n");
out.push_str("declare i64 @__glagol_net_tcp_accept_result(i32)\n\n");
out.push_str("declare ptr @__glagol_net_tcp_read_all_result(i32)\n\n");
out.push_str("declare i32 @__glagol_net_tcp_write_text_result(i32, ptr)\n\n");
out.push_str("declare i32 @__glagol_net_tcp_close_result(i32)\n\n");
out.push_str("declare i1 @__glagol_string_eq(ptr, ptr)\n\n");
out.push_str("declare ptr @__glagol_vec_i32_empty()\n\n");
out.push_str("declare ptr @__glagol_vec_i32_append(ptr, i32)\n\n");
@ -1661,6 +1668,7 @@ impl FunctionGen<'_> {
| "__glagol_env_get_result"
| "__glagol_fs_read_text_result"
| "__glagol_fs_read_open_text_result"
| "__glagol_net_tcp_read_all_result"
| "__glagol_io_read_stdin_result"
) {
return self.emit_string_result_host_call(expr, callee, &arg_values);
@ -1670,11 +1678,17 @@ impl FunctionGen<'_> {
|| callee == "__glagol_fs_remove_file_result"
|| callee == "__glagol_fs_create_dir_result"
|| callee == "__glagol_fs_close_result"
|| callee == "__glagol_net_tcp_write_text_result"
|| callee == "__glagol_net_tcp_close_result"
{
return self.emit_i32_result_status_call(expr, callee, &arg_values);
}
if callee == "__glagol_fs_open_text_read_result"
|| callee == "__glagol_net_tcp_connect_loopback_result"
|| callee == "__glagol_net_tcp_listen_loopback_result"
|| callee == "__glagol_net_tcp_bound_port_result"
|| callee == "__glagol_net_tcp_accept_result"
|| callee == "__glagol_string_parse_i32_result"
{
return self.emit_i32_result_encoded_i64_call(expr, callee, &arg_values);

View File

@ -68,6 +68,7 @@ const F64_PARAM: &[RuntimeType] = &[RuntimeType::F64];
const BOOL_PARAM: &[RuntimeType] = &[RuntimeType::Bool];
const STRING_PARAM: &[RuntimeType] = &[RuntimeType::String];
const STRING_STRING_PARAMS: &[RuntimeType] = &[RuntimeType::String, RuntimeType::String];
const I32_STRING_PARAMS: &[RuntimeType] = &[RuntimeType::I32, RuntimeType::String];
const VEC_I32_PARAM: &[RuntimeType] = &[RuntimeType::VecI32];
const VEC_I32_I32_PARAMS: &[RuntimeType] = &[RuntimeType::VecI32, RuntimeType::I32];
const VEC_I64_PARAM: &[RuntimeType] = &[RuntimeType::VecI64];
@ -350,6 +351,55 @@ pub const FUNCTIONS: &[RuntimeFunction] = &[
return_type: RuntimeType::ResultI32I32,
promoted: true,
},
RuntimeFunction {
source_name: "std.net.tcp_connect_loopback_result",
runtime_symbol: "__glagol_net_tcp_connect_loopback_result",
params: I32_PARAM,
return_type: RuntimeType::ResultI32I32,
promoted: true,
},
RuntimeFunction {
source_name: "std.net.tcp_listen_loopback_result",
runtime_symbol: "__glagol_net_tcp_listen_loopback_result",
params: I32_PARAM,
return_type: RuntimeType::ResultI32I32,
promoted: true,
},
RuntimeFunction {
source_name: "std.net.tcp_bound_port_result",
runtime_symbol: "__glagol_net_tcp_bound_port_result",
params: I32_PARAM,
return_type: RuntimeType::ResultI32I32,
promoted: true,
},
RuntimeFunction {
source_name: "std.net.tcp_accept_result",
runtime_symbol: "__glagol_net_tcp_accept_result",
params: I32_PARAM,
return_type: RuntimeType::ResultI32I32,
promoted: true,
},
RuntimeFunction {
source_name: "std.net.tcp_read_all_result",
runtime_symbol: "__glagol_net_tcp_read_all_result",
params: I32_PARAM,
return_type: RuntimeType::ResultStringI32,
promoted: true,
},
RuntimeFunction {
source_name: "std.net.tcp_write_text_result",
runtime_symbol: "__glagol_net_tcp_write_text_result",
params: I32_STRING_PARAMS,
return_type: RuntimeType::ResultI32I32,
promoted: true,
},
RuntimeFunction {
source_name: "std.net.tcp_close_result",
runtime_symbol: "__glagol_net_tcp_close_result",
params: I32_PARAM,
return_type: RuntimeType::ResultI32I32,
promoted: true,
},
RuntimeFunction {
source_name: "std.vec.i32.empty",
runtime_symbol: "__glagol_vec_i32_empty",
@ -665,6 +715,13 @@ const RESERVED_HELPER_SYMBOLS: &[&str] = &[
"__glagol_fs_open_text_read_result",
"__glagol_fs_read_open_text_result",
"__glagol_fs_close_result",
"__glagol_net_tcp_connect_loopback_result",
"__glagol_net_tcp_listen_loopback_result",
"__glagol_net_tcp_bound_port_result",
"__glagol_net_tcp_accept_result",
"__glagol_net_tcp_read_all_result",
"__glagol_net_tcp_write_text_result",
"__glagol_net_tcp_close_result",
"__glagol_vec_i32_eq",
"__glagol_vec_i32_allocation_trap",
"__glagol_vec_i32_index_trap",

View File

@ -2672,6 +2672,170 @@ fn eval_expr(
payload: if was_open { 0 } else { 1 },
});
}
if runtime_symbol == "__glagol_net_tcp_connect_loopback_result" {
let Some(port) = args.first() else {
return Err(unsupported_test_expr(
file,
expr,
"malformed `std.net.tcp_connect_loopback_result` calls",
));
};
let port = eval_expr(file, port, locals, functions, foreign_imports, depth)?;
let Some(_) = port.as_i32() else {
return Err(unsupported_test_expr(
file,
expr,
"`std.net.tcp_connect_loopback_result` port on non-i32 values",
));
};
return Ok(Value::ResultI32 {
is_ok: false,
payload: 1,
});
}
if runtime_symbol == "__glagol_net_tcp_listen_loopback_result" {
let Some(port) = args.first() else {
return Err(unsupported_test_expr(
file,
expr,
"malformed `std.net.tcp_listen_loopback_result` calls",
));
};
let port = eval_expr(file, port, locals, functions, foreign_imports, depth)?;
let Some(_) = port.as_i32() else {
return Err(unsupported_test_expr(
file,
expr,
"`std.net.tcp_listen_loopback_result` port on non-i32 values",
));
};
return Ok(Value::ResultI32 {
is_ok: false,
payload: 1,
});
}
if runtime_symbol == "__glagol_net_tcp_bound_port_result" {
let Some(handle) = args.first() else {
return Err(unsupported_test_expr(
file,
expr,
"malformed `std.net.tcp_bound_port_result` calls",
));
};
let handle = eval_expr(file, handle, locals, functions, foreign_imports, depth)?;
let Some(_) = handle.as_i32() else {
return Err(unsupported_test_expr(
file,
expr,
"`std.net.tcp_bound_port_result` handle on non-i32 values",
));
};
return Ok(Value::ResultI32 {
is_ok: false,
payload: 1,
});
}
if runtime_symbol == "__glagol_net_tcp_accept_result" {
let Some(listener) = args.first() else {
return Err(unsupported_test_expr(
file,
expr,
"malformed `std.net.tcp_accept_result` calls",
));
};
let listener =
eval_expr(file, listener, locals, functions, foreign_imports, depth)?;
let Some(_) = listener.as_i32() else {
return Err(unsupported_test_expr(
file,
expr,
"`std.net.tcp_accept_result` listener on non-i32 values",
));
};
return Ok(Value::ResultI32 {
is_ok: false,
payload: 1,
});
}
if runtime_symbol == "__glagol_net_tcp_read_all_result" {
let Some(handle) = args.first() else {
return Err(unsupported_test_expr(
file,
expr,
"malformed `std.net.tcp_read_all_result` calls",
));
};
let handle = eval_expr(file, handle, locals, functions, foreign_imports, depth)?;
let Some(_) = handle.as_i32() else {
return Err(unsupported_test_expr(
file,
expr,
"`std.net.tcp_read_all_result` handle on non-i32 values",
));
};
return Ok(Value::ResultStringI32 {
is_ok: false,
ok_payload: String::new(),
err_payload: 1,
});
}
if runtime_symbol == "__glagol_net_tcp_write_text_result" {
let Some(handle) = args.first() else {
return Err(unsupported_test_expr(
file,
expr,
"malformed `std.net.tcp_write_text_result` calls",
));
};
let Some(text) = args.get(1) else {
return Err(unsupported_test_expr(
file,
expr,
"malformed `std.net.tcp_write_text_result` calls",
));
};
let handle = eval_expr(file, handle, locals, functions, foreign_imports, depth)?;
let text = eval_expr(file, text, locals, functions, foreign_imports, depth)?;
let Some(_) = handle.as_i32() else {
return Err(unsupported_test_expr(
file,
expr,
"`std.net.tcp_write_text_result` handle on non-i32 values",
));
};
let Some(_) = text.as_string() else {
return Err(unsupported_test_expr(
file,
expr,
"`std.net.tcp_write_text_result` text on non-string values",
));
};
return Ok(Value::ResultI32 {
is_ok: false,
payload: 1,
});
}
if runtime_symbol == "__glagol_net_tcp_close_result" {
let Some(handle) = args.first() else {
return Err(unsupported_test_expr(
file,
expr,
"malformed `std.net.tcp_close_result` calls",
));
};
let handle = eval_expr(file, handle, locals, functions, foreign_imports, depth)?;
let Some(_) = handle.as_i32() else {
return Err(unsupported_test_expr(
file,
expr,
"`std.net.tcp_close_result` handle on non-i32 values",
));
};
return Ok(Value::ResultI32 {
is_ok: false,
payload: 1,
});
}
if runtime_symbol == "__glagol_vec_i32_empty" {
return Ok(Value::VecI32(Vec::new()));
}

View File

@ -1141,6 +1141,28 @@ const STANDARD_FS_SOURCE_FACADE_ALPHA: &[&str] = &[
"read_bool_or",
];
const STANDARD_NET_SOURCE_FACADE_ALPHA: &[&str] = &[
"tcp_connect_loopback_result",
"tcp_listen_loopback_result",
"tcp_bound_port_result",
"tcp_accept_result",
"tcp_read_all_result",
"tcp_write_text_result",
"tcp_close_result",
"tcp_write_text_ok",
"tcp_close_ok",
];
const STANDARD_NET_RUNTIME_NAMES: &[&str] = &[
"std.net.tcp_connect_loopback_result",
"std.net.tcp_listen_loopback_result",
"std.net.tcp_bound_port_result",
"std.net.tcp_accept_result",
"std.net.tcp_read_all_result",
"std.net.tcp_write_text_result",
"std.net.tcp_close_result",
];
const STANDARD_PROCESS_SOURCE_FACADE_ALPHA: &[&str] = &[
"argc",
"arg",
@ -1715,6 +1737,7 @@ fn promotion_gate_artifacts_are_aligned() {
repo.join("examples/projects/std-layout-local-random");
let glagol_project_std_layout_local_env = repo.join("examples/projects/std-layout-local-env");
let glagol_project_std_layout_local_fs = repo.join("examples/projects/std-layout-local-fs");
let glagol_project_std_layout_local_net = repo.join("examples/projects/std-layout-local-net");
let glagol_project_std_layout_local_io = repo.join("examples/projects/std-layout-local-io");
let glagol_project_std_layout_local_cli = repo.join("examples/projects/std-layout-local-cli");
let glagol_project_std_layout_local_vec_i32 =
@ -1734,6 +1757,7 @@ fn promotion_gate_artifacts_are_aligned() {
let glagol_project_std_import_random = repo.join("examples/projects/std-import-random");
let glagol_project_std_import_env = repo.join("examples/projects/std-import-env");
let glagol_project_std_import_fs = repo.join("examples/projects/std-import-fs");
let glagol_project_std_import_net = repo.join("examples/projects/std-import-net");
let glagol_project_std_import_process = repo.join("examples/projects/std-import-process");
let glagol_project_std_import_string = repo.join("examples/projects/std-import-string");
let glagol_project_std_import_num = repo.join("examples/projects/std-import-num");
@ -2085,6 +2109,7 @@ fn promotion_gate_artifacts_are_aligned() {
assert_project_std_import_random_tooling_matches_fixture(&glagol_project_std_import_random);
assert_project_std_import_env_tooling_matches_fixture(&glagol_project_std_import_env);
assert_project_std_import_fs_tooling_matches_fixture(&glagol_project_std_import_fs);
assert_project_std_import_net_tooling_matches_fixture(&glagol_project_std_import_net);
assert_project_std_import_process_tooling_matches_fixture(&glagol_project_std_import_process);
assert_project_std_import_string_tooling_matches_fixture(&glagol_project_std_import_string);
assert_project_std_import_num_tooling_matches_fixture(&glagol_project_std_import_num);
@ -2123,6 +2148,9 @@ fn promotion_gate_artifacts_are_aligned() {
&glagol_project_std_layout_local_env,
);
assert_project_std_layout_local_fs_tooling_matches_fixture(&glagol_project_std_layout_local_fs);
assert_project_std_layout_local_net_tooling_matches_fixture(
&glagol_project_std_layout_local_net,
);
assert_project_std_layout_local_io_tooling_matches_fixture(&glagol_project_std_layout_local_io);
assert_project_std_layout_local_cli_tooling_matches_fixture(
&glagol_project_std_layout_local_cli,
@ -3310,6 +3338,7 @@ fn assert_slovo_std_source_layout_alpha(repo: &Path, std_dir: &Path) {
std_dir.join("fs.slo"),
std_dir.join("io.slo"),
std_dir.join("math.slo"),
std_dir.join("net.slo"),
std_dir.join("num.slo"),
std_dir.join("option.slo"),
std_dir.join("process.slo"),
@ -3338,6 +3367,7 @@ fn assert_slovo_std_source_layout_alpha(repo: &Path, std_dir: &Path) {
"env.slo",
"fs.slo",
"math.slo",
"net.slo",
"num.slo",
"option.slo",
"process.slo",
@ -4194,6 +4224,56 @@ fn assert_slovo_std_source_layout_alpha(repo: &Path, std_dir: &Path) {
helper
);
}
let local_net = repo.join("examples/projects/std-layout-local-net/src/net.slo");
let slovo_net = read(&std_dir.join("net.slo"));
let glagol_net = read(&local_net);
assert!(
slovo_net.contains("(module net") && glagol_net.contains("(module net"),
"both Slovo std/net.slo and the Glagol local fixture should use explicit `net` module source shape"
);
for runtime_name in STANDARD_NET_RUNTIME_NAMES {
assert!(
slovo_net.contains(runtime_name) && glagol_net.contains(runtime_name),
"standard net facade must stay backed by `{}`",
runtime_name
);
}
assert_std_only_contains(
&slovo_net,
STANDARD_NET_RUNTIME_NAMES,
"Slovo std/net.slo must not introduce other compiler-known std names",
);
assert_std_only_contains(
&glagol_net,
STANDARD_NET_RUNTIME_NAMES,
"Glagol local net fixture must not introduce other compiler-known std names",
);
for source in [&slovo_net, &glagol_net] {
assert!(
!source.contains("dns")
&& !source.contains("tls")
&& !source.contains("udp")
&& !source.contains("async")
&& !source.contains("http")
&& !source.contains("timeout")
&& !source.contains("non_loopback")
&& !source.contains("host_error"),
"standard net facade must not claim deferred networking policies"
);
}
for helper in STANDARD_NET_SOURCE_FACADE_ALPHA {
assert!(
slovo_net.contains(&format!("(fn {} ", helper)),
"Slovo std/net.slo is missing beta6 facade `{}`",
helper
);
assert!(
glagol_net.contains(&format!("(fn {} ", helper)),
"Glagol local net fixture is missing beta6 facade `{}`",
helper
);
}
}
fn assert_source_shaped_file(path: &Path) {
@ -7007,6 +7087,26 @@ fn assert_project_std_import_fs_tooling_matches_fixture(project: &Path) {
);
}
fn assert_project_std_import_net_tooling_matches_fixture(project: &Path) {
assert_project_std_import_host_facade_tooling_matches_fixture(
project,
"net",
STANDARD_NET_SOURCE_FACADE_ALPHA,
concat!(
"test \"explicit std net invalid connect facade\" ... ok\n",
"test \"explicit std net invalid listen facade\" ... ok\n",
"test \"explicit std net invalid bound port facade\" ... ok\n",
"test \"explicit std net invalid accept facade\" ... ok\n",
"test \"explicit std net invalid read facade\" ... ok\n",
"test \"explicit std net invalid write facade\" ... ok\n",
"test \"explicit std net invalid close facade\" ... ok\n",
"test \"explicit std net helper invalids facade\" ... ok\n",
"test \"explicit std net facade all\" ... ok\n",
"9 test(s) passed\n",
),
);
}
fn assert_project_std_import_process_tooling_matches_fixture(project: &Path) {
assert_project_std_import_host_facade_shape(
project,
@ -9089,6 +9189,88 @@ fn assert_standard_fs_source_facade_alpha(project: &Path) {
}
}
fn assert_project_std_layout_local_net_tooling_matches_fixture(project: &Path) {
assert!(project.join("slovo.toml").is_file());
assert!(project.join("src/net.slo").is_file());
assert!(project.join("src/main.slo").is_file());
assert_standard_net_source_facade_alpha(project);
let check = run_glagol([OsStr::new("check"), project.as_os_str()]);
assert_success_stdout(check, "", "std layout local net project check");
let test = run_glagol([OsStr::new("test"), project.as_os_str()]);
assert_success_stdout(
test,
concat!(
"test \"explicit local net invalid connect facade\" ... ok\n",
"test \"explicit local net invalid listen facade\" ... ok\n",
"test \"explicit local net invalid bound port facade\" ... ok\n",
"test \"explicit local net invalid accept facade\" ... ok\n",
"test \"explicit local net invalid read facade\" ... ok\n",
"test \"explicit local net invalid write facade\" ... ok\n",
"test \"explicit local net invalid close facade\" ... ok\n",
"test \"explicit local net helper invalids facade\" ... ok\n",
"test \"explicit local net facade all\" ... ok\n",
"9 test(s) passed\n",
),
"std layout local net project test",
);
}
fn assert_standard_net_source_facade_alpha(project: &Path) {
let net_source = read(&project.join("src/net.slo"));
let main = read(&project.join("src/main.slo"));
assert!(
net_source.starts_with("(module net (export "),
"local net fixture must stay an explicitly exported local module"
);
assert!(
main.starts_with("(module main)\n\n(import net ("),
"local net fixture must stay an explicit local import"
);
assert!(
!main.contains("(import std") && !main.contains("(import slovo.std"),
"standard net source facade fixture must not use automatic std imports"
);
for runtime_name in STANDARD_NET_RUNTIME_NAMES {
assert!(
net_source.contains(runtime_name),
"standard net source facade fixture must wrap `{}`",
runtime_name
);
}
assert_std_only_contains(
&net_source,
STANDARD_NET_RUNTIME_NAMES,
"standard net source facade fixture must use only existing std.net runtime names directly",
);
assert!(
!main.contains("std.")
&& !net_source.contains("dns")
&& !net_source.contains("tls")
&& !net_source.contains("udp")
&& !net_source.contains("async")
&& !net_source.contains("http")
&& !net_source.contains("timeout")
&& !net_source.contains("non_loopback")
&& !net_source.contains("host_error"),
"standard net source facade fixture must remain local and must not claim deferred networking policies"
);
for helper in STANDARD_NET_SOURCE_FACADE_ALPHA {
assert!(
net_source.contains(&format!("(fn {} ", helper)),
"local net fixture is missing facade `{}`",
helper
);
assert!(
main.contains(helper),
"main fixture import/use is missing facade `{}`",
helper
);
}
}
fn assert_project_std_layout_local_io_tooling_matches_fixture(project: &Path) {
assert!(project.join("slovo.toml").is_file());
assert!(project.join("src/io.slo").is_file());

View File

@ -0,0 +1,496 @@
use std::{
env,
ffi::OsStr,
fs,
io::{ErrorKind, Read, Write},
net::{Shutdown, TcpListener, TcpStream},
path::{Path, PathBuf},
process::{Child, Command, Output, Stdio},
sync::atomic::{AtomicUsize, Ordering},
thread,
time::{Duration, Instant},
};
static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0);
#[test]
fn standard_net_lowers_to_private_runtime_helpers() {
let fixture = write_fixture(
"lowering",
r#"
(module main)
(fn main () -> i32
(match (std.net.tcp_connect_loopback_result 1)
((ok client)
(std.net.tcp_write_text_result client "ping")
(std.net.tcp_read_all_result client)
(std.net.tcp_close_result client)
0)
((err code)
(match (std.net.tcp_listen_loopback_result 0)
((ok listener)
(std.net.tcp_bound_port_result listener)
(std.net.tcp_accept_result listener)
(std.net.tcp_close_result listener)
0)
((err listen_code)
listen_code)))))
"#,
);
let output = run_glagol([fixture.as_os_str()]);
assert_success("compile standard net lowering", &output);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("declare i64 @__glagol_net_tcp_connect_loopback_result(i32)")
&& stdout.contains("declare i64 @__glagol_net_tcp_listen_loopback_result(i32)")
&& stdout.contains("declare i64 @__glagol_net_tcp_bound_port_result(i32)")
&& stdout.contains("declare i64 @__glagol_net_tcp_accept_result(i32)")
&& stdout.contains("declare ptr @__glagol_net_tcp_read_all_result(i32)")
&& stdout.contains("declare i32 @__glagol_net_tcp_write_text_result(i32, ptr)")
&& stdout.contains("declare i32 @__glagol_net_tcp_close_result(i32)")
&& stdout.contains("call i64 @__glagol_net_tcp_connect_loopback_result(i32 1)")
&& stdout.contains("call i32 @__glagol_net_tcp_write_text_result(")
&& stdout.contains("call ptr @__glagol_net_tcp_read_all_result(")
&& stdout.contains("call i64 @__glagol_net_tcp_listen_loopback_result(i32 0)")
&& stdout.contains("call i64 @__glagol_net_tcp_bound_port_result(")
&& stdout.contains("call i64 @__glagol_net_tcp_accept_result(")
&& stdout.contains("call i32 @__glagol_net_tcp_close_result(")
&& !stdout.contains("@std.net."),
"standard net LLVM shape drifted\nstdout:\n{}",
stdout
);
}
#[test]
fn test_runner_reports_deterministic_net_result_errors() {
let fixture = write_fixture(
"test-runner",
r#"
(module main)
(test "connect invalid port returns err"
(= (unwrap_err (std.net.tcp_connect_loopback_result 0)) 1))
(test "listen invalid port returns err"
(= (unwrap_err (std.net.tcp_listen_loopback_result -1)) 1))
(test "bound port invalid handle returns err"
(= (unwrap_err (std.net.tcp_bound_port_result -1)) 1))
(test "accept invalid listener returns err"
(= (unwrap_err (std.net.tcp_accept_result -1)) 1))
(test "read invalid handle returns err"
(= (unwrap_err (std.net.tcp_read_all_result -1)) 1))
(test "write invalid handle returns err"
(= (unwrap_err (std.net.tcp_write_text_result -1 "ping")) 1))
(test "close invalid handle returns err"
(= (unwrap_err (std.net.tcp_close_result -1)) 1))
"#,
);
let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
assert_success("run standard net tests", &output);
assert_eq!(
String::from_utf8_lossy(&output.stdout),
concat!(
"test \"connect invalid port returns err\" ... ok\n",
"test \"listen invalid port returns err\" ... ok\n",
"test \"bound port invalid handle returns err\" ... ok\n",
"test \"accept invalid listener returns err\" ... ok\n",
"test \"read invalid handle returns err\" ... ok\n",
"test \"write invalid handle returns err\" ... ok\n",
"test \"close invalid handle returns err\" ... ok\n",
"7 test(s) passed\n",
),
"standard net test runner stdout drifted"
);
}
#[test]
fn standard_net_diagnostics_cover_promoted_and_unpromoted_names() {
let cases = [
(
"connect-arity",
r#"
(module main)
(fn main () -> i32
(std.net.tcp_connect_loopback_result))
"#,
"ArityMismatch",
),
(
"write-type",
r#"
(module main)
(fn main () -> i32
(std.net.tcp_write_text_result "handle" "ping")
0)
"#,
"TypeMismatch",
),
(
"unknown-net",
r#"
(module main)
(fn main () -> i32
(std.net.udp_bind_loopback_result 0))
"#,
"UnsupportedStandardLibraryCall",
),
(
"promoted-shadow",
r#"
(module main)
(fn std.net.tcp_close_result ((handle i32)) -> (result i32 i32)
(ok i32 i32 0))
(fn main () -> i32
(unwrap_ok (std.net.tcp_close_result 1)))
"#,
"DuplicateFunction",
),
];
for (name, source, diagnostic) in cases {
let fixture = write_fixture(name, source);
let output = run_glagol([fixture.as_os_str()]);
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!output.status.success(),
"compiler unexpectedly accepted `{}`\nstdout:\n{}\nstderr:\n{}",
name,
stdout,
stderr
);
assert!(
stdout.is_empty(),
"rejected compile wrote stdout:\n{}",
stdout
);
assert!(
stderr.contains(diagnostic),
"diagnostic `{}` was not reported for `{}`\nstderr:\n{}",
diagnostic,
name,
stderr
);
}
}
#[test]
fn hosted_loopback_client_and_server_smoke_when_clang_is_available() {
let Some(clang) = find_clang() else {
eprintln!("skipping standard net runtime smoke: set GLAGOL_CLANG or install clang");
return;
};
let Some(port) = try_reserve_loopback_port() else {
eprintln!("skipping standard net server smoke: loopback bind is not permitted");
return;
};
let server_source = format!(
r#"
(module main)
(fn main () -> i32
(match (std.net.tcp_listen_loopback_result {port})
((ok listener)
(match (std.net.tcp_accept_result listener)
((ok stream)
(match (std.net.tcp_read_all_result stream)
((ok text)
(std.net.tcp_write_text_result stream "pong")
(std.net.tcp_close_result stream)
(std.net.tcp_close_result listener)
(if (= text "ping") 0 2))
((err read_code)
(std.net.tcp_close_result stream)
(std.net.tcp_close_result listener)
3)))
((err accept_code)
(std.net.tcp_close_result listener)
4)))
((err listen_code)
5)))
"#
);
let server_fixture = write_fixture("runtime-server", &server_source);
let compile = run_glagol([server_fixture.as_os_str()]);
assert_success("compile standard net server smoke", &compile);
let server_exe = compile_with_runtime(&clang, "standard-net-server", &compile.stdout);
let mut child = Command::new(&server_exe)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap_or_else(|err| panic!("spawn `{}`: {}", server_exe.display(), err));
let mut stream = connect_with_retry(port, &mut child);
stream
.write_all(b"ping")
.unwrap_or_else(|err| panic!("write ping to loopback server: {}", err));
stream
.shutdown(Shutdown::Write)
.expect("shutdown loopback client write half");
let mut response = String::new();
stream
.read_to_string(&mut response)
.unwrap_or_else(|err| panic!("read loopback server response: {}", err));
assert_eq!(response, "pong", "loopback response drifted");
let output = wait_child_with_timeout(child, Duration::from_secs(5));
assert_success("run standard net server smoke", &output);
assert!(
output.stdout.is_empty(),
"standard net server smoke wrote stdout:\n{}",
String::from_utf8_lossy(&output.stdout)
);
}
#[test]
fn hosted_loopback_client_smoke_when_clang_is_available() {
let Some(clang) = find_clang() else {
eprintln!("skipping standard net client smoke: set GLAGOL_CLANG or install clang");
return;
};
let listener = match TcpListener::bind(("127.0.0.1", 0)) {
Ok(listener) => listener,
Err(err) if err.kind() == ErrorKind::PermissionDenied => {
eprintln!("skipping standard net client smoke: loopback bind is not permitted");
return;
}
Err(err) => panic!("bind loopback smoke listener: {}", err),
};
let port = listener
.local_addr()
.expect("loopback smoke listener addr")
.port();
let server = thread::spawn(move || {
let (mut stream, _) = listener.accept().expect("accept loopback client");
let mut request = [0u8; 4];
stream
.read_exact(&mut request)
.expect("read loopback client request");
assert_eq!(&request, b"ping");
stream
.write_all(b"pong")
.expect("write loopback client response");
});
let client_source = format!(
r#"
(module main)
(fn main () -> i32
(match (std.net.tcp_connect_loopback_result {port})
((ok client)
(std.net.tcp_write_text_result client "ping")
(match (std.net.tcp_read_all_result client)
((ok text)
(std.net.tcp_close_result client)
(if (= text "pong") 0 2))
((err read_code)
(std.net.tcp_close_result client)
3)))
((err connect_code)
4)))
"#
);
let client_fixture = write_fixture("runtime-client", &client_source);
let compile = run_glagol([client_fixture.as_os_str()]);
assert_success("compile standard net client smoke", &compile);
let run = compile_and_run_with_runtime(&clang, "standard-net-client", &compile.stdout);
assert_success("run standard net client smoke", &run);
assert!(
run.stdout.is_empty(),
"standard net client smoke wrote stdout:\n{}",
String::from_utf8_lossy(&run.stdout)
);
server.join().expect("loopback smoke server thread");
}
fn run_glagol<I, S>(args: I) -> Output
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
Command::new(env!("CARGO_BIN_EXE_glagol"))
.args(args)
.current_dir(Path::new(env!("CARGO_MANIFEST_DIR")))
.output()
.expect("run glagol")
}
fn write_fixture(name: &str, source: &str) -> PathBuf {
let mut path = env::temp_dir();
path.push(format!(
"glagol-standard-net-{}-{}-{}.slo",
name,
std::process::id(),
NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
));
fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err));
path
}
fn try_reserve_loopback_port() -> Option<u16> {
let listener = match TcpListener::bind(("127.0.0.1", 0)) {
Ok(listener) => listener,
Err(err) if err.kind() == ErrorKind::PermissionDenied => return None,
Err(err) => panic!("reserve loopback port: {}", err),
};
Some(
listener
.local_addr()
.expect("reserved loopback addr")
.port(),
)
}
fn connect_with_retry(port: u16, child: &mut Child) -> TcpStream {
let deadline = Instant::now() + Duration::from_secs(5);
loop {
match TcpStream::connect(("127.0.0.1", port)) {
Ok(stream) => return stream,
Err(connect_err) => {
if let Some(status) = child.try_wait().expect("poll loopback server child") {
let mut stdout = String::new();
let mut stderr = String::new();
if let Some(mut out) = child.stdout.take() {
let _ = out.read_to_string(&mut stdout);
}
if let Some(mut err) = child.stderr.take() {
let _ = err.read_to_string(&mut stderr);
}
panic!(
"loopback server exited before connect: {}\nstdout:\n{}\nstderr:\n{}",
status, stdout, stderr
);
}
if Instant::now() >= deadline {
panic!("timed out connecting to loopback server: {}", connect_err);
}
thread::sleep(Duration::from_millis(20));
}
}
}
}
fn wait_child_with_timeout(mut child: Child, timeout: Duration) -> Output {
let deadline = Instant::now() + timeout;
while Instant::now() < deadline {
if child.try_wait().expect("poll child").is_some() {
return child.wait_with_output().expect("collect child output");
}
thread::sleep(Duration::from_millis(20));
}
let _ = child.kill();
let output = child
.wait_with_output()
.expect("collect killed child output");
panic!(
"child timed out\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
fn assert_success(context: &str, output: &Output) {
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"{} failed\nstdout:\n{}\nstderr:\n{}",
context,
stdout,
stderr
);
}
fn compile_and_run_with_runtime(clang: &Path, name: &str, ir: &[u8]) -> Output {
let exe_path = compile_with_runtime(clang, name, ir);
Command::new(&exe_path)
.output()
.unwrap_or_else(|err| panic!("run `{}`: {}", exe_path.display(), err))
}
fn compile_with_runtime(clang: &Path, name: &str, ir: &[u8]) -> PathBuf {
let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
let temp_dir = env::temp_dir().join(format!(
"glagol-standard-net-{}-{}",
std::process::id(),
NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
));
fs::create_dir_all(&temp_dir)
.unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err));
let ir_path = temp_dir.join(format!("{}.ll", name));
let exe_path = temp_dir.join(name);
fs::write(&ir_path, ir).unwrap_or_else(|err| panic!("write `{}`: {}", ir_path.display(), err));
let runtime = manifest.join("../runtime/runtime.c");
let mut clang_command = Command::new(clang);
clang_command
.arg(&runtime)
.arg(&ir_path)
.arg("-o")
.arg(&exe_path)
.current_dir(manifest);
configure_clang_runtime_env(&mut clang_command, clang);
let clang_output = clang_command
.output()
.unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err));
assert_success("clang standard net runtime smoke", &clang_output);
exe_path
}
fn find_clang() -> Option<PathBuf> {
if let Some(path) = env::var_os("GLAGOL_CLANG").filter(|value| !value.is_empty()) {
return Some(PathBuf::from(path));
}
let hermetic_clang = PathBuf::from("/tmp/glagol-clang-root/usr/bin/clang");
if hermetic_clang.is_file() {
return Some(hermetic_clang);
}
find_on_path("clang")
}
fn find_on_path(program: &str) -> Option<PathBuf> {
let path = env::var_os("PATH")?;
env::split_paths(&path)
.map(|dir| dir.join(program))
.find(|candidate| candidate.is_file())
}
fn configure_clang_runtime_env(command: &mut Command, clang: &Path) {
if !clang.starts_with("/tmp/glagol-clang-root") {
return;
}
let root = Path::new("/tmp/glagol-clang-root");
let lib64 = root.join("usr/lib64");
let lib = root.join("usr/lib");
let mut paths = vec![lib64, lib];
if let Some(existing) = env::var_os("LD_LIBRARY_PATH") {
paths.extend(env::split_paths(&existing));
}
let joined = env::join_paths(paths).expect("join LD_LIBRARY_PATH");
command.env("LD_LIBRARY_PATH", joined);
}

View File

@ -0,0 +1,245 @@
use std::{
ffi::OsStr,
fs,
path::Path,
process::{Command, Output},
};
const EXPECTED_LOCAL_TEST_OUTPUT: &str = concat!(
"test \"explicit local net invalid connect facade\" ... ok\n",
"test \"explicit local net invalid listen facade\" ... ok\n",
"test \"explicit local net invalid bound port facade\" ... ok\n",
"test \"explicit local net invalid accept facade\" ... ok\n",
"test \"explicit local net invalid read facade\" ... ok\n",
"test \"explicit local net invalid write facade\" ... ok\n",
"test \"explicit local net invalid close facade\" ... ok\n",
"test \"explicit local net helper invalids facade\" ... ok\n",
"test \"explicit local net facade all\" ... ok\n",
"9 test(s) passed\n",
);
const EXPECTED_STD_IMPORT_TEST_OUTPUT: &str = concat!(
"test \"explicit std net invalid connect facade\" ... ok\n",
"test \"explicit std net invalid listen facade\" ... ok\n",
"test \"explicit std net invalid bound port facade\" ... ok\n",
"test \"explicit std net invalid accept facade\" ... ok\n",
"test \"explicit std net invalid read facade\" ... ok\n",
"test \"explicit std net invalid write facade\" ... ok\n",
"test \"explicit std net invalid close facade\" ... ok\n",
"test \"explicit std net helper invalids facade\" ... ok\n",
"test \"explicit std net facade all\" ... ok\n",
"9 test(s) passed\n",
);
const STANDARD_NET_SOURCE_FACADE_ALPHA: &[&str] = &[
"tcp_connect_loopback_result",
"tcp_listen_loopback_result",
"tcp_bound_port_result",
"tcp_accept_result",
"tcp_read_all_result",
"tcp_write_text_result",
"tcp_close_result",
"tcp_write_text_ok",
"tcp_close_ok",
];
const STANDARD_NET_RUNTIME_NAMES: &[&str] = &[
"std.net.tcp_connect_loopback_result",
"std.net.tcp_listen_loopback_result",
"std.net.tcp_bound_port_result",
"std.net.tcp_accept_result",
"std.net.tcp_read_all_result",
"std.net.tcp_write_text_result",
"std.net.tcp_close_result",
];
#[test]
fn standard_net_source_facade_project_checks_formats_and_tests() {
let project =
Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/projects/std-layout-local-net");
assert_local_net_fixture_is_source_authored(&project);
let fmt = run_glagol([
OsStr::new("fmt"),
OsStr::new("--check"),
project.as_os_str(),
]);
assert_success("std layout local net fmt --check", &fmt);
let check = run_glagol([OsStr::new("check"), project.as_os_str()]);
assert_success_stdout(check, "", "std layout local net check");
let test = run_glagol([OsStr::new("test"), project.as_os_str()]);
assert_success_stdout(
test,
EXPECTED_LOCAL_TEST_OUTPUT,
"std layout local net test output",
);
}
#[test]
fn standard_net_std_import_project_checks_formats_and_tests() {
let project = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/projects/std-import-net");
assert_std_import_net_fixture_uses_repo_std(&project);
let fmt = run_glagol([
OsStr::new("fmt"),
OsStr::new("--check"),
project.as_os_str(),
]);
assert_success("std import net fmt --check", &fmt);
let check = run_glagol([OsStr::new("check"), project.as_os_str()]);
assert_success_stdout(check, "", "std import net check");
let test = run_glagol([OsStr::new("test"), project.as_os_str()]);
assert_success_stdout(
test,
EXPECTED_STD_IMPORT_TEST_OUTPUT,
"std import net test output",
);
}
fn assert_local_net_fixture_is_source_authored(project: &Path) {
let net = read(&project.join("src/net.slo"));
let main = read(&project.join("src/main.slo"));
assert!(
net.starts_with("(module net (export "),
"net.slo must stay an explicit local module export"
);
assert!(
main.starts_with("(module main)\n\n(import net ("),
"main.slo must stay an explicit local net import"
);
assert!(
!main.contains("(import std") && !main.contains("(import slovo.std"),
"net fixture must not depend on automatic or package std imports"
);
assert_net_source_shape(&net, &main, "local net fixture");
assert!(
!main.contains("std."),
"local net main fixture must use only local imports"
);
}
fn assert_std_import_net_fixture_uses_repo_std(project: &Path) {
let std_net = read(&Path::new(env!("CARGO_MANIFEST_DIR")).join("../lib/std/net.slo"));
let main = read(&project.join("src/main.slo"));
assert!(
!project.join("src/net.slo").exists(),
"std import net fixture must use repo-root std/net.slo, not a local copy"
);
assert!(
main.starts_with("(module main)\n\n(import std.net ("),
"std import net fixture must use explicit `std.net` import syntax"
);
assert_net_source_shape(&std_net, &main, "repo std.net fixture");
}
fn assert_net_source_shape(net: &str, main: &str, context: &str) {
for runtime_name in STANDARD_NET_RUNTIME_NAMES {
assert!(
net.contains(runtime_name),
"{} must wrap `{}`",
context,
runtime_name
);
}
assert_std_only_contains(net, STANDARD_NET_RUNTIME_NAMES, context);
assert!(
!net.contains("dns")
&& !net.contains("tls")
&& !net.contains("udp")
&& !net.contains("async")
&& !net.contains("http")
&& !net.contains("timeout")
&& !net.contains("non_loopback")
&& !net.contains("host_error")
&& !main.contains("dns")
&& !main.contains("tls")
&& !main.contains("udp")
&& !main.contains("async")
&& !main.contains("http")
&& !main.contains("timeout")
&& !main.contains("non_loopback")
&& !main.contains("host_error"),
"{} must not claim deferred networking policies",
context
);
for helper in STANDARD_NET_SOURCE_FACADE_ALPHA {
assert!(
net.contains(&format!("(fn {} ", helper)),
"{} is missing source facade `{}`",
context,
helper
);
assert!(
main.contains(helper),
"{} main fixture import/use is missing `{}`",
context,
helper
);
}
}
fn assert_std_only_contains(source: &str, allowed: &[&str], context: &str) {
let mut remaining = source.to_string();
for name in allowed {
remaining = remaining.replace(name, "");
}
assert!(
!remaining.contains("std."),
"{} introduced unexpected compiler-known std names",
context
);
}
fn run_glagol<I, S>(args: I) -> Output
where
I: IntoIterator<Item = S>,
S: AsRef<std::ffi::OsStr>,
{
Command::new(env!("CARGO_BIN_EXE_glagol"))
.args(args)
.current_dir(Path::new(env!("CARGO_MANIFEST_DIR")))
.output()
.expect("run glagol")
}
fn read(path: &Path) -> String {
fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err))
}
fn assert_success(context: &str, output: &Output) {
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"{} failed\nstdout:\n{}\nstderr:\n{}",
context,
stdout,
stderr
);
assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr);
}
fn assert_success_stdout(output: Output, expected: &str, context: &str) {
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"{} failed\nstdout:\n{}\nstderr:\n{}",
context,
stdout,
stderr
);
assert_eq!(stdout, expected, "{} stdout drifted", context);
assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr);
}

View File

@ -157,6 +157,14 @@ Work:
- keep TLS, async, DNS policy, HTTP server frameworks, and event loops deferred
- add small examples such as echo client/server or local request/response
Released in `1.0.0-beta.6`: `lib/std/net.slo` now provides explicit wrappers
for loopback TCP connect, listen, bound-port lookup, accept, read-all,
write-text, and close result calls. The source fixtures use invalid port/handle
checks for deterministic result-shape coverage, and the compiler/runtime tests
cover lowering plus hosted loopback client/server smoke when the local sandbox
allows loopback sockets. DNS, TLS, UDP, async IO, non-loopback binding, HTTP
frameworks, rich host-error ADTs, and stable socket ABI/layout remain deferred.
Why sixth: networking is useful, but doing it before resources and host errors
would create unstable API debt.

View File

@ -12,6 +12,37 @@ integration/readiness release, not the first real beta.
No unreleased changes yet.
## 1.0.0-beta.6
Release label: `1.0.0-beta.6`
Release date: 2026-05-22
Release state: networking foundation beta update
### Summary
Glagol `1.0.0-beta.6` keeps the `1.0.0-beta` compiler support baseline and
adds the first narrow networking foundation:
- compiler-known `std.net.tcp_connect_loopback_result`,
`std.net.tcp_listen_loopback_result`, `std.net.tcp_bound_port_result`,
`std.net.tcp_accept_result`, `std.net.tcp_read_all_result`,
`std.net.tcp_write_text_result`, and `std.net.tcp_close_result`
- POSIX loopback TCP runtime helpers using opaque process-local `i32` handles
and concrete `result` values
- deterministic interpreter/test-runner error behavior for `std.net` invalid
handles and ports
- focused lowering, diagnostics, source-facade, promotion, and hosted loopback
smoke tests, with hosted socket smokes skipped only when the local sandbox
denies loopback sockets
### Explicit Deferrals
This release does not add DNS, TLS, UDP, non-loopback binding, async IO, event
loops, HTTP frameworks, rich host-error ADTs, stable socket ABI/layout,
automatic resource ownership, or a stable standard-library API freeze.
## 1.0.0-beta.5
Release label: `1.0.0-beta.5`

View File

@ -22,15 +22,15 @@ general-purpose beta release.
A Glagol feature is done only when it has parser/lowerer support, checker behavior, diagnostics for invalid forms, backend behavior or explicit unsupported diagnostics, and tests.
Current stage: `1.0.0-beta.5`, released on 2026-05-22 as the first post-beta
package/workspace discipline update. It keeps the `1.0.0-beta`
language/compiler support baseline and includes the `1.0.0-beta.1` tooling
hardening release, the `1.0.0-beta.2` runtime/resource foundation release, the
`1.0.0-beta.3` standard-library stabilization release, the `1.0.0-beta.4`
language-usability diagnostics release, `[workspace] default_package = "name"`
for deterministic build/run entry selection, tighter workspace-member/default
package diagnostics, workspace package/dependency docs, and
`docs/language/PACKAGES.md` for beta local-package rules.
Current stage: `1.0.0-beta.6`, released on 2026-05-22 as the first post-beta
networking foundation update. It keeps the `1.0.0-beta` language/compiler
support baseline and includes the `1.0.0-beta.1` tooling hardening release, the
`1.0.0-beta.2` runtime/resource foundation release, the `1.0.0-beta.3`
standard-library stabilization release, the `1.0.0-beta.4`
language-usability diagnostics release, the `1.0.0-beta.5` package/workspace
discipline release, and the `1.0.0-beta.6` compiler-known `std.net` loopback
TCP runtime family with focused lowering, interpreter, diagnostics,
source-facade, promotion, and hosted smoke coverage.
The final experimental precursor scope is `exp-125`. Its unsigned direct-value
flow, parse/format runtime lanes, and matching staged stdlib helper breadth

View File

@ -8,18 +8,47 @@ Historical `exp-*` releases listed here are experimental maturity milestones.
The pushed tag `v2.0.0-beta.1` is historical. It is now documented as an
experimental integration/readiness release, not as a beta maturity claim.
The current release is `1.0.0-beta.5`, published on 2026-05-22. It keeps the
The current release is `1.0.0-beta.6`, published on 2026-05-22. It keeps the
`1.0.0-beta` language surface, includes the first post-beta tooling/install
hardening bundle from `1.0.0-beta.1`, and adds the first runtime/resource
foundation bundle from `1.0.0-beta.2` plus the first standard-library
stabilization bundle from `1.0.0-beta.3`, the first language-usability
diagnostics bundle from `1.0.0-beta.4`, and the first local
package/workspace discipline bundle.
package/workspace discipline bundle from `1.0.0-beta.5`, plus the first
loopback networking foundation bundle from `1.0.0-beta.6`.
## Unreleased
No unreleased changes yet.
## 1.0.0-beta.6
Release label: `1.0.0-beta.6`
Release name: Networking Foundation Bundle
Release date: 2026-05-22
Status: released beta networking foundation update on the `1.0.0-beta`
language baseline.
- `lib/std/net.slo` stages an explicit `std.net` source facade for blocking
loopback TCP only.
- The facade wraps the targeted compiler-known calls
`std.net.tcp_connect_loopback_result`,
`std.net.tcp_listen_loopback_result`, `std.net.tcp_bound_port_result`,
`std.net.tcp_accept_result`, `std.net.tcp_read_all_result`,
`std.net.tcp_write_text_result`, and `std.net.tcp_close_result`.
- `examples/projects/std-import-net/` and
`examples/projects/std-layout-local-net/` document the explicit import and
local source facade shapes using deterministic invalid port/handle result
checks.
This release does not add DNS, TLS, async IO, UDP, non-loopback binding, HTTP
frameworks, stable socket ABI/layout, platform-specific error codes, rich
host-error ADTs, automatic ownership/finalization, or a stable standard-library
API freeze.
## 1.0.0-beta.5
Release label: `1.0.0-beta.5`

View File

@ -10,15 +10,16 @@ Long-horizon planning lives in
release train from the historical `v2.0.0-beta.1` tag toward and beyond the
first real general-purpose beta Slovo contract.
Current stage: `1.0.0-beta.5`, released on 2026-05-22 as the first post-beta
package/workspace discipline update. It keeps the `1.0.0-beta` language
contract and includes the `1.0.0-beta.1` tooling hardening release, the
`1.0.0-beta.2` runtime/resource foundation release, the `1.0.0-beta.3`
standard-library stabilization release, the `1.0.0-beta.4`
language-usability diagnostics release, `[workspace] default_package = "name"`
for deterministic build/run entry selection, tighter duplicate-member/default
package diagnostics, workspace package/dependency docs, and
`docs/language/PACKAGES.md` for beta local-package rules.
Current stage: `1.0.0-beta.6`, released on 2026-05-22 as the first post-beta
networking foundation update. It keeps the `1.0.0-beta` language contract and
includes the `1.0.0-beta.1` tooling hardening release, the `1.0.0-beta.2`
runtime/resource foundation release, the `1.0.0-beta.3` standard-library
stabilization release, the `1.0.0-beta.4` language-usability diagnostics
release, the `1.0.0-beta.5` package/workspace discipline release, and a narrow
`std.net` source facade for blocking loopback TCP client/server primitives over
opaque `i32` handles and concrete `result` families. DNS, TLS, UDP, async IO,
non-loopback binding, HTTP frameworks, rich host-error ADTs, stable socket
ABI/layout, and a stable standard-library API freeze remain deferred.
The final experimental precursor scope is `exp-125`, defined in
`.llm/EXP_125_UNSIGNED_U32_U64_NUMERIC_AND_STDLIB_BREADTH_ALPHA.md`. Its

View File

@ -1042,6 +1042,40 @@ solving, package publishing, archive formats, optional/dev/target
dependencies, feature flags, package build scripts, or stable package
ABI/layout promises.
### 4.4.4 Post-Beta Networking Foundation
Status: released in `1.0.0-beta.6`.
The `1.0.0-beta.6` networking foundation stages blocking loopback TCP only. It
does not change source syntax.
The source facade is `lib/std/net.slo`, imported explicitly as `std.net`:
```text
std.net.tcp_connect_loopback_result: (i32) -> (result i32 i32)
std.net.tcp_listen_loopback_result: (i32) -> (result i32 i32)
std.net.tcp_bound_port_result: (i32) -> (result i32 i32)
std.net.tcp_accept_result: (i32) -> (result i32 i32)
std.net.tcp_read_all_result: (i32) -> (result string i32)
std.net.tcp_write_text_result: (i32 string) -> (result i32 i32)
std.net.tcp_close_result: (i32) -> (result i32 i32)
```
Successful connect/listen/accept operations return `ok handle`, where
`handle` is a positive opaque process-local `i32`. Successful
`tcp_bound_port_result` returns the bound loopback TCP port. Successful
`tcp_write_text_result` and `tcp_close_result` return `ok 0`. Ordinary host
failures return `err 1`.
The source facade also provides `tcp_write_text_ok` and `tcp_close_ok`, both
ordinary source helpers over the result-returning operations.
This release is not a general networking contract. DNS, TLS, UDP, Unix-domain
sockets, non-loopback binding, async IO, event loops, readiness polling,
timeouts, buffering policy, HTTP frameworks, platform-specific error codes,
rich host-error ADTs, stable runtime helper symbols, stable socket ABI/layout,
automatic cleanup, and affine ownership guarantees remain deferred.
## 4.5 v2.0.0-beta.1 Experimental Integration Readiness
Status: current experimental Slovo-side release contract, released 2026-05-17.

View File

@ -21,6 +21,12 @@ use the existing concrete `result` families where host failures need to be
observable. Handles are positive opaque `i32` process-local tokens, not stable
file descriptors or ABI values.
The `1.0.0-beta.6` networking foundation release adds a narrow loopback TCP
runtime family behind the `std.net` source facade. These names are blocking,
result-returning, and loopback-only; they do not define DNS, TLS, UDP, async
IO, non-loopback binding, HTTP frameworks, stable socket ABI/layout, or rich
platform error values.
The exp-era catalog is closed to names promoted through exp-101. exp-29,
exp-30, exp-32, exp-33, and exp-35 through exp-93 add no new standard-runtime
operation names. exp-32/exp-39/exp-56/exp-57 `std/math.slo` helpers,
@ -97,6 +103,13 @@ source-level result helper names are the `std.result.*` names cataloged below.
| `std.fs.open_text_read_result` | `(string) -> (result i32 i32)` | `1.0.0-beta.2` | `examples/projects/std-layout-local-fs` | Opens a text file for read-only resource-handle flow; success returns `ok handle` where `handle` is a positive opaque process-local `i32`, and ordinary open or handle-table failure returns `err 1`. | Uses existing standard-runtime usage recording if present; no schema change. | Writable handles, binary IO, directory handles, process handles, sockets, async IO, platform-specific error codes, stable handle ABI/layout. |
| `std.fs.read_open_text_result` | `(i32) -> (result string i32)` | `1.0.0-beta.2` | `examples/projects/std-layout-local-fs` | Reads remaining text from an open read handle; success returns `ok text`, while invalid, closed, or failed reads return `err 1`. | Uses existing standard-runtime usage recording if present; no schema change. | Chunked reads, seek, buffering controls, binary IO, async IO, platform-specific error codes, stable handle ABI/layout. |
| `std.fs.close_result` | `(i32) -> (result i32 i32)` | `1.0.0-beta.2` | `examples/projects/std-layout-local-fs` | Closes an open resource handle; success returns `ok 0`, while invalid, closed, or failed close operations return `err 1`. | Uses existing standard-runtime usage recording if present; no schema change. | Finalizers, destructors, affine ownership, automatic cleanup, stable handle ABI/layout. |
| `std.net.tcp_connect_loopback_result` | `(i32) -> (result i32 i32)` | `1.0.0-beta.6` | `examples/projects/std-layout-local-net` | Connects to a loopback TCP port; success returns `ok handle` where `handle` is a positive opaque process-local `i32`, and ordinary host failure returns `err 1`. | Uses existing standard-runtime usage recording if present; no schema change. | DNS, TLS, UDP, non-loopback connect policy, async IO, timeouts, rich host errors, stable socket ABI/layout. |
| `std.net.tcp_listen_loopback_result` | `(i32) -> (result i32 i32)` | `1.0.0-beta.6` | `examples/projects/std-layout-local-net` | Opens a blocking loopback TCP listener; success returns `ok handle`, and ordinary bind/listen or handle-table failure returns `err 1`. | Uses existing standard-runtime usage recording if present; no schema change. | Non-loopback binding, socket options, backlog policy beyond the implementation minimum, async IO, rich host errors, stable socket ABI/layout. |
| `std.net.tcp_bound_port_result` | `(i32) -> (result i32 i32)` | `1.0.0-beta.6` | `examples/projects/std-layout-local-net` | Returns the bound loopback TCP port for a listener handle as `ok port`; invalid handles or host lookup failure return `err 1`. | Uses existing standard-runtime usage recording if present; no schema change. | Stable handle ABI/layout, address inspection, IPv6 policy, rich host errors. |
| `std.net.tcp_accept_result` | `(i32) -> (result i32 i32)` | `1.0.0-beta.6` | `examples/projects/std-layout-local-net` | Accepts one blocking connection from a listener handle; success returns `ok handle`, and invalid handles or host accept failure return `err 1`. | Uses existing standard-runtime usage recording if present; no schema change. | Async accept, cancellation, readiness polling, peer address APIs, rich host errors, stable socket ABI/layout. |
| `std.net.tcp_read_all_result` | `(i32) -> (result string i32)` | `1.0.0-beta.6` | `examples/projects/std-layout-local-net` | Reads remaining text from a stream handle until EOF; success returns `ok text`, and invalid handles or host read failure return `err 1`. | Uses existing standard-runtime usage recording if present; no schema change. | Chunked reads, binary IO, buffering policy, encodings, async IO, rich host errors, stable socket ABI/layout. |
| `std.net.tcp_write_text_result` | `(i32, string) -> (result i32 i32)` | `1.0.0-beta.6` | `examples/projects/std-layout-local-net` | Writes the complete text to a stream handle; success returns `ok 0`, and invalid handles or host write failure return `err 1`. | Uses existing standard-runtime usage recording if present; no schema change. | Partial-write APIs, binary IO, buffering policy, async IO, rich host errors, stable socket ABI/layout. |
| `std.net.tcp_close_result` | `(i32) -> (result i32 i32)` | `1.0.0-beta.6` | `examples/projects/std-layout-local-net` | Closes a TCP stream or listener handle; success returns `ok 0`, while invalid, already closed, or failed close operations return `err 1`. | Uses existing standard-runtime usage recording if present; no schema change. | Finalizers, destructors, affine ownership, automatic cleanup, stable socket ABI/layout. |
| `std.random.i32` | `() -> i32` | exp-11 | `examples/supported/random.slo` | Returns a non-negative implementation-owned random `i32`; unavailable randomness traps as `slovo runtime error: random i32 unavailable`. | Uses existing standard-runtime usage recording if present; no randomness-specific schema field. | Seed APIs, crypto/security promises, ranges, bytes, floats, UUIDs, stable RNG ABI/layout. |
| `std.io.read_stdin_result` | `() -> (result string i32)` | exp-12 | `examples/supported/stdin-result.slo` | Reads remaining stdin as text; success returns `ok` text, ordinary EOF with no bytes returns `ok ""`, ordinary host/input failure returns `err 1`. | Uses existing standard-runtime usage recording if present; no stdin-specific schema field. | Trap stdin, line APIs, prompts, terminal mode, binary/streaming/async stdin. |
| `std.string.parse_i32_result` | `(string) -> (result i32 i32)` | exp-13 | `examples/supported/string-parse-i32-result.slo` | Parses an entire ASCII decimal signed `i32`; success returns `ok` value, ordinary parse failure returns `err 1`. | Uses existing standard-runtime usage recording if present; no parse-specific schema field. | Trap parse, floats/bools/bytes, trimming, locale/radix/underscore/plus parsing, rich parse errors, Unicode digits, slicing/indexing. |

View File

@ -6,15 +6,15 @@ Do not edit this file by hand.
## Stability Tiers
- `beta-supported`: exported from `lib/std` and covered by source-search, promotion, or facade gates in the current beta line.
- `experimental`: not used for exported `lib/std` helpers in `1.0.0-beta.5`; future releases may mark new helpers this way before they graduate.
- `experimental`: not used for exported `lib/std` helpers in `1.0.0-beta.6`; future releases may mark new helpers this way before they graduate.
- `internal`: helper names that are not exported from their module; they are intentionally omitted from this catalog.
The catalog is a beta compatibility aid, not a stable `1.0.0` API freeze.
## Summary
- Modules: 17
- Exported helpers: 539
- Modules: 18
- Exported helpers: 548
- Default tier: `beta-supported`
## Modules
@ -238,6 +238,22 @@ The catalog is a beta compatibility aid, not a stable `1.0.0` API freeze.
- `is_negative_f64`
- `in_range_f64`
### std.net
- Path: `lib/std/net.slo`
- Tier: `beta-supported`
- Exported helpers: 9
- `tcp_connect_loopback_result`
- `tcp_listen_loopback_result`
- `tcp_bound_port_result`
- `tcp_accept_result`
- `tcp_read_all_result`
- `tcp_write_text_result`
- `tcp_close_result`
- `tcp_write_text_ok`
- `tcp_close_ok`
### std.num
- Path: `lib/std/num.slo`

View File

@ -5,18 +5,19 @@
Sanjin Gumbarevic<br>
hermeticum_lab@protonmail.com
Publication release: `1.0.0-beta.5`
Publication release: `1.0.0-beta.6`
Technical behavior baseline: compiler and language support through
`1.0.0-beta`; tooling and install workflow through `1.0.0-beta.1`;
runtime/resource foundation through `1.0.0-beta.2`; standard-library
stabilization through `1.0.0-beta.3`; language-usability diagnostics through
`1.0.0-beta.4`; package/workspace discipline through `1.0.0-beta.5`
`1.0.0-beta.4`; package/workspace discipline through `1.0.0-beta.5`;
loopback networking foundation through `1.0.0-beta.6`
Date: 2026-05-22
Evidence source: paired local Slovo/Glagol monorepo verification and benchmark
reruns from a local checkout; beta.5 release-gate verification from the public
reruns from a local checkout; beta.6 release-gate verification from the public
monorepo
Maturity: beta
@ -28,19 +29,21 @@ Slovo. It exists to make the language support boundary inspectable: tokens,
S-expression tree, AST, typed AST, LLVM IR, hosted native executable, tests,
diagnostics, and release documents should agree.
The current publication release, `1.0.0-beta.5`, keeps the first real
The current publication release, `1.0.0-beta.6`, keeps the first real
general-purpose beta toolchain baseline from `1.0.0-beta` and records the
first post-beta tooling/install hardening update plus the first
runtime/resource foundation update plus the first standard-library
stabilization update plus the first language-usability diagnostics update and
the first local package/workspace discipline update. The beta baseline includes
the completed `u32` / `u64` unsigned compiler and stdlib breadth scope
alongside the current nine-kernel benchmark suite. This paper records the
current beta implementation surface, the benchmark method and results, the
distinction between Glagol and Lisp-family implementations, the beta.1 tooling
update, the beta.2 runtime/resource foundation, the beta.3 standard-library
stabilization slice, the beta.4 diagnostics usability slice, the beta.5 package
discipline slice, and the compiler path from beta to stable.
the first local package/workspace discipline update plus the first loopback
networking foundation update. The beta baseline includes the completed `u32` /
`u64` unsigned compiler and stdlib breadth scope, the narrow `std.net`
loopback TCP runtime family, and the current nine-kernel benchmark suite. This
paper records the current beta implementation surface, the benchmark method and
results, the distinction between Glagol and Lisp-family implementations, the
beta.1 tooling update, the beta.2 runtime/resource foundation, the beta.3
standard-library stabilization slice, the beta.4 diagnostics usability slice,
the beta.5 package discipline slice, the beta.6 networking foundation slice,
and the compiler path from beta to stable.
## 1. Compiler Thesis
@ -124,9 +127,9 @@ At the current technical behavior beta baseline, Glagol supports:
- benchmark scaffolds for Slovo, C, Rust, Python, Clojure, and Common
Lisp/SBCL, with `cold-process` and `hot-loop` timing modes
The current release, `1.0.0-beta.5`, is a beta package/workspace discipline
update on the first release line that may honestly use beta maturity language
for this toolchain.
The current release, `1.0.0-beta.6`, is a beta networking foundation update on
the first release line that may honestly use beta maturity language for this
toolchain.
## 4. Diagnostics And Support Discipline
@ -285,8 +288,9 @@ The benchmark rows below remain the full-suite `1.0.0-beta` publication
baseline. `1.0.0-beta.1` changes tooling and install workflow, and
`1.0.0-beta.2` adds runtime/resource APIs, `1.0.0-beta.3` adds
standard-library catalog and composition coverage, `1.0.0-beta.4` improves
diagnostics, and `1.0.0-beta.5` tightens package/workspace discipline. None of
these post-beta slices claims changed benchmark performance.
diagnostics, `1.0.0-beta.5` tightens package/workspace discipline, and
`1.0.0-beta.6` adds a narrow loopback networking foundation. None of these
post-beta slices claims changed benchmark performance.
The exp-123 publication baseline widened the paired same-machine result set
from seven rows to nine by adding two owned-vector kernels:
@ -374,13 +378,14 @@ coverage and compatibility:
- package behavior becoming stable before dependency, manifest, and versioning
rules are precise
## 9. Path Beyond `1.0.0-beta.5`
## 9. Path Beyond `1.0.0-beta.6`
Glagol now implements the first real beta Slovo contract, the first
post-beta tooling/install hardening release, the first runtime/resource
foundation release, the first standard-library stabilization release, and the
first diagnostics usability release, and the first package/workspace
discipline release. The remaining path is from beta to stable.
discipline release, and the first loopback networking foundation release. The
remaining path is from beta to stable.
Recommended compiler sequence:

Binary file not shown.

Binary file not shown.

View File

@ -5,18 +5,19 @@
Sanjin Gumbarevic<br>
hermeticum_lab@protonmail.com
Publication release: `1.0.0-beta.5`
Publication release: `1.0.0-beta.6`
Technical behavior baseline: language surface through `1.0.0-beta`; tooling
and install workflow through `1.0.0-beta.1`; runtime/resource foundation through
`1.0.0-beta.2`; standard-library stabilization through `1.0.0-beta.3`;
language-usability diagnostics through `1.0.0-beta.4`; package/workspace
discipline through `1.0.0-beta.5`
discipline through `1.0.0-beta.5`; loopback networking foundation through
`1.0.0-beta.6`
Date: 2026-05-22
Evidence source: paired local Slovo/Glagol monorepo verification and benchmark
reruns from a local checkout; beta.5 release-gate verification from the public
reruns from a local checkout; beta.6 release-gate verification from the public
monorepo
Maturity: beta
@ -31,26 +32,28 @@ explicit types, explicit failure through `option` and `result`, lexical
`unsafe`, and native compilation through the Glagol compiler to LLVM IR and
hosted executables.
The current publication release, `1.0.0-beta.5`, keeps the first real
The current publication release, `1.0.0-beta.6`, keeps the first real
general-purpose beta language baseline from `1.0.0-beta` and records the first
post-beta tooling/install hardening update plus the first runtime/resource
foundation update, the first standard-library stabilization update, and the
first language-usability diagnostics update, plus the first local
package/workspace discipline update. The beta baseline includes the completed
`u32` / `u64` unsigned scope, the staged stdlib breadth that makes ordinary
command-line programs practical, and the current nine-kernel benchmark suite.
This paper records the current beta technical state, the difference between
Slovo and Lisp-family languages, the benchmark methodology, the beta.1 tooling
update, the beta.2 runtime/resource foundation, the beta.3 standard-library
stabilization slice, the beta.4 diagnostics usability slice, the beta.5 package
discipline slice, and the remaining path from beta to stable.
package/workspace discipline update, and the first loopback networking
foundation update. The beta baseline includes the completed `u32` / `u64`
unsigned scope, the staged stdlib breadth that makes ordinary command-line
programs practical, the current narrow `std.net` loopback TCP surface, and the
current nine-kernel benchmark suite. This paper records the current beta
technical state, the difference between Slovo and Lisp-family languages, the
benchmark methodology, the beta.1 tooling update, the beta.2 runtime/resource
foundation, the beta.3 standard-library stabilization slice, the beta.4
diagnostics usability slice, the beta.5 package discipline slice, the beta.6
networking foundation slice, and the remaining path from beta to stable.
## 1. Scope
This document is a technical state paper for the current beta baseline. It
summarizes the behavior represented by the paired local Slovo and Glagol
workspaces, with `1.0.0-beta` as the current language-surface baseline and
`1.0.0-beta.5` as the current publication baseline.
`1.0.0-beta.6` as the current publication baseline.
The support rule remains strict:
@ -62,7 +65,7 @@ The support rule remains strict:
- partial parser recognition or speculative examples do not count as support
Historical `exp-*` releases remain experimental alpha maturity. The current
publication accompanies `1.0.0-beta.5`.
publication accompanies `1.0.0-beta.6`.
## 2. Design Thesis
@ -362,8 +365,9 @@ The benchmark rows below remain the full-suite `1.0.0-beta` publication
baseline. `1.0.0-beta.1` changes tooling and install workflow, and
`1.0.0-beta.2` adds runtime/resource APIs, `1.0.0-beta.3` adds
standard-library catalog and composition coverage, `1.0.0-beta.4` improves
diagnostics, and `1.0.0-beta.5` tightens package/workspace discipline. None of
these post-beta slices claims changed benchmark performance.
diagnostics, `1.0.0-beta.5` tightens package/workspace discipline, and
`1.0.0-beta.6` adds a narrow loopback networking foundation. None of these
post-beta slices claims changed benchmark performance.
The exp-123 publication baseline widened the paired same-machine result set
from seven rows to nine by adding two owned-vector kernels:
@ -484,7 +488,7 @@ Major remaining gaps before `1.0.0`:
- semantic versioning and deprecation policy
- a clear separation between stable and experimental features
## 10. Path Beyond `1.0.0-beta.5`
## 10. Path Beyond `1.0.0-beta.6`
The beta threshold is now real. The next work should treat `1.0.0-beta` as
the language compatibility-governed baseline, `1.0.0-beta.1` as the first
@ -492,6 +496,7 @@ tooling/install hardening point, `1.0.0-beta.2` as the first runtime/resource
foundation point, and `1.0.0-beta.3` as the first standard-library
stabilization point, and `1.0.0-beta.4` as the first diagnostics usability
point, and `1.0.0-beta.5` as the first package/workspace discipline point,
and `1.0.0-beta.6` as the first loopback networking foundation point,
then move deliberately toward stable general-purpose status.
Recommended sequence:

Binary file not shown.

View File

@ -0,0 +1,4 @@
[project]
name = "std-import-net"
source_root = "src"
entry = "main"

View File

@ -0,0 +1,94 @@
(module main)
(import std.net (tcp_connect_loopback_result tcp_listen_loopback_result tcp_bound_port_result tcp_accept_result tcp_read_all_result tcp_write_text_result tcp_close_result tcp_write_text_ok tcp_close_ok))
(fn i32_err_one ((value (result i32 i32))) -> bool
(match value
((ok payload)
false)
((err code)
(= code 1))))
(fn string_err_one ((value (result string i32))) -> bool
(match value
((ok payload)
false)
((err code)
(= code 1))))
(fn imported_connect_invalid_port_err () -> bool
(i32_err_one (tcp_connect_loopback_result 0)))
(fn imported_listen_invalid_port_err () -> bool
(i32_err_one (tcp_listen_loopback_result -1)))
(fn imported_bound_port_invalid_handle_err () -> bool
(i32_err_one (tcp_bound_port_result -1)))
(fn imported_accept_invalid_listener_err () -> bool
(i32_err_one (tcp_accept_result -1)))
(fn imported_read_invalid_handle_err () -> bool
(string_err_one (tcp_read_all_result -1)))
(fn imported_write_invalid_handle_err () -> bool
(i32_err_one (tcp_write_text_result -1 "ping")))
(fn imported_close_invalid_handle_err () -> bool
(i32_err_one (tcp_close_result -1)))
(fn imported_helper_invalids_false () -> bool
(if (tcp_write_text_ok -1 "ping")
false
(if (tcp_close_ok -1)
false
true)))
(fn imported_invalid_paths_ok () -> bool
(if (imported_connect_invalid_port_err)
(if (imported_listen_invalid_port_err)
(if (imported_bound_port_invalid_handle_err)
(if (imported_accept_invalid_listener_err)
(if (imported_read_invalid_handle_err)
(if (imported_write_invalid_handle_err)
(if (imported_close_invalid_handle_err)
(imported_helper_invalids_false)
false)
false)
false)
false)
false)
false)
false))
(fn main () -> i32
(if (imported_invalid_paths_ok)
42
1))
(test "explicit std net invalid connect facade"
(imported_connect_invalid_port_err))
(test "explicit std net invalid listen facade"
(imported_listen_invalid_port_err))
(test "explicit std net invalid bound port facade"
(imported_bound_port_invalid_handle_err))
(test "explicit std net invalid accept facade"
(imported_accept_invalid_listener_err))
(test "explicit std net invalid read facade"
(imported_read_invalid_handle_err))
(test "explicit std net invalid write facade"
(imported_write_invalid_handle_err))
(test "explicit std net invalid close facade"
(imported_close_invalid_handle_err))
(test "explicit std net helper invalids facade"
(imported_helper_invalids_false))
(test "explicit std net facade all"
(= (main) 42))

View File

@ -0,0 +1,4 @@
[project]
name = "std-layout-local-net"
source_root = "src"
entry = "main"

View File

@ -0,0 +1,94 @@
(module main)
(import net (tcp_connect_loopback_result tcp_listen_loopback_result tcp_bound_port_result tcp_accept_result tcp_read_all_result tcp_write_text_result tcp_close_result tcp_write_text_ok tcp_close_ok))
(fn i32_err_one ((value (result i32 i32))) -> bool
(match value
((ok payload)
false)
((err code)
(= code 1))))
(fn string_err_one ((value (result string i32))) -> bool
(match value
((ok payload)
false)
((err code)
(= code 1))))
(fn imported_connect_invalid_port_err () -> bool
(i32_err_one (tcp_connect_loopback_result 0)))
(fn imported_listen_invalid_port_err () -> bool
(i32_err_one (tcp_listen_loopback_result -1)))
(fn imported_bound_port_invalid_handle_err () -> bool
(i32_err_one (tcp_bound_port_result -1)))
(fn imported_accept_invalid_listener_err () -> bool
(i32_err_one (tcp_accept_result -1)))
(fn imported_read_invalid_handle_err () -> bool
(string_err_one (tcp_read_all_result -1)))
(fn imported_write_invalid_handle_err () -> bool
(i32_err_one (tcp_write_text_result -1 "ping")))
(fn imported_close_invalid_handle_err () -> bool
(i32_err_one (tcp_close_result -1)))
(fn imported_helper_invalids_false () -> bool
(if (tcp_write_text_ok -1 "ping")
false
(if (tcp_close_ok -1)
false
true)))
(fn imported_invalid_paths_ok () -> bool
(if (imported_connect_invalid_port_err)
(if (imported_listen_invalid_port_err)
(if (imported_bound_port_invalid_handle_err)
(if (imported_accept_invalid_listener_err)
(if (imported_read_invalid_handle_err)
(if (imported_write_invalid_handle_err)
(if (imported_close_invalid_handle_err)
(imported_helper_invalids_false)
false)
false)
false)
false)
false)
false)
false))
(fn main () -> i32
(if (imported_invalid_paths_ok)
42
1))
(test "explicit local net invalid connect facade"
(imported_connect_invalid_port_err))
(test "explicit local net invalid listen facade"
(imported_listen_invalid_port_err))
(test "explicit local net invalid bound port facade"
(imported_bound_port_invalid_handle_err))
(test "explicit local net invalid accept facade"
(imported_accept_invalid_listener_err))
(test "explicit local net invalid read facade"
(imported_read_invalid_handle_err))
(test "explicit local net invalid write facade"
(imported_write_invalid_handle_err))
(test "explicit local net invalid close facade"
(imported_close_invalid_handle_err))
(test "explicit local net helper invalids facade"
(imported_helper_invalids_false))
(test "explicit local net facade all"
(= (main) 42))

View File

@ -0,0 +1,36 @@
(module net (export tcp_connect_loopback_result tcp_listen_loopback_result tcp_bound_port_result tcp_accept_result tcp_read_all_result tcp_write_text_result tcp_close_result tcp_write_text_ok tcp_close_ok))
(fn tcp_connect_loopback_result ((port i32)) -> (result i32 i32)
(std.net.tcp_connect_loopback_result port))
(fn tcp_listen_loopback_result ((port i32)) -> (result i32 i32)
(std.net.tcp_listen_loopback_result port))
(fn tcp_bound_port_result ((handle i32)) -> (result i32 i32)
(std.net.tcp_bound_port_result handle))
(fn tcp_accept_result ((listener i32)) -> (result i32 i32)
(std.net.tcp_accept_result listener))
(fn tcp_read_all_result ((handle i32)) -> (result string i32)
(std.net.tcp_read_all_result handle))
(fn tcp_write_text_result ((handle i32) (text string)) -> (result i32 i32)
(std.net.tcp_write_text_result handle text))
(fn tcp_close_result ((handle i32)) -> (result i32 i32)
(std.net.tcp_close_result handle))
(fn tcp_write_text_ok ((handle i32) (text string)) -> bool
(match (tcp_write_text_result handle text)
((ok status)
true)
((err code)
false)))
(fn tcp_close_ok ((handle i32)) -> bool
(match (tcp_close_result handle)
((ok status)
true)
((err code)
false)))

View File

@ -31,7 +31,9 @@ updated through `exp-66`; fs option helpers updated through `exp-110`; num
fallback helpers updated through `exp-64`; concrete vec helpers updated
through released `exp-108`, including the current concrete
`std/vec_string.slo`, `std/vec_f64.slo`, and `std/vec_bool.slo`
prefix/suffix helper scopes.
prefix/suffix helper scopes; `1.0.0-beta.6` networking foundation work releases
`std/net.slo` as an experimental loopback TCP facade over matching
compiler-known runtime calls.
This directory is the source home for staged standard library modules and
examples. exp-44 lets project-mode source explicitly import `std/math.slo` as
@ -83,6 +85,13 @@ filesystem status/mutation helpers over `std.fs.exists`, `std.fs.is_file`,
`std.fs.is_dir`, `std.fs.remove_file_result`, and
`std.fs.create_dir_result`, while keeping handles beta-scoped opaque `i32`
values and leaving directory enumeration deferred;
`1.0.0-beta.6` releases `std/net.slo` as an experimental blocking
loopback TCP facade over `std.net.tcp_connect_loopback_result`,
`std.net.tcp_listen_loopback_result`, `std.net.tcp_bound_port_result`,
`std.net.tcp_accept_result`, `std.net.tcp_read_all_result`,
`std.net.tcp_write_text_result`, and `std.net.tcp_close_result`, while keeping
socket handles beta-scoped opaque `i32` values and leaving DNS, TLS, async,
UDP, non-loopback binding, and stable ABI/layout deferred;
exp-76 extends project-mode source search to `std/vec_i32.slo`, a concrete
source-authored collection facade over the current promoted `std.vec.i32`
runtime family; exp-77 extends that facade with concrete option-returning
@ -127,12 +136,13 @@ For exp-44, exp-45, exp-47, exp-48, exp-49, exp-52, exp-53, exp-76, exp-94,
exp-96, exp-97, exp-98, exp-99, exp-103, exp-104, exp-105, exp-107, and
exp-108, `std/math.slo`, `std/result.slo`,
`std/option.slo`, `std/time.slo`, `std/random.slo`, `std/env.slo`,
`std/fs.slo`, `std/string.slo`, `std/num.slo`, `std/io.slo`,
`std/process.slo`, `std/cli.slo`, `std/vec_i32.slo`, `std/vec_f64.slo`,
`std/vec_i64.slo`, and `std/vec_string.slo` carry explicit export lists.
`std/fs.slo`, `std/net.slo`, `std/string.slo`, `std/num.slo`,
`std/io.slo`, `std/process.slo`, `std/cli.slo`, `std/vec_i32.slo`,
`std/vec_f64.slo`, `std/vec_i64.slo`, and `std/vec_string.slo` carry explicit
export lists.
Glagol may address them externally as `std.math`, `std.result`,
`std.option`, `std.time`, `std.random`, `std.env`, `std.fs`, `std.string`,
`std.num`, `std.io`, `std.process`, `std.cli`, `std.vec_i32`,
`std.net`, `std.num`, `std.io`, `std.process`, `std.cli`, `std.vec_i32`,
`std.vec_f64`, `std.vec_bool`, `std.vec_i64`, and `std.vec_string`.
The file layout is the contract:
@ -153,6 +163,7 @@ The file layout is the contract:
- `std/random.slo`
- `std/env.slo`
- `std/fs.slo`
- `std/net.slo`
This follows a Zig-like standard-library facade discipline in a Slovo-sized
form: flat `std/*.slo` facade files are the staged source surface now, and a
@ -261,6 +272,12 @@ over `read_text_result` plus those same concrete parse result families from
`read_i64_option`, `read_f64_option`, and `read_bool_option` as ordinary
source option helpers over those same read/parse result families through the
exp-109 `std.result.ok_or_none_*` bridge helpers.
`std/net.slo` is the beta.6 networking foundation source facade. It wraps
blocking loopback TCP connect/listen/bound-port/accept/read-all/write-text/
close result calls and adds only `tcp_write_text_ok` and `tcp_close_ok` source
helpers. It is not a general networking module: DNS, TLS, async IO, UDP,
non-loopback binding, socket options, rich host errors, and stable handle ABI
remain deferred.
`std/process.slo` includes the exp-52 narrow source wrappers over already
released process argument runtime calls and a source-authored `has_arg`
predicate. exp-61 adds `arg_or` and `arg_or_empty` as ordinary source

36
lib/std/net.slo Normal file
View File

@ -0,0 +1,36 @@
(module net (export tcp_connect_loopback_result tcp_listen_loopback_result tcp_bound_port_result tcp_accept_result tcp_read_all_result tcp_write_text_result tcp_close_result tcp_write_text_ok tcp_close_ok))
(fn tcp_connect_loopback_result ((port i32)) -> (result i32 i32)
(std.net.tcp_connect_loopback_result port))
(fn tcp_listen_loopback_result ((port i32)) -> (result i32 i32)
(std.net.tcp_listen_loopback_result port))
(fn tcp_bound_port_result ((handle i32)) -> (result i32 i32)
(std.net.tcp_bound_port_result handle))
(fn tcp_accept_result ((listener i32)) -> (result i32 i32)
(std.net.tcp_accept_result listener))
(fn tcp_read_all_result ((handle i32)) -> (result string i32)
(std.net.tcp_read_all_result handle))
(fn tcp_write_text_result ((handle i32) (text string)) -> (result i32 i32)
(std.net.tcp_write_text_result handle text))
(fn tcp_close_result ((handle i32)) -> (result i32 i32)
(std.net.tcp_close_result handle))
(fn tcp_write_text_ok ((handle i32) (text string)) -> bool
(match (tcp_write_text_result handle text)
((ok status)
true)
((err code)
false)))
(fn tcp_close_ok ((handle i32)) -> bool
(match (tcp_close_result handle)
((ok status)
true)
((err code)
false)))

View File

@ -1,15 +1,18 @@
#define _POSIX_C_SOURCE 200809L
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <math.h>
#include <netinet/in.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
@ -48,6 +51,9 @@ static struct timespec __glagol_time_base;
#define __GLAGOL_FILE_HANDLE_LIMIT 1024
static FILE *__glagol_file_handles[__GLAGOL_FILE_HANDLE_LIMIT];
#define __GLAGOL_NET_HANDLE_LIMIT 1024
static int __glagol_net_handles[__GLAGOL_NET_HANDLE_LIMIT];
static int64_t __glagol_result_i32_encode(uint32_t status, int32_t payload) {
uint64_t encoded = ((uint64_t)status << 32) | (uint32_t)payload;
return (int64_t)encoded;
@ -57,6 +63,54 @@ static int64_t __glagol_result_i32_err(void) {
return __glagol_result_i32_encode(1, 1);
}
static int32_t __glagol_net_handle_insert(int fd) {
if (fd < 0 || fd == INT_MAX) {
return 0;
}
for (int32_t handle = 1; handle < __GLAGOL_NET_HANDLE_LIMIT; handle++) {
if (__glagol_net_handles[handle] == 0) {
__glagol_net_handles[handle] = fd + 1;
return handle;
}
}
return 0;
}
static int __glagol_net_handle_get(int32_t handle) {
if (handle <= 0 || handle >= __GLAGOL_NET_HANDLE_LIMIT) {
return -1;
}
int stored = __glagol_net_handles[handle];
return stored == 0 ? -1 : stored - 1;
}
static int __glagol_net_handle_take(int32_t handle) {
int fd = __glagol_net_handle_get(handle);
if (fd >= 0) {
__glagol_net_handles[handle] = 0;
}
return fd;
}
static bool __glagol_net_connect_port_is_valid(int32_t port) {
return port > 0 && port <= 65535;
}
static bool __glagol_net_bind_port_is_valid(int32_t port) {
return port >= 0 && port <= 65535;
}
static struct sockaddr_in __glagol_net_loopback_addr(int32_t port) {
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_port = htons((uint16_t)port);
return addr;
}
void print_i32(int32_t value) {
printf("%d\n", value);
}
@ -433,6 +487,222 @@ int32_t __glagol_fs_close_result(int32_t handle) {
return fclose(file) == 0 ? 0 : 1;
}
int64_t __glagol_net_tcp_connect_loopback_result(int32_t port) {
if (!__glagol_net_connect_port_is_valid(port)) {
return __glagol_result_i32_err();
}
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
return __glagol_result_i32_err();
}
struct sockaddr_in addr = __glagol_net_loopback_addr(port);
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
close(fd);
return __glagol_result_i32_err();
}
int32_t handle = __glagol_net_handle_insert(fd);
if (handle == 0) {
close(fd);
return __glagol_result_i32_err();
}
return __glagol_result_i32_encode(0, handle);
}
int64_t __glagol_net_tcp_listen_loopback_result(int32_t port) {
if (!__glagol_net_bind_port_is_valid(port)) {
return __glagol_result_i32_err();
}
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
return __glagol_result_i32_err();
}
int enabled = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled)) != 0) {
close(fd);
return __glagol_result_i32_err();
}
struct sockaddr_in addr = __glagol_net_loopback_addr(port);
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
close(fd);
return __glagol_result_i32_err();
}
if (listen(fd, 1) != 0) {
close(fd);
return __glagol_result_i32_err();
}
int32_t handle = __glagol_net_handle_insert(fd);
if (handle == 0) {
close(fd);
return __glagol_result_i32_err();
}
return __glagol_result_i32_encode(0, handle);
}
int64_t __glagol_net_tcp_bound_port_result(int32_t handle) {
int fd = __glagol_net_handle_get(handle);
if (fd < 0) {
return __glagol_result_i32_err();
}
struct sockaddr_in addr;
socklen_t addr_len = sizeof(addr);
if (getsockname(fd, (struct sockaddr *)&addr, &addr_len) != 0) {
return __glagol_result_i32_err();
}
return __glagol_result_i32_encode(0, (int32_t)ntohs(addr.sin_port));
}
int64_t __glagol_net_tcp_accept_result(int32_t listener) {
int fd = __glagol_net_handle_get(listener);
if (fd < 0) {
return __glagol_result_i32_err();
}
int accepted;
do {
accepted = accept(fd, NULL, NULL);
} while (accepted < 0 && errno == EINTR);
if (accepted < 0) {
return __glagol_result_i32_err();
}
int32_t handle = __glagol_net_handle_insert(accepted);
if (handle == 0) {
close(accepted);
return __glagol_result_i32_err();
}
return __glagol_result_i32_encode(0, handle);
}
char *__glagol_net_tcp_read_all_result(int32_t handle) {
int fd = __glagol_net_handle_get(handle);
if (fd < 0) {
return NULL;
}
size_t capacity = 1024;
size_t length = 0;
char *value = malloc(capacity);
if (value == NULL) {
return NULL;
}
for (;;) {
if (length == capacity) {
if (capacity > SIZE_MAX / 2) {
free(value);
return NULL;
}
size_t next_capacity = capacity * 2;
char *next = realloc(value, next_capacity);
if (next == NULL) {
free(value);
return NULL;
}
value = next;
capacity = next_capacity;
}
size_t available = capacity - length;
if (available > (size_t)SSIZE_MAX) {
available = (size_t)SSIZE_MAX;
}
ssize_t read_count = recv(fd, value + length, available, 0);
if (read_count < 0) {
if (errno == EINTR) {
continue;
}
free(value);
return NULL;
}
if (read_count == 0) {
break;
}
length += (size_t)read_count;
}
if (length == capacity) {
if (capacity == SIZE_MAX) {
free(value);
return NULL;
}
char *next = realloc(value, capacity + 1);
if (next == NULL) {
free(value);
return NULL;
}
value = next;
}
value[length] = '\0';
return value;
}
int32_t __glagol_net_tcp_write_text_result(int32_t handle, const char *text) {
int fd = __glagol_net_handle_get(handle);
if (fd < 0) {
return 1;
}
size_t length = strlen(text);
size_t written = 0;
while (written < length) {
size_t remaining = length - written;
if (remaining > (size_t)SSIZE_MAX) {
remaining = (size_t)SSIZE_MAX;
}
#ifdef MSG_NOSIGNAL
ssize_t sent = send(fd, text + written, remaining, MSG_NOSIGNAL);
#else
ssize_t sent = send(fd, text + written, remaining, 0);
#endif
if (sent < 0) {
if (errno == EINTR) {
continue;
}
return 1;
}
if (sent == 0) {
return 1;
}
written += (size_t)sent;
}
return 0;
}
int32_t __glagol_net_tcp_close_result(int32_t handle) {
int fd = __glagol_net_handle_take(handle);
if (fd < 0) {
return 1;
}
return close(fd) == 0 ? 0 : 1;
}
static void __glagol_allocation_trap(void) {
fputs("slovo runtime error: string allocation failed\n", stderr);
exit(1);