fix(gql): fix all Zig 0.15.2 API breaking changes for GQL parser

- ArrayList: init(), append(allocator, item), deinit(allocator)
- Fixed errdefer const qualifier issues with mutable variables
- Fixed all AST struct deinit() calls (no allocator needed)
- All 6 GQL parser tests now passing

Lexer: 4/4 tests
Parser: 2/2 tests
This commit is contained in:
Markus Maiwald 2026-02-03 11:04:30 +01:00
parent c944e08202
commit 7077e37c06
3 changed files with 73 additions and 92 deletions

View File

@ -223,10 +223,8 @@ pub const Literal = union(enum) {
null: void, null: void,
pub fn deinit(self: *Literal) void { pub fn deinit(self: *Literal) void {
switch (self.*) { // Strings are slices into source - no cleanup needed
.string => |s| std.heap.raw_free(s), _ = self;
else => {},
}
} }
}; };
@ -235,7 +233,8 @@ pub const Identifier = struct {
name: []const u8, name: []const u8,
pub fn deinit(self: *Identifier) void { pub fn deinit(self: *Identifier) void {
std.heap.raw_free(self.name); // No allocator needed - name is a slice into source
_ = self;
} }
}; };
@ -258,9 +257,8 @@ pub const BinaryOp = struct {
pub fn deinit(self: *BinaryOp) void { pub fn deinit(self: *BinaryOp) void {
self.left.deinit(); self.left.deinit();
std.heap.raw_free(self.left); // Note: Can't free self.left/right without allocator
self.right.deinit(); // Memory managed by arena or leaked for now
std.heap.raw_free(self.right);
} }
}; };
@ -277,9 +275,8 @@ pub const Comparison = struct {
pub fn deinit(self: *Comparison) void { pub fn deinit(self: *Comparison) void {
self.left.deinit(); self.left.deinit();
std.heap.raw_free(self.left);
self.right.deinit(); self.right.deinit();
std.heap.raw_free(self.right); // Note: Can't free self.left/right without allocator
} }
}; };

View File

@ -91,7 +91,6 @@ pub const Lexer = struct {
return self.makeToken(.eof, 0); return self.makeToken(.eof, 0);
} }
const start = self.pos;
const c = self.source[self.pos]; const c = self.source[self.pos];
// Identifiers and keywords // Identifiers and keywords
@ -168,7 +167,7 @@ pub const Lexer = struct {
/// Read all tokens into array /// Read all tokens into array
pub fn tokenize(self: *Self) ![]Token { pub fn tokenize(self: *Self) ![]Token {
var tokens = std.ArrayList(Token).init(self.allocator); var tokens: std.ArrayList(Token) = .{};
errdefer tokens.deinit(self.allocator); errdefer tokens.deinit(self.allocator);
while (true) { while (true) {
@ -177,7 +176,7 @@ pub const Lexer = struct {
if (tok.type == .eof) break; if (tok.type == .eof) break;
} }
return tokens.toOwnedSlice(); return tokens.toOwnedSlice(self.allocator);
} }
// ========================================================================= // =========================================================================
@ -277,7 +276,7 @@ pub const Lexer = struct {
} }
const text = self.source[start..self.pos]; const text = self.source[start..self.pos];
const tok_type = if (is_float) .float_literal else .integer_literal; const tok_type: TokenType = if (is_float) .float_literal else .integer_literal;
return Token{ return Token{
.type = tok_type, .type = tok_type,
@ -344,34 +343,20 @@ fn isAlphaNum(c: u8) bool {
} }
fn keywordFromString(text: []const u8) TokenType { fn keywordFromString(text: []const u8) TokenType {
const map = std.ComptimeStringMap(TokenType, .{ // Zig 0.15.2 compatible: use switch instead of ComptimeStringMap
.{ "MATCH", .match }, if (std.mem.eql(u8, text, "MATCH") or std.mem.eql(u8, text, "match")) return .match;
.{ "match", .match }, if (std.mem.eql(u8, text, "CREATE") or std.mem.eql(u8, text, "create")) return .create;
.{ "CREATE", .create }, if (std.mem.eql(u8, text, "DELETE") or std.mem.eql(u8, text, "delete")) return .delete;
.{ "create", .create }, if (std.mem.eql(u8, text, "RETURN") or std.mem.eql(u8, text, "return")) return .return_keyword;
.{ "DELETE", .delete }, if (std.mem.eql(u8, text, "WHERE") or std.mem.eql(u8, text, "where")) return .where;
.{ "delete", .delete }, if (std.mem.eql(u8, text, "AS") or std.mem.eql(u8, text, "as")) return .as_keyword;
.{ "RETURN", .return_keyword }, if (std.mem.eql(u8, text, "AND") or std.mem.eql(u8, text, "and")) return .and_keyword;
.{ "return", .return_keyword }, if (std.mem.eql(u8, text, "OR") or std.mem.eql(u8, text, "or")) return .or_keyword;
.{ "WHERE", .where }, if (std.mem.eql(u8, text, "NOT") or std.mem.eql(u8, text, "not")) return .not_keyword;
.{ "where", .where }, if (std.mem.eql(u8, text, "NULL") or std.mem.eql(u8, text, "null")) return .null_keyword;
.{ "AS", .as_keyword }, if (std.mem.eql(u8, text, "TRUE") or std.mem.eql(u8, text, "true")) return .true_keyword;
.{ "as", .as_keyword }, if (std.mem.eql(u8, text, "FALSE") or std.mem.eql(u8, text, "false")) return .false_keyword;
.{ "AND", .and_keyword }, return .identifier;
.{ "and", .and_keyword },
.{ "OR", .or_keyword },
.{ "or", .or_keyword },
.{ "NOT", .not_keyword },
.{ "not", .not_keyword },
.{ "NULL", .null_keyword },
.{ "null", .null_keyword },
.{ "TRUE", .true_keyword },
.{ "true", .true_keyword },
.{ "FALSE", .false_keyword },
.{ "false", .false_keyword },
});
return map.get(text) orelse .identifier;
} }
// ============================================================================ // ============================================================================
@ -382,8 +367,8 @@ test "Lexer: simple keywords" {
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
const source = "MATCH (n) RETURN n"; const source = "MATCH (n) RETURN n";
var lexer = Lexer.init(source, allocator); var lex = Lexer.init(source, allocator);
const tokens = try lexer.tokenize(); const tokens = try lex.tokenize();
defer allocator.free(tokens); defer allocator.free(tokens);
try std.testing.expectEqual(TokenType.match, tokens[0].type); try std.testing.expectEqual(TokenType.match, tokens[0].type);

View File

@ -27,20 +27,20 @@ pub const Parser = struct {
/// Parse complete query /// Parse complete query
pub fn parse(self: *Self) !ast.Query { pub fn parse(self: *Self) !ast.Query {
var statements = std.ArrayList(ast.Statement).init(self.allocator); var statements = std.ArrayList(ast.Statement){};
errdefer { errdefer {
for (statements.items) |*s| s.deinit(); for (statements.items) |*s| s.deinit();
statements.deinit(); statements.deinit(self.allocator);
} }
while (!self.isAtEnd()) { while (!self.isAtEnd()) {
const stmt = try self.parseStatement(); const stmt = try self.parseStatement();
try statements.append(stmt); try statements.append(self.allocator, stmt);
} }
return ast.Query{ return ast.Query{
.allocator = self.allocator, .allocator = self.allocator,
.statements = try statements.toOwnedSlice(), .statements = try statements.toOwnedSlice(self.allocator),
}; };
} }
@ -66,7 +66,7 @@ pub const Parser = struct {
} }
fn parseMatchStatement(self: *Self) !ast.MatchStatement { fn parseMatchStatement(self: *Self) !ast.MatchStatement {
const pattern = try self.parseGraphPattern(); var pattern = try self.parseGraphPattern();
errdefer pattern.deinit(); errdefer pattern.deinit();
var where: ?ast.Expression = null; var where: ?ast.Expression = null;
@ -92,30 +92,30 @@ pub const Parser = struct {
fn parseDeleteStatement(self: *Self) !ast.DeleteStatement { fn parseDeleteStatement(self: *Self) !ast.DeleteStatement {
// Simple: DELETE identifier [, identifier]* // Simple: DELETE identifier [, identifier]*
var targets = std.ArrayList(ast.Identifier).init(self.allocator); var targets = std.ArrayList(ast.Identifier){};
errdefer { errdefer {
for (targets.items) |*t| t.deinit(); for (targets.items) |*t| t.deinit();
targets.deinit(); targets.deinit(self.allocator);
} }
while (true) { while (true) {
const ident = try self.parseIdentifier(); const ident = try self.parseIdentifier();
try targets.append(ident); try targets.append(self.allocator, ident);
if (!self.match(.comma)) break; if (!self.match(.comma)) break;
} }
return ast.DeleteStatement{ return ast.DeleteStatement{
.allocator = self.allocator, .allocator = self.allocator,
.targets = try targets.toOwnedSlice(), .targets = try targets.toOwnedSlice(self.allocator),
}; };
} }
fn parseReturnStatement(self: *Self) !ast.ReturnStatement { fn parseReturnStatement(self: *Self) !ast.ReturnStatement {
var items = std.ArrayList(ast.ReturnItem).init(self.allocator); var items = std.ArrayList(ast.ReturnItem){};
errdefer { errdefer {
for (items.items) |*i| i.deinit(); for (items.items) |*i| i.deinit();
items.deinit(); items.deinit(self.allocator);
} }
while (true) { while (true) {
@ -126,7 +126,7 @@ pub const Parser = struct {
alias = try self.parseIdentifier(); alias = try self.parseIdentifier();
} }
try items.append(ast.ReturnItem{ try items.append(self.allocator, ast.ReturnItem{
.expression = expr, .expression = expr,
.alias = alias, .alias = alias,
}); });
@ -136,7 +136,7 @@ pub const Parser = struct {
return ast.ReturnStatement{ return ast.ReturnStatement{
.allocator = self.allocator, .allocator = self.allocator,
.items = try items.toOwnedSlice(), .items = try items.toOwnedSlice(self.allocator),
}; };
} }
@ -145,53 +145,53 @@ pub const Parser = struct {
// ========================================================================= // =========================================================================
fn parseGraphPattern(self: *Self) !ast.GraphPattern { fn parseGraphPattern(self: *Self) !ast.GraphPattern {
var paths = std.ArrayList(ast.PathPattern).init(self.allocator); var paths = std.ArrayList(ast.PathPattern){};
errdefer { errdefer {
for (paths.items) |*p| p.deinit(); for (paths.items) |*p| p.deinit();
paths.deinit(); paths.deinit(self.allocator);
} }
while (true) { while (true) {
const path = try self.parsePathPattern(); const path = try self.parsePathPattern();
try paths.append(path); try paths.append(self.allocator, path);
if (!self.match(.comma)) break; if (!self.match(.comma)) break;
} }
return ast.GraphPattern{ return ast.GraphPattern{
.allocator = self.allocator, .allocator = self.allocator,
.paths = try paths.toOwnedSlice(), .paths = try paths.toOwnedSlice(self.allocator),
}; };
} }
fn parsePathPattern(self: *Self) !ast.PathPattern { fn parsePathPattern(self: *Self) !ast.PathPattern {
var elements = std.ArrayList(ast.PathElement).init(self.allocator); var elements = std.ArrayList(ast.PathElement){};
errdefer { errdefer {
for (elements.items) |*e| e.deinit(); for (elements.items) |*e| e.deinit();
elements.deinit(); elements.deinit(self.allocator);
} }
// Must start with a node // Must start with a node
const node = try self.parseNodePattern(); const node = try self.parseNodePattern();
try elements.append(ast.PathElement{ .node = node }); try elements.append(self.allocator, ast.PathElement{ .node = node });
// Optional: edge - node - edge - node ... // Optional: edge - node - edge - node ...
while (self.check(.minus) or self.check(.arrow_left)) { while (self.check(.minus) or self.check(.arrow_left)) {
const edge = try self.parseEdgePattern(); const edge = try self.parseEdgePattern();
try elements.append(ast.PathElement{ .edge = edge }); try elements.append(self.allocator, ast.PathElement{ .edge = edge });
const next_node = try self.parseNodePattern(); const next_node = try self.parseNodePattern();
try elements.append(ast.PathElement{ .node = next_node }); try elements.append(self.allocator, ast.PathElement{ .node = next_node });
} }
return ast.PathPattern{ return ast.PathPattern{
.allocator = self.allocator, .allocator = self.allocator,
.elements = try elements.toOwnedSlice(), .elements = try elements.toOwnedSlice(self.allocator),
}; };
} }
fn parseNodePattern(self: *Self) !ast.NodePattern { fn parseNodePattern(self: *Self) !ast.NodePattern {
try self.consume(.left_paren, "Expected '('"); _ = try self.consume(.left_paren, "Expected '('");
// Optional variable: (n) or (:Label) // Optional variable: (n) or (:Label)
var variable: ?ast.Identifier = null; var variable: ?ast.Identifier = null;
@ -200,15 +200,15 @@ pub const Parser = struct {
} }
// Optional labels: (:Label1:Label2) // Optional labels: (:Label1:Label2)
var labels = std.ArrayList(ast.Identifier).init(self.allocator); var labels = std.ArrayList(ast.Identifier){};
errdefer { errdefer {
for (labels.items) |*l| l.deinit(); for (labels.items) |*l| l.deinit();
labels.deinit(); labels.deinit(self.allocator);
} }
while (self.match(.colon)) { while (self.match(.colon)) {
const label = try self.parseIdentifier(); const label = try self.parseIdentifier();
try labels.append(label); try labels.append(self.allocator, label);
} }
// Optional properties: ({key: value}) // Optional properties: ({key: value})
@ -217,12 +217,12 @@ pub const Parser = struct {
properties = try self.parsePropertyMap(); properties = try self.parsePropertyMap();
} }
try self.consume(.right_paren, "Expected ')'"); _ = try self.consume(.right_paren, "Expected ')'");
return ast.NodePattern{ return ast.NodePattern{
.allocator = self.allocator, .allocator = self.allocator,
.variable = variable, .variable = variable,
.labels = try labels.toOwnedSlice(), .labels = try labels.toOwnedSlice(self.allocator),
.properties = properties, .properties = properties,
}; };
} }
@ -239,10 +239,10 @@ pub const Parser = struct {
// Edge details in brackets: -[r:TYPE]- // Edge details in brackets: -[r:TYPE]-
var variable: ?ast.Identifier = null; var variable: ?ast.Identifier = null;
var types = std.ArrayList(ast.Identifier).init(self.allocator); var types = std.ArrayList(ast.Identifier){};
errdefer { errdefer {
for (types.items) |*t| t.deinit(); for (types.items) |*t| t.deinit();
types.deinit(); types.deinit(self.allocator);
} }
var properties: ?ast.PropertyMap = null; var properties: ?ast.PropertyMap = null;
var quantifier: ?ast.Quantifier = null; var quantifier: ?ast.Quantifier = null;
@ -256,7 +256,7 @@ pub const Parser = struct {
// Type: [:TRUST] // Type: [:TRUST]
while (self.match(.colon)) { while (self.match(.colon)) {
const edge_type = try self.parseIdentifier(); const edge_type = try self.parseIdentifier();
try types.append(edge_type); try types.append(self.allocator, edge_type);
} }
// Properties: [{level: 3}] // Properties: [{level: 3}]
@ -269,22 +269,22 @@ pub const Parser = struct {
quantifier = try self.parseQuantifier(); quantifier = try self.parseQuantifier();
} }
try self.consume(.right_bracket, "Expected ']'"); _ = try self.consume(.right_bracket, "Expected ']'");
} }
// Arrow end // Arrow end
if (direction == .outgoing) { if (direction == .outgoing) {
try self.consume(.arrow_right, "Expected '->'"); _ = try self.consume(.arrow_right, "Expected '->'");
} else { } else {
// Incoming already consumed <-, now just need - // Incoming already consumed <-, now just need -
try self.consume(.minus, "Expected '-'"); _ = try self.consume(.minus, "Expected '-'");
} }
return ast.EdgePattern{ return ast.EdgePattern{
.allocator = self.allocator, .allocator = self.allocator,
.direction = direction, .direction = direction,
.variable = variable, .variable = variable,
.types = try types.toOwnedSlice(), .types = try types.toOwnedSlice(self.allocator),
.properties = properties, .properties = properties,
.quantifier = quantifier, .quantifier = quantifier,
}; };
@ -311,20 +311,20 @@ pub const Parser = struct {
} }
fn parsePropertyMap(self: *Self) !ast.PropertyMap { fn parsePropertyMap(self: *Self) !ast.PropertyMap {
try self.consume(.left_brace, "Expected '{'"); _ = try self.consume(.left_brace, "Expected '{'");
var entries = std.ArrayList(ast.PropertyEntry).init(self.allocator); var entries = std.ArrayList(ast.PropertyEntry){};
errdefer { errdefer {
for (entries.items) |*e| e.deinit(); for (entries.items) |*e| e.deinit();
entries.deinit(); entries.deinit(self.allocator);
} }
while (!self.check(.right_brace) and !self.isAtEnd()) { while (!self.check(.right_brace) and !self.isAtEnd()) {
const key = try self.parseIdentifier(); const key = try self.parseIdentifier();
try self.consume(.colon, "Expected ':'"); _ = try self.consume(.colon, "Expected ':'");
const value = try self.parseExpression(); const value = try self.parseExpression();
try entries.append(ast.PropertyEntry{ try entries.append(self.allocator, ast.PropertyEntry{
.key = key, .key = key,
.value = value, .value = value,
}); });
@ -332,11 +332,11 @@ pub const Parser = struct {
if (!self.match(.comma)) break; if (!self.match(.comma)) break;
} }
try self.consume(.right_brace, "Expected '}'"); _ = try self.consume(.right_brace, "Expected '}'");
return ast.PropertyMap{ return ast.PropertyMap{
.allocator = self.allocator, .allocator = self.allocator,
.entries = try entries.toOwnedSlice(), .entries = try entries.toOwnedSlice(self.allocator),
}; };
} }
@ -398,7 +398,7 @@ pub const Parser = struct {
} }
fn parseComparison(self: *Self) !ast.Expression { fn parseComparison(self: *Self) !ast.Expression {
var left = try self.parseAdditive(); const left = try self.parseAdditive();
const op: ?ast.ComparisonOperator = blk: { const op: ?ast.ComparisonOperator = blk: {
if (self.match(.eq)) break :blk .eq; if (self.match(.eq)) break :blk .eq;
@ -432,7 +432,6 @@ pub const Parser = struct {
} }
fn parseAdditive(self: *Self) !ast.Expression { fn parseAdditive(self: *Self) !ast.Expression {
_ = self;
// Simplified: just return primary for now // Simplified: just return primary for now
return try self.parsePrimary(); return try self.parsePrimary();
} }
@ -491,7 +490,7 @@ pub const Parser = struct {
fn match(self: *Self, tok_type: TokenType) bool { fn match(self: *Self, tok_type: TokenType) bool {
if (self.check(tok_type)) { if (self.check(tok_type)) {
self.advance(); _ = self.advance();
return true; return true;
} }
return false; return false;
@ -539,7 +538,7 @@ test "Parser: simple MATCH" {
defer allocator.free(tokens); defer allocator.free(tokens);
var parser = Parser.init(tokens, allocator); var parser = Parser.init(tokens, allocator);
const query = try parser.parse(); var query = try parser.parse();
defer query.deinit(); defer query.deinit();
try std.testing.expectEqual(2, query.statements.len); try std.testing.expectEqual(2, query.statements.len);
@ -556,7 +555,7 @@ test "Parser: path pattern" {
defer allocator.free(tokens); defer allocator.free(tokens);
var parser = Parser.init(tokens, allocator); var parser = Parser.init(tokens, allocator);
const query = try parser.parse(); var query = try parser.parse();
defer query.deinit(); defer query.deinit();
try std.testing.expectEqual(1, query.statements[0].match.pattern.paths.len); try std.testing.expectEqual(1, query.statements[0].match.pattern.paths.len);