276 lines
9.9 KiB
Zig
276 lines
9.9 KiB
Zig
const std = @import("std");
|
|
const zig_builtin = @import("builtin");
|
|
const builtin = @import("builtin");
|
|
const Config = @import("Config.zig");
|
|
const ast = @import("ast.zig");
|
|
const tracy = @import("tracy.zig");
|
|
const Ast = std.zig.Ast;
|
|
const URI = @import("uri.zig");
|
|
const ZCS = @import("ZigCompileServer.zig");
|
|
const log = std.log.scoped(.zls_translate_c);
|
|
|
|
/// converts a `@cInclude` node into an equivalent c header file
|
|
/// which can then be handed over to `zig translate-c`
|
|
/// Caller owns returned memory.
|
|
///
|
|
/// **Example**
|
|
/// ```zig
|
|
/// const glfw = @cImport(
|
|
/// @cDefine("GLFW_INCLUDE_VULKAN", {})
|
|
/// @cInclude("GLFW/glfw3.h")
|
|
/// );
|
|
/// ```
|
|
/// gets converted into:
|
|
/// ```c
|
|
/// #define GLFW_INCLUDE_VULKAN
|
|
/// #include "GLFW/glfw3.h"
|
|
/// ```
|
|
pub fn convertCInclude(allocator: std.mem.Allocator, tree: Ast, node: Ast.Node.Index) error{ OutOfMemory, Unsupported }![]const u8 {
|
|
const tracy_zone = tracy.trace(@src());
|
|
defer tracy_zone.end();
|
|
|
|
const main_tokens = tree.nodes.items(.main_token);
|
|
|
|
std.debug.assert(ast.isBuiltinCall(tree, node));
|
|
std.debug.assert(std.mem.eql(u8, Ast.tokenSlice(tree, main_tokens[node]), "@cImport"));
|
|
|
|
var output = std.ArrayListUnmanaged(u8){};
|
|
errdefer output.deinit(allocator);
|
|
|
|
var stack_allocator = std.heap.stackFallback(512, allocator);
|
|
|
|
var buffer: [2]Ast.Node.Index = undefined;
|
|
for (ast.builtinCallParams(tree, node, &buffer).?) |child| {
|
|
try convertCIncludeInternal(allocator, stack_allocator.get(), tree, child, &output);
|
|
}
|
|
|
|
return output.toOwnedSlice(allocator);
|
|
}
|
|
|
|
/// HACK self-hosted has not implemented async yet
|
|
fn callConvertCIncludeInternal(allocator: std.mem.Allocator, args: anytype) error{ OutOfMemory, Unsupported }!void {
|
|
if (zig_builtin.zig_backend == .other or zig_builtin.zig_backend == .stage1) {
|
|
const FrameSize = @sizeOf(@Frame(convertCIncludeInternal));
|
|
var child_frame = try allocator.alignedAlloc(u8, std.Target.stack_align, FrameSize);
|
|
defer allocator.free(child_frame);
|
|
|
|
return await @asyncCall(child_frame, {}, convertCIncludeInternal, args);
|
|
} else {
|
|
// TODO find a non recursive solution
|
|
return @call(.auto, convertCIncludeInternal, args);
|
|
}
|
|
}
|
|
|
|
fn convertCIncludeInternal(
|
|
allocator: std.mem.Allocator,
|
|
stack_allocator: std.mem.Allocator,
|
|
tree: Ast,
|
|
node: Ast.Node.Index,
|
|
output: *std.ArrayListUnmanaged(u8),
|
|
) error{ OutOfMemory, Unsupported }!void {
|
|
const node_tags = tree.nodes.items(.tag);
|
|
const main_tokens = tree.nodes.items(.main_token);
|
|
|
|
var writer = output.writer(allocator);
|
|
|
|
var buffer: [2]Ast.Node.Index = undefined;
|
|
if (ast.blockStatements(tree, node, &buffer)) |statements| {
|
|
for (statements) |statement| {
|
|
try callConvertCIncludeInternal(stack_allocator, .{ allocator, stack_allocator, tree, statement, output });
|
|
}
|
|
} else if (ast.builtinCallParams(tree, node, &buffer)) |params| {
|
|
if (params.len < 1) return;
|
|
|
|
const call_name = Ast.tokenSlice(tree, main_tokens[node]);
|
|
|
|
if (node_tags[params[0]] != .string_literal) return error.Unsupported;
|
|
const first = extractString(Ast.tokenSlice(tree, main_tokens[params[0]]));
|
|
|
|
if (std.mem.eql(u8, call_name, "@cInclude")) {
|
|
try writer.print("#include <{s}>\n", .{first});
|
|
} else if (std.mem.eql(u8, call_name, "@cDefine")) {
|
|
if (params.len < 2) return;
|
|
|
|
var buffer2: [2]Ast.Node.Index = undefined;
|
|
const is_void = if (ast.blockStatements(tree, params[1], &buffer2)) |block| block.len == 0 else false;
|
|
|
|
if (is_void) {
|
|
try writer.print("#define {s}\n", .{first});
|
|
} else {
|
|
if (node_tags[params[1]] != .string_literal) return error.Unsupported;
|
|
const second = extractString(Ast.tokenSlice(tree, main_tokens[params[1]]));
|
|
try writer.print("#define {s} {s}\n", .{ first, second });
|
|
}
|
|
} else if (std.mem.eql(u8, call_name, "@cUndef")) {
|
|
try writer.print("#undef {s}\n", .{first});
|
|
} else {
|
|
return error.Unsupported;
|
|
}
|
|
}
|
|
}
|
|
|
|
pub const Result = union(enum) {
|
|
// uri to the generated zig file
|
|
success: []const u8,
|
|
// zig translate-c failed with the given stderr content
|
|
failure: []const u8,
|
|
|
|
pub fn deinit(self: *Result, allocator: std.mem.Allocator) void {
|
|
switch (self.*) {
|
|
.success => |path| allocator.free(path),
|
|
.failure => |stderr| allocator.free(stderr),
|
|
}
|
|
}
|
|
|
|
pub fn dupe(self: Result, allocator: std.mem.Allocator) !Result {
|
|
return switch (self) {
|
|
.success => |path| .{ .success = try allocator.dupe(u8, path) },
|
|
.failure => |stderr| .{ .failure = try allocator.dupe(u8, stderr) },
|
|
};
|
|
}
|
|
};
|
|
|
|
/// takes a c header file and returns the result from calling `zig translate-c`
|
|
/// returns a URI to the generated zig file on success or the content of stderr on failure
|
|
/// null indicates a failure which is automatically logged
|
|
/// Caller owns returned memory.
|
|
pub fn translate(
|
|
allocator: std.mem.Allocator,
|
|
config: Config,
|
|
include_dirs: []const []const u8,
|
|
source: []const u8,
|
|
) !?Result {
|
|
const tracy_zone = tracy.trace(@src());
|
|
defer tracy_zone.end();
|
|
|
|
const file_path = try std.fs.path.join(allocator, &[_][]const u8{ config.global_cache_path.?, "cimport.h" });
|
|
defer allocator.free(file_path);
|
|
|
|
var file = std.fs.createFileAbsolute(file_path, .{}) catch |err| {
|
|
log.warn("failed to create file '{s}': {}", .{ file_path, err });
|
|
return null;
|
|
};
|
|
defer file.close();
|
|
defer std.fs.deleteFileAbsolute(file_path) catch |err| {
|
|
log.warn("failed to delete file '{s}': {}", .{ file_path, err });
|
|
};
|
|
|
|
_ = file.write(source) catch |err| {
|
|
log.warn("failed to write to '{s}': {}", .{ file_path, err });
|
|
return null;
|
|
};
|
|
|
|
const base_args = &[_][]const u8{
|
|
config.zig_exe_path orelse return null,
|
|
"translate-c",
|
|
"--zig-lib-dir",
|
|
config.zig_lib_path orelse return null,
|
|
"--cache-dir",
|
|
config.global_cache_path.?,
|
|
"-lc",
|
|
"--listen=-",
|
|
};
|
|
|
|
const argc = base_args.len + 2 * include_dirs.len + 1;
|
|
var argv = try std.ArrayListUnmanaged([]const u8).initCapacity(allocator, argc);
|
|
defer argv.deinit(allocator);
|
|
|
|
argv.appendSliceAssumeCapacity(base_args);
|
|
|
|
for (include_dirs) |include_dir| {
|
|
argv.appendAssumeCapacity("-I");
|
|
argv.appendAssumeCapacity(include_dir);
|
|
}
|
|
|
|
argv.appendAssumeCapacity(file_path);
|
|
|
|
var process = std.ChildProcess.init(argv.items, allocator);
|
|
process.stdin_behavior = .Pipe;
|
|
process.stdout_behavior = .Pipe;
|
|
process.stderr_behavior = .Pipe;
|
|
|
|
errdefer |err| if (!zig_builtin.is_test) blk: {
|
|
const joined = std.mem.join(allocator, " ", argv.items) catch break :blk;
|
|
defer allocator.free(joined);
|
|
if (process.stderr) |stderr| {
|
|
const stderr_output = stderr.readToEndAlloc(allocator, std.math.maxInt(usize)) catch break :blk;
|
|
defer allocator.free(stderr_output);
|
|
log.err("failed zig translate-c command:\n{s}\nstderr:{s}\nerror:{}\n", .{ joined, stderr_output, err });
|
|
} else {
|
|
log.err("failed zig translate-c command:\n{s}\nerror:{}\n", .{ joined, err });
|
|
}
|
|
};
|
|
|
|
process.spawn() catch |err| {
|
|
log.err("failed to spawn zig translate-c process, error: {}", .{err});
|
|
return null;
|
|
};
|
|
|
|
defer _ = process.wait() catch |wait_err| blk: {
|
|
log.err("zig translate-c process did not terminate, error: {}", .{wait_err});
|
|
break :blk process.kill() catch |kill_err| {
|
|
std.debug.panic("failed to terminate zig translate-c process, error: {}", .{kill_err});
|
|
};
|
|
};
|
|
|
|
var zcs = ZCS.init(.{
|
|
.gpa = allocator,
|
|
.in = process.stdout.?,
|
|
.out = process.stdin.?,
|
|
});
|
|
defer zcs.deinit();
|
|
|
|
try zcs.serveMessage(.{ .tag = .update, .bytes_len = 0 }, &.{});
|
|
try zcs.serveMessage(.{ .tag = .exit, .bytes_len = 0 }, &.{});
|
|
|
|
while (true) {
|
|
const header = try zcs.receiveMessage();
|
|
// log.debug("received header: {}", .{header});
|
|
|
|
switch (header.tag) {
|
|
.zig_version => {
|
|
// log.debug("zig-version: {s}", .{zcs.receive_fifo.readableSliceOfLen(header.bytes_len)});
|
|
zcs.pooler.fifo(.in).discard(header.bytes_len);
|
|
},
|
|
.emit_bin_path => {
|
|
const body_size = @sizeOf(std.zig.Server.Message.EmitBinPath);
|
|
if (header.bytes_len <= body_size) return error.InvalidResponse;
|
|
|
|
const trailing_size = header.bytes_len - body_size;
|
|
|
|
_ = try zcs.receiveEmitBinPath();
|
|
|
|
const result_path = try zcs.receiveBytes(allocator, trailing_size);
|
|
defer allocator.free(result_path);
|
|
|
|
return Result{ .success = try URI.fromPath(allocator, std.mem.sliceTo(result_path, '\n')) };
|
|
},
|
|
.error_bundle => {
|
|
const error_bundle_header = try zcs.receiveErrorBundle();
|
|
|
|
const extra = try zcs.receiveIntArray(allocator, error_bundle_header.extra_len);
|
|
defer allocator.free(extra);
|
|
|
|
const string_bytes = try zcs.receiveBytes(allocator, error_bundle_header.string_bytes_len);
|
|
defer allocator.free(string_bytes);
|
|
|
|
const error_bundle = std.zig.ErrorBundle{ .string_bytes = string_bytes, .extra = extra };
|
|
|
|
return Result{ .failure = try allocator.dupe(u8, error_bundle.getCompileLogOutput()) };
|
|
},
|
|
else => {
|
|
log.warn("received unexpected message {} from zig compile server", .{header.tag});
|
|
return null;
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
fn extractString(str: []const u8) []const u8 {
|
|
if (std.mem.startsWith(u8, str, "\"") and std.mem.endsWith(u8, str, "\"")) {
|
|
return str[1 .. str.len - 1];
|
|
} else {
|
|
return str;
|
|
}
|
|
}
|