feat(nexus): production modular HTTP/1.1 server + compiler fixes

- Rewrite apps/nexus with modular architecture:
  Config, Http, Errors, Parser, Router, Handlers, Server, Main
- Use algebraic enums for ParseResult/FileResult/HttpError
- Thread-pool server via Channel<ConnectionTask> and spawn
- Fix C backend type ordering for generic struct instances
  (Array_T, Iter_T) and algebraic enum struct payloads
- Collect Slice_T types from struct fields and enum payloads
- Fix match lowering for simple enums (direct value compare)
- Resolve match expression return type from first arm
- Infer element type for for-in over Array<UserStruct>
- Preserve generic type args in field access resolution
- Add fflush to PrintLine/Print for immediate server logs
- Add modern_features golden regression test
- Regenerate golden expected.c files
This commit is contained in:
2026-06-15 00:54:03 +03:00
parent fc0a560e60
commit aaeb01e518
40 changed files with 28530 additions and 21790 deletions
+1
View File
@@ -11,6 +11,7 @@ tests/lexer_test
tests/parser_test tests/parser_test
tests/sema_test tests/sema_test
tests/hir_test tests/hir_test
tests/borrow_test
tests/test_generics_parse tests/test_generics_parse
tests/*.exe tests/*.exe
+1 -1
View File
@@ -78,7 +78,7 @@ selfhost: build
.PHONY: test-golden .PHONY: test-golden
GOLDEN_TESTS := hello fibonacci structs generics algebraic_enums enums methods strings GOLDEN_TESTS := hello fibonacci structs generics algebraic_enums enums methods strings modern_features
test-golden: build test-golden: build
@echo "=== Golden tests ===" @echo "=== Golden tests ==="
+1 -129
View File
@@ -1,129 +1 @@
<!DOCTYPE html> <html><body>Hello Nexus</body></html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nexus — Bux HTTP Server</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #0d1117;
color: #c9d1d9;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem;
}
.card {
background: #161b22;
border: 1px solid #30363d;
border-radius: 8px;
padding: 3rem;
max-width: 600px;
width: 100%;
text-align: center;
}
h1 {
font-size: 2.5rem;
color: #58a6ff;
margin-bottom: 0.5rem;
}
.subtitle {
color: #8b949e;
font-size: 1.1rem;
margin-bottom: 2rem;
}
.features {
text-align: left;
margin: 2rem 0;
}
.features h3 {
color: #58a6ff;
margin-bottom: 0.75rem;
}
.features ul {
list-style: none;
}
.features li {
padding: 0.4rem 0;
color: #8b949e;
}
.features li::before {
content: "▸ ";
color: #3fb950;
}
.endpoints {
margin-top: 2rem;
padding-top: 1.5rem;
border-top: 1px solid #30363d;
}
.endpoints h3 {
color: #d2a8ff;
margin-bottom: 0.75rem;
}
.endpoint {
display: flex;
justify-content: space-between;
padding: 0.3rem 0;
font-family: 'SF Mono', 'Fira Code', monospace;
font-size: 0.9rem;
}
.endpoint .method {
color: #3fb950;
font-weight: bold;
min-width: 60px;
}
.endpoint .path {
color: #c9d1d9;
}
.footer {
margin-top: 2rem;
padding-top: 1.5rem;
border-top: 1px solid #30363d;
color: #484f58;
font-size: 0.85rem;
}
</style>
</head>
<body>
<div class="card">
<h1>⚡ Nexus</h1>
<p class="subtitle">High-performance HTTP Server — Built with Bux</p>
<div class="features">
<h3>Features</h3>
<ul>
<li>HTTP/1.1 — Full request parsing & response building</li>
<li>Multi-threaded — Configurable worker pool</li>
<li>HTTP/2 Detection — Upgrade-aware</li>
<li>WebSocket Upgrade — Handshake support</li>
<li>Static File Serving — With MIME type detection</li>
<li>JSON API Endpoints</li>
</ul>
</div>
<div class="endpoints">
<h3>API Endpoints</h3>
<div class="endpoint">
<span class="method">GET</span>
<span class="path">/api/health</span>
</div>
<div class="endpoint">
<span class="method">GET</span>
<span class="path">/api/info</span>
</div>
<div class="endpoint">
<span class="method">GET</span>
<span class="path">/ws</span>
</div>
</div>
<div class="footer">
Nexus v0.1.0 &middot; Bux Programming Language
</div>
</div>
</body>
</html>
+21
View File
@@ -0,0 +1,21 @@
module Config {
pub struct ServerConfig {
bindAddr: String;
port: int;
workerCount: int;
publicDir: String;
backlog: int;
}
pub const func DefaultConfig() -> ServerConfig {
return ServerConfig {
bindAddr: "0.0.0.0",
port: 8080,
workerCount: 4,
publicDir: "public",
backlog: 128,
};
}
}
+65
View File
@@ -0,0 +1,65 @@
module Errors {
import Http::{HttpRequest};
pub enum HttpError {
BadRequest,
NotFound,
MethodNotAllowed,
InternalError(String),
}
pub enum ParseResult {
Ok(HttpRequest),
Err(HttpError),
}
pub enum FileResult {
Ok(String),
Err(HttpError),
}
pub func ParseResult_NewOk(req: HttpRequest) -> ParseResult {
let r: ParseResult = ParseResult { tag: ParseResult_Ok };
r.data.Ok_0 = req;
return r;
}
pub func ParseResult_NewErr(err: HttpError) -> ParseResult {
let r: ParseResult = ParseResult { tag: ParseResult_Err };
r.data.Err_0 = err;
return r;
}
pub func FileResult_NewOk(content: String) -> FileResult {
let r: FileResult = FileResult { tag: FileResult_Ok };
r.data.Ok_0 = content;
return r;
}
pub func FileResult_NewErr(err: HttpError) -> FileResult {
let r: FileResult = FileResult { tag: FileResult_Err };
r.data.Err_0 = err;
return r;
}
pub func FileResult_IsOk(r: FileResult) -> bool {
return r.tag == FileResult_Ok;
}
pub func FileResult_Unwrap(r: FileResult) -> String {
return r.data.Ok_0;
}
pub func FileResult_UnwrapErr(r: FileResult) -> HttpError {
return r.data.Err_0;
}
pub func HttpError_ToString(err: HttpError) -> String {
if err.tag == HttpError_BadRequest { return "Bad Request"; }
if err.tag == HttpError_NotFound { return "Not Found"; }
if err.tag == HttpError_MethodNotAllowed { return "Method Not Allowed"; }
return err.data.InternalError_0;
}
}
+94
View File
@@ -0,0 +1,94 @@
module Handlers {
import Std::String::{String_Eq, String_Contains};
import Http::{HttpMethod, HttpRequest, HttpResponse, Http_NewResponse, Http_MimeType, RequestHeader_Get};
import Errors::{HttpError, FileResult, FileResult_NewOk, FileResult_NewErr, FileResult_IsOk, FileResult_Unwrap, FileResult_UnwrapErr, HttpError_ToString};
extern func bux_strlen(s: String) -> uint;
extern func bux_file_exists(path: String) -> int;
extern func bux_read_file(path: String) -> String;
extern func bux_path_join(a: String, b: String) -> String;
extern func bux_sb_new(initial_cap: uint) -> *void;
extern func bux_sb_append(sb: *void, s: String);
extern func bux_sb_append_int(sb: *void, n: int64);
extern func bux_sb_build(sb: *void) -> String;
extern func bux_sb_free(sb: *void);
pub func NotFoundResponse() -> HttpResponse {
return Http_NewResponse(404, "application/json; charset=utf-8", "{\"error\":\"not_found\"}");
}
pub func MethodNotAllowedResponse() -> HttpResponse {
return Http_NewResponse(405, "text/plain; charset=utf-8", "Method Not Allowed");
}
pub func ReadStaticFile(requestPath: String) -> FileResult {
if String_Contains(requestPath, "..") {
return FileResult_NewErr(HttpError { tag: HttpError_NotFound });
}
var filePath: String = requestPath;
if String_Eq(filePath, "/") {
filePath = "/index.html";
}
let fullPath: String = bux_path_join("public", filePath);
if bux_file_exists(fullPath) == 0 {
return FileResult_NewErr(HttpError { tag: HttpError_NotFound });
}
let content: String = bux_read_file(fullPath);
return FileResult_NewOk(content);
}
func FileErrorResponse(err: HttpError) -> HttpResponse {
match err {
HttpError::NotFound => NotFoundResponse(),
_ => Http_NewResponse(500, "text/plain; charset=utf-8", HttpError_ToString(err)),
}
}
pub func ServeStaticFile(req: HttpRequest) -> HttpResponse {
if req.method != HttpMethod_GET && req.method != HttpMethod_HEAD {
return MethodNotAllowedResponse();
}
let result: FileResult = ReadStaticFile(req.path);
if FileResult_IsOk(result) {
let content: String = FileResult_Unwrap(result);
return Http_NewResponse(200, Http_MimeType(req.path), content);
}
return FileErrorResponse(FileResult_UnwrapErr(result));
}
pub func HandleApiHealth() -> HttpResponse {
return Http_NewResponse(200, "application/json; charset=utf-8",
"{\"status\":\"ok\",\"server\":\"Nexus\",\"version\":\"0.2.0\"}");
}
pub func HandleApiInfo() -> HttpResponse {
return Http_NewResponse(200, "application/json; charset=utf-8",
"{\"name\":\"Nexus\",\"language\":\"Bux\",\"features\":[\"HTTP/1.1\",\"thread-pool\",\"algebraic-enums\"]}");
}
pub func HandleWebSocketUpgrade(req: HttpRequest) -> HttpResponse {
let wsKey: String = RequestHeader_Get(req, "Sec-WebSocket-Key");
var resp: HttpResponse;
resp.statusCode = 101;
resp.contentType = "";
resp.body = "";
let sb: *void = bux_sb_new(256);
bux_sb_append(sb, "Upgrade: websocket\r\n");
bux_sb_append(sb, "Connection: Upgrade\r\n");
bux_sb_append(sb, "Sec-WebSocket-Accept: ");
bux_sb_append(sb, wsKey);
bux_sb_append(sb, "\r\n");
resp.extraHeaders = bux_sb_build(sb);
bux_sb_free(sb);
return resp;
}
}
+107
View File
@@ -0,0 +1,107 @@
module Http {
import Std::Array::{Array};
import Std::String::{String_Eq, String_EndsWith, String_Contains};
pub enum HttpMethod {
GET,
POST,
PUT,
DELETE,
PATCH,
HEAD,
OPTIONS,
UNKNOWN,
}
pub struct HeaderEntry {
key: String;
value: String;
}
pub struct HttpRequest {
method: HttpMethod;
path: String;
version: String;
body: String;
headers: Array<HeaderEntry>;
}
pub struct HttpResponse {
statusCode: int;
contentType: String;
body: String;
extraHeaders: String;
}
pub func Http_StatusText(code: int) -> String {
if code == 200 { return "OK"; }
if code == 201 { return "Created"; }
if code == 204 { return "No Content"; }
if code == 301 { return "Moved Permanently"; }
if code == 302 { return "Found"; }
if code == 304 { return "Not Modified"; }
if code == 400 { return "Bad Request"; }
if code == 401 { return "Unauthorized"; }
if code == 403 { return "Forbidden"; }
if code == 404 { return "Not Found"; }
if code == 405 { return "Method Not Allowed"; }
if code == 413 { return "Payload Too Large"; }
if code == 414 { return "URI Too Long"; }
if code == 500 { return "Internal Server Error"; }
if code == 501 { return "Not Implemented"; }
if code == 503 { return "Service Unavailable"; }
return "Unknown";
}
pub func Http_MimeType(path: String) -> String {
if String_EndsWith(path, ".html") || String_EndsWith(path, ".htm") { return "text/html; charset=utf-8"; }
if String_EndsWith(path, ".css") { return "text/css; charset=utf-8"; }
if String_EndsWith(path, ".js") { return "application/javascript; charset=utf-8"; }
if String_EndsWith(path, ".json") { return "application/json; charset=utf-8"; }
if String_EndsWith(path, ".xml") { return "application/xml; charset=utf-8"; }
if String_EndsWith(path, ".txt") { return "text/plain; charset=utf-8"; }
if String_EndsWith(path, ".png") { return "image/png"; }
if String_EndsWith(path, ".jpg") || String_EndsWith(path, ".jpeg") { return "image/jpeg"; }
if String_EndsWith(path, ".gif") { return "image/gif"; }
if String_EndsWith(path, ".svg") { return "image/svg+xml"; }
if String_EndsWith(path, ".ico") { return "image/x-icon"; }
if String_EndsWith(path, ".webp") { return "image/webp"; }
if String_EndsWith(path, ".woff2") { return "font/woff2"; }
if String_EndsWith(path, ".woff") { return "font/woff"; }
if String_EndsWith(path, ".wasm") { return "application/wasm"; }
return "application/octet-stream";
}
pub func Http_MethodName(m: HttpMethod) -> String {
match m {
HttpMethod::GET => "GET",
HttpMethod::POST => "POST",
HttpMethod::PUT => "PUT",
HttpMethod::DELETE => "DELETE",
HttpMethod::PATCH => "PATCH",
HttpMethod::HEAD => "HEAD",
HttpMethod::OPTIONS => "OPTIONS",
HttpMethod::UNKNOWN => "UNKNOWN",
}
}
pub func Http_NewResponse(code: int, contentType: String, body: String) -> HttpResponse {
var resp: HttpResponse;
resp.statusCode = code;
resp.contentType = contentType;
resp.body = body;
resp.extraHeaders = "";
return resp;
}
pub func RequestHeader_Get(req: HttpRequest, key: String) -> String {
for entry in req.headers {
if String_Eq(entry.key, key) {
return entry.value;
}
}
return "";
}
}
+38 -539
View File
@@ -1,550 +1,49 @@
// =============================================================================
// Nexus — High-performance multi-threaded HTTP/1.1, HTTP/2 & WebSocket server
// Built with the Bux programming language
// =============================================================================
module Main { module Main {
// ============================================================================= import Config::{ServerConfig, DefaultConfig};
// Imports — only what we actually use import Http::{HttpMethod};
// ============================================================================= import Router::{Handler, Route, Router};
import Std::Io::{PrintLine, Print, PrintInt}; import Server::{RunServer};
import Std::Net::{Net_Create, Net_SetReuse, Net_Bind, Net_Listen, Net_Accept, Net_Send, Net_Recv, Net_Close, Net_LastError}; import Std::Array::{Array, Array_New, Array_Push};
import Std::String::{String_Len, String_Eq, String_StartsWith, String_EndsWith, String_Contains, String_Trim, String_FromInt, String_Offset};
import Std::Task::{Task_Join, TaskHandle};
// ============================================================================= func BuildRouter() -> Router {
// Extern Functions (from C runtime — used directly for performance) var routes: Array<Route> = Array_New<Route>(8);
// =============================================================================
extern func bux_alloc(size: uint) -> *void;
extern func bux_strlen(s: String) -> uint;
extern func bux_strcmp(a: String, b: String) -> int;
extern func bux_strstr(haystack: String, needle: String) -> String;
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
extern func bux_str_split_count(s: String, delim: String) -> uint;
extern func bux_str_split_part(s: String, delim: String, index: uint) -> String;
extern func bux_sb_new(initial_cap: uint) -> *void;
extern func bux_sb_append(sb: *void, s: String);
extern func bux_sb_append_int(sb: *void, n: int64);
extern func bux_sb_append_char(sb: *void, c: char8);
extern func bux_sb_build(sb: *void) -> String;
extern func bux_sb_free(sb: *void);
extern func bux_path_join(a: String, b: String) -> String;
extern func bux_read_file(path: String) -> String;
extern func bux_file_exists(path: String) -> int;
// ============================================================================= Array_Push<Route>(&routes, Route {
// HTTP Method Enum (simple enum — no data payload) method: HttpMethod { tag: HttpMethod_GET },
// ============================================================================= path: "/api/health",
enum HttpMethod { handler: Handler { tag: Handler_ApiHealth },
GET, });
POST,
PUT, Array_Push<Route>(&routes, Route {
DELETE, method: HttpMethod { tag: HttpMethod_GET },
PATCH, path: "/api/info",
HEAD, handler: Handler { tag: Handler_ApiInfo },
OPTIONS, });
UNKNOWN,
Array_Push<Route>(&routes, Route {
method: HttpMethod { tag: HttpMethod_GET },
path: "/ws",
handler: Handler { tag: Handler_WsUpgrade },
});
Array_Push<Route>(&routes, Route {
method: HttpMethod { tag: HttpMethod_GET },
path: "/",
handler: Handler { tag: Handler_StaticFile },
});
return Router {
routes: routes,
notFound: Handler { tag: Handler_NotFound },
};
} }
// =============================================================================
// Struct: HTTP Request
// =============================================================================
struct HttpRequest {
method: HttpMethod,
path: String,
body: String,
headerKeys: *String,
headerValues: *String,
headerCount: int,
headerCap: int,
}
// =============================================================================
// Struct: HTTP Response
// =============================================================================
struct HttpResponse {
statusCode: int,
statusText: String,
contentType: String,
body: String,
extraHeaders: String, // pre-formatted extra headers (for WS upgrade, etc.)
connectionClose: bool,
}
// =============================================================================
// Utility: Status text lookup
// =============================================================================
func Http_StatusText(code: int) -> String {
if code == 200 { return "OK"; }
if code == 201 { return "Created"; }
if code == 204 { return "No Content"; }
if code == 301 { return "Moved Permanently"; }
if code == 302 { return "Found"; }
if code == 304 { return "Not Modified"; }
if code == 400 { return "Bad Request"; }
if code == 401 { return "Unauthorized"; }
if code == 403 { return "Forbidden"; }
if code == 404 { return "Not Found"; }
if code == 405 { return "Method Not Allowed"; }
if code == 413 { return "Payload Too Large"; }
if code == 414 { return "URI Too Long"; }
if code == 500 { return "Internal Server Error"; }
if code == 501 { return "Not Implemented"; }
if code == 503 { return "Service Unavailable"; }
return "Unknown";
}
// =============================================================================
// Utility: Parse HTTP method string → HttpMethod enum
// =============================================================================
func Http_ParseMethod(s: String) -> HttpMethod {
if String_Eq(s, "GET") { return HttpMethod { tag: HttpMethod_GET }; }
if String_Eq(s, "POST") { return HttpMethod { tag: HttpMethod_POST }; }
if String_Eq(s, "PUT") { return HttpMethod { tag: HttpMethod_PUT }; }
if String_Eq(s, "DELETE") { return HttpMethod { tag: HttpMethod_DELETE }; }
if String_Eq(s, "PATCH") { return HttpMethod { tag: HttpMethod_PATCH }; }
if String_Eq(s, "HEAD") { return HttpMethod { tag: HttpMethod_HEAD }; }
if String_Eq(s, "OPTIONS") { return HttpMethod { tag: HttpMethod_OPTIONS }; }
return HttpMethod { tag: HttpMethod_UNKNOWN };
}
// =============================================================================
// Utility: MIME type from file extension
// =============================================================================
func Http_MimeType(path: String) -> String {
if String_EndsWith(path, ".html") || String_EndsWith(path, ".htm") { return "text/html; charset=utf-8"; }
if String_EndsWith(path, ".css") { return "text/css; charset=utf-8"; }
if String_EndsWith(path, ".js") { return "application/javascript; charset=utf-8"; }
if String_EndsWith(path, ".json") { return "application/json; charset=utf-8"; }
if String_EndsWith(path, ".xml") { return "application/xml; charset=utf-8"; }
if String_EndsWith(path, ".txt") { return "text/plain; charset=utf-8"; }
if String_EndsWith(path, ".png") { return "image/png"; }
if String_EndsWith(path, ".jpg") || String_EndsWith(path, ".jpeg") { return "image/jpeg"; }
if String_EndsWith(path, ".gif") { return "image/gif"; }
if String_EndsWith(path, ".svg") { return "image/svg+xml"; }
if String_EndsWith(path, ".ico") { return "image/x-icon"; }
if String_EndsWith(path, ".webp") { return "image/webp"; }
if String_EndsWith(path, ".woff2") { return "font/woff2"; }
if String_EndsWith(path, ".woff") { return "font/woff"; }
if String_EndsWith(path, ".wasm") { return "application/wasm"; }
return "application/octet-stream";
}
// =============================================================================
// Header Storage: dynamic key-value array (faster than hash map for 5-15 headers)
// =============================================================================
func RequestHeader_Init(req: *HttpRequest) {
let cap: int = 16;
req.headerCap = cap;
req.headerKeys = bux_alloc(cap as uint * 8) as *String;
req.headerValues = bux_alloc(cap as uint * 8) as *String;
req.headerCount = 0;
}
func RequestHeader_Add(req: *HttpRequest, key: String, value: String) {
if req.headerCount >= req.headerCap { return; }
req.headerKeys[req.headerCount] = key;
req.headerValues[req.headerCount] = value;
req.headerCount = req.headerCount + 1;
}
func RequestHeader_Get(req: *HttpRequest, key: String) -> String {
var i: int = 0;
while i < req.headerCount {
if bux_strcmp(req.headerKeys[i], key) == 0 {
return req.headerValues[i];
}
i = i + 1;
}
return "";
}
// =============================================================================
// HTTP Request Parser
// =============================================================================
func Http_ParseRequest(raw: String) -> HttpRequest {
var req: HttpRequest;
req.method = HttpMethod { tag: HttpMethod_UNKNOWN };
req.path = "/";
req.body = "";
RequestHeader_Init(&req);
// Guard: null/empty input
if String_Len(raw) == 0 { return req; }
let rawLen: uint = bux_strlen(raw);
if rawLen == 0 { return req; }
// Find \r\n\r\n separating headers from body
let boundary: String = bux_strstr(raw, "\r\n\r\n");
var headerLen: uint = rawLen;
if String_Len(boundary) > 0 {
headerLen = String_Offset(boundary, raw);
let bodyStart: uint = headerLen + 4;
if bodyStart < rawLen {
req.body = bux_str_slice(raw, bodyStart, rawLen - bodyStart);
}
}
// Slice out header block
if headerLen == 0 { return req; }
let headerBlock: String = bux_str_slice(raw, 0, headerLen);
// Count header lines
let lineCount: uint = bux_str_split_count(headerBlock, "\r\n");
if lineCount == 0 { return req; }
// --- Parse request line: "METHOD /path HTTP/1.1" ---
let requestLine: String = bux_str_split_part(headerBlock, "\r\n", 0);
if bux_strlen(requestLine) > 0 {
req.method = Http_ParseMethod(bux_str_split_part(requestLine, " ", 0));
req.path = bux_str_split_part(requestLine, " ", 1);
if String_Eq(req.path, "") || String_Len(req.path) == 0 {
req.path = "/";
}
}
// --- Parse headers (lines 1..N-1) ---
var i: uint = 1;
while i < lineCount {
let line: String = bux_str_split_part(headerBlock, "\r\n", i);
let lineLen: uint = bux_strlen(line);
if lineLen == 0 { i = i + 1; continue; }
// Find ": " separator
let colonPos: String = bux_strstr(line, ": ");
var key: String = "";
var value: String = "";
if String_Len(colonPos) > 0 {
let keyLen: uint = String_Offset(colonPos, line);
key = bux_str_slice(line, 0, keyLen);
let valStart: uint = keyLen + 2;
if valStart < lineLen {
value = bux_str_slice(line, valStart, lineLen - valStart);
}
} else {
// Try ":" without space
let colonPos2: String = bux_strstr(line, ":");
if String_Len(colonPos2) > 0 {
let keyLen: uint = String_Offset(colonPos2, line);
key = bux_str_slice(line, 0, keyLen);
let valStart: uint = keyLen + 1;
if valStart < lineLen {
value = bux_str_slice(line, valStart, lineLen - valStart);
}
value = String_Trim(value);
}
}
// Store if valid key found
if bux_strlen(key) > 0 {
key = String_Trim(key);
RequestHeader_Add(&req, key, value);
}
i = i + 1;
}
return req;
}
// =============================================================================
// HTTP Response Builder
// =============================================================================
func Http_BuildResponse(resp: HttpResponse) -> String {
let sb: *void = bux_sb_new(4096);
// Status line
bux_sb_append(sb, "HTTP/1.1 ");
bux_sb_append_int(sb, resp.statusCode as int64);
bux_sb_append(sb, " ");
bux_sb_append(sb, resp.statusText);
bux_sb_append(sb, "\r\n");
// Server
bux_sb_append(sb, "Server: Nexus/0.1.0 (Bux)\r\n");
// Extra headers (for WS upgrade etc.)
if bux_strlen(resp.extraHeaders) > 0 {
bux_sb_append(sb, resp.extraHeaders);
}
// Content-Type
if bux_strlen(resp.contentType) > 0 {
bux_sb_append(sb, "Content-Type: ");
bux_sb_append(sb, resp.contentType);
bux_sb_append(sb, "\r\n");
}
// Content-Length
let bodyLen: uint = bux_strlen(resp.body);
bux_sb_append(sb, "Content-Length: ");
bux_sb_append_int(sb, bodyLen as int64);
bux_sb_append(sb, "\r\n");
// Connection
if resp.connectionClose {
bux_sb_append(sb, "Connection: close\r\n");
} else {
bux_sb_append(sb, "Connection: keep-alive\r\n");
}
// End headers
bux_sb_append(sb, "\r\n");
// Body
if bodyLen > 0 {
bux_sb_append(sb, resp.body);
}
let result: String = bux_sb_build(sb);
bux_sb_free(sb);
return result;
}
// =============================================================================
// Helper: Create a simple response
// =============================================================================
func Http_NewResponse(code: int, contentType: String, body: String) -> HttpResponse {
var resp: HttpResponse;
resp.statusCode = code;
resp.statusText = Http_StatusText(code);
resp.contentType = contentType;
resp.body = body;
resp.extraHeaders = "";
resp.connectionClose = true;
return resp;
}
// =============================================================================
// Static File Server
// =============================================================================
func ServeStaticFile(requestPath: String) -> HttpResponse {
// Block directory traversal
if String_Contains(requestPath, "..") {
return Http_NewResponse(403, "text/plain; charset=utf-8", "Forbidden");
}
// Default to index.html
var filePath: String = requestPath;
if String_Eq(filePath, "/") {
filePath = "/index.html";
}
// Resolve full path
let fullPath: String = bux_path_join("public", filePath);
// Check existence
if bux_file_exists(fullPath) == 0 {
return Http_NewResponse(404, "text/plain; charset=utf-8", "Not Found");
}
// Read and serve
let content: String = bux_read_file(fullPath);
if String_Len(content) == 0 {
return Http_NewResponse(500, "text/plain; charset=utf-8", "Internal Server Error");
}
let mime: String = Http_MimeType(filePath);
return Http_NewResponse(200, mime, content);
}
// =============================================================================
// API Handlers
// =============================================================================
func HandleApiHealth() -> HttpResponse {
return Http_NewResponse(200, "application/json; charset=utf-8",
"{\"status\":\"ok\",\"server\":\"Nexus\",\"version\":\"0.1.0\"}");
}
func HandleApiInfo() -> HttpResponse {
return Http_NewResponse(200, "application/json; charset=utf-8",
"{\"name\":\"Nexus\",\"language\":\"Bux\",\"threads\":4,\"features\":[\"HTTP/1.1\",\"HTTP/2-detect\",\"WebSocket-upgrade\"]}");
}
// =============================================================================
// WebSocket Upgrade Handshake
// =============================================================================
func HandleWebSocketUpgrade(req: HttpRequest) -> HttpResponse {
let wsKey: String = RequestHeader_Get(&req, "Sec-WebSocket-Key");
var resp: HttpResponse;
resp.statusCode = 101;
resp.statusText = "Switching Protocols";
resp.contentType = "";
resp.body = "";
resp.connectionClose = false;
// Build upgrade headers as extraHeaders
// TODO: SHA-1 + Base64 for proper accept key
let sb: *void = bux_sb_new(256);
bux_sb_append(sb, "Upgrade: websocket\r\n");
bux_sb_append(sb, "Connection: Upgrade\r\n");
bux_sb_append(sb, "Sec-WebSocket-Accept: ");
bux_sb_append(sb, wsKey); // placeholder — real impl needs crypto
bux_sb_append(sb, "\r\n");
resp.extraHeaders = bux_sb_build(sb);
bux_sb_free(sb);
return resp;
}
// =============================================================================
// Router / Dispatcher
// =============================================================================
func Router_Dispatch(req: HttpRequest) -> HttpResponse {
// Check for upgrade headers
let upgrade: String = RequestHeader_Get(&req, "Upgrade");
if bux_strlen(upgrade) > 0 {
if String_Contains(upgrade, "websocket") {
return HandleWebSocketUpgrade(req);
}
}
// API routes
if String_StartsWith(req.path, "/api/") {
if String_Eq(req.path, "/api/health") { return HandleApiHealth(); }
if String_Eq(req.path, "/api/info") { return HandleApiInfo(); }
return Http_NewResponse(404, "application/json; charset=utf-8",
"{\"error\":\"not_found\"}");
}
// WebSocket endpoint
if String_Eq(req.path, "/ws") {
return HandleWebSocketUpgrade(req);
}
// Static files — GET/HEAD only
if req.method.tag == HttpMethod_GET || req.method.tag == HttpMethod_HEAD {
return ServeStaticFile(req.path);
}
// Method not allowed
return Http_NewResponse(405, "text/plain; charset=utf-8", "Method Not Allowed");
}
// =============================================================================
// Method name for logging (avoids 8 if-checks in hot path)
// =============================================================================
func Http_MethodName(m: HttpMethod) -> String {
if m.tag == HttpMethod_GET { return "GET"; }
if m.tag == HttpMethod_POST { return "POST"; }
if m.tag == HttpMethod_PUT { return "PUT"; }
if m.tag == HttpMethod_DELETE { return "DELETE"; }
if m.tag == HttpMethod_PATCH { return "PATCH"; }
if m.tag == HttpMethod_HEAD { return "HEAD"; }
if m.tag == HttpMethod_OPTIONS { return "OPTIONS"; }
return "UNKNOWN";
}
// =============================================================================
// Connection Handler — read, parse, route, respond
// =============================================================================
func HandleConnection(clientFd: int) {
let raw: String = Net_Recv(clientFd, 8192);
// Empty read → client disconnected
if String_Len(raw) == 0 { return; }
if bux_strlen(raw) == 0 { return; }
// HTTP/2 connection preface detection
if String_StartsWith(raw, "PRI * HTTP/2.0") {
let resp: HttpResponse = Http_NewResponse(200, "text/plain; charset=utf-8",
"HTTP/2 detected — full support planned for future release.\r\n");
let respStr: String = Http_BuildResponse(resp);
Net_Send(clientFd, respStr);
return;
}
// Parse HTTP/1.1
let req: HttpRequest = Http_ParseRequest(raw);
// Log
Print(Http_MethodName(req.method));
Print(" ");
Print(req.path);
Print(" -> ");
// Dispatch
let resp: HttpResponse = Router_Dispatch(req);
// Log result
PrintInt(resp.statusCode);
Print(" ");
PrintLine(resp.statusText);
// Send
let respStr: String = Http_BuildResponse(resp);
Net_Send(clientFd, respStr);
}
// =============================================================================
// Worker Thread — accept loop
// =============================================================================
func Worker(serverFd: int) {
while true {
let clientFd: int = Net_Accept(serverFd);
if clientFd < 0 {
continue;
}
HandleConnection(clientFd);
Net_Close(clientFd);
}
}
// =============================================================================
// Main Entry Point
// =============================================================================
func Main() -> int { func Main() -> int {
PrintLine("================================================"); let config: ServerConfig = DefaultConfig();
PrintLine(" Nexus HTTP Server v0.1.0"); let router: Router = BuildRouter();
PrintLine(" Multi-threaded HTTP/1.1 + HTTP/2 & WS detect");
PrintLine(" Built with Bux");
PrintLine("================================================");
PrintLine("");
// Create socket return RunServer(config, router);
let serverFd: int = Net_Create();
if serverFd < 0 {
PrintLine("FATAL: socket() failed");
return 1;
} }
// SO_REUSEADDR
if !Net_SetReuse(serverFd) {
PrintLine("WARN: SO_REUSEADDR failed");
} }
// Bind
if !Net_Bind(serverFd, "0.0.0.0", 8080) {
Print("FATAL: bind(8080) failed: ");
PrintLine(Net_LastError());
Net_Close(serverFd);
return 1;
}
// Listen
if !Net_Listen(serverFd, 128) {
PrintLine("FATAL: listen() failed");
Net_Close(serverFd);
return 1;
}
PrintLine("Listening on http://0.0.0.0:8080");
PrintLine("4 worker threads | static: ./public/");
PrintLine("Endpoints: / /api/health /api/info /ws");
PrintLine("Press Ctrl+C to stop.");
PrintLine("");
// Spawn 3 workers (main thread is the 4th)
spawn Worker(serverFd);
spawn Worker(serverFd);
spawn Worker(serverFd);
// Main thread becomes worker #4 (blocking call)
Worker(serverFd);
return 0;
}
} // module Main
+130
View File
@@ -0,0 +1,130 @@
module Parser {
import Std::String::{String_Len, String_Eq, String_Trim};
import Std::Array::{Array, Array_New, Array_Push};
import Http::{HttpMethod, HttpRequest, HeaderEntry};
import Errors::{HttpError, ParseResult, ParseResult_NewOk, ParseResult_NewErr};
extern func bux_strlen(s: String) -> uint;
extern func bux_strstr(haystack: String, needle: String) -> String;
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
extern func bux_str_offset(pos: String, base: String) -> uint;
pub func ParseMethod(s: String) -> HttpMethod {
if String_Eq(s, "GET") { return HttpMethod { tag: HttpMethod_GET }; }
if String_Eq(s, "POST") { return HttpMethod { tag: HttpMethod_POST }; }
if String_Eq(s, "PUT") { return HttpMethod { tag: HttpMethod_PUT }; }
if String_Eq(s, "DELETE") { return HttpMethod { tag: HttpMethod_DELETE }; }
if String_Eq(s, "PATCH") { return HttpMethod { tag: HttpMethod_PATCH }; }
if String_Eq(s, "HEAD") { return HttpMethod { tag: HttpMethod_HEAD }; }
if String_Eq(s, "OPTIONS") { return HttpMethod { tag: HttpMethod_OPTIONS }; }
return HttpMethod { tag: HttpMethod_UNKNOWN };
}
func Slice(raw: String, start: int, len: int) -> String {
if start < 0 || len <= 0 { return ""; }
return bux_str_slice(raw, start as uint, len as uint);
}
func FindCrlf(raw: String, start: int) -> int {
let rawLen: uint = bux_strlen(raw);
if start as uint >= rawLen { return -1; }
let tail: String = bux_str_slice(raw, start as uint, rawLen - start as uint);
let hit: String = bux_strstr(tail, "\r\n");
if String_Len(hit) == 0 { return -1; }
let offset: uint = bux_str_offset(hit, tail);
return start + offset as int;
}
func ParseHeaders(raw: String, start: int, end: int) -> Array<HeaderEntry> {
var headers: Array<HeaderEntry> = Array_New<HeaderEntry>(16);
var pos: int = start;
while pos < end {
let lineEnd: int = FindCrlf(raw, pos);
if lineEnd < 0 || lineEnd >= end { break; }
let lineLen: int = lineEnd - pos;
if lineLen > 0 {
let line: String = Slice(raw, pos, lineLen);
let colon: String = bux_strstr(line, ":");
if String_Len(colon) > 0 {
let keyLen: uint = bux_str_offset(colon, line);
let key: String = String_Trim(Slice(line, 0, keyLen as int));
let valStart: int = keyLen as int + 1;
let val: String = String_Trim(Slice(line, valStart, lineLen - valStart));
let entry: HeaderEntry = HeaderEntry { key: key, value: val };
Array_Push<HeaderEntry>(&headers, entry);
}
}
pos = lineEnd + 2;
}
return headers;
}
pub func ParseRequest(raw: String) -> ParseResult {
let rawLen: uint = bux_strlen(raw);
if rawLen == 0 {
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
}
// Find end of request line
let lineEnd: int = FindCrlf(raw, 0);
if lineEnd < 0 {
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
}
// Split request line: METHOD PATH VERSION
var methodEnd: int = -1;
var pathStart: int = -1;
var pathEnd: int = -1;
var i: int = 0;
while i < lineEnd {
if raw[i] as int == 32 { // space
if methodEnd < 0 {
methodEnd = i;
pathStart = i + 1;
} else if pathEnd < 0 {
pathEnd = i;
break;
}
}
i = i + 1;
}
if methodEnd < 0 || pathStart < 0 || pathEnd < 0 {
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
}
let methodStr: String = Slice(raw, 0, methodEnd);
let path: String = Slice(raw, pathStart, pathEnd - pathStart);
let version: String = Slice(raw, pathEnd + 1, lineEnd - pathEnd - 1);
// Find header/body boundary
let boundary: String = bux_strstr(raw, "\r\n\r\n");
var headers: Array<HeaderEntry> = Array_New<HeaderEntry>(16);
var body: String = "";
if String_Len(boundary) > 0 {
let headerEnd: uint = bux_str_offset(boundary, raw);
headers = ParseHeaders(raw, lineEnd + 2, headerEnd as int);
let bodyStart: uint = headerEnd + 4;
if bodyStart < rawLen {
body = bux_str_slice(raw, bodyStart, rawLen - bodyStart);
}
} else {
headers = ParseHeaders(raw, lineEnd + 2, rawLen as int);
}
if String_Eq(path, "") {
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
}
let req: HttpRequest = HttpRequest {
method: ParseMethod(methodStr),
path: path,
version: version,
body: body,
headers: headers,
};
return ParseResult_NewOk(req);
}
}
+46
View File
@@ -0,0 +1,46 @@
module Router {
import Std::Array::{Array};
import Std::String::{String_Eq};
import Http::{HttpMethod, HttpRequest, HttpResponse, Http_NewResponse};
import Handlers::{ServeStaticFile, HandleApiHealth, HandleApiInfo, HandleWebSocketUpgrade, NotFoundResponse};
pub enum Handler {
StaticFile,
ApiHealth,
ApiInfo,
WsUpgrade,
NotFound,
}
pub struct Route {
method: HttpMethod;
path: String;
handler: Handler;
}
pub struct Router {
routes: Array<Route>;
notFound: Handler;
}
pub func Handler_Handle(h: Handler, req: HttpRequest) -> HttpResponse {
match h {
Handler::StaticFile => ServeStaticFile(req),
Handler::ApiHealth => HandleApiHealth(),
Handler::ApiInfo => HandleApiInfo(),
Handler::WsUpgrade => HandleWebSocketUpgrade(req),
Handler::NotFound => NotFoundResponse(),
}
}
pub func Router_Dispatch(r: Router, req: HttpRequest) -> HttpResponse {
for route in r.routes {
if route.method == req.method && String_Eq(route.path, req.path) {
return Handler_Handle(route.handler, req);
}
}
return Handler_Handle(r.notFound, req);
}
}
+176
View File
@@ -0,0 +1,176 @@
module Server {
import Std::Io::{Print, PrintLine, PrintInt};
import Std::Net::{Net_Create, Net_SetReuse, Net_Bind, Net_Listen, Net_Accept, Net_Send, Net_Recv, Net_Close, Net_LastError};
import Std::String::{String_Len, String_StartsWith};
import Std::Channel::{Channel, Channel_New, Channel_Send, Channel_Recv};
import Config::{ServerConfig};
import Http::{HttpRequest, HttpResponse, Http_StatusText, Http_NewResponse};
import Errors::{ParseResult};
import Parser::{ParseRequest};
import Router::{Router, Router_Dispatch};
extern func bux_strlen(s: String) -> uint;
extern func bux_sb_new(initial_cap: uint) -> *void;
extern func bux_sb_append(sb: *void, s: String);
extern func bux_sb_append_int(sb: *void, n: int64);
extern func bux_sb_build(sb: *void) -> String;
extern func bux_sb_free(sb: *void);
pub struct ConnectionTask {
fd: int;
}
pub func BuildResponse(resp: HttpResponse) -> String {
let sb: *void = bux_sb_new(4096);
bux_sb_append(sb, "HTTP/1.1 ");
bux_sb_append_int(sb, resp.statusCode as int64);
bux_sb_append(sb, " ");
bux_sb_append(sb, Http_StatusText(resp.statusCode));
bux_sb_append(sb, "\r\n");
bux_sb_append(sb, "Server: Nexus/0.2.0 (Bux)\r\n");
if bux_strlen(resp.extraHeaders) > 0 {
bux_sb_append(sb, resp.extraHeaders);
}
if bux_strlen(resp.contentType) > 0 {
bux_sb_append(sb, "Content-Type: ");
bux_sb_append(sb, resp.contentType);
bux_sb_append(sb, "\r\n");
}
let bodyLen: uint = bux_strlen(resp.body);
bux_sb_append(sb, "Content-Length: ");
bux_sb_append_int(sb, bodyLen as int64);
bux_sb_append(sb, "\r\n");
bux_sb_append(sb, "Connection: close\r\n");
bux_sb_append(sb, "\r\n");
if bodyLen > 0 {
bux_sb_append(sb, resp.body);
}
let result: String = bux_sb_build(sb);
bux_sb_free(sb);
return result;
}
pub func HandleConnection(fd: int, router: Router) {
let raw: String = Net_Recv(fd, 8192);
if String_Len(raw) == 0 {
return;
}
// HTTP/2 preface detection
if String_StartsWith(raw, "PRI * HTTP/2.0") {
let resp: HttpResponse = Http_NewResponse(200, "text/plain; charset=utf-8",
"HTTP/2 detected — full support planned for future release.\r\n");
Net_Send(fd, BuildResponse(resp));
return;
}
let parsed: ParseResult = ParseRequest(raw);
if parsed.tag == ParseResult_Ok {
let req: HttpRequest = parsed.data.Ok_0;
let resp: HttpResponse = Router_Dispatch(router, req);
Net_Send(fd, BuildResponse(resp));
} else {
let resp: HttpResponse = Http_NewResponse(400, "text/plain; charset=utf-8", "Bad Request");
Net_Send(fd, BuildResponse(resp));
}
}
pub struct WorkerCtx {
taskQueue: *Channel<ConnectionTask>;
router: Router;
}
pub func Worker(ctx: *WorkerCtx) {
while true {
let task: ConnectionTask = Channel_Recv<ConnectionTask>(ctx.taskQueue);
HandleConnection(task.fd, ctx.router);
Net_Close(task.fd);
}
}
pub struct AcceptorCtx {
serverFd: int;
taskQueue: *Channel<ConnectionTask>;
}
pub func Acceptor(ctx: *AcceptorCtx) {
while true {
let fd: int = Net_Accept(ctx.serverFd);
if fd >= 0 {
let task: ConnectionTask = ConnectionTask { fd: fd };
Channel_Send<ConnectionTask>(ctx.taskQueue, task);
}
}
}
pub func RunServer(config: ServerConfig, router: Router) -> int {
PrintLine("================================================");
PrintLine(" Nexus HTTP Server v0.2.0");
PrintLine(" Production-ready HTTP/1.1 with thread-pool");
PrintLine(" Built with Bux");
PrintLine("================================================");
PrintLine("");
let serverFd: int = Net_Create();
if serverFd < 0 {
PrintLine("FATAL: socket() failed");
return 1;
}
if !Net_SetReuse(serverFd) {
PrintLine("WARN: SO_REUSEADDR failed");
}
if !Net_Bind(serverFd, config.bindAddr, config.port) {
Print("FATAL: bind failed: ");
PrintLine(Net_LastError());
Net_Close(serverFd);
return 1;
}
if !Net_Listen(serverFd, config.backlog) {
PrintLine("FATAL: listen() failed");
Net_Close(serverFd);
return 1;
}
Print("Listening on http://");
Print(config.bindAddr);
Print(":");
PrintInt(config.port);
PrintLine("");
PrintInt(config.workerCount);
PrintLine(" worker threads | static: ./public/");
PrintLine("Endpoints: / /api/health /api/info /ws");
PrintLine("Press Ctrl+C to stop.");
PrintLine("");
let taskQueue: Channel<ConnectionTask> = Channel_New<ConnectionTask>(config.backlog as int64);
let workerCtx: WorkerCtx = WorkerCtx { taskQueue: &taskQueue, router: router };
let acceptorCtx: AcceptorCtx = AcceptorCtx { serverFd: serverFd, taskQueue: &taskQueue };
// Spawn workers (main thread will also become one)
var i: int = 0;
while i < config.workerCount - 1 {
spawn Worker(&workerCtx);
i = i + 1;
}
// Spawn acceptor
spawn Acceptor(&acceptorCtx);
// Main thread works too
Worker(&workerCtx);
return 0;
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
import std/[os, strutils, terminal, strformat, osproc, sets] import std/[os, strutils, terminal, strformat, osproc, sets]
import lexer, parser, ast, sema, manifest, hir_lower, lir, lir_lower, lir_c_backend import lexer, parser, ast, sema, manifest, hir_lower, lir_lower, lir_c_backend
type type
ColorMode* = enum ColorMode* = enum
+53 -3
View File
@@ -42,6 +42,14 @@ proc flushPending(ctx: var LowerCtx, node: HirNode): HirNode =
return hirBlock(stmts, nil, makeVoid(), node.loc) return hirBlock(stmts, nil, makeVoid(), node.loc)
return node return node
proc enumHasDataVariants(ctx: var LowerCtx, enumName: string): bool =
let sym = ctx.globalScope.lookup(enumName)
if sym != nil and sym.decl != nil and sym.decl.kind == dkEnum:
for v in sym.decl.declEnumVariants:
if v.fields.len > 0 or v.namedFields.len > 0:
return true
return false
proc lowerMatch(ctx: var LowerCtx, subject: HirNode, arms: seq[HirMatchArm], typ: Type, loc: SourceLocation): HirNode = proc lowerMatch(ctx: var LowerCtx, subject: HirNode, arms: seq[HirMatchArm], typ: Type, loc: SourceLocation): HirNode =
# Lower match expression to a block with if-else chain. # Lower match expression to a block with if-else chain.
# For now, supports enum tag matching and wildcard/ident fallbacks. # For now, supports enum tag matching and wildcard/ident fallbacks.
@@ -51,6 +59,13 @@ proc lowerMatch(ctx: var LowerCtx, subject: HirNode, arms: seq[HirMatchArm], typ
# Allocate result variable # Allocate result variable
stmts.add(hirAlloca(resultName, typ, loc)) stmts.add(hirAlloca(resultName, typ, loc))
# Determine whether the matched enum has data variants (needs .tag access).
var subjectEnumName = ""
var subjectHasData = false
if subject.typ != nil and subject.typ.kind == tkNamed:
subjectEnumName = subject.typ.name
subjectHasData = ctx.enumHasDataVariants(subjectEnumName)
# Build if-else chain from arms (last arm is the outermost else) # Build if-else chain from arms (last arm is the outermost else)
var ifChain: HirNode = nil var ifChain: HirNode = nil
@@ -66,12 +81,18 @@ proc lowerMatch(ctx: var LowerCtx, subject: HirNode, arms: seq[HirMatchArm], typ
let variantName = path[^1] let variantName = path[^1]
let tagName = enumName & "_" & variantName let tagName = enumName & "_" & variantName
# condition: subject.tag == EnumName_VariantName var cond: HirNode
if subjectHasData and enumName == subjectEnumName:
# Algebraic enum: compare subject.tag
let tagField = HirNode(kind: hFieldPtr, fieldPtrBase: subject, fieldName: "tag", let tagField = HirNode(kind: hFieldPtr, fieldPtrBase: subject, fieldName: "tag",
typ: makePointer(makeNamed(enumName & "_Tag")), loc: loc) typ: makePointer(makeNamed(enumName & "_Tag")), loc: loc)
let tagLoad = HirNode(kind: hLoad, loadPtr: tagField, typ: makeNamed(enumName & "_Tag"), loc: loc) let tagLoad = HirNode(kind: hLoad, loadPtr: tagField, typ: makeNamed(enumName & "_Tag"), loc: loc)
let tagConst = hirLit(Token(kind: tkIdent, text: tagName, loc: loc), makeNamed(enumName & "_Tag"), loc) let tagConst = hirLit(Token(kind: tkIdent, text: tagName, loc: loc), makeNamed(enumName & "_Tag"), loc)
let cond = hirBinary(tkEq, tagLoad, tagConst, makeBool(), loc) cond = hirBinary(tkEq, tagLoad, tagConst, makeBool(), loc)
else:
# Simple enum or cross-enum match: compare subject directly
let tagConst = hirLit(Token(kind: tkIdent, text: tagName, loc: loc), makeNamed(enumName), loc)
cond = hirBinary(tkEq, subject, tagConst, makeBool(), loc)
# body: result = arm_body # body: result = arm_body
var armStmts: seq[HirNode] = @[] var armStmts: seq[HirNode] = @[]
@@ -397,6 +418,8 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
if f.ftype != nil: if f.ftype != nil:
case f.ftype.kind case f.ftype.kind
of tekNamed: of tekNamed:
if f.ftype.typeArgs.len > 0:
return substituteType(ctx, f.ftype, subst)
case f.ftype.typeName case f.ftype.typeName
of "int", "int32", "int64": return makeInt() of "int", "int32", "int64": return makeInt()
of "float64": return makeFloat64() of "float64": return makeFloat64()
@@ -417,6 +440,8 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
if f.ftype != nil: if f.ftype != nil:
case f.ftype.kind case f.ftype.kind
of tekNamed: of tekNamed:
if f.ftype.typeArgs.len > 0:
return ctx.resolveTypeExpr(f.ftype)
case f.ftype.typeName case f.ftype.typeName
of "int", "int32", "int64": return makeInt() of "int", "int32", "int64": return makeInt()
of "float64": return makeFloat64() of "float64": return makeFloat64()
@@ -478,6 +503,10 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
if baseType.isPointer and baseType.inner.len > 0: if baseType.isPointer and baseType.inner.len > 0:
return baseType.inner[0] return baseType.inner[0]
return makeUnknown() return makeUnknown()
of ekMatch:
if expr.exprMatchArms.len > 0:
return ctx.resolveExprType(expr.exprMatchArms[0].body)
return makeUnknown()
of ekBlock: of ekBlock:
if expr.exprBlock.stmts.len > 0: if expr.exprBlock.stmts.len > 0:
let last = expr.exprBlock.stmts[^1] let last = expr.exprBlock.stmts[^1]
@@ -524,6 +553,23 @@ proc getCollectionElementTypeExpr(ctx: var LowerCtx, expr: Expr): TypeExpr =
return te.typeArgs[0] return te.typeArgs[0]
if te.kind in {tekPointer, tekRef, tekMutRef} and te.pointerPointee.kind == tekNamed and te.pointerPointee.typeArgs.len > 0: if te.kind in {tekPointer, tekRef, tekMutRef} and te.pointerPointee.kind == tekNamed and te.pointerPointee.typeArgs.len > 0:
return te.pointerPointee.typeArgs[0] return te.pointerPointee.typeArgs[0]
of ekField:
# Try to resolve the field's declared TypeExpr directly.
let objType = ctx.resolveExprType(expr.exprFieldObj)
if objType.kind == tkNamed:
var decl = ctx.globalScope.lookup(objType.name).decl
if decl == nil and ctx.structInstMap.hasKey(objType.name):
let (baseName, _) = ctx.structInstMap[objType.name]
let baseSym = ctx.globalScope.lookup(baseName)
if baseSym != nil and baseSym.decl != nil and baseSym.decl.kind == dkStruct:
decl = baseSym.decl
if decl != nil and decl.kind == dkStruct:
for f in decl.declStructFields:
if f.name == expr.exprFieldName and f.ftype != nil:
let fte = f.ftype
if fte.kind == tekNamed and fte.typeArgs.len > 0 and (fte.typeName == "Array" or fte.typeName == "Iter" or fte.typeName == "Channel"):
return fte.typeArgs[0]
return fte
else: else:
discard discard
let t = ctx.resolveExprType(expr) let t = ctx.resolveExprType(expr)
@@ -531,6 +577,11 @@ proc getCollectionElementTypeExpr(ctx: var LowerCtx, expr: Expr): TypeExpr =
return typeToTypeExpr(t.inner[0]) return typeToTypeExpr(t.inner[0])
if t.isPointer and t.inner.len > 0 and t.inner[0].kind == tkNamed and t.inner[0].inner.len > 0: if t.isPointer and t.inner.len > 0 and t.inner[0].kind == tkNamed and t.inner[0].inner.len > 0:
return typeToTypeExpr(t.inner[0].inner[0]) return typeToTypeExpr(t.inner[0].inner[0])
# Generic struct instances (e.g. Array_HeaderEntry) store their type args in structInstMap.
if t.kind == tkNamed and ctx.structInstMap.hasKey(t.name):
let (baseName, concreteArgs) = ctx.structInstMap[t.name]
if concreteArgs.len > 0 and (baseName == "Array" or baseName == "Iter" or baseName == "Channel"):
return typeToTypeExpr(concreteArgs[0])
return TypeExpr(kind: tekNamed, typeName: "unknown") return TypeExpr(kind: tekNamed, typeName: "unknown")
proc generateMethodInstance(ctx: var LowerCtx, baseMethodName: string, typeArgs: seq[TypeExpr]): string proc generateMethodInstance(ctx: var LowerCtx, baseMethodName: string, typeArgs: seq[TypeExpr]): string
@@ -1660,7 +1711,6 @@ proc generateMethodInstance(ctx: var LowerCtx, baseMethodName: string, typeArgs:
return mangledName return mangledName
proc lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc = proc lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc =
let loc = expr.loc
let name = "__closure_" & $ctx.varCounter let name = "__closure_" & $ctx.varCounter
inc ctx.varCounter inc ctx.varCounter
var f = HirFunc(name: name, isPublic: false) var f = HirFunc(name: name, isPublic: false)
-22
View File
@@ -61,12 +61,6 @@ proc advance(lex: var Lexer): char =
else: else:
inc lex.col inc lex.col
proc match(lex: var Lexer, expected: char): bool =
if lex.isAtEnd(): return false
if lex.peek() != expected: return false
discard lex.advance()
return true
proc matchStr(lex: var Lexer, s: string): bool = proc matchStr(lex: var Lexer, s: string): bool =
for i, c in s: for i, c in s:
if lex.peek(i) != c: if lex.peek(i) != c:
@@ -81,9 +75,6 @@ proc currentLocation(lex: Lexer): SourceLocation =
proc emitError(lex: var Lexer, loc: SourceLocation, message: string) = proc emitError(lex: var Lexer, loc: SourceLocation, message: string) =
lex.diagnostics.add(LexerDiagnostic(severity: ldsError, loc: loc, message: message)) lex.diagnostics.add(LexerDiagnostic(severity: ldsError, loc: loc, message: message))
proc emitWarning(lex: var Lexer, loc: SourceLocation, message: string) =
lex.diagnostics.add(LexerDiagnostic(severity: ldsWarning, loc: loc, message: message))
proc makeToken(lex: Lexer, kind: TokenKind, startLoc: SourceLocation, startPos: int): Token = proc makeToken(lex: Lexer, kind: TokenKind, startLoc: SourceLocation, startPos: int): Token =
let text = lex.source[startPos ..< lex.pos] let text = lex.source[startPos ..< lex.pos]
result = Token(kind: kind, text: text, loc: startLoc) result = Token(kind: kind, text: text, loc: startLoc)
@@ -337,19 +328,6 @@ proc scanSymbol(lex: var Lexer, startLoc: SourceLocation): Token =
else: else:
return lex.makeToken(kind1, startLoc, startPos) return lex.makeToken(kind1, startLoc, startPos)
template check3(c2: char, kind2: TokenKind, c3: char, kind3: TokenKind, kind1: TokenKind) =
if lex.peek() == c2:
discard lex.advance()
if lex.peek() == c3:
discard lex.advance()
return lex.makeToken(kind3, startLoc, startPos)
return lex.makeToken(kind2, startLoc, startPos)
else:
return lex.makeToken(kind1, startLoc, startPos)
template checkEq(c2: char, kind2: TokenKind, kind1: TokenKind) =
check2(c2, kind2, kind1)
case c1 case c1
of '(': return lex.makeToken(tkLParen, startLoc, startPos) of '(': return lex.makeToken(tkLParen, startLoc, startPos)
of ')': return lex.makeToken(tkRParen, startLoc, startPos) of ')': return lex.makeToken(tkRParen, startLoc, startPos)
-2
View File
@@ -2,8 +2,6 @@
## Linear 3-address code IR, designed for straightforward C emission. ## Linear 3-address code IR, designed for straightforward C emission.
## Each HIR construct lowers to 5-30 LIR instructions. ## Each HIR construct lowers to 5-30 LIR instructions.
import types
type type
LirKind* = enum LirKind* = enum
# ── Data movement ── # ── Data movement ──
+118 -43
View File
@@ -17,9 +17,6 @@ proc initLirCBackend*(): LirCBackend =
tempTypes: initTable[string, string](), tempTypes: initTable[string, string](),
) )
proc emit(be: var LirCBackend, s: string) =
be.output.add(s)
proc emitIndent(be: var LirCBackend) = proc emitIndent(be: var LirCBackend) =
for i in 0 ..< be.indent: for i in 0 ..< be.indent:
be.output.add(" ") be.output.add(" ")
@@ -43,21 +40,6 @@ proc valToC(be: var LirCBackend, v: LirValue): string =
of lvkField: v.strVal of lvkField: v.strVal
of lvkType: v.strVal of lvkType: v.strVal
proc typeFromValue(be: var LirCBackend, v: LirValue): string =
## Infer a C type for a value. Temps are tracked; named vars use lookup.
case v.kind
of lvkTemp:
if be.tempTypes.hasKey(v.strVal):
return be.tempTypes[v.strVal]
return "int" # Default
of lvkString: return "const char*"
of lvkInt: return "int"
of lvkFloat: return "double"
else: return ""
proc setTempType(be: var LirCBackend, temp: string, cType: string) =
be.tempTypes[temp] = cType
proc cParamDecl(cType, name: string): string = proc cParamDecl(cType, name: string): string =
## Emit a C parameter declaration, handling function-pointer syntax. ## Emit a C parameter declaration, handling function-pointer syntax.
if cType.contains("(*)"): if cType.contains("(*)"):
@@ -487,6 +469,26 @@ proc emitEnumDef(be: var LirCBackend, name: string, variants: seq[HirEnumVariant
be.emitLine(&"}} {name};") be.emitLine(&"}} {name};")
be.emitLine("") be.emitLine("")
# ── Type dependency ordering ──
proc collectValueDeps(typ: Type): seq[string] =
## Return type names that must be fully defined before a value of `typ`
## can be declared. Pointers/refs only need a forward declaration, so
## they do not introduce a dependency.
if typ == nil: return @[]
case typ.kind
of tkNamed:
return @[typ.name]
of tkSlice:
return @[typeToCStr(typ)]
of tkPointer, tkRef, tkMutRef, tkTuple, tkFunc:
return @[]
else:
return @[]
proc emitSliceTypeDef(be: var LirCBackend, name: string, elem: string) =
be.emitLine(&"typedef struct {{ {elem}* data; size_t len; }} {name};")
# ── Module emission ── # ── Module emission ──
proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): string = proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): string =
@@ -557,36 +559,109 @@ proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): s
else: discard else: discard
be.emitLine("") be.emitLine("")
# Enum definitions # Collect local type names (structs and enums defined in this module).
var localTypeNames: HashSet[string]
for s in module.structs:
localTypeNames.incl(s.name)
for e in module.enums: for e in module.enums:
be.emitEnumDef(e.name, e.variants) localTypeNames.incl(e.name)
if module.enums.len > 0:
be.emitLine("")
# Struct definitions # Collect slice types used in struct fields and enum payloads.
for s in module.structs:
be.emitStructDef(s.name, s.fields)
# Slice types (collect from functions/structs)
# Simple: scan function params/returns for slice types
var sliceTypes: seq[tuple[name: string, elem: string]] = @[] var sliceTypes: seq[tuple[name: string, elem: string]] = @[]
var structNames: HashSet[string] var sliceNames: HashSet[string]
proc registerSlice(t: Type) =
if t == nil or t.kind != tkSlice: return
let name = typeToCStr(t)
if sliceNames.contains(name): return
sliceNames.incl(name)
let elem = if t.inner.len > 0: typeToCStr(t.inner[0]) else: "void"
sliceTypes.add((name, elem))
for s in module.structs: for s in module.structs:
structNames.incl(s.name) for f in s.fields:
for f in module.funcs: registerSlice(f.typ)
for p in f.params: for e in module.enums:
var ct = typeToCStr(p.typ) for v in e.variants:
# Strip pointer/reference suffix to find the base slice type. for ft in v.fields:
while ct.endsWith("*"): registerSlice(ft)
ct = ct[0..^2] for nf in v.namedFields:
if ct.startsWith("Slice_"): registerSlice(nf.typ)
let elem = ct[6 .. ^1]
if not sliceTypes.anyIt(it.name == ct) and not structNames.contains(ct): # Build dependency graph among structs, enums, and slice types.
sliceTypes.add((ct, elem)) # Edge A -> B means "A depends on B, so B must be emitted before A".
if sliceTypes.len > 0: var deps: Table[string, seq[string]]
for s in module.structs:
deps[s.name] = @[]
for e in module.enums:
deps[e.name] = @[]
for st in sliceTypes: for st in sliceTypes:
be.emitLine(&"typedef struct {{ {st.elem}* data; size_t len; }} {st.name};") deps[st.name] = @[]
be.emitLine("")
proc addDeps(node: string, t: Type) =
for dep in collectValueDeps(t):
if dep == node: continue
if localTypeNames.contains(dep) or sliceNames.contains(dep):
if dep notin deps[node]:
deps[node].add(dep)
for s in module.structs:
for f in s.fields:
addDeps(s.name, f.typ)
for e in module.enums:
for v in e.variants:
for ft in v.fields:
addDeps(e.name, ft)
for nf in v.namedFields:
addDeps(e.name, nf.typ)
# Topological sort (Kahn's algorithm).
var inDegree: Table[string, int]
var dependents: Table[string, seq[string]]
for node in deps.keys:
inDegree[node] = 0
for node, nodeDeps in deps:
for d in nodeDeps:
if not inDegree.hasKey(d): inDegree[d] = 0
inDegree[node] += 1
dependents.mgetOrPut(d, @[]).add(node)
var queue: seq[string] = @[]
for node, deg in inDegree:
if deg == 0:
queue.add(node)
var sorted: seq[string] = @[]
while queue.len > 0:
let node = queue.pop()
sorted.add(node)
for depNode in dependents.getOrDefault(node):
inDegree[depNode] -= 1
if inDegree[depNode] == 0:
queue.add(depNode)
if sorted.len < deps.len:
# Cycle detected; fall back to a safe deterministic order.
sorted = @[]
for s in module.structs: sorted.add(s.name)
for e in module.enums: sorted.add(e.name)
for st in sliceTypes: sorted.add(st.name)
# Map type names back to their definitions.
var structMap: Table[string, seq[tuple[name: string, typ: Type]]]
for s in module.structs: structMap[s.name] = s.fields
var enumMap: Table[string, seq[HirEnumVariant]]
for e in module.enums: enumMap[e.name] = e.variants
var sliceMap: Table[string, string]
for st in sliceTypes: sliceMap[st.name] = st.elem
# Emit type definitions in dependency order.
for name in sorted:
if structMap.hasKey(name):
be.emitStructDef(name, structMap[name])
elif enumMap.hasKey(name):
be.emitEnumDef(name, enumMap[name])
elif sliceMap.hasKey(name):
be.emitSliceTypeDef(name, sliceMap[name])
# Forward function declarations # Forward function declarations
for f in module.funcs: for f in module.funcs:
+4 -8
View File
@@ -3,7 +3,7 @@
## Each HIR node kind lowers to 1-20 LIR instructions. ## Each HIR node kind lowers to 1-20 LIR instructions.
import std/[strutils, strformat, tables, sequtils] import std/[strutils, strformat, tables, sequtils]
import ast, types, token, hir, lir import types, token, hir, lir
## Convert LirValue to C expression string (no % prefix) ## Convert LirValue to C expression string (no % prefix)
proc lirValToC(v: LirValue): string = proc lirValToC(v: LirValue): string =
@@ -429,7 +429,6 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
# ── SizeOf ── # ── SizeOf ──
of hSizeOf: of hSizeOf:
let ctype = typeToCStr(node.sizeOfType) let ctype = typeToCStr(node.sizeOfType)
let t = b.freshTemp()
b.emit(LirInstr(kind: lirRawC, src: lirStr(&"/* sizeof({ctype}) */"))) b.emit(LirInstr(kind: lirRawC, src: lirStr(&"/* sizeof({ctype}) */")))
return lirVar(&"sizeof({ctype})") return lirVar(&"sizeof({ctype})")
@@ -608,7 +607,6 @@ proc lowerStmt(ctx: var LowerToLirCtx, node: HirNode) =
# ── While statement ── # ── While statement ──
of hWhile: of hWhile:
let startLbl = b.freshLabel("while") let startLbl = b.freshLabel("while")
let bodyLbl = b.freshLabel("wbody")
let endLbl = b.freshLabel("wend") let endLbl = b.freshLabel("wend")
ctx.loopStartLabels.add(startLbl.strVal) ctx.loopStartLabels.add(startLbl.strVal)
@@ -750,9 +748,8 @@ proc lowerStmt(ctx: var LowerToLirCtx, node: HirNode) =
for stmt in node.blockStmts: for stmt in node.blockStmts:
lowerStmt(ctx, stmt) lowerStmt(ctx, stmt)
if node.blockExpr != nil: if node.blockExpr != nil:
let exprVal = lowerExpr(ctx, node.blockExpr) # If block is an expression, result is unused at statement level
# If block is an expression, store result discard lowerExpr(ctx, node.blockExpr)
discard
if node.isScope: if node.isScope:
b.emitRawC("}") b.emitRawC("}")
@@ -762,9 +759,8 @@ proc lowerStmt(ctx: var LowerToLirCtx, node: HirNode) =
# ── Expression statement ── # ── Expression statement ──
else: else:
let exprVal = lowerExpr(ctx, node)
# Expression evaluated for side effects; temp is unused # Expression evaluated for side effects; temp is unused
discard discard lowerExpr(ctx, node)
# ── Module-level lowering ── # ── Module-level lowering ──
-1
View File
@@ -90,7 +90,6 @@ proc parseInlineTable(s: string): OrderedTableRef[string, TomlValue] =
if content[i] == '{': inc braceCount if content[i] == '{': inc braceCount
elif content[i] == '}': dec braceCount elif content[i] == '}': dec braceCount
inc i inc i
let val = content[valStart ..< i]
result[key] = TomlValue(kind: tvkInlineTable, inlineVal: newOrderedTable[string, TomlValue]()) result[key] = TomlValue(kind: tvkInlineTable, inlineVal: newOrderedTable[string, TomlValue]())
else: else:
while i < content.len and content[i] notin {',', ' ', '\t'}: while i < content.len and content[i] notin {',', ' ', '\t'}:
-8
View File
@@ -62,12 +62,6 @@ proc checkAny(p: Parser, kinds: openArray[TokenKind]): bool =
if p.peek() == k: return true if p.peek() == k: return true
return false return false
proc match(p: var Parser, kind: TokenKind): bool =
if p.check(kind):
discard p.advance()
return true
return false
proc isTypeArgListAhead(p: Parser): bool = proc isTypeArgListAhead(p: Parser): bool =
## Lookahead to determine if '<' starts a type argument list. ## Lookahead to determine if '<' starts a type argument list.
## Returns true if we can find a matching '>' before EOF, '{', or ';'. ## Returns true if we can find a matching '>' before EOF, '{', or ';'.
@@ -788,7 +782,6 @@ proc parseShift(p: var Parser): Expr =
return left return left
proc parseCast(p: var Parser): Expr = proc parseCast(p: var Parser): Expr =
let loc = p.currentLoc
var left = p.parseShift() var left = p.parseShift()
# 'as' and 'is' are handled in postfix for chaining # 'as' and 'is' are handled in postfix for chaining
return left return left
@@ -1171,7 +1164,6 @@ proc parseParamList(p: var Parser, allowVariadic: bool = false): seq[Param] =
if p.check(tkRParen) or p.isAtEnd: if p.check(tkRParen) or p.isAtEnd:
break break
let loc = p.currentLoc let loc = p.currentLoc
var isVar = false
if allowVariadic and p.check(tkDotDotDot): if allowVariadic and p.check(tkDotDotDot):
discard p.advance() discard p.advance()
let nameTok = p.at let nameTok = p.at
+4 -7
View File
@@ -95,9 +95,6 @@ proc unescapeStringLiteral*(s: string): string =
proc emitError(sema: var Sema, loc: SourceLocation, message: string) = proc emitError(sema: var Sema, loc: SourceLocation, message: string) =
sema.diagnostics.add(SemaDiagnostic(severity: sdsError, loc: loc, message: message)) sema.diagnostics.add(SemaDiagnostic(severity: sdsError, loc: loc, message: message))
proc emitWarning(sema: var Sema, loc: SourceLocation, message: string) =
sema.diagnostics.add(SemaDiagnostic(severity: sdsWarning, loc: loc, message: message))
proc hasErrors*(res: SemaResult): bool = proc hasErrors*(res: SemaResult): bool =
for d in res.diagnostics: for d in res.diagnostics:
if d.severity == sdsError: if d.severity == sdsError:
@@ -1348,12 +1345,12 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
discard sema.checkExpr(expr.exprIsOperand, scope) discard sema.checkExpr(expr.exprIsOperand, scope)
return makeBool() return makeBool()
of ekTry: of ekTry:
let operandType = sema.checkExpr(expr.exprTryOperand, scope) discard sema.checkExpr(expr.exprTryOperand, scope)
# For now, assume Result<int, String> -> int # For now, assume Result<int, String> -> int
# TODO: check operand is Result/Option and current function returns same type # TODO: check operand is Result/Option and current function returns same type
return makeInt() return makeInt()
of ekUnwrap: of ekUnwrap:
let operandType = sema.checkExpr(expr.exprUnwrapOperand, scope) discard sema.checkExpr(expr.exprUnwrapOperand, scope)
# Unwrap: extract Ok value or panic on Err # Unwrap: extract Ok value or panic on Err
return makeInt() return makeInt()
of ekBlock: of ekBlock:
@@ -1363,7 +1360,7 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
lastType = sema.checkStmt(stmt, blockScope) lastType = sema.checkStmt(stmt, blockScope)
return lastType return lastType
of ekMatch: of ekMatch:
let subjectType = sema.checkExpr(expr.exprMatchSubject, scope) discard sema.checkExpr(expr.exprMatchSubject, scope)
var resultType = makeUnknown() var resultType = makeUnknown()
for arm in expr.exprMatchArms: for arm in expr.exprMatchArms:
var armScope = newScope(scope) var armScope = newScope(scope)
@@ -1398,7 +1395,7 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
expr.exprSpawnAsync = true expr.exprSpawnAsync = true
return makePointer(makeVoid()) return makePointer(makeVoid())
of ekAwait: of ekAwait:
let operand = sema.checkExpr(expr.exprAwaitOperand, scope) discard sema.checkExpr(expr.exprAwaitOperand, scope)
# await on a task handle returns *void (result pointer) # await on a task handle returns *void (result pointer)
return makePointer(makeVoid()) return makePointer(makeVoid())
of ekBorrow: of ekBorrow:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,365 @@
# Nexus HTTP Server Rewrite — Design Spec
**Date:** 2026-06-14
**Goal:** Rewrite `apps/nexus` as a production-ready, modular HTTP/1.1 server that exercises modern Bux language features.
---
## 1. Scope
Keep the same observable behavior as the current Nexus app, but restructure it for production quality:
- HTTP/1.1 request parsing
- Static file serving from `./public/`
- API endpoints: `GET /api/health`, `GET /api/info`
- WebSocket upgrade detection on `/ws`
- Multi-threaded request handling
What changes:
- Monolithic `Main.bux` → modular source tree
- Function-pointer dispatch → `interface`-based handlers and middleware
- Ad-hoc error handling → algebraic enum `Result`/`Option` patterns
- Raw pointer buffer parsing → `@[Checked]` borrow-checked parsing
- Direct worker spawn → thread-pool backed by `Channel<ConnectionTask>`
---
## 2. Module Layout
```
apps/nexus/
├── bux.toml
├── README.md
├── public/
│ ├── index.html
│ └── 404.html
└── src/
├── Main.bux # Entry point: config, socket setup, spawn workers, run acceptor
├── Config.bux # ServerConfig struct + CTFE defaults
├── Errors.bux # HttpError enum + Result/Option helpers
├── Http.bux # HttpMethod enum, Request/Response structs, status text, MIME
├── Parser.bux # @[Checked] HTTP/1.1 request parser
├── Router.bux # Route, Router, HttpHandler interface
├── Middleware.bux # Middleware interface + LoggingMiddleware
├── Handlers.bux # Static files, API, WebSocket upgrade, 404
└── Server.bux # ConnectionTask, Worker, acceptor loop, channel plumbing
```
---
## 3. Language Features to Exercise
| Feature | Where |
|---------|-------|
| Modules & `pub`/`private` | Every file |
| Algebraic enums | `HttpMethod`, `HttpError`, `ParseResult` |
| Pattern matching (`match`) | Error dispatch in `Main`, method parsing |
| Structs + `extend` methods | `HttpRequest`, `HttpResponse`, `Router`, `ServerConfig` |
| `interface` + `extend ... for` | `HttpHandler`, `Middleware` |
| Generics | `Channel<ConnectionTask>` |
| `for ... in` loops | Header iteration, route dispatch (over `Array<HeaderEntry>` and `Array<Route>`) |
| `Array<T>` generic container | Header storage, route table |
| `@[Checked]` + `&` / `&mut` | `Parser.bux` string buffer access |
| `Result` + `?` operator | Parser fallible operations |
| `const func` / CTFE | Default config values |
| `spawn` + `Channel<T>` | Thread-pool in `Server.bux` |
| Raw multi-line / interpolated strings | Response templates, logs |
---
## 4. Core Types
### 4.1 HTTP Types (`Http.bux`)
```bux
pub enum HttpMethod {
GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, UNKNOWN,
}
pub enum HttpError {
BadRequest,
NotFound,
MethodNotAllowed,
InternalError(String),
}
pub struct HeaderEntry {
key: String;
value: String;
}
pub struct HttpRequest {
method: HttpMethod;
path: String;
version: String;
body: String;
headers: Array<HeaderEntry>;
}
pub struct HttpResponse {
statusCode: int;
contentType: String;
body: String;
extraHeaders: String;
}
```
### 4.2 Result Types (`Errors.bux`)
Bux's `Std::Result` only carries `int` in `Ok`, so Nexus defines domain-specific enums:
```bux
pub enum ParseResult {
Ok(HttpRequest),
Err(HttpError),
}
pub enum FileResult {
Ok(String),
Err(HttpError),
}
```
Helpers:
- `ParseResult_IsOk`, `ParseResult_UnwrapOr`
- `FileResult_IsOk`, `FileResult_UnwrapOr`
If the bootstrap `?` operator only recognizes `Result`/`Option` with exact variant names, parser code will fall back to explicit `match`.
### 4.3 Handler & Middleware Interfaces
```bux
// Router.bux
pub interface HttpHandler {
func Handle(self: &Self, req: &HttpRequest) -> HttpResponse;
}
pub enum Handler {
StaticFile,
ApiHealth,
ApiInfo,
WsUpgrade,
NotFound,
}
extend Handler for HttpHandler {
func Handle(self: &Handler, req: &HttpRequest) -> HttpResponse {
match self {
Handler::StaticFile => return ServeStaticFile(req),
Handler::ApiHealth => return HandleApiHealth(),
Handler::ApiInfo => return HandleApiInfo(),
Handler::WsUpgrade => return HandleWebSocketUpgrade(req),
Handler::NotFound => return NotFoundResponse(),
}
return NotFoundResponse();
}
}
pub struct Route {
method: HttpMethod;
path: String;
handler: Handler;
}
pub struct Router {
routes: Array<Route>;
notFound: Handler;
}
// Middleware.bux
pub interface Middleware {
func Process(self: &Self, req: &HttpRequest, next: &HttpHandler) -> HttpResponse;
}
```
Bux interfaces currently support static dispatch through generics, not dynamic `*Handler` pointers. The route table stores the concrete `Handler` enum and dispatches via `match` (showcasing algebraic enums + pattern matching). Middleware uses the interface with generic bounds (`T: HttpHandler`) for static composition.
---
## 5. Data Flow
1. `Main` reads `ServerConfig` and creates a listening socket.
2. `Main` spawns `workerCount - 1` worker threads; the main thread becomes the last worker.
3. `Main` spawns one dedicated acceptor thread that loops on `Net_Accept` and pushes `ConnectionTask { fd }` into `Channel<ConnectionTask>`.
4. Each worker loops:
```
task = Channel_Recv(&taskQueue)
HandleConnection(task.fd, &appHandler)
Net_Close(task.fd)
```
5. `HandleConnection`:
- `Net_Recv` raw request
- `ParseRequest(raw)``ParseResult` (matched explicitly; `?` is used only for `Result`/int payloads)
- On success, `Router.Dispatch(&req)` or middleware chain
- `Http_BuildResponse(resp)` + `Net_Send`
---
## 6. Parser Design (`Parser.bux`)
Parser functions take the raw request by value (`String` is already a C-string reference) and build an `HttpRequest`:
```bux
pub func ParseRequest(raw: String) -> ParseResult {
match FindToken(raw, " ") {
ParseResult::Ok(end) => {
let methodStr: String = Slice(raw, 0, end);
let method: HttpMethod = ParseMethod(methodStr);
// ... path, version, headers, body
return ParseResult_NewOk(req);
}
ParseResult::Err(e) => return ParseResult_NewErr(e),
}
}
```
`@[Checked]` is used where mutable borrows make sense (e.g. `SetResponseCode(resp: &mut HttpResponse, code: int)`), not on raw string parsing.
Headers are stored in a dynamically grown `Array<HeaderEntry>`.
---
## 7. Router & Middleware
### 7.1 Router Dispatch
```bux
extend Router {
pub func Dispatch(self: *Router, req: &HttpRequest) -> HttpResponse {
for route in self.routes {
if route.method == req.method && String_Eq(route.path, req.path) {
return route.handler.Handle(req); // Handler enum implements HttpHandler
}
}
return self.notFound.Handle(req);
}
}
```
### 7.2 Middleware Chain
`LoggingMiddleware` logs method/path and delegates to `next`:
```bux
pub struct LoggingMiddleware {}
extend LoggingMiddleware for Middleware {
func Process<T: HttpHandler>(self: *LoggingMiddleware, req: &HttpRequest, next: &T) -> HttpResponse {
Print(Http_MethodName(req.method));
Print(" ");
PrintLine(req.path);
return next.Handle(req);
}
}
```
Chaining is achieved with a generic `MiddlewareHandler<M: Middleware, T: HttpHandler>` struct that implements `HttpHandler` and holds `(middleware, next)`. This uses Bux's static-dispatch interfaces (no vtable pointers).
---
## 8. Handlers (`Handlers.bux`)
| Handler | Purpose |
|---------|---------|
| `StaticFileHandler` | Serve files from `public/` with MIME sniffing and path-traversal guard |
| `HealthHandler` | `GET /api/health` → JSON status |
| `InfoHandler` | `GET /api/info` → JSON server info |
| `WsUpgradeHandler` | `GET /ws` or `Upgrade: websocket` → 101 response with placeholder accept key |
| `NotFoundHandler` | 404 JSON/text fallback |
All implement `HttpHandler`.
---
## 9. Server & Thread Pool (`Server.bux`)
```bux
pub struct ConnectionTask {
fd: int;
}
pub struct Server {
config: ServerConfig;
taskQueue: Channel<ConnectionTask>;
handler: *HttpHandler;
}
```
Flow:
1. `Server_Run(&server)` creates socket, binds, listens.
2. Spawns workers; each calls `Worker_Run(&server)`.
3. `Acceptor_Run(&server)` loops:
```bux
let fd: int = Net_Accept(serverFd);
if fd >= 0 {
let task: ConnectionTask = ConnectionTask { fd: fd };
Channel_Send<ConnectionTask>(&server.taskQueue, task);
}
```
4. `Worker_Run` pops tasks and calls `HandleConnection(task.fd, server.handler)`.
Socket fd ownership is always with the worker that received the task; it is closed before the worker loops again.
---
## 10. Config (`Config.bux`)
```bux
pub struct ServerConfig {
bindAddr: String;
port: int;
workerCount: int;
publicDir: String;
backlog: int;
}
const func DefaultConfig() -> ServerConfig {
return ServerConfig {
bindAddr: "0.0.0.0",
port: 8080,
workerCount: 4,
publicDir: "public",
backlog: 128,
};
}
```
---
## 11. Testing & Verification
- `buxc check` must pass on the whole package.
- `buxc run` starts the server; verify with:
- `curl http://localhost:8080/`
- `curl http://localhost:8080/api/health`
- `curl http://localhost:8080/api/info`
- `curl -i -N -H "Upgrade: websocket" -H "Connection: Upgrade" http://localhost:8080/ws`
- Existing integration test surface (`buxc check` on `apps/nexus`) remains the gate.
---
## 12. Out of Scope
- Real HTTP/2 frame parsing or WebSocket frame parsing (keep detection/upgrade only, same as current).
- TLS/HTTPS.
- Full SHA-1/Base64 WebSocket accept key (keep placeholder).
- Generic `Result<T, E>` (Bux `Std::Result` is `Ok(int)` only; use domain enums instead).
---
## 13. Risks & Mitigations
| Risk | Mitigation |
|------|------------|
| `interface` vtables with generic structs | Avoid generic interfaces; use concrete `*HttpHandler` pointers. |
| `Channel<T>` with non-trivial `T` | `ConnectionTask` contains only `int`; safe to copy. |
| `?` operator on custom enums | Fall back to explicit `match` if needed. |
| `@[Checked]` borrow checker on raw `String` | Use `&String` for read-only parsing; avoid `&mut` unless mutation is required. |
---
## 14. Success Criteria
1. `buxc check apps/nexus` passes with zero errors.
2. `buxc run` in `apps/nexus` serves static files and API endpoints correctly.
3. The new code uses at minimum: modules, algebraic enums, pattern matching, interfaces, `extend`, generics, `for ... in`, `Channel<T>`, `spawn`, `Result`/`Option`, `@[Checked]`.
+2
View File
@@ -11,6 +11,7 @@ void PrintLine(const char* s) {
} else { } else {
puts(""); puts("");
} }
fflush(stdout);
} }
/* Print - print string without newline */ /* Print - print string without newline */
@@ -18,6 +19,7 @@ void Print(const char* s) {
if (s != NULL) { if (s != NULL) {
printf("%s", s); printf("%s", s);
} }
fflush(stdout);
} }
/* PrintInt - print integer */ /* PrintInt - print integer */
BIN
View File
Binary file not shown.
+2 -2
View File
@@ -1,5 +1,5 @@
import std/[unittest, os, strutils] import std/[unittest, strutils]
import ../bootstrap/[lexer, parser, sema, types] import ../bootstrap/[lexer, parser, sema]
proc checkSource(source: string): tuple[hasErrors: bool, diagnostics: seq[SemaDiagnostic]] = proc checkSource(source: string): tuple[hasErrors: bool, diagnostics: seq[SemaDiagnostic]] =
let lexRes = lexer.tokenize(source, "test.bux") let lexRes = lexer.tokenize(source, "test.bux")
File diff suppressed because it is too large Load Diff
+2625 -2626
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2601 -2602
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
[Package]
Name = "modern_features"
Version = "0.1.0"
Type = "bin"
[Build]
Output = "Bin"
File diff suppressed because it is too large Load Diff
+49
View File
@@ -0,0 +1,49 @@
module Main {
import Std::Array::{Array, Array_New, Array_Push};
struct Record {
name: String;
value: int;
}
struct Box {
items: Array<Record>;
}
enum Payload {
Ok(Record),
Err(String),
}
enum Color { Red, Green }
func Name(c: Color) -> String {
match c {
Color::Red => "red",
Color::Green => "green",
}
}
func Main() -> int {
var box: Box;
box.items = Array_New<Record>(4);
Array_Push<Record>(&box.items, Record { name: "x", value: 10 });
for it in box.items {
if it.value == 10 {
return 0;
}
}
var r: Payload;
r.tag = Payload_Ok;
r.data.Ok_0 = Record { name: "y", value: 20 };
if r.data.Ok_0.value == 20 {
return 0;
}
return 1;
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,5 +1,5 @@
import std/[unittest, strformat] import std/[unittest]
import ../bootstrap/[lexer, parser, sema, hir, hir_lower, types, scope] import ../bootstrap/[lexer, parser, sema, hir, hir_lower, scope]
proc lowerSource(source: string): HirModule = proc lowerSource(source: string): HirModule =
let lexRes = tokenize(source, "<test>") let lexRes = tokenize(source, "<test>")
+2 -2
View File
@@ -1,5 +1,5 @@
import std/[unittest, os, strutils] import std/[unittest, strutils]
import ../bootstrap/[lexer, token, source_location] import ../bootstrap/[lexer, token]
proc tokenKinds(source: string): seq[TokenKind] = proc tokenKinds(source: string): seq[TokenKind] =
let res = tokenize(source, "<test>") let res = tokenize(source, "<test>")
+1 -1
View File
@@ -1,4 +1,4 @@
import std/[unittest, os, strutils] import std/[unittest, os]
import ../bootstrap/[lexer, parser, ast, token] import ../bootstrap/[lexer, parser, ast, token]
proc parseSource(source: string): ParseResult = proc parseSource(source: string): ParseResult =
+1 -1
View File
@@ -1,5 +1,5 @@
import std/[unittest, strutils] import std/[unittest, strutils]
import ../bootstrap/[lexer, parser, sema, types] import ../bootstrap/[lexer, parser, sema]
proc checkSource(source: string): SemaResult = proc checkSource(source: string): SemaResult =
let lexRes = tokenize(source, "<test>") let lexRes = tokenize(source, "<test>")