lib/chant/src/parse/expression.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const chant = @import("../root.zig");
  3 const ast = @import("../ast/root.zig");
  4 const ctype = @import("type/root.zig");
  5 const state = @import("state/root.zig");
  6 const cursor = state.cursor;
  7 const diagnostic = state.diagnostic;
  8 const memory = state.memory;
  9 const object = state.object;
 10 const start_mod = state.start;
 11 const Error = @import("error.zig").Error;
 12 
 13 const Parser = state.Parser;
 14 const token = chant.token;
 15 const Kind = token.Kind;
 16 
 17 pub fn parseExpression(parser: *Parser) Error!*ast.Expr {
 18     return parseAssignment(parser);
 19 }
 20 
 21 pub fn parseAssignment(parser: *Parser) Error!*ast.Expr {
 22     const target = try parseConditional(parser);
 23     const op: ?ast.expr.BinaryOp = switch (cursor.peek(parser).kind) {
 24         .assign => null,
 25         .plus_assign => .add,
 26         .minus_assign => .sub,
 27         .star_assign => .mul,
 28         .slash_assign => .div,
 29         .percent_assign => .rem,
 30         .amp_assign => .bit_and,
 31         .pipe_assign => .bit_or,
 32         .caret_assign => .bit_xor,
 33         .shl_assign => .shl,
 34         .shr_assign => .shr,
 35         else => return target,
 36     };
 37     _ = cursor.advance(parser);
 38     const value = try parseAssignment(parser);
 39     return memory.create(parser, ast.Expr, .{ .assign = .{ .op = op, .target = target, .value = value } });
 40 }
 41 
 42 fn parseConditional(parser: *Parser) Error!*ast.Expr {
 43     const condition = try parseBinary(parser, 0);
 44     if (!cursor.consume(parser, .question)) return condition;
 45     const then_value = try parseExpression(parser);
 46     _ = try cursor.expect(parser, .colon);
 47     const else_value = try parseConditional(parser);
 48     return memory.create(parser, ast.Expr, .{ .conditional = .{
 49         .condition = condition,
 50         .then_value = then_value,
 51         .else_value = else_value,
 52     } });
 53 }
 54 
 55 const Level = struct {
 56     kind: Kind,
 57     op: ast.expr.BinaryOp,
 58     precedence: u8,
 59 };
 60 
 61 const levels = [_]Level{
 62     .{ .kind = .pipe_pipe, .op = .logical_or, .precedence = 1 },
 63     .{ .kind = .amp_amp, .op = .logical_and, .precedence = 2 },
 64     .{ .kind = .pipe, .op = .bit_or, .precedence = 3 },
 65     .{ .kind = .caret, .op = .bit_xor, .precedence = 4 },
 66     .{ .kind = .amp, .op = .bit_and, .precedence = 5 },
 67     .{ .kind = .eq, .op = .eq, .precedence = 6 },
 68     .{ .kind = .ne, .op = .ne, .precedence = 6 },
 69     .{ .kind = .lt, .op = .lt, .precedence = 7 },
 70     .{ .kind = .gt, .op = .gt, .precedence = 7 },
 71     .{ .kind = .le, .op = .le, .precedence = 7 },
 72     .{ .kind = .ge, .op = .ge, .precedence = 7 },
 73     .{ .kind = .shl, .op = .shl, .precedence = 8 },
 74     .{ .kind = .shr, .op = .shr, .precedence = 8 },
 75     .{ .kind = .plus, .op = .add, .precedence = 9 },
 76     .{ .kind = .minus, .op = .sub, .precedence = 9 },
 77     .{ .kind = .star, .op = .mul, .precedence = 10 },
 78     .{ .kind = .slash, .op = .div, .precedence = 10 },
 79     .{ .kind = .percent, .op = .rem, .precedence = 10 },
 80 };
 81 
 82 fn binaryLevel(kind: Kind) ?Level {
 83     for (levels) |level| {
 84         if (level.kind == kind) return level;
 85     }
 86     return null;
 87 }
 88 
 89 fn parseBinary(parser: *Parser, min_precedence: u8) Error!*ast.Expr {
 90     var lhs = try parseUnary(parser);
 91     while (true) {
 92         const level = binaryLevel(cursor.peek(parser).kind) orelse break;
 93         if (level.precedence < min_precedence) break;
 94         _ = cursor.advance(parser);
 95         const rhs = try parseBinary(parser, level.precedence + 1);
 96         lhs = try memory.create(parser, ast.Expr, .{ .binary = .{ .op = level.op, .lhs = lhs, .rhs = rhs } });
 97     }
 98     return lhs;
 99 }
100 
101 fn parseUnary(parser: *Parser) Error!*ast.Expr {
102     switch (cursor.peek(parser).kind) {
103         .plus => {
104             _ = cursor.advance(parser);
105             return parseUnary(parser);
106         },
107         .minus => {
108             _ = cursor.advance(parser);
109             const operand = try parseUnary(parser);
110             return memory.create(parser, ast.Expr, .{ .unary = .{ .op = .negate, .operand = operand } });
111         },
112         .bang => {
113             _ = cursor.advance(parser);
114             const operand = try parseUnary(parser);
115             return memory.create(parser, ast.Expr, .{ .unary = .{ .op = .logical_not, .operand = operand } });
116         },
117         .tilde => {
118             _ = cursor.advance(parser);
119             const operand = try parseUnary(parser);
120             return memory.create(parser, ast.Expr, .{ .unary = .{ .op = .bit_not, .operand = operand } });
121         },
122         .star => {
123             _ = cursor.advance(parser);
124             const operand = try parseUnary(parser);
125             return memory.create(parser, ast.Expr, .{ .unary = .{ .op = .deref, .operand = operand } });
126         },
127         .amp => {
128             const amp_index = parser.index;
129             _ = cursor.advance(parser);
130             const operand = try parseUnary(parser);
131             return memory.createExprAt(
132                 parser,
133                 .{ .unary = .{ .op = .address_of, .operand = operand } },
134                 amp_index,
135             );
136         },
137         .plus_plus => {
138             _ = cursor.advance(parser);
139             return desugarIncrement(parser, try parseUnary(parser), .add);
140         },
141         .minus_minus => {
142             _ = cursor.advance(parser);
143             return desugarIncrement(parser, try parseUnary(parser), .sub);
144         },
145         .kw_sizeof => {
146             _ = cursor.advance(parser);
147             return parseSizeof(parser);
148         },
149         .kw_alignof => {
150             _ = cursor.advance(parser);
151             return parseAlignof(parser);
152         },
153         .lparen => {
154             if (start_mod.typename(parser, 1)) {
155                 _ = cursor.advance(parser);
156                 const specifiers = try ctype.parseSpecifiers(parser);
157                 if (specifiers.requires_standalone_declaration) {
158                     return diagnostic.fail(parser, error.UnsupportedConstruct, "fixed enum forward declaration must be standalone");
159                 }
160                 const declarator = try ctype.parseDeclarator(parser, specifiers.type, parseAssignment);
161                 if (declarator.name != null) {
162                     return diagnostic.fail(parser, error.UnexpectedToken, "cast names a declarator");
163                 }
164                 _ = try cursor.expect(parser, .rparen);
165                 const operand = try parseUnary(parser);
166                 return memory.create(parser, ast.Expr, .{ .cast = .{ .target = declarator.type, .operand = operand } });
167             }
168             return parsePostfixChain(parser, try parsePrimary(parser));
169         },
170         else => return parsePostfixChain(parser, try parsePrimary(parser)),
171     }
172 }
173 
174 fn desugarIncrement(parser: *Parser, target: *ast.Expr, op: ast.expr.BinaryOp) Error!*ast.Expr {
175     const one = try memory.create(parser, ast.Expr, .{ .integer_literal = .{ .value = 1, .type = &ast.types.int_type } });
176     return memory.create(parser, ast.Expr, .{ .assign = .{ .op = op, .target = target, .value = one } });
177 }
178 
179 fn integerConstantExpression(parser: *Parser, value: i128, c_type: *const ast.Type) Error!*ast.Expr {
180     if (value >= 0) {
181         const unsigned: u64 = std.math.cast(u64, value) orelse return diagnostic.fail(parser, error.InvalidConstant, "integer constant is too large");
182         return memory.create(parser, ast.Expr, .{ .integer_literal = .{ .value = unsigned, .type = c_type } });
183     }
184     if (value == std.math.minInt(i128)) return diagnostic.fail(parser, error.InvalidConstant, "integer constant is too small");
185     const magnitude: u64 = std.math.cast(u64, -value) orelse return diagnostic.fail(parser, error.InvalidConstant, "integer constant is too small");
186     const operand = try memory.create(parser, ast.Expr, .{ .integer_literal = .{ .value = magnitude, .type = c_type } });
187     return memory.create(parser, ast.Expr, .{ .unary = .{ .op = .negate, .operand = operand } });
188 }
189 
190 pub fn evaluateIntegerConstant(parser: *const Parser, expr: *ast.Expr) ?i128 {
191     switch (expr.*) {
192         .integer_literal => |literal| return @intCast(literal.value),
193         .identifier => |identifier| return object.constantValue(parser, identifier.name),
194         .float_literal, .string_literal, .call, .index, .initializer_list => return null,
195         .unary => |unary| {
196             const operand = evaluateIntegerConstant(parser, unary.operand) orelse return null;
197             return switch (unary.op) {
198                 .negate => -operand,
199                 .logical_not => if (operand == 0) 1 else 0,
200                 .bit_not => ~operand,
201                 .address_of, .deref => null,
202             };
203         },
204         .binary => |binary| {
205             const lhs = evaluateIntegerConstant(parser, binary.lhs) orelse return null;
206             const rhs = evaluateIntegerConstant(parser, binary.rhs) orelse return null;
207             return switch (binary.op) {
208                 .add => lhs + rhs,
209                 .sub => lhs - rhs,
210                 .mul => lhs * rhs,
211                 .div => if (rhs == 0) null else @divTrunc(lhs, rhs),
212                 .rem => if (rhs == 0) null else @rem(lhs, rhs),
213                 .lt => if (lhs < rhs) 1 else 0,
214                 .gt => if (lhs > rhs) 1 else 0,
215                 .le => if (lhs <= rhs) 1 else 0,
216                 .ge => if (lhs >= rhs) 1 else 0,
217                 .eq => if (lhs == rhs) 1 else 0,
218                 .ne => if (lhs != rhs) 1 else 0,
219                 .logical_and => if (lhs != 0 and rhs != 0) 1 else 0,
220                 .logical_or => if (lhs != 0 or rhs != 0) 1 else 0,
221                 .bit_and => lhs & rhs,
222                 .bit_or => lhs | rhs,
223                 .bit_xor => lhs ^ rhs,
224                 .shl => if (rhs < 0 or rhs >= 128) null else lhs << @intCast(rhs),
225                 .shr => if (rhs < 0 or rhs >= 128) null else lhs >> @intCast(rhs),
226             };
227         },
228         .assign => return null,
229         .conditional => |conditional| {
230             const condition = evaluateIntegerConstant(parser, conditional.condition) orelse return null;
231             return evaluateIntegerConstant(parser, if (condition != 0) conditional.then_value else conditional.else_value);
232         },
233         .cast => |cast| return evaluateIntegerConstant(parser, cast.operand),
234     }
235 }
236 
237 pub fn inferType(parser: *Parser, expr: *ast.Expr) Error!?*const ast.Type {
238     switch (expr.*) {
239         .integer_literal => |literal| return literal.type,
240         .float_literal => |literal| return literal.type,
241         .string_literal => return makePointerType(
242             parser,
243             expr,
244             &ast.types.char_type,
245         ),
246         .identifier => |identifier| return state.object.lookup(parser, identifier.name),
247         .call => |call| {
248             const callee_type = state.object.lookup(parser, call.callee) orelse return null;
249             if (callee_type.kind != .function) return null;
250             return callee_type.child;
251         },
252         .index => |index| {
253             const base = try inferType(parser, index.base) orelse return null;
254             return ast.types.element(base);
255         },
256         .initializer_list => |list| return list.type,
257         .unary => |unary| {
258             const operand = try inferType(parser, unary.operand) orelse return null;
259             return switch (unary.op) {
260                 .negate, .bit_not => if (ast.types.isArithmetic(operand)) operand else null,
261                 .logical_not => &ast.types.int_type,
262                 .address_of => makePointerType(parser, expr, operand),
263                 .deref => ast.types.element(operand),
264             };
265         },
266         .binary => |binary| {
267             const lhs = try inferType(parser, binary.lhs) orelse return null;
268             const rhs = try inferType(parser, binary.rhs) orelse return null;
269             return switch (binary.op) {
270                 .lt, .gt, .le, .ge, .eq, .ne, .logical_and, .logical_or => &ast.types.int_type,
271                 .shl, .shr => if (ast.types.isInteger(lhs) and ast.types.isInteger(rhs)) lhs else null,
272                 .add, .sub, .mul, .div, .rem, .bit_and, .bit_or, .bit_xor => if (ast.types.isArithmetic(lhs) and ast.types.isArithmetic(rhs))
273                     ast.types.commonArithmetic(lhs, rhs)
274                 else
275                     null,
276             };
277         },
278         .assign => |assign| return inferType(parser, assign.target),
279         .conditional => |conditional| {
280             const then_type = try inferType(parser, conditional.then_value) orelse return null;
281             const else_type = try inferType(parser, conditional.else_value) orelse return null;
282             if (ast.types.isArithmetic(then_type) and ast.types.isArithmetic(else_type)) {
283                 return ast.types.commonArithmetic(then_type, else_type);
284             }
285             if (then_type == else_type) return then_type;
286             if (then_type.kind == .nullptr_type and ast.types.isPointerLike(else_type)) return else_type;
287             if (else_type.kind == .nullptr_type and ast.types.isPointerLike(then_type)) return then_type;
288             return null;
289         },
290         .cast => |cast| return cast.target,
291     }
292 }
293 
294 fn makePointerType(
295     parser: *Parser,
296     expr: *ast.Expr,
297     child: *const ast.Type,
298 ) Error!*const ast.Type {
299     if (parser.nodes.inferredType(expr)) |cached| return cached;
300     const token_index = parser.nodes.expressionOrigin(expr);
301     const inferred = try memory.createType(
302         parser,
303         .{ .kind = .pointer, .child = child },
304         memory.directType(token_index, null),
305     );
306     parser.nodes.setInferredType(expr, inferred);
307     return inferred;
308 }
309 
310 fn parseSizeof(parser: *Parser) Error!*ast.Expr {
311     if (cursor.peek(parser).kind == .lparen and start_mod.typename(parser, 1)) {
312         _ = cursor.advance(parser);
313         const specifiers = try ctype.parseSpecifiers(parser);
314         if (specifiers.requires_standalone_declaration) {
315             return diagnostic.fail(parser, error.UnsupportedConstruct, "fixed enum forward declaration must be standalone");
316         }
317         const declarator = try ctype.parseDeclarator(parser, specifiers.type, parseAssignment);
318         _ = try cursor.expect(parser, .rparen);
319         const size = ast.types.byteSize(declarator.type) orelse
320             return diagnostic.fail(parser, error.UnsupportedConstruct, "sizeof of an incomplete type");
321         return memory.create(parser, ast.Expr, .{ .integer_literal = .{ .value = size, .type = &ast.types.ulong_type } });
322     }
323     const operand = try parseUnary(parser);
324     _ = operand;
325     return diagnostic.fail(parser, error.UnsupportedConstruct, "sizeof of an expression");
326 }
327 
328 fn parseAlignof(parser: *Parser) Error!*ast.Expr {
329     if (cursor.peek(parser).kind == .lparen and start_mod.typename(parser, 1)) {
330         _ = cursor.advance(parser);
331         const specifiers = try ctype.parseSpecifiers(parser);
332         if (specifiers.requires_standalone_declaration) {
333             return diagnostic.fail(parser, error.UnsupportedConstruct, "fixed enum forward declaration must be standalone");
334         }
335         const declarator = try ctype.parseDeclarator(parser, specifiers.type, parseAssignment);
336         _ = try cursor.expect(parser, .rparen);
337         const alignment = ast.types.byteAlign(declarator.type) orelse
338             return diagnostic.fail(parser, error.UnsupportedConstruct, "alignof of an incomplete type");
339         return memory.create(parser, ast.Expr, .{ .integer_literal = .{ .value = alignment, .type = &ast.types.ulong_type } });
340     }
341     const operand = try parseUnary(parser);
342     _ = operand;
343     return diagnostic.fail(parser, error.UnsupportedConstruct, "alignof of an expression");
344 }
345 
346 fn parsePostfixChain(parser: *Parser, start: *ast.Expr) Error!*ast.Expr {
347     var current = start;
348     while (true) {
349         switch (cursor.peek(parser).kind) {
350             .lbracket => {
351                 _ = cursor.advance(parser);
352                 const subscript = try parseExpression(parser);
353                 _ = try cursor.expect(parser, .rbracket);
354                 current = try memory.create(parser, ast.Expr, .{ .index = .{ .base = current, .subscript = subscript } });
355             },
356             .plus_plus => {
357                 _ = cursor.advance(parser);
358                 current = try desugarIncrement(parser, current, .add);
359             },
360             .minus_minus => {
361                 _ = cursor.advance(parser);
362                 current = try desugarIncrement(parser, current, .sub);
363             },
364             else => return current,
365         }
366     }
367 }
368 
369 fn parsePrimary(parser: *Parser) Error!*ast.Expr {
370     switch (cursor.peek(parser).kind) {
371         .integer => {
372             const token_index = parser.index;
373             const tok = cursor.advance(parser);
374             const decoded = token.decodeInteger(tok.text) orelse
375                 return diagnostic.fail(parser, error.InvalidConstant, "invalid integer constant");
376             const literal_type: *const ast.Type = if (decoded.bit_width) |width|
377                 try memory.createType(
378                     parser,
379                     .{
380                         .kind = .bitint_type,
381                         .is_unsigned = decoded.is_unsigned,
382                         .bit_width = width,
383                     },
384                     memory.directType(token_index, null),
385                 )
386             else if (decoded.is_unsigned)
387                 (if (decoded.is_long) &ast.types.ulong_type else &ast.types.uint_type)
388             else
389                 (if (decoded.is_long) &ast.types.long_type else &ast.types.int_type);
390             return memory.create(parser, ast.Expr, .{ .integer_literal = .{ .value = decoded.value, .type = literal_type } });
391         },
392         .floating => {
393             const tok = cursor.advance(parser);
394             const decoded = token.decodeFloat(parser.arena, tok.text) orelse
395                 return diagnostic.fail(parser, error.InvalidConstant, "invalid float constant");
396             const literal_type: *const ast.Type = switch (decoded.kind) {
397                 .float => &ast.types.float_type,
398                 .double => &ast.types.double_type,
399                 .decimal32 => &ast.types.decimal32_type,
400                 .decimal64 => &ast.types.decimal64_type,
401                 .decimal128 => &ast.types.decimal128_type,
402             };
403             return memory.create(parser, ast.Expr, .{ .float_literal = .{ .value = decoded.value, .type = literal_type } });
404         },
405         .kw_true, .kw_false => {
406             const tok = cursor.advance(parser);
407             const value: u64 = if (tok.kind == .kw_true) 1 else 0;
408             return memory.create(parser, ast.Expr, .{ .integer_literal = .{ .value = value, .type = &ast.types.uchar_type } });
409         },
410         .kw_nullptr => {
411             _ = cursor.advance(parser);
412             return memory.create(parser, ast.Expr, .{ .integer_literal = .{ .value = 0, .type = &ast.types.nullptr_type } });
413         },
414         .character => {
415             const tok = cursor.advance(parser);
416             const value = token.decodeCharacter(tok.text) orelse
417                 return diagnostic.fail(parser, error.InvalidConstant, "invalid character constant");
418             return memory.create(parser, ast.Expr, .{ .integer_literal = .{ .value = value, .type = &ast.types.int_type } });
419         },
420         .string => {
421             const token_index = parser.index;
422             const tok = cursor.advance(parser);
423             const text = token.decodeString(parser.arena, tok.text) orelse
424                 return diagnostic.fail(parser, error.InvalidConstant, "invalid string constant");
425             return memory.createExprAt(
426                 parser,
427                 .{ .string_literal = .{ .text = text } },
428                 token_index,
429             );
430         },
431         .identifier => {
432             const tok = cursor.advance(parser);
433             if (cursor.peek(parser).kind == .lparen) {
434                 _ = cursor.advance(parser);
435                 var arguments = std.ArrayListUnmanaged(*ast.Expr).empty;
436                 if (!cursor.consume(parser, .rparen)) {
437                     while (true) {
438                         try arguments.append(parser.arena, try parseAssignment(parser));
439                         if (!cursor.consume(parser, .comma)) break;
440                     }
441                     _ = try cursor.expect(parser, .rparen);
442                 }
443                 return memory.create(parser, ast.Expr, .{ .call = .{
444                     .callee = tok.text,
445                     .arguments = try arguments.toOwnedSlice(parser.arena),
446                 } });
447             }
448             if (object.constantValue(parser, tok.text)) |value| {
449                 const constant_type = object.lookup(parser, tok.text) orelse &ast.types.int_type;
450                 return integerConstantExpression(parser, value, constant_type);
451             }
452             return memory.create(parser, ast.Expr, .{ .identifier = .{ .name = tok.text } });
453         },
454         .lparen => {
455             _ = cursor.advance(parser);
456             const inner = try parseExpression(parser);
457             _ = try cursor.expect(parser, .rparen);
458             return inner;
459         },
460         else => return diagnostic.fail(parser, error.UnexpectedToken, "expected an expression"),
461     }
462 }
463 
464 fn parseSource(arena: std.mem.Allocator, source: []const u8) !*ast.Expr {
465     const lexer = @import("../lexer/root.zig");
466     const token_survey = try lexer.survey(source, "expr.c");
467     const capacity = try lexer.Capacity.derive(token_survey.limits);
468     const bytes = try arena.alignedAlloc(
469         u8,
470         .fromByteUnits(lexer.Storage.storage_alignment),
471         capacity.storage_bytes,
472     );
473     var storage = try lexer.Storage.init(bytes, token_survey.limits);
474     storage.activate();
475     defer _ = storage.deinit();
476     const tokens = try storage.fill(token_survey, source, "expr.c");
477     var parser = try @import("state/test.zig").initParser(arena, tokens);
478     return parseExpression(&parser);
479 }
480 
481 test "precedence binds multiplication over addition" {
482     var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);
483     defer arena_state.deinit();
484     const arena = arena_state.allocator();
485 
486     const sum = try parseSource(arena, "a + b * c");
487     try std.testing.expectEqual(ast.expr.BinaryOp.add, sum.binary.op);
488     try std.testing.expectEqual(ast.expr.BinaryOp.mul, sum.binary.rhs.binary.op);
489 
490     const comparison = try parseSource(arena, "i * n + j < bound == 0");
491     try std.testing.expectEqual(ast.expr.BinaryOp.eq, comparison.binary.op);
492     try std.testing.expectEqual(ast.expr.BinaryOp.lt, comparison.binary.lhs.binary.op);
493 }
494 
495 test "assignments nest right and compound ops decode" {
496     var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);
497     defer arena_state.deinit();
498     const arena = arena_state.allocator();
499 
500     const chain = try parseSource(arena, "a = b = 1");
501     try std.testing.expect(chain.assign.op == null);
502     try std.testing.expect(chain.assign.value.assign.op == null);
503 
504     const compound = try parseSource(arena, "C[i][j] += alpha * A[i][k]");
505     try std.testing.expectEqual(ast.expr.BinaryOp.add, compound.assign.op.?);
506     try std.testing.expect(compound.assign.target.* == .index);
507     try std.testing.expect(compound.assign.target.index.base.* == .index);
508 }
509 
510 test "unary postfix and casts parse" {
511     var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);
512     defer arena_state.deinit();
513     const arena = arena_state.allocator();
514 
515     const increment = try parseSource(arena, "i++");
516     try std.testing.expectEqual(ast.expr.BinaryOp.add, increment.assign.op.?);
517 
518     const cast = try parseSource(arena, "(double)(i % n) / n");
519     try std.testing.expectEqual(ast.expr.BinaryOp.div, cast.binary.op);
520     try std.testing.expect(cast.binary.lhs.* == .cast);
521 
522     const ternary = try parseSource(arena, "x > 0 ? f(x, 1) : -x");
523     try std.testing.expect(ternary.* == .conditional);
524     try std.testing.expectEqual(@as(usize, 2), ternary.conditional.then_value.call.arguments.len);
525 
526     const size = try parseSource(arena, "sizeof(double)");
527     try std.testing.expectEqual(@as(u64, 8), size.integer_literal.value);
528 
529     const alignment = try parseSource(arena, "alignof(double)");
530     try std.testing.expectEqual(@as(u64, 8), alignment.integer_literal.value);
531 }
532 
533 test "c23 constants parse as existing scalar expressions" {
534     var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);
535     defer arena_state.deinit();
536     const arena = arena_state.allocator();
537 
538     const binary = try parseSource(arena, "0b1010'0101");
539     try std.testing.expectEqual(@as(u64, 0xa5), binary.integer_literal.value);
540 
541     const signed_bitint = try parseSource(arena, "3wb");
542     try std.testing.expectEqual(ast.types.Kind.bitint_type, signed_bitint.integer_literal.type.kind);
543     try std.testing.expectEqual(@as(u16, 3), signed_bitint.integer_literal.type.bit_width);
544 
545     const unsigned_bitint = try parseSource(arena, "3uwb");
546     try std.testing.expectEqual(ast.types.Kind.bitint_type, unsigned_bitint.integer_literal.type.kind);
547     try std.testing.expect(unsigned_bitint.integer_literal.type.is_unsigned);
548     try std.testing.expectEqual(@as(u16, 2), unsigned_bitint.integer_literal.type.bit_width);
549 
550     const separated = try parseSource(arena, "1.25'5");
551     try std.testing.expectEqual(@as(f64, 1.255), separated.float_literal.value);
552 
553     const decimal32 = try parseSource(arena, "1.25df");
554     try std.testing.expectEqual(ast.types.Kind.decimal32_type, decimal32.float_literal.type.kind);
555 
556     const decimal64 = try parseSource(arena, "1.25DD");
557     try std.testing.expectEqual(ast.types.Kind.decimal64_type, decimal64.float_literal.type.kind);
558 
559     const decimal128 = try parseSource(arena, "1.25DL");
560     try std.testing.expectEqual(ast.types.Kind.decimal128_type, decimal128.float_literal.type.kind);
561 
562     const truth = try parseSource(arena, "true ? 299'792'458 : false");
563     try std.testing.expect(truth.* == .conditional);
564     try std.testing.expectEqual(@as(u64, 1), truth.conditional.condition.integer_literal.value);
565     try std.testing.expectEqual(@as(u64, 0), truth.conditional.else_value.integer_literal.value);
566 
567     const null_expr = try parseSource(arena, "nullptr");
568     try std.testing.expectEqual(ast.types.Kind.nullptr_type, null_expr.integer_literal.type.kind);
569 }
570 
571 test "integer constant expressions evaluate" {
572     var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);
573     defer arena_state.deinit();
574     const arena = arena_state.allocator();
575 
576     const expr = try parseSource(arena, "alignof(double) == 8 && (1 + 2 * 3) == 7");
577     var parser = try @import("state/test.zig").initParser(arena, &.{});
578     try std.testing.expectEqual(@as(i128, 1), evaluateIntegerConstant(&parser, expr).?);
579 }
580 
581 test "expression types infer from parser objects" {
582     const lexer = @import("../lexer/root.zig");
583     var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);
584     defer arena_state.deinit();
585     const arena = arena_state.allocator();
586 
587     const source = "x + 1.0";
588     const token_survey = try lexer.survey(source, "expr.c");
589     const capacity = try lexer.Capacity.derive(token_survey.limits);
590     const bytes = try arena.alignedAlloc(
591         u8,
592         .fromByteUnits(lexer.Storage.storage_alignment),
593         capacity.storage_bytes,
594     );
595     var storage = try lexer.Storage.init(bytes, token_survey.limits);
596     storage.activate();
597     defer _ = storage.deinit();
598     const tokens = try storage.fill(token_survey, source, "expr.c");
599     var parser = try @import("state/test.zig").initParser(arena, tokens);
600     try state.object.register(&parser, "x", &ast.types.int_type);
601 
602     const expr = try parseExpression(&parser);
603     const inferred = (try inferType(&parser, expr)).?;
604     try std.testing.expectEqual(ast.types.Kind.double_type, inferred.kind);
605 }
606 
607 test "pointer inference reuses one charged type" {
608     comptime {
609         @stardustClaim(
610             @import("alloc_phase").capacity.witness(@import("./state/root.zig").Storage, "chant_parser_type_cache"),
611             null,
612             null,
613             null,
614             null,
615             null,
616             null,
617         );
618     }
619 
620     const lexer = @import("../lexer/root.zig");
621     var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);
622     defer arena_state.deinit();
623     const arena = arena_state.allocator();
624 
625     const source = "&x";
626     const token_survey = try lexer.survey(source, "infer.c");
627     const capacity = try lexer.Capacity.derive(token_survey.limits);
628     const bytes = try arena.alignedAlloc(
629         u8,
630         .fromByteUnits(lexer.Storage.storage_alignment),
631         capacity.storage_bytes,
632     );
633     var storage = try lexer.Storage.init(bytes, token_survey.limits);
634     storage.activate();
635     defer _ = storage.deinit();
636     const tokens = try storage.fill(token_survey, source, "infer.c");
637     var parser = try @import("state/test.zig").initParser(arena, tokens);
638     try state.object.register(&parser, "x", &ast.types.int_type);
639     const expr = try parseExpression(&parser);
640 
641     const first = (try inferType(&parser, expr)).?;
642     const after_first = parser.nodes.status();
643     const second = (try inferType(&parser, expr)).?;
644     try std.testing.expectEqual(first, second);
645     try std.testing.expectEqual(after_first, parser.nodes.status());
646 }