Last active
April 19, 2025 04:13
-
-
Save raspberrypisig/7c34f9e7a242ecb6683d110c3d0526bc to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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": [] | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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