Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some fixes from downstream #14

Merged
merged 3 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
* text=auto
*.zig text=auto eol=lf
*.zon text=auto eol=lf
18 changes: 8 additions & 10 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,26 @@ jobs:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: goto-bus-stop/setup-zig@v1
- uses: actions/checkout@v4

- uses: goto-bus-stop/setup-zig@v2
with:
version: master

- run: zig version
- run: zig env

- name: Build
run: zig build
- run: zig build

- name: Build artifacts
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
mkdir -p "artifacts/"
cd artifacts
wget https://raw.githubusercontent.com/microsoft/vscode-languageserver-node/main/protocol/metaModel.json
../zig-out/bin/lspmm-zig
zig build run -- metaModel.json artifacts/lsp.zig

- run: zig fmt --ast-check --check artifacts/lsp.zig

- name: Upload artifacts
if: ${{ matrix.os == 'ubuntu-latest' }}
uses: actions/upload-artifact@v2
with:
name: builds
path: artifacts/*
path: artifacts/*
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ Zig LSP codegen from the newly released, official metamodel! This actually good

1. `git clone`
2. Plop `metaModel.json` in this cloned repo. A copy can be found [here](https://github.com/microsoft/vscode-languageserver-node/blob/main/protocol/metaModel.json).
3. `zig build run`
3. `zig build run -- metaModel.json lsp.zig`
4. Tada! You should now have a `lsp.zig` file that can be used to your heart's content! Enjoy :)
5 changes: 3 additions & 2 deletions build.zig
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
const std = @import("std");

pub fn build(b: *std.build.Builder) void {
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});

const exe = b.addExecutable(.{
.name = "lspmm-zig",
.name = "zig-lsp-codegen",
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
Expand All @@ -28,6 +28,7 @@ pub fn build(b: *std.build.Builder) void {
const tests = b.addTest(.{
.root_source_file = .{ .path = "tests/tests.zig" },
.target = target,
.optimize = optimize,
});

test_step.dependOn(&b.addRunArtifact(tests).step);
Expand Down
1 change: 0 additions & 1 deletion libs/tres
Submodule tres deleted from 93d0cf
12 changes: 0 additions & 12 deletions src/MetaModel.zig
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ pub const BaseTypes = enum {
string,
boolean,
null,

pub fn jsonStringify(self: @This(), options: std.json.StringifyOptions, out_stream: anytype) !void {
try std.json.stringify(@tagName(self), options, out_stream);
}
};

pub const TypeKind = enum {
Expand All @@ -45,21 +41,13 @@ pub const TypeKind = enum {
stringLiteral,
integerLiteral,
booleanLiteral,

pub fn jsonStringify(self: @This(), options: std.json.StringifyOptions, out_stream: anytype) !void {
try std.json.stringify(@tagName(self), options, out_stream);
}
};

/// Indicates in which direction a message is sent in the protocol.
pub const MessageDirection = enum {
clientToServer,
serverToClient,
both,

pub fn jsonStringify(self: @This(), options: std.json.StringifyOptions, out_stream: anytype) !void {
try std.json.stringify(@tagName(self), options, out_stream);
}
};

/// Represents a base type like `string` or `DocumentUri`.
Expand Down
8 changes: 4 additions & 4 deletions src/base.zig
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pub fn Map(comptime Key: type, comptime Value: type) type {
pub fn UnionParser(comptime T: type) type {
return struct {
pub fn jsonParse(allocator: std.mem.Allocator, source: anytype, options: std.json.ParseOptions) std.json.ParseError(@TypeOf(source.*))!T {
const json_value = try std.json.parseFromTokenSourceLeaky(std.json.Value, allocator, source, options);
const json_value = try std.json.Value.jsonParse(allocator, source, options);
return try jsonParseFromValue(allocator, json_value, options);
}

Expand All @@ -95,7 +95,7 @@ pub fn EnumCustomStringValues(comptime T: type, comptime contains_empty_enum: bo
const KV = struct { []const u8, T };
const fields = @typeInfo(T).Union.fields;
var kvs_array: [fields.len - 1]KV = undefined;
inline for (fields[0 .. fields.len - 1], 0..) |field, i| {
for (fields[0 .. fields.len - 1], 0..) |field, i| {
kvs_array[i] = .{ field.name, @field(T, field.name) };
}
break :build_kvs kvs_array[0..];
Expand All @@ -117,13 +117,13 @@ pub fn EnumCustomStringValues(comptime T: type, comptime contains_empty_enum: bo
}

pub fn jsonParse(allocator: std.mem.Allocator, source: anytype, options: std.json.ParseOptions) std.json.ParseError(@TypeOf(source.*))!T {
const slice = try std.json.parseFromTokenSourceLeaky([]const u8, allocator, source, options);
const slice = try std.json.Value.jsonParse(allocator, source, options);
if (contains_empty_enum and slice.len == 0) return .empty;
return map.get(slice) orelse return .{ .custom_value = slice };
}

pub fn jsonParseFromValue(allocator: std.mem.Allocator, source: std.json.Value, options: std.json.ParseOptions) std.json.ParseFromValueError!T {
const slice = try std.json.parseFromValueLeaky([]const u8, allocator, source, options);
const slice = try std.json.innerParse([]const u8, allocator, source, options);
if (contains_empty_enum and slice.len == 0) return .empty;
return map.get(slice) orelse return .{ .custom_value = slice };
}
Expand Down
23 changes: 19 additions & 4 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@ pub fn main() !void {

const gpa = general_purpose_allocator.allocator();

const model_file = try std.fs.cwd().openFile("metaModel.json", .{});
defer model_file.close();
var arg_it = try std.process.ArgIterator.initWithAllocator(gpa);

const model_file_source = try model_file.readToEndAlloc(gpa, std.math.maxInt(usize));
_ = arg_it.skip(); // skip self exe
const model_file_path = try gpa.dupe(u8, arg_it.next() orelse std.debug.panic("first argument must be the path to the metaModel.json", .{}));
defer gpa.free(model_file_path);

const out_file_path = try gpa.dupe(u8, arg_it.next() orelse std.debug.panic("second argument must be the output path to the generated zig code", .{}));
defer gpa.free(out_file_path);

const model_file_source = try std.fs.cwd().readFileAlloc(gpa, model_file_path, std.math.maxInt(usize));
defer gpa.free(model_file_source);

const json_value = try std.json.parseFromSlice(std.json.Value, gpa, model_file_source, .{});
Expand All @@ -37,7 +43,9 @@ pub fn main() !void {
} else try zig_tree.render(gpa);
defer if (zig_tree.errors.len == 0) gpa.free(output_source);

var out_file = try std.fs.cwd().createFile("lsp.zig", .{});
std.fs.cwd().makePath(std.fs.path.dirname(out_file_path) orelse ".") catch {};

var out_file = try std.fs.cwd().createFile(out_file_path, .{});
defer out_file.close();

try out_file.writeAll(output_source);
Expand Down Expand Up @@ -379,11 +387,18 @@ fn writeEnumeration(writer: anytype, meta_model: MetaModel, enumeration: MetaMod
.uinteger => try writer.print("pub const {} = enum(u32) {{\n", .{std.zig.fmtId(enumeration.name)}),
}

// WORKAROUND: the enumeration value `pascal` appears twice in LanguageKind
var found_pascal = false;

var contains_empty_enum = false;
for (enumeration.values) |entry| {
if (entry.documentation) |docs| try writeDocs(writer, docs);
switch (entry.value) {
.string => |value| {
if (std.mem.eql(u8, value, "pascal")) {
if (found_pascal) continue;
found_pascal = true;
}
if (value.len == 0) contains_empty_enum = true;
const name = if (value.len == 0) "empty" else value;
try writer.print("{},\n", .{std.zig.fmtId(name)});
Expand Down
78 changes: 15 additions & 63 deletions tests/tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@ const Server = @This();

const lsp = @import("lsp");
const std = @import("std");
const tres = @import("tres");

arena: std.heap.ArenaAllocator,
parser: std.json.Parser,

read_buf: std.ArrayList(u8),
write_buf: std.ArrayList(u8),

const SampleDirection = enum {
client_to_server,
Expand Down Expand Up @@ -36,72 +29,34 @@ const SampleEntryKind = enum {
// TODO: Handle responses
const SampleEntry = struct {
isLSPMessage: bool,
@"type": SampleEntryKind,
type: SampleEntryKind,
message: std.json.Value,
};

pub fn readLine(self: *Server, reader: anytype) !void {
while (true) {
var byte = try reader.readByte();

if (byte == '\n') {
return;
}

if (self.read_buf.items.len == self.read_buf.capacity) {
try self.read_buf.ensureTotalCapacity(self.read_buf.capacity + 1);
}

try self.read_buf.append(byte);
}
}

pub fn flushArena(self: *Server) void {
self.arena.deinit();
self.arena.state = .{};
}

test {
@setEvalBranchQuota(100_000);

var log_dir = try std.fs.cwd().openDir("samples", .{ .iterate = true });
defer log_dir.close();

var log = try log_dir.openFile("amogus-json.log", .{});
defer log.close();
var log_file = try std.fs.cwd().openFile("samples/amogus-json.log", .{});
defer log_file.close();

var reader = log.reader();
// reader.readAll()
const reader = log_file.reader();

const allocator = std.heap.page_allocator;
var arena = std.heap.ArenaAllocator.init(allocator);

var server = Server{
.arena = arena,
.parser = std.json.Parser.init(arena.allocator(), false),

.read_buf = try std.ArrayList(u8).initCapacity(allocator, 1024),
.write_buf = try std.ArrayList(u8).initCapacity(allocator, 1024),
};

var parser = std.json.Parser.init(server.arena.allocator(), false);
var read_buffer = std.ArrayList(u8).init(std.testing.allocator);
defer read_buffer.deinit();

while (true) {
server.readLine(reader) catch |err| switch (err) {
error.EndOfStream => return,
else => return std.log.err("{s}", .{err}),
try reader.readUntilDelimiterArrayList(&read_buffer, '\n', std.math.maxInt(u32)) catch |err| switch (err) {
error.EndOfStream => break,
else => |e| return e,
};

const tree = try parser.parse(server.read_buf.items);
defer parser.reset();
const entry = try tres.parse(SampleEntry, tree.root, arena.allocator());
const parsed_sample_entry = try std.json.parseFromSlice(SampleEntry, std.testing.allocator, read_buffer.items, .{});
const sample_entry = parsed_sample_entry.value;

if (entry.isLSPMessage) {
switch (entry.@"type") {
if (sample_entry.isLSPMessage) {
switch (sample_entry.type) {
.@"send-notification",
.@"receive-notification",
=> a: {
_ = tres.parse(lsp.Notification, entry.message, allocator) catch |err| {
_ = tres.parse(lsp.Notification, sample_entry.message, std.testing.allocator) catch |err| {
// Ignore unknown methods such as custom VSCode LSP methods
if (err == error.UnknownMethod) break :a;
std.log.err("Cannot handle Request or Notification of method \"{s}\"", .{entry.message.Object.get("method").?.String});
Expand All @@ -111,7 +66,7 @@ test {
.@"send-request",
.@"receive-request",
=> a: {
_ = tres.parse(lsp.Request, entry.message, allocator) catch |err| {
_ = tres.parse(lsp.Request, entry.message, std.testing.allocator) catch |err| {
// Ignore unknown methods such as custom VSCode LSP methods
if (err == error.UnknownMethod) break :a;
std.log.err("Cannot handle Request or Notification of method \"{s}\"", .{entry.message.Object.get("method").?.String});
Expand All @@ -121,8 +76,5 @@ test {
else => {},
}
}

server.read_buf.items.len = 0;
// arena.deinit();
}
}
Loading