Skip to content

Instantly share code, notes, and snippets.

@raspberrypisig
Last active April 19, 2025 04:13
Show Gist options
  • Save raspberrypisig/7c34f9e7a242ecb6683d110c3d0526bc to your computer and use it in GitHub Desktop.
Save raspberrypisig/7c34f9e7a242ecb6683d110c3d0526bc to your computer and use it in GitHub Desktop.
pub fn main() !void {
// const justfile = try std.fs.cwd().openFile("justfile.json", .{});
// var buffer: [3000]u8 = undefined;
// const bytes_read = try justfile.readAll(&buffer);
// const content = buffer[0..bytes_read];
// const stdout = std.io.getStdOut().writer();
// var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
// defer _ = gpa.deinit();
// const allocator = gpa.allocator();
// var parsed = try std.json.parseFromSlice(std.json.Value, allocator, content, .{ .allocate = .alloc_always });
// defer parsed.deinit();
// const root = parsed.value;
// const recipes = root.object.get("recipes");
// const keys = recipes.?.object.keys();
// for (keys) |key| {
// try stdout.print("{s}\n", .{key});
// }
}
// Find a recipe in the justfile with name $recipe_key
fn findRecipe(allocator: std.mem.Allocator, content: []const u8, recipe_key: []const u8) !?std.ArrayList(std.json.Value) {
var parsed = try std.json.parseFromSlice(std.json.Value, allocator, content, .{ .allocate = .alloc_always });
defer parsed.deinit();
const root = parsed.value;
const recipes = root.object.get("recipes");
const keys = recipes.?.object.keys();
const ret = blk: for (keys) |key| {
if (std.mem.eql(u8, key, recipe_key)) {
const recipe_json = recipes.?.object.get(key);
const body_json = recipe_json.?.object.get("body");
const body_array = body_json.?.array;
//const array = body.?.array.getLast();
//const boo = array.string;
break :blk try body_array.clone();
}
} else {
return null;
};
return ret;
}
test "simple test" {
const justfile =
\\{
\\"first": "build",
\\"recipes": {
\\"build": {
\\"body": [
\\[
\\ "zig build"
\\]
\\],
\\"name": "build",
\\"namepath": "build"
\\}
\\}
\\}
;
const build = try findRecipe(std.testing.allocator, justfile, "build");
if (build) |body| {
try std.testing.expectEqual(body.items.len, 1);
} else {
try std.testing.expect(false);
}
}
const std = @import("std");
/// This imports the separate module containing `root.zig`. Take a look in `build.zig` for details.
const lib = @import("parseJson_lib");
set shell := ["sh", "-c"]
set windows-shell := ["powershell", "-c"]
buildrun:
zig build run
build:
zig build
test:
zig build test
choose:
@just --choose
dump:
@just --dump --dump-format json
{
"aliases": {},
"assignments": {},
"first": "buildrun",
"doc": null,
"groups": [],
"modules": {},
"recipes": {
"build": {
"attributes": [],
"body": [
[
"zig build"
]
],
"dependencies": [],
"doc": null,
"name": "build",
"namepath": "build",
"parameters": [],
"priors": 0,
"private": false,
"quiet": false,
"shebang": false
},
"buildrun": {
"attributes": [],
"body": [
[
"zig build run"
]
],
"dependencies": [],
"doc": null,
"name": "buildrun",
"namepath": "buildrun",
"parameters": [],
"priors": 0,
"private": false,
"quiet": false,
"shebang": false
},
"choose": {
"attributes": [],
"body": [
[
"@just --choose"
]
],
"dependencies": [],
"doc": null,
"name": "choose",
"namepath": "choose",
"parameters": [],
"priors": 0,
"private": false,
"quiet": false,
"shebang": false
},
"dump": {
"attributes": [],
"body": [
[
"@just --dump --dump-format json"
]
],
"dependencies": [],
"doc": null,
"name": "dump",
"namepath": "dump",
"parameters": [],
"priors": 0,
"private": false,
"quiet": false,
"shebang": false
},
"test": {
"attributes": [],
"body": [
[
"zig build test"
]
],
"dependencies": [],
"doc": null,
"name": "test",
"namepath": "test",
"parameters": [],
"priors": 0,
"private": false,
"quiet": false,
"shebang": false
}
},
"settings": {
"allow_duplicate_recipes": false,
"allow_duplicate_variables": false,
"dotenv_filename": null,
"dotenv_load": false,
"dotenv_path": null,
"dotenv_required": false,
"export": false,
"fallback": false,
"ignore_comments": false,
"no_exit_message": false,
"positional_arguments": false,
"quiet": false,
"shell": {
"arguments": [
"-c"
],
"command": "sh"
},
"tempdir": null,
"unstable": false,
"windows_powershell": false,
"windows_shell": {
"arguments": [
"-c"
],
"command": "powershell"
},
"working_directory": null
},
"source": "C:\\Users\\Mohan\\Developer\\zig\\fundamentals\\parseJson\\justfile",
"unexports": [],
"warnings": []
}
// justfile_ast.zig
const std = @import("std");
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
// --- Forward Declarations ---
const Expression = union(enum) {
Disjunction: struct { left: *Expression, right: *Expression },
Conjunction: struct { left: *Expression, right: *Expression },
Conditional: struct { condition: *Condition, then_expr: *Expression, else_expr: *Expression },
Assert: struct { condition: *Condition, message_expr: *Expression },
Concatenation: struct { left: *Expression, right: *Expression }, // '/' operator
Addition: struct { left: *Expression, right: *Expression }, // '+' operator
Value: Value,
pub fn tagName(self: Expression) []const u8 {
return @tagName(self);
}
// Recursive deinit (if needed - depends on parser implementation,
// not strictly required if using Arena for Expression nodes)
// pub fn deinit(self: Expression, allocator: Allocator) void {
// switch (self) {
// .Disjunction, .Conjunction => |bin| {
// bin.left.deinit(allocator);
// bin.right.deinit(allocator);
// allocator.destroy(bin.left);
// allocator.destroy(bin.right);
// },
// .Conditional => |cond| {
// cond.condition.deinit(allocator);
// cond.then_expr.deinit(allocator);
// cond.else_expr.deinit(allocator);
// allocator.destroy(cond.condition);
// allocator.destroy(cond.then_expr);
// allocator.destroy(cond.else_expr);
// },
// // ... handle other variants similarly if they allocate nodes ...
// .Value => |v| v.deinit(allocator), // Delegate to Value.deinit
// else => {},
// }
// }
};
const Condition = union(enum) {
Equals: struct { left: *Expression, right: *Expression },
NotEquals: struct { left: *Expression, right: *Expression },
RegexMatch: struct { left: *Expression, right: *Expression },
// Recursive deinit (if needed)
// pub fn deinit(self: Condition, allocator: Allocator) void {
// switch(self) {
// .Equals, .NotEquals, .RegexMatch => |bin| {
// bin.left.deinit(allocator);
// bin.right.deinit(allocator);
// allocator.destroy(bin.left);
// allocator.destroy(bin.right);
// }
// }
// }
};
const Value = union(enum) {
FunctionCall: struct { name: []const u8, arguments: ArrayList(Expression) },
Backtick: struct { content: []const u8, indented: bool },
Variable: struct { name: []const u8 },
StringLiteral: String,
Parenthesized: *Expression,
// Deinit for Value (if needed, mainly for FunctionCall arguments)
pub fn deinit(self: Value, allocator: Allocator) void {
switch (self) {
.FunctionCall => |call| {
// Assuming expressions might need deiniting
// for (call.arguments.items) |arg| arg.deinit(allocator);
call.arguments.deinit(allocator);
},
.Parenthesized => |expr_ptr| {
// expr_ptr.deinit(allocator);
// allocator.destroy(expr_ptr);
_ = expr_ptr; // Placeholder if not deiniting expression
},
else => {}, // Other variants don't own heap data directly in this structure
}
//_ = allocator; // Placeholder if no deinit needed
}
};
const String = struct {
kind: enum { Normal, Indented, Raw, IndentedRaw },
content: []const u8,
is_executable: bool,
};
const Dependency = union(enum) {
Simple: struct { name: []const u8 },
WithArguments: struct { name: []const u8, arguments: ArrayList(Expression) },
// Deinit for Dependency (for WithArguments)
pub fn deinit(self: Dependency) void {
switch (self) {
.WithArguments => |wa| {
// Assuming expressions might need deiniting
// for (wa.arguments.items) |arg| arg.deinit(allocator);
wa.arguments.deinit();
},
.Simple => {},
}
//_ = allocator; // Placeholder if no deinit needed
}
};
const LinePart = union(enum) {
Text: struct { text: []const u8 },
Interpolation: struct { expression: Expression },
// Deinit for LinePart (for Interpolation)
pub fn deinit(self: LinePart, allocator: Allocator) void {
switch (self) {
.Interpolation => |interp| {
// Assuming expressions might need deiniting
// interp.expression.deinit(allocator);
_ = interp;
},
.Text => {},
}
_ = allocator; // Placeholder if no deinit needed
}
};
const Line = union(enum) {
Empty,
Content: struct {
prefix: ?enum { AtMinus, MinusAt, At, Minus },
parts: ArrayList(LinePart),
},
};
// Helper to deinit a Line
fn deinitLine(line: Line, allocator: Allocator) void {
switch (line) {
.Content => |content| {
for (content.parts.items) |part| {
part.deinit(allocator); // Deinit each part
}
content.parts.deinit(); // Deinit the list of parts
},
.Empty => {},
}
}
// --- Main AST Structures ---
pub const Item = union(enum) {
Alias: Alias,
Assignment: Assignment,
Eol: Eol,
Export: Export,
Import: Import,
Module: Module,
Recipe: Recipe,
Set: Set,
// Recursive deinit function for Item
pub fn deinit(self: Item, allocator: Allocator) void {
switch (self) {
.Alias => |*alias| {
_ = alias;
},
.Assignment => |*assignment| {
// assignment.value.deinit(allocator); // Uncomment if Expression deinit is needed
_ = assignment;
},
.Eol => |*eol| {
_ = eol;
},
.Export => |*exp| {
// exp.value.deinit(allocator); // Uncomment if Expression deinit is needed
_ = exp;
},
.Import => |*imp| {
_ = imp;
},
.Module => |*modu| {
_ = modu;
},
.Recipe => |*recipe| {
recipe.attributes.deinit();
recipe.parameters.deinit();
// Deinit variadic parameter if it exists and needs it
if (recipe.variadic) |*variadic| {
// variadic.parameter.default_value.?.deinit(allocator); // If default values allocate
_ = variadic;
}
// Deinit dependencies
for (recipe.dependencies.dependencies.items) |dep| dep.deinit();
recipe.dependencies.dependencies.deinit();
for (recipe.dependencies.after_dependencies.items) |dep| dep.deinit();
recipe.dependencies.after_dependencies.deinit();
// Deinit body
if (recipe.body) |*body| {
for (body.lines.items) |line| {
deinitLine(line, allocator);
}
body.lines.deinit();
}
},
.Set => |*set_item| {
switch (set_item.setting) {
.Shell => |list| list.deinit(),
.WindowsShell => |list| list.deinit(),
.ScriptInterpreter => |list| list.deinit(),
else => {}, // Other settings don't hold lists here
}
},
}
}
};
pub const Justfile = struct {
items: ArrayList(Item),
pub fn deinit(self: *Justfile, allocator: Allocator) void {
for (self.items.items) |item| {
item.deinit(allocator); // Deinit each item
}
self.items.deinit(); // Deinit the main list
}
};
pub const Eol = struct {
comment: ?[]const u8 = null,
};
pub const Alias = struct {
name: []const u8,
target: []const u8,
eol: Eol,
};
pub const Assignment = struct {
name: []const u8,
value: Expression,
is_export: bool = false,
eol: Eol,
};
pub const Export = Assignment;
pub const Set = struct {
setting: Setting,
eol: Eol,
};
// Corrected Setting Union (no explicit tag values)
pub const Setting = union(enum) {
AllowDuplicateRecipes: ?bool,
AllowDuplicateVariables: ?bool,
DotenvFilename: String,
DotenvLoad: ?bool,
DotenvPath: String,
DotenvRequired: ?bool,
Export: ?bool,
Fallback: ?bool,
IgnoreComments: ?bool,
PositionalArguments: ?bool,
ScriptInterpreter: ArrayList(String),
Quiet: ?bool,
Shell: ArrayList(String),
Tempdir: String,
Unstable: ?bool,
WindowsPowershell: ?bool,
WindowsShell: ArrayList(String),
WorkingDirectory: String,
};
pub const Import = struct {
optional: bool,
path: ?String = null,
eol: Eol,
};
pub const Module = struct {
optional: bool,
name: []const u8,
path: ?String = null,
eol: Eol,
};
pub const Attribute = struct {
name: []const u8,
argument: ?String = null,
};
pub const Parameter = struct {
prefix_dollar: bool,
name: []const u8,
default_value: ?Expression = null,
};
pub const VariadicParameter = struct {
kind: enum { Star, Plus },
parameter: Parameter,
};
pub const Dependencies = struct {
dependencies: ArrayList(Dependency),
after_dependencies: ArrayList(Dependency),
};
pub const Body = struct {
lines: ArrayList(Line),
};
pub const Recipe = struct {
attributes: ArrayList(Attribute),
quiet_prefix: bool,
name: []const u8,
parameters: ArrayList(Parameter),
variadic: ?VariadicParameter = null,
dependencies: Dependencies,
eol: Eol,
body: ?Body = null,
};
// Example AST Creation Function (Corrected Ownership/Deinit)
pub fn exampleAst(allocator: Allocator) !Justfile {
var items = ArrayList(Item).init(allocator);
errdefer items.deinit(); // Deinit top list if function fails
// --- Set Shell ---
var shell_args = ArrayList(String).init(allocator);
errdefer shell_args.deinit(); // Deinit if appends fail
try shell_args.append(.{ .kind = .Normal, .content = "sh", .is_executable = false });
try shell_args.append(.{ .kind = .Normal, .content = "-c", .is_executable = false });
try items.append(.{
.Set = .{
.setting = .{ .Shell = shell_args }, // Ownership transferred
.eol = .{},
},
});
shell_args = undefined; // Prevent use after move
// --- Recipe 'build' ---
var build_attributes = ArrayList(Attribute).init(allocator);
errdefer build_attributes.deinit();
var build_parameters = ArrayList(Parameter).init(allocator);
errdefer build_parameters.deinit();
var build_deps = ArrayList(Dependency).init(allocator);
errdefer build_deps.deinit();
var build_after_deps = ArrayList(Dependency).init(allocator);
errdefer build_after_deps.deinit();
var build_body_lines = ArrayList(Line).init(allocator);
errdefer build_body_lines.deinit();
// Create the body first
var build_line_parts = ArrayList(LinePart).init(allocator);
//defer build_line_parts.deinit(); // Deinit if append fails
try build_line_parts.append(.{ .Text = .{ .text = "zig build" } });
try build_body_lines.append(.{ .Content = .{ .prefix = null, .parts = build_line_parts } }); // parts ownership transferred
build_line_parts = undefined; // Prevent use after move
// Create the recipe, transferring ownership of lists
const build_recipe = Recipe{
.attributes = build_attributes,
.quiet_prefix = false,
.name = "build",
.parameters = build_parameters,
.variadic = null,
.dependencies = .{
.dependencies = build_deps,
.after_dependencies = build_after_deps,
},
.eol = .{},
.body = .{ .lines = build_body_lines },
};
// Prevent use after move
build_attributes = undefined;
build_parameters = undefined;
build_deps = undefined;
build_after_deps = undefined;
build_body_lines = undefined;
try items.append(.{ .Recipe = build_recipe }); // recipe ownership transferred
// --- Set quiet ---
try items.append(.{ .Set = .{
.setting = .{ .Quiet = true },
.eol = .{},
} });
// Success: Return the Justfile; items errdefer is cancelled.
return Justfile{ .items = items };
}
// Test Case (Corrected)
test "Example AST Creation" {
var jf = try exampleAst(std.testing.allocator);
// CRITICAL: Deinitialize the returned Justfile struct
defer jf.deinit(std.testing.allocator);
// Assertions
try std.testing.expect(jf.items.items.len == 3); // Set, Recipe, Set
// Check Set Shell
const set_shell_item = jf.items.items[0];
try std.testing.expect(set_shell_item == .Set);
try std.testing.expect(set_shell_item.Set.setting == .Shell);
try std.testing.expectEqualStrings("sh", set_shell_item.Set.setting.Shell.items[0].content);
try std.testing.expectEqualStrings("-c", set_shell_item.Set.setting.Shell.items[1].content);
// Check Recipe build
const recipe_item = jf.items.items[1];
try std.testing.expect(recipe_item == .Recipe);
try std.testing.expectEqualStrings("build", recipe_item.Recipe.name);
try std.testing.expect(recipe_item.Recipe.body != null);
try std.testing.expect(recipe_item.Recipe.body.?.lines.items.len == 1);
const recipe_body_line = recipe_item.Recipe.body.?.lines.items[0];
try std.testing.expect(recipe_body_line == .Content);
try std.testing.expect(recipe_body_line.Content.parts.items.len == 1);
const line_part = recipe_body_line.Content.parts.items[0];
try std.testing.expect(line_part == .Text);
try std.testing.expectEqualStrings("zig build", line_part.Text.text);
// Check Set Quiet
const set_quiet_item = jf.items.items[2];
try std.testing.expect(set_quiet_item == .Set);
try std.testing.expect(set_quiet_item.Set.setting == .Quiet);
try std.testing.expect(set_quiet_item.Set.setting.Quiet.? == true);
}
// Dummy main for compilation if needed
pub fn main() !void {
std.debug.print("AST Definition Compiled.\n", .{});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment