Skip to content

Commit

Permalink
Merge pull request #14 from nullptrdevs/patch-1
Browse files Browse the repository at this point in the history
Techatrix authored Feb 16, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2 parents fe870dc + bb83587 commit 7b4e4ec
Showing 9 changed files with 51 additions and 97 deletions.
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
@@ -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
@@ -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,
@@ -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);
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
@@ -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 {
@@ -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`.
8 changes: 4 additions & 4 deletions src/base.zig
Original file line number Diff line number Diff line change
@@ -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);
}

@@ -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..];
@@ -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 };
}
23 changes: 19 additions & 4 deletions src/main.zig
Original file line number Diff line number Diff line change
@@ -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, .{});
@@ -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);
@@ -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)});
78 changes: 15 additions & 63 deletions tests/tests.zig
Original file line number Diff line number Diff line change
@@ -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,
@@ -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});
@@ -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});
@@ -121,8 +76,5 @@ test {
else => {},
}
}

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

0 comments on commit 7b4e4ec

Please sign in to comment.