This commit is contained in:
agra
2026-02-11 21:22:03 +02:00
parent 9d96f05d3b
commit 9a2501f662
5 changed files with 449 additions and 56 deletions

View File

@@ -19,22 +19,75 @@ pub fn main(init: std.process.Init) !void {
return;
}
if (args.len < 3) {
printUsage();
return;
// Parse flags and positional arguments
var input_path: ?[]const u8 = null;
var target_config = sx.codegen.TargetConfig{};
var lib_paths = std.ArrayList([]const u8).empty;
var i: usize = 2;
while (i < args.len) : (i += 1) {
const arg = args[i];
if (std.mem.eql(u8, arg, "--target")) {
i += 1;
if (i >= args.len) { std.debug.print("error: --target requires a value\n", .{}); return; }
target_config.triple = (try allocator.dupeZ(u8, args[i])).ptr;
} else if (std.mem.eql(u8, arg, "--cpu")) {
i += 1;
if (i >= args.len) { std.debug.print("error: --cpu requires a value\n", .{}); return; }
target_config.cpu = (try allocator.dupeZ(u8, args[i])).ptr;
} else if (std.mem.eql(u8, arg, "--opt")) {
i += 1;
if (i >= args.len) { std.debug.print("error: --opt requires a value\n", .{}); return; }
target_config.opt_level = parseOptLevel(args[i]) orelse {
std.debug.print("error: invalid --opt value '{s}' (expected: none/0, less/1, default/2, aggressive/3)\n", .{args[i]});
return;
};
} else if (std.mem.eql(u8, arg, "-o")) {
i += 1;
if (i >= args.len) { std.debug.print("error: -o requires a value\n", .{}); return; }
target_config.output_path = args[i];
} else if (std.mem.eql(u8, arg, "--linker")) {
i += 1;
if (i >= args.len) { std.debug.print("error: --linker requires a value\n", .{}); return; }
target_config.linker = args[i];
} else if (std.mem.eql(u8, arg, "--sysroot")) {
i += 1;
if (i >= args.len) { std.debug.print("error: --sysroot requires a value\n", .{}); return; }
target_config.sysroot = args[i];
} else if (std.mem.startsWith(u8, arg, "-L")) {
if (arg.len > 2) {
try lib_paths.append(allocator, arg[2..]);
} else {
i += 1;
if (i >= args.len) { std.debug.print("error: -L requires a value\n", .{}); return; }
try lib_paths.append(allocator, args[i]);
}
} else if (!std.mem.startsWith(u8, arg, "-")) {
input_path = arg;
} else {
std.debug.print("error: unknown flag '{s}'\n", .{arg});
return;
}
}
const input_path = args[2];
target_config.lib_paths = try lib_paths.toOwnedSlice(allocator);
const path = input_path orelse {
printUsage();
return;
};
if (std.mem.eql(u8, command, "build")) {
const output_name = deriveOutputName(input_path);
compile(allocator, io, input_path, output_name) catch return;
const output_name = target_config.output_path orelse deriveOutputName(path);
compile(allocator, io, path, output_name, target_config) catch return;
std.debug.print("compiled: {s}\n", .{output_name});
} else if (std.mem.eql(u8, command, "ir")) {
emitIR(allocator, io, input_path) catch return;
emitIR(allocator, io, path, target_config) catch return;
} else if (std.mem.eql(u8, command, "asm")) {
emitAsm(allocator, io, path, target_config) catch return;
} else if (std.mem.eql(u8, command, "run")) {
const tmp_bin = "/tmp/sx_run_tmp";
compile(allocator, io, input_path, tmp_bin) catch return;
const tmp_bin = if (comptime @import("builtin").os.tag == .windows) "sx_run_tmp.exe" else "/tmp/sx_run_tmp";
compile(allocator, io, path, tmp_bin, target_config) catch return;
defer {
std.Io.Dir.deleteFile(.cwd(), io, tmp_bin) catch {};
}
@@ -53,16 +106,34 @@ pub fn main(init: std.process.Init) !void {
}
}
fn parseOptLevel(s: []const u8) ?sx.codegen.TargetConfig.OptLevel {
if (std.mem.eql(u8, s, "none") or std.mem.eql(u8, s, "0")) return .none;
if (std.mem.eql(u8, s, "less") or std.mem.eql(u8, s, "1")) return .less;
if (std.mem.eql(u8, s, "default") or std.mem.eql(u8, s, "2")) return .default;
if (std.mem.eql(u8, s, "aggressive") or std.mem.eql(u8, s, "3")) return .aggressive;
return null;
}
fn printUsage() void {
std.debug.print(
\\Usage: sx <command> [file.sx]
\\Usage: sx <command> [options] <file.sx>
\\
\\Commands:
\\ run Build and run immediately
\\ build Build binary in current directory
\\ ir Print LLVM IR to stdout
\\ asm Emit assembly (.s) file
\\ lsp Start language server (LSP)
\\
\\Options:
\\ --target <triple> Target triple (default: host)
\\ --cpu <name> CPU name (default: generic)
\\ --opt <level> Optimization: none/0, less/1, default/2, aggressive/3
\\ -o <path> Output path
\\ -L <path> Library search path (repeatable)
\\ --linker <cmd> Linker command (default: cc)
\\ --sysroot <path> Sysroot for cross-compilation
\\
, .{});
}
@@ -96,7 +167,7 @@ fn deriveOutputName(input_path: []const u8) []const u8 {
// Get basename (strip directory)
var start: usize = 0;
for (input_path, 0..) |ch, i| {
if (ch == '/') start = i + 1;
if (ch == '/' or ch == '\\') start = i + 1;
}
const basename = input_path[start..];
// Strip .sx extension
@@ -115,10 +186,10 @@ fn readSource(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8)
return try allocator.dupeZ(u8, source_bytes);
}
fn compilePipeline(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8) !sx.core.Compilation {
fn compilePipeline(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.TargetConfig) !sx.core.Compilation {
const source = try readSource(allocator, io, input_path);
var comp = sx.core.Compilation.init(allocator, io, input_path, source);
var comp = sx.core.Compilation.init(allocator, io, input_path, source, target_config);
errdefer comp.deinit();
comp.parse() catch { comp.renderErrors(); return error.CompileError; };
@@ -131,14 +202,26 @@ fn compilePipeline(allocator: std.mem.Allocator, io: std.Io, input_path: []const
return comp;
}
fn emitIR(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8) !void {
var comp = try compilePipeline(allocator, io, input_path);
fn emitIR(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.TargetConfig) !void {
var comp = try compilePipeline(allocator, io, input_path, target_config);
defer comp.deinit();
comp.cg.?.printIR();
}
fn compile(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, output_path: []const u8) !void {
var comp = try compilePipeline(allocator, io, input_path);
fn emitAsm(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.TargetConfig) !void {
var comp = try compilePipeline(allocator, io, input_path, target_config);
defer comp.deinit();
const asm_path = target_config.output_path orelse blk: {
const name = deriveOutputName(input_path);
break :blk try std.fmt.allocPrint(allocator, "{s}.s", .{name});
};
const asm_path_z = try allocator.dupeZ(u8, asm_path);
comp.cg.?.emitAssembly(asm_path_z.ptr) catch { comp.renderErrors(); return error.CompileError; };
std.debug.print("emitted: {s}\n", .{asm_path});
}
fn compile(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, output_path: []const u8, target_config: sx.codegen.TargetConfig) !void {
var comp = try compilePipeline(allocator, io, input_path, target_config);
defer comp.deinit();
var cg = &comp.cg.?;
@@ -148,7 +231,7 @@ fn compile(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, out
cg.emitObject(obj_path.ptr) catch { comp.renderErrors(); return error.CompileError; };
// Link
sx.codegen.CodeGen.link(allocator, io, obj_path, output_path, cg.foreign_libraries.items) catch {
sx.codegen.CodeGen.link(allocator, io, obj_path, output_path, cg.foreign_libraries.items, target_config) catch {
std.debug.print("error: linking failed\n", .{});
return error.CompileError;
};