zls/src/translate_c.zig

205 lines
7.3 KiB
Zig
Raw Normal View History

2022-08-18 23:00:46 +01:00
const std = @import("std");
const zig_builtin = @import("builtin");
2022-08-18 23:00:46 +01:00
const builtin = @import("builtin");
const Config = @import("Config.zig");
const ast = @import("ast.zig");
const Ast = std.zig.Ast;
const URI = @import("uri.zig");
const log = std.log.scoped(.zls_translate_c);
2022-08-18 23:00:46 +01:00
/// 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 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"));
2022-08-23 11:44:26 +01:00
var output = std.ArrayListUnmanaged(u8){};
errdefer output.deinit(allocator);
2022-08-18 23:00:46 +01:00
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);
2022-08-18 23:00:46 +01:00
}
2022-08-23 11:44:26 +01:00
return output.toOwnedSlice(allocator);
2022-08-18 23:00:46 +01:00
}
/// 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 {
2022-08-18 23:00:46 +01:00
const node_tags = tree.nodes.items(.tag);
const main_tokens = tree.nodes.items(.main_token);
2022-08-23 11:44:26 +01:00
var writer = output.writer(allocator);
2022-08-18 23:00:46 +01:00
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 });
2022-08-18 23:00:46 +01:00
}
} 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")) {
2022-08-23 11:44:26 +01:00
try writer.print("#include <{s}>\n", .{first});
2022-08-18 23:00:46 +01:00
} 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) {
2022-08-23 11:44:26 +01:00
try writer.print("#define {s}\n", .{first});
2022-08-18 23:00:46 +01:00
} else {
if (node_tags[params[1]] != .string_literal) return error.Unsupported;
const second = extractString(Ast.tokenSlice(tree, main_tokens[params[1]]));
2022-08-23 11:44:26 +01:00
try writer.print("#define {s} {s}\n", .{ first, second });
2022-08-18 23:00:46 +01:00
}
} else if (std.mem.eql(u8, call_name, "@cUndef")) {
try writer.print("#undef {s}\n", .{first});
2022-08-18 23:00:46 +01:00
} 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) },
};
}
};
2022-08-18 23:00:46 +01:00
/// 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
2022-08-18 23:00:46 +01:00
/// Caller owns returned memory.
pub fn translate(allocator: std.mem.Allocator, config: Config, include_dirs: []const []const u8, source: []const u8) error{OutOfMemory}!?Result {
2022-08-18 23:00:46 +01:00
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 });
2022-08-18 23:00:46 +01:00
return null;
};
defer file.close();
defer std.fs.deleteFileAbsolute(file_path) catch |err| {
log.warn("failed to delete file '{s}': {}", .{ file_path, err });
2022-08-18 23:00:46 +01:00
};
_ = file.write(source) catch |err| {
log.warn("failed to write to '{s}': {}", .{ file_path, err });
return null;
2022-08-18 23:00:46 +01:00
};
const base_args = &[_][]const u8{
config.zig_exe_path orelse return null,
2022-08-18 23:00:46 +01:00
"translate-c",
"--enable-cache",
"--zig-lib-dir",
config.zig_lib_path orelse return null,
2022-08-18 23:00:46 +01:00
"--cache-dir",
config.global_cache_path.?,
2022-09-17 20:16:36 +01:00
"-lc",
2022-08-18 23:00:46 +01:00
};
const argc = base_args.len + 2 * include_dirs.len + 1;
2022-08-18 23:00:46 +01:00
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);
const result = std.ChildProcess.exec(.{
.allocator = allocator,
.argv = argv.items,
}) catch |err| {
log.err("Failed to execute zig translate-c process, error: {}", .{err});
2022-08-18 23:00:46 +01:00
return null;
};
defer allocator.free(result.stdout);
defer allocator.free(result.stderr);
return switch (result.term) {
.Exited => |code| if (code == 0) {
return Result{ .success = try URI.fromPath(allocator, std.mem.sliceTo(result.stdout, '\n')) };
2022-08-18 23:00:46 +01:00
} else {
return Result{ .failure = try allocator.dupe(u8, std.mem.sliceTo(result.stderr, '\n')) };
2022-08-18 23:00:46 +01:00
},
else => {
log.err("zig translate-c process terminated '{}'", .{result.term});
2022-08-18 23:00:46 +01:00
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;
}
}