Skip to content

Commit

Permalink
add option for parsing a single file
Browse files Browse the repository at this point in the history
  • Loading branch information
nolanderc committed Oct 11, 2023
1 parent 6023718 commit 53dfed0
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 32 deletions.
16 changes: 8 additions & 8 deletions src/Document.zig
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub fn invalidate(self: *@This()) void {
self.parse_tree = null;
}

pub fn source(self: *@This()) []const u8 {
pub fn source(self: @This()) []const u8 {
return self.contents.items;
}

Expand Down Expand Up @@ -64,20 +64,20 @@ pub fn utf8FromPosition(self: @This(), position: lsp.Position) u32 {
return @intCast(i + codepoints.i);
}

pub fn positionFromUtf8(self: @This(), offset: u32) lsp.Position {
pub fn positionFromUtf8(text: []const u8, offset: u32) lsp.Position {
var line_breaks: u32 = 0;
var line_start: usize = 0;

const text = self.contents.items[0..offset];
const before = text[0..offset];

for (text, 0..) |ch, index| {
for (before, 0..) |ch, index| {
if (ch == '\n') {
line_breaks += 1;
line_start = index + 1;
}
}

const last_line = text[line_start..];
const last_line = before[line_start..];
const character = std.unicode.calcUtf16LeLen(last_line) catch last_line.len;

return .{ .line = line_breaks, .character = @intCast(character) };
Expand All @@ -86,16 +86,16 @@ pub fn positionFromUtf8(self: @This(), offset: u32) lsp.Position {
pub fn wholeRange(self: @This()) lsp.Range {
return .{
.start = .{ .line = 0, .character = 0 },
.end = self.positionFromUtf8(@intCast(self.contents.items.len)),
.end = positionFromUtf8(self.source(), @intCast(self.contents.items.len)),
};
}

pub fn nodeRange(self: *@This(), node: u32) !lsp.Range {
const parsed = try self.parseTree();
const span = parsed.tree.nodeSpan(node);
return .{
.start = self.positionFromUtf8(span.start),
.end = self.positionFromUtf8(span.end),
.start = positionFromUtf8(self.source(), span.start),
.end = positionFromUtf8(self.source(), span.end),
};
}

Expand Down
74 changes: 53 additions & 21 deletions src/cli.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ const std = @import("std");
pub const NAME = "glsl_analyzer";

pub const Arguments = struct {
version: bool = false,
channel: ChannelKind = .stdio,
client_pid: ?c_int = null,
dev_mode: ?[]const u8 = null,
version: bool = false,
parse_file: ?[]const u8 = null,
print_ast: bool = false,

pub const ChannelKind = union(enum) {
stdio: void,
Expand All @@ -17,11 +19,16 @@ pub const Arguments = struct {
"Usage: " ++ NAME ++
\\ [OPTIONS]
\\
\\LSP:
\\ --clientProcessId <PID> PID of the client process (used by LSP client).
\\
\\Options:
\\ --stdio Communicate over stdio [default]
\\ -p, --port <PORT> Communicate over socket
\\ --clientProcessId <PID> PID of the client process
\\ --dev-mode <path> Enable development mode
\\ -h, --help Print this message.
\\ --stdio Communicate over stdio. [default]
\\ -p, --port <PORT> Communicate over socket.
\\ --dev-mode <PATH> Enable development mode: redirects stderr to the given path.
\\ --parse-file <PATH> Parses the given file, prints diagnostics, then exits.
\\ --print-ast Prints the parse tree. Only valid with --parse-file.
\\
\\
;
Expand All @@ -42,6 +49,18 @@ pub const Arguments = struct {
std.process.exit(1);
}

const ValueParser = struct {
args: *std.process.ArgIterator,
option: []const u8,
value: ?[]const u8,

pub fn get(self: *@This(), name: []const u8) []const u8 {
if (self.value) |value| return value;
if (self.args.next()) |value| return value;
fail("'{s}' expects an argument '{s}'", .{ self.option, name });
}
};

pub fn parse(allocator: std.mem.Allocator) !Arguments {
var args = try std.process.argsWithAllocator(allocator);
defer args.deinit();
Expand All @@ -50,43 +69,56 @@ pub const Arguments = struct {
var parsed = Arguments{};

while (args.next()) |arg| {
const name_end = std.mem.indexOfScalar(u8, arg, '=') orelse arg.len;
const name = arg[0..name_end];
const extra_value = if (name_end == arg.len) null else arg[name_end + 1 ..];
const option_end = std.mem.indexOfScalar(u8, arg, '=') orelse arg.len;
const option = arg[0..option_end];

if (isAny(name, &.{ "--help", "-h" })) {
var value_parser = ValueParser{
.args = &args,
.option = option,
.value = if (option_end == arg.len) null else arg[option_end + 1 ..],
};

if (isAny(option, &.{ "--help", "-h" })) {
printHelp();
}

if (isAny(name, &.{ "--version", "-v" })) {
if (isAny(option, &.{ "--version", "-v" })) {
printVersion();
}

if (isAny(name, &.{"--stdio"})) {
if (isAny(option, &.{"--stdio"})) {
parsed.channel = .stdio;
continue;
}

if (isAny(name, &.{"--dev-mode"})) {
const path = extra_value orelse args.next() orelse
fail("{s}: expected a path", .{name});
if (isAny(option, &.{"--dev-mode"})) {
const path = value_parser.get("PATH");
parsed.dev_mode = path;
continue;
}

if (isAny(name, &.{ "--port", "-p" })) {
const value = extra_value orelse args.next() orelse
fail("{s}: expected port number", .{name});
if (isAny(option, &.{ "--port", "-p" })) {
const value = value_parser.get("PORT");
const port = std.fmt.parseInt(u16, value, 10) catch
fail("{s}: not a valid port number: {s}", .{ name, value });
fail("{s}: not a valid port number: {s}", .{ option, value });
parsed.channel = .{ .socket = port };
continue;
}

if (isAny(name, &.{"--clientProcessId"})) {
const value = extra_value orelse args.next() orelse fail("expected PID", .{});
if (isAny(option, &.{"--clientProcessId"})) {
const value = value_parser.get("PID");
parsed.client_pid = std.fmt.parseInt(c_int, value, 10) catch
fail("{s}: not a valid PID: {s}", .{ name, value });
fail("{s}: not a valid PID: {s}", .{ option, value });
continue;
}

if (isAny(option, &.{"--parse-file"})) {
parsed.parse_file = value_parser.get("PATH");
continue;
}

if (isAny(option, &.{"--print-ast"})) {
parsed.print_ast = true;
continue;
}

Expand Down
39 changes: 36 additions & 3 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ fn enableDevelopmentMode(stderr_target: []const u8) !void {
}
}

pub fn main() !void {
defer std.debug.print("exited\n", .{});

pub fn main() !u8 {
var gpa = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = 8 }){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
Expand All @@ -55,6 +53,39 @@ pub fn main() !void {
}
}

if (args.parse_file) |path| {
const source = std.fs.cwd().readFileAlloc(allocator, path, 1 << 30) catch |err| {
std.log.err("could not open '{s}': {s}", .{ path, @errorName(err) });
return err;
};
defer allocator.free(source);

var diagnostics = std.ArrayList(parse.Diagnostic).init(allocator);
defer diagnostics.deinit();

var tree = try parse.parse(allocator, source, .{ .diagnostics = &diagnostics });
defer tree.deinit(allocator);

if (args.print_ast) {
var buffered_stdout = std.io.bufferedWriter(std.io.getStdOut().writer());
try buffered_stdout.writer().print("{}", .{tree.format(source)});
try buffered_stdout.flush();
}

if (diagnostics.items.len != 0) {
for (diagnostics.items) |diagnostic| {
const position = Workspace.Document.positionFromUtf8(source, diagnostic.span.start);
try std.io.getStdErr().writer().print(
"{s}:{}:{}: {s}\n",
.{ path, position.line + 1, position.character + 1, diagnostic.message },
);
}
return 1;
}

return 0;
}

var channel: Channel = switch (args.channel) {
.stdio => .{ .stdio = .{
.stdout = std.io.getStdOut(),
Expand Down Expand Up @@ -148,6 +179,8 @@ pub fn main() !void {
else => return err,
};
}

return 0;
}

fn logJsonError(err: []const u8, diagnostics: std.json.Diagnostics, bytes: []const u8) void {
Expand Down

0 comments on commit 53dfed0

Please sign in to comment.