arena
This commit is contained in:
@@ -3,6 +3,20 @@
|
||||
#import "modules/std.sx";
|
||||
#import "modules/socket.sx";
|
||||
|
||||
// --- Logger ---
|
||||
|
||||
Logger :: struct {
|
||||
prefix: string;
|
||||
count: s64;
|
||||
}
|
||||
|
||||
log :: (logger: *Logger, msg: string) {
|
||||
logger.count += 1;
|
||||
print("[{}] {}\n", logger.prefix, msg);
|
||||
}
|
||||
|
||||
|
||||
|
||||
main :: () -> s32 {
|
||||
PORT :: 8080;
|
||||
|
||||
@@ -29,29 +43,39 @@ main :: () -> s32 {
|
||||
|
||||
print("listening on http://localhost:{}\n", PORT);
|
||||
|
||||
arena := arena_create(65536);
|
||||
logger := Logger.{ prefix = "http", count = 0 };
|
||||
|
||||
while true {
|
||||
client := accept(fd, null, null);
|
||||
if client < 0 { continue; }
|
||||
|
||||
// Read request
|
||||
buf : [4096]u8 = ---;
|
||||
read(client, buf, buf.len);
|
||||
|
||||
// Send response
|
||||
body := "<html><body><h1>Hello from sx!</h1></body></html>";
|
||||
response := format("HTTP/1.1 200 OK\r
|
||||
Content-Type: text/html\r
|
||||
Connection: close\r
|
||||
Content-Length: {}\r
|
||||
\r
|
||||
{}", body.len, body);
|
||||
|
||||
write(client, response, response.len);
|
||||
push Context.{ arena = @arena, data = xx @logger } {
|
||||
handle(client);
|
||||
}
|
||||
|
||||
arena_reset(@arena);
|
||||
close(client);
|
||||
print(" served request\n");
|
||||
}
|
||||
|
||||
arena_destroy(@arena);
|
||||
close(fd);
|
||||
0;
|
||||
}
|
||||
|
||||
handle :: (client: s32) {
|
||||
// Read request
|
||||
buf : [4096]u8 = ---;
|
||||
read(client, buf, buf.len);
|
||||
|
||||
body := "<html><body><h1>Hello from sx!</h1></body></html>";
|
||||
response := format("HTTP/1.1 200 OK\r
|
||||
Content-Type: text/html\r
|
||||
Connection: close\r
|
||||
Content-Length: {}\r
|
||||
\r\n{}", body.len, body);
|
||||
|
||||
write(client, response, response.len);
|
||||
logger : *Logger = xx context.data;
|
||||
log(logger, format("served request #{}", logger.count + 1));
|
||||
}
|
||||
@@ -1184,5 +1184,33 @@ END;
|
||||
print("compound: {}\n", arr[2]);
|
||||
}
|
||||
|
||||
// === 26. #using struct composition ===
|
||||
print("=== 26. #using ===\n");
|
||||
{
|
||||
UBase :: struct { x: s32; y: s32; }
|
||||
UExt :: struct { #using UBase; z: s32; }
|
||||
e := UExt.{ x = 1, y = 2, z = 3 };
|
||||
print("using-x: {}\n", e.x);
|
||||
print("using-y: {}\n", e.y);
|
||||
print("using-z: {}\n", e.z);
|
||||
|
||||
// #using in middle position
|
||||
UHeader :: struct { version: s32; }
|
||||
UPacket :: struct { id: s32; #using UHeader; payload: s32; }
|
||||
p := UPacket.{ id = 10, version = 42, payload = 99 };
|
||||
print("pkt-id: {}\n", p.id);
|
||||
print("pkt-ver: {}\n", p.version);
|
||||
print("pkt-pay: {}\n", p.payload);
|
||||
|
||||
// Multiple #using
|
||||
UPos :: struct { px: s32; py: s32; }
|
||||
UCol :: struct { r: s32; g: s32; }
|
||||
USprite :: struct { #using UPos; #using UCol; scale: s32; }
|
||||
s := USprite.{ px = 10, py = 20, r = 255, g = 128, scale = 1 };
|
||||
print("sprite-px: {}\n", s.px);
|
||||
print("sprite-r: {}\n", s.r);
|
||||
print("sprite-scale: {}\n", s.scale);
|
||||
}
|
||||
|
||||
print("=== DONE ===\n");
|
||||
}
|
||||
|
||||
@@ -18,13 +18,52 @@ field_value_int :: ($T: Type, idx: s64) -> s64 #builtin;
|
||||
field_index :: ($T: Type, val: T) -> s64 #builtin;
|
||||
string :: []u8 #builtin;
|
||||
|
||||
// --- Arena allocator & Context ---
|
||||
|
||||
Arena :: struct {
|
||||
buf: string;
|
||||
pos: s64;
|
||||
}
|
||||
|
||||
Context :: struct {
|
||||
arena: *Arena;
|
||||
data: *void;
|
||||
}
|
||||
|
||||
context : Context = ---;
|
||||
|
||||
arena_create :: (size: s64) -> Arena {
|
||||
Arena.{ buf = cstring(size), pos = 0 };
|
||||
}
|
||||
|
||||
arena_alloc :: (arena: *Arena, size: s64) -> *void {
|
||||
aligned := (size + 7) & (0 - 8);
|
||||
if arena.pos + aligned > arena.buf.len {
|
||||
return malloc(aligned);
|
||||
}
|
||||
ptr : *void = xx @arena.buf[arena.pos];
|
||||
arena.pos = arena.pos + aligned;
|
||||
ptr;
|
||||
}
|
||||
|
||||
arena_reset :: (arena: *Arena) {
|
||||
arena.pos = 0;
|
||||
}
|
||||
|
||||
arena_destroy :: (arena: *Arena) {
|
||||
free(arena.buf.ptr);
|
||||
}
|
||||
|
||||
// --- String allocation ---
|
||||
|
||||
CString :: union {
|
||||
s: string;
|
||||
struct { ptr: *void; len: s64; };
|
||||
}
|
||||
|
||||
cstring :: (size: s64) -> string {
|
||||
raw := malloc(size + 1);
|
||||
p : s64 = xx context.arena;
|
||||
raw := if p != 0 then arena_alloc(context.arena, size + 1) else malloc(size + 1);
|
||||
memset(raw, 0, size + 1);
|
||||
rs : CString = ---;
|
||||
rs.ptr = raw;
|
||||
|
||||
12
src/ast.zig
12
src/ast.zig
@@ -39,6 +39,7 @@ pub const Node = struct {
|
||||
type_expr: TypeExpr,
|
||||
param: Param,
|
||||
defer_stmt: DeferStmt,
|
||||
push_stmt: PushStmt,
|
||||
comptime_expr: ComptimeExpr,
|
||||
insert_expr: InsertExpr,
|
||||
return_stmt: ReturnStmt,
|
||||
@@ -253,12 +254,18 @@ pub const StructTypeParam = struct {
|
||||
constraint: *Node, // type_expr: "u32" for value param, "Type" for type param
|
||||
};
|
||||
|
||||
pub const UsingEntry = struct {
|
||||
insert_index: u32, // position in field_names where used fields are spliced
|
||||
type_name: []const u8, // struct type to inline
|
||||
};
|
||||
|
||||
pub const StructDecl = struct {
|
||||
name: []const u8,
|
||||
field_names: []const []const u8,
|
||||
field_types: []const *Node, // type_expr nodes
|
||||
field_defaults: []const ?*Node, // default value per field, null if none
|
||||
type_params: []const StructTypeParam = &.{},
|
||||
using_entries: []const UsingEntry = &.{},
|
||||
};
|
||||
|
||||
pub const StructFieldInit = struct {
|
||||
@@ -288,6 +295,11 @@ pub const DeferStmt = struct {
|
||||
expr: *Node,
|
||||
};
|
||||
|
||||
pub const PushStmt = struct {
|
||||
context_expr: *Node,
|
||||
body: *Node,
|
||||
};
|
||||
|
||||
pub const ComptimeExpr = struct {
|
||||
expr: *Node,
|
||||
};
|
||||
|
||||
140
src/codegen.zig
140
src/codegen.zig
@@ -506,6 +506,12 @@ pub const CodeGen = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Lookup a named value in locals or global mutables (for field access/assignment).
|
||||
fn getNamedOrGlobal(self: *CodeGen, name: []const u8) ?NamedValue {
|
||||
if (self.named_values.get(name)) |nv| return nv;
|
||||
return self.global_mutable_vars.get(name);
|
||||
}
|
||||
|
||||
/// Build an alloca in the entry block of the current function so that
|
||||
/// stack space is reserved once, not on every loop iteration.
|
||||
fn buildEntryBlockAlloca(self: *CodeGen, ty: c.LLVMTypeRef, name: [*:0]const u8) c.LLVMValueRef {
|
||||
@@ -2266,6 +2272,14 @@ pub const CodeGen = struct {
|
||||
const info = try self.getTaggedEnumInfo(resolved);
|
||||
return self.loadIfPointer(raw_val, info.llvm_type, "retval");
|
||||
} else {
|
||||
// If ret_type is a pointer/many-pointer/fn-pointer and the LLVM value is already
|
||||
// an opaque ptr, return it directly. llvmTypeToSxType would misclassify LLVM ptr
|
||||
// as string_type, causing a bogus slice_to_ptr conversion and crash.
|
||||
if ((ret_type.isPointer() or ret_type.isManyPointer() or ret_type == .function_type) and
|
||||
c.LLVMGetTypeKind(c.LLVMTypeOf(raw_val)) == c.LLVMPointerTypeKind)
|
||||
{
|
||||
return raw_val;
|
||||
}
|
||||
const src_ty = self.llvmTypeToSxType(c.LLVMTypeOf(raw_val));
|
||||
return self.convertValue(raw_val, src_ty, ret_type);
|
||||
}
|
||||
@@ -2512,6 +2526,9 @@ pub const CodeGen = struct {
|
||||
}
|
||||
return null;
|
||||
},
|
||||
.push_stmt => |ps| {
|
||||
return self.genPushStmt(ps);
|
||||
},
|
||||
.insert_expr => |ins| {
|
||||
// Substitute comptime param nodes before evaluation (e.g., replace $fmt identifier with literal)
|
||||
const expr = if (self.comptime_param_nodes != null)
|
||||
@@ -3063,7 +3080,7 @@ pub const CodeGen = struct {
|
||||
// Object must be an identifier for now
|
||||
if (fa.object.data != .identifier) return self.emitError("field assignment target must be a variable");
|
||||
const obj_name = fa.object.data.identifier.name;
|
||||
const entry = self.named_values.get(obj_name) orelse return self.emitErrorFmt("undefined variable '{s}'", .{obj_name});
|
||||
const entry = self.getNamedOrGlobal(obj_name) orelse return self.emitErrorFmt("undefined variable '{s}'", .{obj_name});
|
||||
|
||||
// Pointer auto-deref: p.field = val
|
||||
if (entry.ty.isPointer()) {
|
||||
@@ -3487,7 +3504,7 @@ pub const CodeGen = struct {
|
||||
if (operand.data == .field_access) {
|
||||
const fa = operand.data.field_access;
|
||||
if (fa.object.data == .identifier) {
|
||||
if (self.named_values.get(fa.object.data.identifier.name)) |entry| {
|
||||
if (self.getNamedOrGlobal(fa.object.data.identifier.name)) |entry| {
|
||||
if (entry.ty.isStruct()) {
|
||||
const sname = entry.ty.struct_type;
|
||||
const info = try self.getStructInfo(sname);
|
||||
@@ -3616,6 +3633,56 @@ pub const CodeGen = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Expand #using entries: interleave used struct fields with declared fields.
|
||||
fn expandStructUsing(self: *CodeGen, sd: ast.StructDecl) !StructInfo {
|
||||
var all_names = std.ArrayList([]const u8).empty;
|
||||
var all_types = std.ArrayList(Type).empty;
|
||||
var all_defaults = std.ArrayList(?*Node).empty;
|
||||
|
||||
var using_idx: usize = 0;
|
||||
for (0..sd.field_names.len + 1) |i| {
|
||||
// Splice #using entries whose insert_index matches current position
|
||||
while (using_idx < sd.using_entries.len and
|
||||
sd.using_entries[using_idx].insert_index == i)
|
||||
{
|
||||
const entry = sd.using_entries[using_idx];
|
||||
const used_name = self.resolveAlias(entry.type_name);
|
||||
const used_info = self.lookupStructInfo(used_name) orelse
|
||||
return self.emitErrorFmt("#using: struct '{s}' not found (declared before '{s}'?)", .{ entry.type_name, sd.name });
|
||||
|
||||
for (used_info.field_names, 0..) |fname, fi| {
|
||||
try all_names.append(self.allocator, fname);
|
||||
try all_types.append(self.allocator, used_info.field_types[fi]);
|
||||
try all_defaults.append(self.allocator, used_info.field_defaults[fi]);
|
||||
}
|
||||
using_idx += 1;
|
||||
}
|
||||
// Append own field at position i
|
||||
if (i < sd.field_names.len) {
|
||||
try all_names.append(self.allocator, sd.field_names[i]);
|
||||
try all_types.append(self.allocator, self.resolveType(sd.field_types[i]));
|
||||
try all_defaults.append(self.allocator, sd.field_defaults[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Build LLVM struct type with expanded fields
|
||||
var llvm_types = std.ArrayList(c.LLVMTypeRef).empty;
|
||||
for (all_types.items) |ty| {
|
||||
try llvm_types.append(self.allocator, self.typeToLLVM(ty));
|
||||
}
|
||||
const name_z = try self.allocator.dupeZ(u8, sd.name);
|
||||
const struct_ty = c.LLVMStructCreateNamed(self.context, name_z.ptr);
|
||||
const llvm_slice = try llvm_types.toOwnedSlice(self.allocator);
|
||||
c.LLVMStructSetBody(struct_ty, if (llvm_slice.len > 0) llvm_slice.ptr else null, @intCast(llvm_slice.len), 0);
|
||||
|
||||
return StructInfo{
|
||||
.field_names = try all_names.toOwnedSlice(self.allocator),
|
||||
.field_types = try all_types.toOwnedSlice(self.allocator),
|
||||
.field_defaults = try all_defaults.toOwnedSlice(self.allocator),
|
||||
.llvm_type = struct_ty,
|
||||
};
|
||||
}
|
||||
|
||||
fn registerStructType(self: *CodeGen, sd: ast.StructDecl) anyerror!void {
|
||||
// Generic struct: store as template instead of registering now
|
||||
if (sd.type_params.len > 0) {
|
||||
@@ -3628,29 +3695,34 @@ pub const CodeGen = struct {
|
||||
try self.hoistInlineTypeDecl(sd.name, sd.field_names[i], ft);
|
||||
}
|
||||
|
||||
const build = try self.buildStructFields(sd.name, sd.field_types);
|
||||
const sinfo = if (sd.using_entries.len > 0)
|
||||
try self.expandStructUsing(sd)
|
||||
else blk: {
|
||||
const build = try self.buildStructFields(sd.name, sd.field_types);
|
||||
|
||||
// Process field defaults: replace #run expressions with comptime global references
|
||||
var resolved_defaults = try self.allocator.alloc(?*Node, sd.field_defaults.len);
|
||||
for (sd.field_defaults, 0..) |fd, i| {
|
||||
if (fd != null and fd.?.data == .comptime_expr) {
|
||||
const synthetic_name = try std.fmt.allocPrint(self.allocator, "__struct_{s}_field_{d}", .{ sd.name, i });
|
||||
const field_type_override: ?Type = if (i < build.field_sx_types.len) build.field_sx_types[i] else null;
|
||||
try self.registerComptimeGlobal(synthetic_name, fd.?.data.comptime_expr.expr, field_type_override);
|
||||
const id_node = try self.allocator.create(Node);
|
||||
id_node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .identifier = .{ .name = synthetic_name } } };
|
||||
resolved_defaults[i] = id_node;
|
||||
} else {
|
||||
resolved_defaults[i] = fd;
|
||||
// Process field defaults: replace #run expressions with comptime global references
|
||||
var resolved_defaults = try self.allocator.alloc(?*Node, sd.field_defaults.len);
|
||||
for (sd.field_defaults, 0..) |fd, i| {
|
||||
if (fd != null and fd.?.data == .comptime_expr) {
|
||||
const synthetic_name = try std.fmt.allocPrint(self.allocator, "__struct_{s}_field_{d}", .{ sd.name, i });
|
||||
const field_type_override: ?Type = if (i < build.field_sx_types.len) build.field_sx_types[i] else null;
|
||||
try self.registerComptimeGlobal(synthetic_name, fd.?.data.comptime_expr.expr, field_type_override);
|
||||
const id_node = try self.allocator.create(Node);
|
||||
id_node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .identifier = .{ .name = synthetic_name } } };
|
||||
resolved_defaults[i] = id_node;
|
||||
} else {
|
||||
resolved_defaults[i] = fd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sinfo = StructInfo{
|
||||
.field_names = sd.field_names,
|
||||
.field_types = build.field_sx_types,
|
||||
.field_defaults = resolved_defaults,
|
||||
.llvm_type = build.llvm_type,
|
||||
break :blk StructInfo{
|
||||
.field_names = sd.field_names,
|
||||
.field_types = build.field_sx_types,
|
||||
.field_defaults = resolved_defaults,
|
||||
.llvm_type = build.llvm_type,
|
||||
};
|
||||
};
|
||||
|
||||
try self.type_registry.put(sd.name, .{ .struct_info = sinfo });
|
||||
_ = try self.getAnyTypeId(sd.name, .{ .struct_type = sd.name });
|
||||
}
|
||||
@@ -5016,7 +5088,7 @@ pub const CodeGen = struct {
|
||||
fn genFieldAccess(self: *CodeGen, fa: ast.FieldAccess) !c.LLVMValueRef {
|
||||
// Check if the object is a struct or vector variable
|
||||
if (fa.object.data == .identifier) {
|
||||
if (self.named_values.get(fa.object.data.identifier.name)) |entry| {
|
||||
if (self.getNamedOrGlobal(fa.object.data.identifier.name)) |entry| {
|
||||
// Pointer auto-deref: p.field → p.*.field
|
||||
if (entry.ty.isPointer()) {
|
||||
const pointee_ty = self.resolveTypeFromName(entry.ty.pointer_type.pointee_name) orelse
|
||||
@@ -6528,6 +6600,29 @@ pub const CodeGen = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
fn genPushStmt(self: *CodeGen, ps: ast.PushStmt) !c.LLVMValueRef {
|
||||
// Look up the 'context' global mutable variable
|
||||
const ctx_entry = self.global_mutable_vars.get("context") orelse
|
||||
return self.emitError("push requires a global 'context' variable");
|
||||
const ctx_ty = ctx_entry.ty;
|
||||
const llvm_ty = self.typeToLLVM(ctx_ty);
|
||||
|
||||
// Save current context value
|
||||
const saved = c.LLVMBuildLoad2(self.builder, llvm_ty, ctx_entry.ptr, "saved_ctx");
|
||||
|
||||
// Evaluate new context expression and store to global
|
||||
const new_ctx = try self.genExprAsType(ps.context_expr, ctx_ty);
|
||||
_ = c.LLVMBuildStore(self.builder, new_ctx, ctx_entry.ptr);
|
||||
|
||||
// Generate body
|
||||
_ = try self.genExpr(ps.body);
|
||||
|
||||
// Restore saved context
|
||||
_ = c.LLVMBuildStore(self.builder, saved, ctx_entry.ptr);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn genForExpr(self: *CodeGen, for_expr: ast.ForExpr) !c.LLVMValueRef {
|
||||
const i64_type = self.i64Type();
|
||||
|
||||
@@ -7202,6 +7297,7 @@ pub const CodeGen = struct {
|
||||
.bool_literal => .boolean,
|
||||
.string_literal => .string_type,
|
||||
.insert_expr => .void_type,
|
||||
.push_stmt => .void_type,
|
||||
.comptime_expr => |ct| self.inferType(ct.expr),
|
||||
.binary_op => |binop| {
|
||||
switch (binop.op) {
|
||||
|
||||
@@ -968,7 +968,11 @@ pub const Compiler = struct {
|
||||
.pointer_type_expr, .many_pointer_type_expr => {
|
||||
try self.emit(.push_void); // type expressions not meaningful as values
|
||||
},
|
||||
.undef_literal => {
|
||||
try self.emit(.push_void);
|
||||
},
|
||||
.defer_stmt => {}, // defer not meaningful in comptime
|
||||
.push_stmt => {}, // push not meaningful in comptime
|
||||
.insert_expr => {}, // handled by codegen, not VM
|
||||
else => {
|
||||
return error.UnsupportedExpression;
|
||||
@@ -1259,6 +1263,9 @@ pub const VM = struct {
|
||||
} else {
|
||||
try self.push(.{ .void_val = {} });
|
||||
}
|
||||
} else if (obj == .void_val) {
|
||||
// Undefined/zeroed struct — all fields are zero
|
||||
try self.push(.{ .int_val = 0 });
|
||||
} else {
|
||||
return error.TypeError;
|
||||
}
|
||||
@@ -1807,6 +1814,9 @@ pub const VM = struct {
|
||||
.var_decl => |vd| {
|
||||
if (std.mem.eql(u8, vd.name, name)) {
|
||||
if (vd.value) |val_expr| {
|
||||
// undef_literal (= ---) means zeroed/undefined — return void
|
||||
if (val_expr.data == .undef_literal)
|
||||
return .{ .void_val = {} };
|
||||
return self.compileAndEvalGlobal(name, val_expr);
|
||||
}
|
||||
return .{ .void_val = {} };
|
||||
|
||||
@@ -71,6 +71,7 @@ pub const Lexer = struct {
|
||||
.{ "#builtin", Tag.hash_builtin },
|
||||
.{ "#foreign", Tag.hash_foreign },
|
||||
.{ "#library", Tag.hash_library },
|
||||
.{ "#using", Tag.hash_using },
|
||||
};
|
||||
inline for (directives) |d| {
|
||||
const keyword = d[0];
|
||||
|
||||
@@ -992,12 +992,14 @@ pub const Server = struct {
|
||||
.kw_and,
|
||||
.kw_or,
|
||||
.kw_null,
|
||||
.kw_push,
|
||||
.hash_run,
|
||||
.hash_import,
|
||||
.hash_insert,
|
||||
.hash_builtin,
|
||||
.hash_foreign,
|
||||
.hash_library,
|
||||
.hash_using,
|
||||
=> ST.keyword,
|
||||
|
||||
.kw_f32, .kw_f64, .kw_Type => ST.type_,
|
||||
|
||||
@@ -553,8 +553,25 @@ pub const Parser = struct {
|
||||
var field_names = std.ArrayList([]const u8).empty;
|
||||
var field_types = std.ArrayList(*Node).empty;
|
||||
var field_defaults = std.ArrayList(?*Node).empty;
|
||||
var using_entries = std.ArrayList(ast.UsingEntry).empty;
|
||||
|
||||
while (self.current.tag != .r_brace and self.current.tag != .eof) {
|
||||
// Check for #using directive
|
||||
if (self.current.tag == .hash_using) {
|
||||
self.advance(); // skip #using
|
||||
if (self.current.tag != .identifier) {
|
||||
return self.fail("expected type name after '#using'");
|
||||
}
|
||||
const used_type = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
try using_entries.append(self.allocator, .{
|
||||
.insert_index = @intCast(field_names.items.len),
|
||||
.type_name = used_type,
|
||||
});
|
||||
if (self.current.tag == .semicolon) self.advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse field group: name1, name2, ...: type (= default)?;
|
||||
var group_names = std.ArrayList([]const u8).empty;
|
||||
|
||||
@@ -607,6 +624,7 @@ pub const Parser = struct {
|
||||
.field_types = try field_types.toOwnedSlice(self.allocator),
|
||||
.field_defaults = try field_defaults.toOwnedSlice(self.allocator),
|
||||
.type_params = try type_params.toOwnedSlice(self.allocator),
|
||||
.using_entries = try using_entries.toOwnedSlice(self.allocator),
|
||||
} });
|
||||
}
|
||||
|
||||
@@ -959,6 +977,9 @@ pub const Parser = struct {
|
||||
try self.expectSemicolonAfter(expr);
|
||||
return expr;
|
||||
}
|
||||
if (self.current.tag == .kw_push) {
|
||||
return try self.parsePushStmt();
|
||||
}
|
||||
|
||||
// Expression statement
|
||||
const expr = try self.parseExpr();
|
||||
@@ -1326,6 +1347,9 @@ pub const Parser = struct {
|
||||
.kw_for => {
|
||||
return self.parseForExpr();
|
||||
},
|
||||
.kw_push => {
|
||||
return self.parsePushStmt();
|
||||
},
|
||||
.kw_break => {
|
||||
self.advance();
|
||||
return try self.createNode(start, .{ .break_expr = {} });
|
||||
@@ -1473,6 +1497,19 @@ pub const Parser = struct {
|
||||
} });
|
||||
}
|
||||
|
||||
fn parsePushStmt(self: *Parser) anyerror!*Node {
|
||||
const start = self.current.loc.start;
|
||||
self.advance(); // skip 'push'
|
||||
|
||||
const context_expr = try self.parseExpr();
|
||||
const body = try self.parseBlock();
|
||||
|
||||
return try self.createNode(start, .{ .push_stmt = .{
|
||||
.context_expr = context_expr,
|
||||
.body = body,
|
||||
} });
|
||||
}
|
||||
|
||||
fn parseForExpr(self: *Parser) anyerror!*Node {
|
||||
const start = self.current.loc.start;
|
||||
self.advance(); // skip 'for'
|
||||
|
||||
55
src/sema.zig
55
src/sema.zig
@@ -197,16 +197,45 @@ pub const Analyzer = struct {
|
||||
},
|
||||
.struct_decl => |sd| {
|
||||
try self.addSymbol(sd.name, .struct_type, .{ .struct_type = sd.name }, node.span);
|
||||
// Populate struct_types registry
|
||||
var field_types = std.ArrayList(Type).empty;
|
||||
for (sd.field_types) |ft| {
|
||||
const resolved = Type.fromTypeExpr(ft) orelse Type.s(64);
|
||||
try field_types.append(self.allocator, resolved);
|
||||
// Populate struct_types registry, expanding #using entries
|
||||
if (sd.using_entries.len > 0) {
|
||||
var all_names = std.ArrayList([]const u8).empty;
|
||||
var all_types = std.ArrayList(Type).empty;
|
||||
var using_idx: usize = 0;
|
||||
for (0..sd.field_names.len + 1) |i| {
|
||||
while (using_idx < sd.using_entries.len and
|
||||
sd.using_entries[using_idx].insert_index == i)
|
||||
{
|
||||
const entry = sd.using_entries[using_idx];
|
||||
if (self.struct_types.get(entry.type_name)) |used| {
|
||||
for (used.field_names, 0..) |fname, fi| {
|
||||
try all_names.append(self.allocator, fname);
|
||||
try all_types.append(self.allocator, used.field_types[fi]);
|
||||
}
|
||||
}
|
||||
using_idx += 1;
|
||||
}
|
||||
if (i < sd.field_names.len) {
|
||||
try all_names.append(self.allocator, sd.field_names[i]);
|
||||
const resolved = Type.fromTypeExpr(sd.field_types[i]) orelse Type.s(64);
|
||||
try all_types.append(self.allocator, resolved);
|
||||
}
|
||||
}
|
||||
try self.struct_types.put(sd.name, .{
|
||||
.field_names = try all_names.toOwnedSlice(self.allocator),
|
||||
.field_types = try all_types.toOwnedSlice(self.allocator),
|
||||
});
|
||||
} else {
|
||||
var field_types = std.ArrayList(Type).empty;
|
||||
for (sd.field_types) |ft| {
|
||||
const resolved = Type.fromTypeExpr(ft) orelse Type.s(64);
|
||||
try field_types.append(self.allocator, resolved);
|
||||
}
|
||||
try self.struct_types.put(sd.name, .{
|
||||
.field_names = sd.field_names,
|
||||
.field_types = try field_types.toOwnedSlice(self.allocator),
|
||||
});
|
||||
}
|
||||
try self.struct_types.put(sd.name, .{
|
||||
.field_names = sd.field_names,
|
||||
.field_types = try field_types.toOwnedSlice(self.allocator),
|
||||
});
|
||||
},
|
||||
.union_decl => |ud| {
|
||||
try self.addSymbol(ud.name, .enum_type, .{ .union_type = ud.name }, node.span);
|
||||
@@ -701,6 +730,10 @@ pub const Analyzer = struct {
|
||||
.defer_stmt => |ds| {
|
||||
try self.analyzeNode(ds.expr);
|
||||
},
|
||||
.push_stmt => |ps| {
|
||||
try self.analyzeNode(ps.context_expr);
|
||||
try self.analyzeNode(ps.body);
|
||||
},
|
||||
.comptime_expr => |ct| {
|
||||
try self.analyzeNode(ct.expr);
|
||||
},
|
||||
@@ -979,6 +1012,10 @@ pub fn findNodeAtOffset(node: *Node, offset: u32) ?*Node {
|
||||
.defer_stmt => |ds| {
|
||||
if (findNodeAtOffset(ds.expr, offset)) |found| return found;
|
||||
},
|
||||
.push_stmt => |ps| {
|
||||
if (findNodeAtOffset(ps.context_expr, offset)) |found| return found;
|
||||
if (findNodeAtOffset(ps.body, offset)) |found| return found;
|
||||
},
|
||||
.comptime_expr => |ct| {
|
||||
if (findNodeAtOffset(ct.expr, offset)) |found| return found;
|
||||
},
|
||||
|
||||
@@ -29,6 +29,7 @@ pub const Tag = enum {
|
||||
kw_or,
|
||||
kw_Type, // Type (metatype keyword)
|
||||
kw_null, // null
|
||||
kw_push, // push
|
||||
|
||||
// Symbols
|
||||
colon, // :
|
||||
@@ -82,6 +83,7 @@ pub const Tag = enum {
|
||||
hash_builtin, // #builtin
|
||||
hash_foreign, // #foreign
|
||||
hash_library, // #library
|
||||
hash_using, // #using
|
||||
triple_minus, // ---
|
||||
|
||||
// Special
|
||||
@@ -178,6 +180,7 @@ pub const keywords = std.StaticStringMap(Tag).initComptime(.{
|
||||
.{ "or", .kw_or },
|
||||
.{ "Type", .kw_Type },
|
||||
.{ "null", .kw_null },
|
||||
.{ "push", .kw_push },
|
||||
});
|
||||
|
||||
pub fn getKeyword(bytes: []const u8) ?Tag {
|
||||
|
||||
@@ -280,4 +280,14 @@ empty-eq: true
|
||||
=== 25. Array Loop Mutation ===
|
||||
loop-fill: 1 2 3 4
|
||||
compound: 13
|
||||
=== 26. #using ===
|
||||
using-x: 1
|
||||
using-y: 2
|
||||
using-z: 3
|
||||
pkt-id: 10
|
||||
pkt-ver: 42
|
||||
pkt-pay: 99
|
||||
sprite-px: 10
|
||||
sprite-r: 255
|
||||
sprite-scale: 1
|
||||
=== DONE ===
|
||||
|
||||
68
tests/stress-http.sh
Executable file
68
tests/stress-http.sh
Executable file
@@ -0,0 +1,68 @@
|
||||
#!/bin/bash
|
||||
# Stress test for the HTTP server's arena memory management.
|
||||
# Sends requests in rounds and reports memory stats per round.
|
||||
|
||||
set -e
|
||||
|
||||
PORT=9876
|
||||
|
||||
# Build
|
||||
echo "building..."
|
||||
zig build
|
||||
SRC=examples/32-http-server.sx
|
||||
cp "$SRC" "$SRC.bak"
|
||||
sed "s/PORT :: 8080/PORT :: $PORT/" "$SRC.bak" > "$SRC"
|
||||
./zig-out/bin/sx build examples/32-http-server.sx -o /tmp/sx-http-stress
|
||||
mv "$SRC.bak" "$SRC"
|
||||
|
||||
# Start server
|
||||
/tmp/sx-http-stress &
|
||||
SERVER_PID=$!
|
||||
sleep 0.3
|
||||
|
||||
if ! kill -0 $SERVER_PID 2>/dev/null; then
|
||||
echo "FAIL: server did not start"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cleanup() {
|
||||
kill $SERVER_PID 2>/dev/null || true
|
||||
wait $SERVER_PID 2>/dev/null || true
|
||||
rm -f /tmp/sx-http-stress
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
initial_rss=$(ps -o rss= -p $SERVER_PID | tr -d ' ')
|
||||
|
||||
run_round() {
|
||||
local count=$1
|
||||
local failures=0
|
||||
|
||||
for i in $(seq 1 $count); do
|
||||
resp=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:$PORT 2>/dev/null) || true
|
||||
if [ "$resp" != "200" ]; then
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
local rss=$(ps -o rss= -p $SERVER_PID | tr -d ' ')
|
||||
local delta=$((rss - initial_rss))
|
||||
printf " %-8s requests → RSS: %s KB (delta: %+d KB, %d failed)\n" "$count" "$rss" "$delta" "$failures"
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "initial RSS: ${initial_rss} KB"
|
||||
echo ""
|
||||
|
||||
run_round 2000
|
||||
run_round 3000
|
||||
run_round 10000
|
||||
|
||||
final_rss=$(ps -o rss= -p $SERVER_PID | tr -d ' ')
|
||||
total=$((2000 + 3000 + 10000))
|
||||
echo ""
|
||||
echo "--- summary ---"
|
||||
echo "total requests: $total"
|
||||
echo "initial RSS: ${initial_rss} KB"
|
||||
echo "final RSS: ${final_rss} KB"
|
||||
echo "total delta: $((final_rss - initial_rss)) KB"
|
||||
Reference in New Issue
Block a user