const std = @import("std");
const math = std.math;
const mem = std.mem;
const assert = std.debug.assert;

const aro = @import("aro");
const CToken = aro.Tokenizer.Token;

const ast = @import("ast.zig");
const builtins = @import("builtins.zig");
const ZigNode = ast.Node;
const ZigTag = ZigNode.Tag;
const Scope = @import("Scope.zig");
const Translator = @import("Translator.zig");

const Error = Translator.Error;
pub const ParseError = Error || error{ParseError};

const MacroTranslator = @This();

t: *Translator,
macro: aro.Preprocessor.Macro,
name: []const u8,

tokens: []const CToken,
source: []const u8,
i: usize = 0,
/// If an object macro references a global var it needs to be converted into
/// an inline function.
refs_var_decl: bool = false,

fn peek(mt: *MacroTranslator) CToken.Id {
    if (mt.i >= mt.tokens.len) return .eof;
    return mt.tokens[mt.i].id;
}

fn eat(mt: *MacroTranslator, expected_id: CToken.Id) bool {
    if (mt.peek() == expected_id) {
        mt.i += 1;
        return true;
    }
    return false;
}

fn expect(mt: *MacroTranslator, expected_id: CToken.Id) ParseError!void {
    const next_id = mt.peek();
    if (next_id != expected_id and !(expected_id == .identifier and next_id == .extended_identifier)) {
        try mt.fail(
            "unable to translate C expr: expected '{s}' instead got '{s}'",
            .{ expected_id.symbol(), next_id.symbol() },
        );
        return error.ParseError;
    }
    mt.i += 1;
}

fn fail(mt: *MacroTranslator, comptime fmt: []const u8, args: anytype) !void {
    return mt.t.failDeclExtra(&mt.t.global_scope.base, mt.macro.loc, mt.name, fmt, args);
}

fn tokSlice(mt: *const MacroTranslator) []const u8 {
    const tok = mt.tokens[mt.i];
    return mt.source[tok.start..tok.end];
}

pub fn transFnMacro(mt: *MacroTranslator) ParseError!void {
    var block_scope = try Scope.Block.init(mt.t, &mt.t.global_scope.base, false);
    defer block_scope.deinit();
    const scope = &block_scope.base;

    const fn_params = try mt.t.arena.alloc(ast.Payload.Param, mt.macro.params.len);
    for (fn_params, mt.macro.params) |*param, param_name| {
        const mangled_name = try block_scope.makeMangledName(param_name);
        param.* = .{
            .is_noalias = false,
            .name = mangled_name,
            .type = ZigTag.@"anytype".init(),
        };
        try block_scope.discardVariable(mangled_name);
    }

    // #define FOO(x)
    if (mt.peek() == .eof) {
        try block_scope.statements.append(mt.t.gpa, ZigTag.return_void.init());

        const fn_decl = try ZigTag.pub_inline_fn.create(mt.t.arena, .{
            .name = mt.name,
            .params = fn_params,
            .return_type = ZigTag.void_type.init(),
            .body = try block_scope.complete(),
        });
        try mt.t.addTopLevelDecl(mt.name, fn_decl);
        return;
    }

    const expr = try mt.parseCExpr(scope);
    const last = mt.peek();
    if (last != .eof)
        return mt.fail("unable to translate C expr: unexpected token '{s}'", .{last.symbol()});

    const typeof_arg = if (expr.castTag(.block)) |some| blk: {
        const stmts = some.data.stmts;
        const blk_last = stmts[stmts.len - 1];
        const br = blk_last.castTag(.break_val).?;
        break :blk br.data.val;
    } else expr;

    const return_type = ret: {
        if (typeof_arg.castTag(.helper_call)) |some| {
            if (std.mem.eql(u8, some.data.name, "cast")) {
                break :ret some.data.args[0];
            }
        }
        if (typeof_arg.castTag(.std_mem_zeroinit)) |some| break :ret some.data.lhs;
        if (typeof_arg.castTag(.std_mem_zeroes)) |some| break :ret some.data;
        break :ret try ZigTag.typeof.create(mt.t.arena, typeof_arg);
    };

    const return_expr = try ZigTag.@"return".create(mt.t.arena, expr);
    try block_scope.statements.append(mt.t.gpa, return_expr);

    const fn_decl = try ZigTag.pub_inline_fn.create(mt.t.arena, .{
        .name = mt.name,
        .params = fn_params,
        .return_type = return_type,
        .body = try block_scope.complete(),
    });
    try mt.t.addTopLevelDecl(mt.name, fn_decl);
}

pub fn transMacro(mt: *MacroTranslator) ParseError!void {
    const scope = &mt.t.global_scope.base;

    // Check if the macro only uses other blank macros.
    while (true) {
        switch (mt.peek()) {
            .identifier, .extended_identifier => {
                if (mt.t.global_scope.blank_macros.contains(mt.tokSlice())) {
                    mt.i += 1;
                    continue;
                }
            },
            .eof, .nl => {
                try mt.t.global_scope.blank_macros.put(mt.t.gpa, mt.name, {});
                const init_node = try ZigTag.string_literal.create(mt.t.arena, "\"\"");
                const var_decl = try ZigTag.pub_var_simple.create(mt.t.arena, .{ .name = mt.name, .init = init_node });
                try mt.t.addTopLevelDecl(mt.name, var_decl);
                return;
            },
            else => {},
        }
        break;
    }

    const init_node = try mt.parseCExpr(scope);
    const last = mt.peek();
    if (last != .eof)
        return mt.fail("unable to translate C expr: unexpected token '{s}'", .{last.symbol()});

    const node = node: {
        const var_decl = try ZigTag.pub_var_simple.create(mt.t.arena, .{ .name = mt.name, .init = init_node });

        if (mt.t.getFnProto(var_decl)) |proto_node| {
            // If a macro aliases a global variable which is a function pointer, we conclude that
            // the macro is intended to represent a function that assumes the function pointer
            // variable is non-null and calls it.
            break :node try mt.createMacroFn(mt.name, var_decl, proto_node);
        } else if (mt.refs_var_decl) {
            const return_type = try ZigTag.typeof.create(mt.t.arena, init_node);
            const return_expr = try ZigTag.@"return".create(mt.t.arena, init_node);
            const block = try ZigTag.block_single.create(mt.t.arena, return_expr);

            const loc_str = try mt.t.locStr(mt.macro.loc);
            const value = try std.fmt.allocPrint(mt.t.arena, "\n// {s}: warning: macro '{s}' contains a runtime value, translated to function", .{ loc_str, mt.name });
            try scope.appendNode(try ZigTag.warning.create(mt.t.arena, value));

            break :node try ZigTag.pub_inline_fn.create(mt.t.arena, .{
                .name = mt.name,
                .params = &.{},
                .return_type = return_type,
                .body = block,
            });
        }

        break :node var_decl;
    };

    try mt.t.addTopLevelDecl(mt.name, node);
}

fn createMacroFn(mt: *MacroTranslator, name: []const u8, ref: ZigNode, proto_alias: *ast.Payload.Func) !ZigNode {
    const gpa = mt.t.gpa;
    const arena = mt.t.arena;
    var fn_params: std.ArrayList(ast.Payload.Param) = .empty;
    defer fn_params.deinit(gpa);

    var block_scope = try Scope.Block.init(mt.t, &mt.t.global_scope.base, false);
    defer block_scope.deinit();

    for (proto_alias.data.params) |param| {
        const param_name = try block_scope.makeMangledName(param.name orelse "arg");

        try fn_params.append(gpa, .{
            .name = param_name,
            .type = param.type,
            .is_noalias = param.is_noalias,
        });
    }

    const init = if (ref.castTag(.var_decl)) |v|
        v.data.init.?
    else if (ref.castTag(.var_simple) orelse ref.castTag(.pub_var_simple)) |v|
        v.data.init
    else
        unreachable;

    const unwrap_expr = try ZigTag.unwrap.create(arena, init);
    const args = try arena.alloc(ZigNode, fn_params.items.len);
    for (fn_params.items, 0..) |param, i| {
        args[i] = try ZigTag.identifier.create(arena, param.name.?);
    }
    const call_expr = try ZigTag.call.create(arena, .{
        .lhs = unwrap_expr,
        .args = args,
    });
    const return_expr = try ZigTag.@"return".create(arena, call_expr);
    const block = try ZigTag.block_single.create(arena, return_expr);

    return ZigTag.pub_inline_fn.create(arena, .{
        .name = name,
        .params = try arena.dupe(ast.Payload.Param, fn_params.items),
        .return_type = proto_alias.data.return_type,
        .body = block,
    });
}

fn parseCExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode {
    const arena = mt.t.arena;
    // TODO parseCAssignExpr here
    var block_scope = try Scope.Block.init(mt.t, scope, true);
    defer block_scope.deinit();

    const node = try mt.parseCCondExpr(&block_scope.base);
    if (!mt.eat(.comma)) return node;

    var last = node;
    while (true) {
        // suppress result
        const ignore = try ZigTag.discard.create(arena, .{ .should_skip = false, .value = last });
        try block_scope.statements.append(mt.t.gpa, ignore);

        last = try mt.parseCCondExpr(&block_scope.base);
        if (!mt.eat(.comma)) break;
    }

    const break_node = try ZigTag.break_val.create(arena, .{
        .label = block_scope.label,
        .val = last,
    });
    try block_scope.statements.append(mt.t.gpa, break_node);
    return try block_scope.complete();
}

fn parseCNumLit(mt: *MacroTranslator) ParseError!ZigNode {
    const arena = mt.t.arena;
    const lit_bytes = mt.tokSlice();
    mt.i += 1;

    // +3 for prefix and +2 for suffix
    var bytes = try std.ArrayList(u8).initCapacity(arena, lit_bytes.len + 3 + 2);

    const prefix = aro.Tree.Token.NumberPrefix.fromString(lit_bytes);
    switch (prefix) {
        .binary => bytes.appendSliceAssumeCapacity("0b"),
        .octal => bytes.appendSliceAssumeCapacity("0o"),
        .hex => bytes.appendSliceAssumeCapacity("0x"),
        .decimal => {},
    }

    const after_prefix = lit_bytes[prefix.stringLen()..];
    const after_int = for (after_prefix, 0..) |c, i| switch (c) {
        '.' => {
            if (i == 0) {
                bytes.appendAssumeCapacity('0');
            }
            break after_prefix[i..];
        },
        'e', 'E' => {
            if (prefix != .hex) break after_prefix[i..];
            bytes.appendAssumeCapacity(c);
        },
        'p', 'P' => break after_prefix[i..],
        '0'...'9', 'a'...'d', 'A'...'D', 'f', 'F' => {
            if (!prefix.digitAllowed(c)) break after_prefix[i..];
            bytes.appendAssumeCapacity(c);
        },
        '\'' => {
            bytes.appendAssumeCapacity('_');
        },
        else => break after_prefix[i..],
    } else "";

    const after_frac = frac: {
        if (after_int.len == 0 or after_int[0] != '.') break :frac after_int;
        bytes.appendAssumeCapacity('.');
        for (after_int[1..], 1..) |c, i| {
            if (c == '\'') {
                bytes.appendAssumeCapacity('_');
                continue;
            }
            if (!prefix.digitAllowed(c)) break :frac after_int[i..];
            bytes.appendAssumeCapacity(c);
        }
        break :frac "";
    };

    const suffix_str = exponent: {
        if (after_frac.len == 0) break :exponent after_frac;
        switch (after_frac[0]) {
            'e', 'E' => {},
            'p', 'P' => if (prefix != .hex) break :exponent after_frac,
            else => break :exponent after_frac,
        }
        bytes.appendAssumeCapacity(after_frac[0]);
        for (after_frac[1..], 1..) |c, i| switch (c) {
            '+', '-', '0'...'9' => {
                bytes.appendAssumeCapacity(c);
            },
            '\'' => {
                bytes.appendAssumeCapacity('_');
            },
            else => break :exponent after_frac[i..],
        };
        break :exponent "";
    };

    const is_float = after_int.len != suffix_str.len;
    const suffix = aro.Tree.Token.NumberSuffix.fromString(suffix_str, if (is_float) .float else .int) orelse {
        try mt.fail("invalid number suffix: '{s}'", .{suffix_str});
        return error.ParseError;
    };
    if (suffix.isImaginary()) {
        try mt.fail("TODO: imaginary literals", .{});
        return error.ParseError;
    }
    if (suffix.isBitInt()) {
        try mt.fail("TODO: _BitInt literals", .{});
        return error.ParseError;
    }

    if (is_float) {
        const type_node = try ZigTag.type.create(arena, switch (suffix) {
            .F16 => "f16",
            .F, .F32 => "f32",
            .None, .F32x, .F64 => "f64",
            .L, .F64x => "c_longdouble",
            .W => "f80",
            .Q, .F128 => "f128",
            else => {
                try mt.fail("TODO: float literal suffix: '{s}'", .{suffix_str});
                return error.ParseError;
            },
        });
        if (bytes.getLast() == '.') {
            bytes.appendAssumeCapacity('0');
        } else if (mem.findAny(u8, bytes.items, ".eEpP") == null) {
            bytes.appendSliceAssumeCapacity(".0");
        }
        const rhs = try ZigTag.float_literal.create(arena, bytes.items);
        return ZigTag.as.create(arena, .{ .lhs = type_node, .rhs = rhs });
    } else {
        const type_node = try ZigTag.type.create(arena, switch (suffix) {
            .None => "c_int",
            .U => "c_uint",
            .L => "c_long",
            .UL => "c_ulong",
            .LL => "c_longlong",
            .ULL => "c_ulonglong",
            else => unreachable,
        });
        const value = std.fmt.parseInt(i128, bytes.items, 0) catch math.maxInt(i128);

        // make the output less noisy by skipping promoteIntLiteral where
        // it's guaranteed to not be required because of C standard type constraints
        const guaranteed_to_fit = switch (suffix) {
            .None => math.cast(i16, value) != null,
            .U => math.cast(u16, value) != null,
            .L => math.cast(i32, value) != null,
            .UL => math.cast(u32, value) != null,
            .LL => math.cast(i64, value) != null,
            .ULL => math.cast(u64, value) != null,
            else => unreachable,
        };

        const literal_node = try ZigTag.integer_literal.create(arena, bytes.items);
        if (guaranteed_to_fit) {
            return ZigTag.as.create(arena, .{ .lhs = type_node, .rhs = literal_node });
        } else {
            return mt.t.createHelperCallNode(.promoteIntLiteral, &.{ type_node, literal_node, try ZigTag.enum_literal.create(arena, @tagName(prefix)) });
        }
    }
}

fn zigifyEscapeSequences(mt: *MacroTranslator, slice: []const u8) ![]const u8 {
    var source = slice;
    for (source, 0..) |c, i| {
        if (c == '\"' or c == '\'') {
            source = source[i..];
            break;
        }
    }
    for (source) |c| {
        if (c == '\\' or c == '\t') {
            break;
        }
    } else return source;
    const bytes = try mt.t.arena.alloc(u8, source.len * 2);
    var state: enum {
        start,
        escape,
        hex,
        octal,
    } = .start;
    var i: usize = 0;
    var count: u8 = 0;
    var num: u8 = 0;
    for (source) |c| {
        switch (state) {
            .escape => {
                switch (c) {
                    'n', 'r', 't', '\\', '\'', '\"' => {
                        bytes[i] = c;
                    },
                    '0'...'7' => {
                        count += 1;
                        num += c - '0';
                        state = .octal;
                        bytes[i] = 'x';
                    },
                    'x' => {
                        state = .hex;
                        bytes[i] = 'x';
                    },
                    'a' => {
                        bytes[i] = 'x';
                        i += 1;
                        bytes[i] = '0';
                        i += 1;
                        bytes[i] = '7';
                    },
                    'b' => {
                        bytes[i] = 'x';
                        i += 1;
                        bytes[i] = '0';
                        i += 1;
                        bytes[i] = '8';
                    },
                    'f' => {
                        bytes[i] = 'x';
                        i += 1;
                        bytes[i] = '0';
                        i += 1;
                        bytes[i] = 'C';
                    },
                    'v' => {
                        bytes[i] = 'x';
                        i += 1;
                        bytes[i] = '0';
                        i += 1;
                        bytes[i] = 'B';
                    },
                    '?' => {
                        i -= 1;
                        bytes[i] = '?';
                    },
                    'u', 'U' => {
                        try mt.fail("macro tokenizing failed: TODO unicode escape sequences", .{});
                        return error.ParseError;
                    },
                    else => {
                        try mt.fail("macro tokenizing failed: unknown escape sequence", .{});
                        return error.ParseError;
                    },
                }
                i += 1;
                if (state == .escape)
                    state = .start;
            },
            .start => {
                if (c == '\t') {
                    bytes[i] = '\\';
                    i += 1;
                    bytes[i] = 't';
                    i += 1;
                    continue;
                }
                if (c == '\\') {
                    state = .escape;
                }
                bytes[i] = c;
                i += 1;
            },
            .hex => {
                switch (c) {
                    '0'...'9' => {
                        num = std.math.mul(u8, num, 16) catch {
                            try mt.fail("macro tokenizing failed: hex literal overflowed", .{});
                            return error.ParseError;
                        };
                        num += c - '0';
                    },
                    'a'...'f' => {
                        num = std.math.mul(u8, num, 16) catch {
                            try mt.fail("macro tokenizing failed: hex literal overflowed", .{});
                            return error.ParseError;
                        };
                        num += c - 'a' + 10;
                    },
                    'A'...'F' => {
                        num = std.math.mul(u8, num, 16) catch {
                            try mt.fail("macro tokenizing failed: hex literal overflowed", .{});
                            return error.ParseError;
                        };
                        num += c - 'A' + 10;
                    },
                    else => {
                        i += std.fmt.printInt(bytes[i..], num, 16, .lower, .{ .fill = '0', .width = 2 });
                        num = 0;
                        if (c == '\\')
                            state = .escape
                        else
                            state = .start;
                        bytes[i] = c;
                        i += 1;
                    },
                }
            },
            .octal => {
                const accept_digit = switch (c) {
                    // The maximum length of a octal literal is 3 digits
                    '0'...'7' => count < 3,
                    else => false,
                };

                if (accept_digit) {
                    count += 1;
                    num = std.math.mul(u8, num, 8) catch {
                        try mt.fail("macro tokenizing failed: octal literal overflowed", .{});
                        return error.ParseError;
                    };
                    num += c - '0';
                } else {
                    i += std.fmt.printInt(bytes[i..], num, 16, .lower, .{ .fill = '0', .width = 2 });
                    num = 0;
                    count = 0;
                    if (c == '\\')
                        state = .escape
                    else
                        state = .start;
                    bytes[i] = c;
                    i += 1;
                }
            },
        }
    }
    if (state == .hex or state == .octal) {
        i += std.fmt.printInt(bytes[i..], num, 16, .lower, .{ .fill = '0', .width = 2 });
    }

    return bytes[0..i];
}

/// non-ASCII characters (mt > 127) are also treated as non-printable by fmtSliceEscapeLower.
/// If a C string literal or char literal in a macro is not valid UTF-8, we need to escape
/// non-ASCII characters so that the Zig source we output will itself be UTF-8.
fn escapeUnprintables(mt: *MacroTranslator) ![]const u8 {
    const slice = mt.tokSlice();
    mt.i += 1;

    const zigified = try mt.zigifyEscapeSequences(slice);
    if (std.unicode.utf8ValidateSlice(zigified)) return zigified;

    const formatter = std.ascii.hexEscape(zigified, .lower);
    const encoded_size = @as(usize, @intCast(std.fmt.count("{f}", .{formatter})));
    const output = try mt.t.arena.alloc(u8, encoded_size);
    return std.fmt.bufPrint(output, "{f}", .{formatter}) catch |err| switch (err) {
        error.NoSpaceLeft => unreachable,
        else => |e| return e,
    };
}

fn parseCPrimaryExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode {
    const arena = mt.t.arena;
    const gpa = mt.t.gpa;
    const tok = mt.peek();
    switch (tok) {
        .char_literal,
        .char_literal_utf_8,
        .char_literal_utf_16,
        .char_literal_utf_32,
        .char_literal_wide,
        => {
            const slice = mt.tokSlice();
            if (slice[0] != '\'' or slice[1] == '\\' or slice.len == 3) {
                return ZigTag.char_literal.create(arena, try mt.escapeUnprintables());
            } else {
                mt.i += 1;

                const str = try std.fmt.allocPrint(arena, "0x{x}", .{slice[1 .. slice.len - 1]});
                return ZigTag.integer_literal.create(arena, str);
            }
        },
        .string_literal,
        .string_literal_utf_16,
        .string_literal_utf_8,
        .string_literal_utf_32,
        .string_literal_wide,
        => return ZigTag.string_literal.create(arena, try mt.escapeUnprintables()),
        .pp_num => return mt.parseCNumLit(),
        .l_paren => {
            mt.i += 1;
            const inner_node = try mt.parseCExpr(scope);

            try mt.expect(.r_paren);
            return inner_node;
        },
        .macro_param, .macro_param_no_expand => {
            const param = mt.macro.params[mt.tokens[mt.i].end];
            mt.i += 1;

            const mangled_name = scope.getAlias(param) orelse param;
            return try ZigTag.identifier.create(arena, mangled_name);
        },
        .identifier, .extended_identifier => {
            const slice = mt.tokSlice();
            mt.i += 1;

            const mangled_name = scope.getAlias(slice) orelse slice;
            if (Translator.builtin_typedef_map.get(mangled_name)) |ty| {
                return ZigTag.type.create(arena, ty);
            }
            if (builtins.map.get(mangled_name)) |builtin| {
                const builtin_identifier = try ZigTag.identifier.create(arena, "__builtin");
                return ZigTag.field_access.create(arena, .{
                    .lhs = builtin_identifier,
                    .field_name = builtin.name,
                });
            }

            const identifier = try ZigTag.identifier.create(arena, mangled_name);
            scope.skipVariableDiscard(mangled_name);
            refs_var: {
                const ident_node = mt.t.global_scope.sym_table.get(slice) orelse break :refs_var;
                const var_decl_node = ident_node.castTag(.var_decl) orelse break :refs_var;
                if (!var_decl_node.data.is_const) mt.refs_var_decl = true;
            }
            return identifier;
        },
        .keyword_generic => {
            mt.i += 1;

            try mt.expect(.l_paren);
            const param = try mt.parseCCondExpr(scope);
            const typeof_param = try ZigTag.typeof.create(arena, param);
            try mt.expect(.comma);

            var cases: std.ArrayList(ZigNode) = .empty;
            defer cases.deinit(gpa);
            var has_default = false;
            while (true) {
                const case = if (mt.eat(.keyword_default)) blk: {
                    has_default = true;
                    try mt.expect(.colon);
                    const expr = try mt.parseCCondExpr(scope);
                    break :blk try ZigTag.switch_else.create(arena, expr);
                } else blk: {
                    const case_type = try mt.parseCTypeName(scope) orelse {
                        try mt.fail("unable to translate C expr: expected type instead got '{s}'", .{mt.peek().symbol()});
                        return error.ParseError;
                    };
                    try mt.expect(.colon);
                    const expr = try mt.parseCCondExpr(scope);
                    break :blk try ZigTag.switch_prong.create(arena, .{
                        .cases = try arena.dupe(ZigNode, &.{case_type}),
                        .cond = expr,
                    });
                };
                try cases.append(gpa, case);
                if (!mt.eat(.comma)) break;
            }
            try mt.expect(.r_paren);

            if (!has_default) try cases.append(gpa, try ZigTag.switch_else.create(
                arena,
                try ZigTag.@"comptime".create(arena, ZigTag.@"unreachable".init()),
            ));

            const sw = try ZigTag.@"switch".create(arena, .{
                .cond = typeof_param,
                .cases = try arena.dupe(ZigNode, cases.items),
            });
            return sw;
        },
        else => {},
    }

    // for handling type macros (EVIL)
    // TODO maybe detect and treat type macros as typedefs in parseCSpecifierQualifierList?
    if (try mt.parseCTypeName(scope)) |type_name| {
        return type_name;
    }

    try mt.fail("unable to translate C expr: unexpected token '{s}'", .{tok.symbol()});
    return error.ParseError;
}

fn macroIntFromBool(mt: *MacroTranslator, node: ZigNode) !ZigNode {
    if (!node.isBoolRes()) return node;

    return ZigTag.int_from_bool.create(mt.t.arena, node);
}

fn macroIntToBool(mt: *MacroTranslator, node: ZigNode) !ZigNode {
    if (node.isBoolRes()) return node;

    if (node.tag() == .string_literal) {
        // @intFromPtr(node) != 0
        const int_from_ptr = try ZigTag.int_from_ptr.create(mt.t.arena, node);
        return ZigTag.not_equal.create(mt.t.arena, .{ .lhs = int_from_ptr, .rhs = ZigTag.zero_literal.init() });
    }
    // node != 0
    return ZigTag.not_equal.create(mt.t.arena, .{ .lhs = node, .rhs = ZigTag.zero_literal.init() });
}

fn parseCCondExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode {
    const condition = try mt.parseCOrExpr(scope);
    if (!mt.eat(.question_mark)) return condition;
    const bool_ty = try ZigTag.type.create(mt.t.arena, "bool");
    const node = try mt.t.createHelperCallNode(.cast, &.{ bool_ty, condition });

    const then_body = try mt.parseCOrExpr(scope);
    try mt.expect(.colon);
    const else_body = try mt.parseCCondExpr(scope);
    return ZigTag.@"if".create(mt.t.arena, .{ .cond = node, .then = then_body, .@"else" = else_body });
}

fn parseCOrExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode {
    var node = try mt.parseCAndExpr(scope);
    while (mt.eat(.pipe_pipe)) {
        const lhs = try mt.macroIntToBool(node);
        const rhs = try mt.macroIntToBool(try mt.parseCAndExpr(scope));
        node = try ZigTag.@"or".create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs });
    }
    return node;
}

fn parseCAndExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode {
    var node = try mt.parseCBitOrExpr(scope);
    while (mt.eat(.ampersand_ampersand)) {
        const lhs = try mt.macroIntToBool(node);
        const rhs = try mt.macroIntToBool(try mt.parseCBitOrExpr(scope));
        node = try ZigTag.@"and".create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs });
    }
    return node;
}

fn parseCBitOrExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode {
    var node = try mt.parseCBitXorExpr(scope);
    while (mt.eat(.pipe)) {
        const lhs = try mt.macroIntFromBool(node);
        const rhs = try mt.macroIntFromBool(try mt.parseCBitXorExpr(scope));
        node = try ZigTag.bit_or.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs });
    }
    return node;
}

fn parseCBitXorExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode {
    var node = try mt.parseCBitAndExpr(scope);
    while (mt.eat(.caret)) {
        const lhs = try mt.macroIntFromBool(node);
        const rhs = try mt.macroIntFromBool(try mt.parseCBitAndExpr(scope));
        node = try ZigTag.bit_xor.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs });
    }
    return node;
}

fn parseCBitAndExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode {
    var node = try mt.parseCEqExpr(scope);
    while (mt.eat(.ampersand)) {
        const lhs = try mt.macroIntFromBool(node);
        const rhs = try mt.macroIntFromBool(try mt.parseCEqExpr(scope));
        node = try ZigTag.bit_and.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs });
    }
    return node;
}

fn parseCEqExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode {
    var node = try mt.parseCRelExpr(scope);
    while (true) {
        switch (mt.peek()) {
            .bang_equal => {
                mt.i += 1;
                const lhs = try mt.macroIntFromBool(node);
                const rhs = try mt.macroIntFromBool(try mt.parseCRelExpr(scope));
                node = try ZigTag.not_equal.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs });
            },
            .equal_equal => {
                mt.i += 1;
                const lhs = try mt.macroIntFromBool(node);
                const rhs = try mt.macroIntFromBool(try mt.parseCRelExpr(scope));
                node = try ZigTag.equal.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs });
            },
            else => return node,
        }
    }
}

fn parseCRelExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode {
    var node = try mt.parseCShiftExpr(scope);
    while (true) {
        switch (mt.peek()) {
            .angle_bracket_right => {
                mt.i += 1;
                const lhs = try mt.macroIntFromBool(node);
                const rhs = try mt.macroIntFromBool(try mt.parseCShiftExpr(scope));
                node = try ZigTag.greater_than.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs });
            },
            .angle_bracket_right_equal => {
                mt.i += 1;
                const lhs = try mt.macroIntFromBool(node);
                const rhs = try mt.macroIntFromBool(try mt.parseCShiftExpr(scope));
                node = try ZigTag.greater_than_equal.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs });
            },
            .angle_bracket_left => {
                mt.i += 1;
                const lhs = try mt.macroIntFromBool(node);
                const rhs = try mt.macroIntFromBool(try mt.parseCShiftExpr(scope));
                node = try ZigTag.less_than.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs });
            },
            .angle_bracket_left_equal => {
                mt.i += 1;
                const lhs = try mt.macroIntFromBool(node);
                const rhs = try mt.macroIntFromBool(try mt.parseCShiftExpr(scope));
                node = try ZigTag.less_than_equal.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs });
            },
            else => return node,
        }
    }
}

fn parseCShiftExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode {
    var node = try mt.parseCAddSubExpr(scope);
    while (true) {
        switch (mt.peek()) {
            .angle_bracket_angle_bracket_left => {
                mt.i += 1;
                const lhs = try mt.macroIntFromBool(node);
                const rhs = try mt.macroIntFromBool(try mt.parseCAddSubExpr(scope));
                node = try ZigTag.shl.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs });
            },
            .angle_bracket_angle_bracket_right => {
                mt.i += 1;
                const lhs = try mt.macroIntFromBool(node);
                const rhs = try mt.macroIntFromBool(try mt.parseCAddSubExpr(scope));
                node = try ZigTag.shr.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs });
            },
            else => return node,
        }
    }
}

fn parseCAddSubExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode {
    var node = try mt.parseCMulExpr(scope);
    while (true) {
        switch (mt.peek()) {
            .plus => {
                mt.i += 1;
                const lhs = try mt.macroIntFromBool(node);
                const rhs = try mt.macroIntFromBool(try mt.parseCMulExpr(scope));
                node = try ZigTag.add.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs });
            },
            .minus => {
                mt.i += 1;
                const lhs = try mt.macroIntFromBool(node);
                const rhs = try mt.macroIntFromBool(try mt.parseCMulExpr(scope));
                node = try ZigTag.sub.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs });
            },
            else => return node,
        }
    }
}

fn parseCMulExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode {
    var node = try mt.parseCCastExpr(scope);
    while (true) {
        switch (mt.peek()) {
            .asterisk => {
                mt.i += 1;
                switch (mt.peek()) {
                    .comma, .r_paren, .eof => {
                        // This is probably a pointer type
                        return ZigTag.c_pointer.create(mt.t.arena, .{
                            .is_const = false,
                            .is_volatile = false,
                            .is_allowzero = false,
                            .elem_type = node,
                        });
                    },
                    else => {},
                }
                const lhs = try mt.macroIntFromBool(node);
                const rhs = try mt.macroIntFromBool(try mt.parseCCastExpr(scope));
                node = try ZigTag.mul.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs });
            },
            .slash => {
                mt.i += 1;
                const lhs = try mt.macroIntFromBool(node);
                const rhs = try mt.macroIntFromBool(try mt.parseCCastExpr(scope));
                node = try mt.t.createHelperCallNode(.div, &.{ lhs, rhs });
            },
            .percent => {
                mt.i += 1;
                const lhs = try mt.macroIntFromBool(node);
                const rhs = try mt.macroIntFromBool(try mt.parseCCastExpr(scope));
                node = try mt.t.createHelperCallNode(.rem, &.{ lhs, rhs });
            },
            else => return node,
        }
    }
}

fn parseCCastExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode {
    if (mt.eat(.l_paren)) {
        if (try mt.parseCTypeName(scope)) |type_name| {
            while (true) {
                const next_tok = mt.peek();
                if (next_tok == .r_paren) {
                    mt.i += 1;
                    break;
                }
                // Skip trailing blank defined before the RParen.
                if ((next_tok == .identifier or next_tok == .extended_identifier) and
                    mt.t.global_scope.blank_macros.contains(mt.tokSlice()))
                {
                    mt.i += 1;
                    continue;
                }

                try mt.fail(
                    "unable to translate C expr: expected ')' instead got '{s}'",
                    .{next_tok.symbol()},
                );
                return error.ParseError;
            }
            if (mt.peek() == .l_brace) {
                // initializer list
                return mt.parseCPostfixExpr(scope, type_name);
            }
            const node_to_cast = try mt.parseCCastExpr(scope);
            return mt.t.createHelperCallNode(.cast, &.{ type_name, node_to_cast });
        }
        mt.i -= 1; // l_paren
    }
    return mt.parseCUnaryExpr(scope);
}

// allow_fail is set when unsure if we are parsing a type-name
fn parseCTypeName(mt: *MacroTranslator, scope: *Scope) ParseError!?ZigNode {
    if (try mt.parseCSpecifierQualifierList(scope)) |node| {
        return try mt.parseCAbstractDeclarator(node);
    }
    return null;
}

fn parseCSpecifierQualifierList(mt: *MacroTranslator, scope: *Scope) ParseError!?ZigNode {
    const tok = mt.peek();
    switch (tok) {
        .macro_param, .macro_param_no_expand => {
            const param = mt.macro.params[mt.tokens[mt.i].end];

            // Assume that this is only a cast if the next token is ')'
            // e.g. param)identifier
            if (mt.macro.tokens.len < mt.i + 3 or
                mt.macro.tokens[mt.i + 1].id != .r_paren or
                mt.macro.tokens[mt.i + 2].id != .identifier)
                return null;

            mt.i += 1;
            const mangled_name = scope.getAlias(param) orelse param;
            return try ZigTag.identifier.create(mt.t.arena, mangled_name);
        },
        .identifier, .extended_identifier => {
            const slice = mt.tokSlice();
            const mangled_name = scope.getAlias(slice) orelse slice;

            if (mt.t.global_scope.blank_macros.contains(slice)) {
                mt.i += 1;
                return try mt.parseCSpecifierQualifierList(scope);
            }

            if (mt.t.typedefs.contains(mangled_name)) {
                mt.i += 1;
                if (Translator.builtin_typedef_map.get(mangled_name)) |ty| {
                    return try ZigTag.type.create(mt.t.arena, ty);
                }
                if (builtins.map.get(mangled_name)) |builtin| {
                    const builtin_identifier = try ZigTag.identifier.create(mt.t.arena, "__builtin");
                    return try ZigTag.field_access.create(mt.t.arena, .{
                        .lhs = builtin_identifier,
                        .field_name = builtin.name,
                    });
                }

                return try ZigTag.identifier.create(mt.t.arena, mangled_name);
            }
        },
        .keyword_void => {
            mt.i += 1;
            return try ZigTag.type.create(mt.t.arena, "anyopaque");
        },
        .keyword_bool => {
            mt.i += 1;
            return try ZigTag.type.create(mt.t.arena, "bool");
        },
        .keyword_char,
        .keyword_int,
        .keyword_short,
        .keyword_long,
        .keyword_float,
        .keyword_double,
        .keyword_signed,
        .keyword_unsigned,
        .keyword_complex,
        => return try mt.parseCNumericType(),
        .keyword_enum, .keyword_struct, .keyword_union => {
            const tag_name = mt.tokSlice();
            mt.i += 1;
            if (mt.peek() != .identifier) {
                mt.i -= 1;
                return null;
            }

            // struct Foo will be declared as struct_Foo by transRecordDecl
            const identifier = mt.tokSlice();
            try mt.expect(.identifier);

            const name = try std.fmt.allocPrint(mt.t.arena, "{s}_{s}", .{ tag_name, identifier });
            if (!mt.t.global_scope.contains(name)) {
                try mt.fail("unable to translate C expr: '{s}' not found", .{name});
                return error.ParseError;
            }

            return try ZigTag.identifier.create(mt.t.arena, name);
        },
        else => {},
    }

    return null;
}

fn parseCNumericType(mt: *MacroTranslator) ParseError!ZigNode {
    const KwCounter = struct {
        double: u8 = 0,
        long: u8 = 0,
        int: u8 = 0,
        float: u8 = 0,
        short: u8 = 0,
        char: u8 = 0,
        unsigned: u8 = 0,
        signed: u8 = 0,
        complex: u8 = 0,

        fn eql(self: @This(), other: @This()) bool {
            return std.meta.eql(self, other);
        }
    };

    // Yes, these can be in *any* order
    // This still doesn't cover cases where for example volatile is intermixed

    var kw = KwCounter{};
    // prevent overflow
    var i: u8 = 0;
    while (i < math.maxInt(u8)) : (i += 1) {
        switch (mt.peek()) {
            .keyword_double => kw.double += 1,
            .keyword_long => kw.long += 1,
            .keyword_int => kw.int += 1,
            .keyword_float => kw.float += 1,
            .keyword_short => kw.short += 1,
            .keyword_char => kw.char += 1,
            .keyword_unsigned => kw.unsigned += 1,
            .keyword_signed => kw.signed += 1,
            .keyword_complex => kw.complex += 1,
            else => break,
        }
        mt.i += 1;
    }

    if (kw.eql(.{ .int = 1 }) or kw.eql(.{ .signed = 1 }) or kw.eql(.{ .signed = 1, .int = 1 }))
        return ZigTag.type.create(mt.t.arena, "c_int");

    if (kw.eql(.{ .unsigned = 1 }) or kw.eql(.{ .unsigned = 1, .int = 1 }))
        return ZigTag.type.create(mt.t.arena, "c_uint");

    if (kw.eql(.{ .long = 1 }) or kw.eql(.{ .signed = 1, .long = 1 }) or kw.eql(.{ .long = 1, .int = 1 }) or kw.eql(.{ .signed = 1, .long = 1, .int = 1 }))
        return ZigTag.type.create(mt.t.arena, "c_long");

    if (kw.eql(.{ .unsigned = 1, .long = 1 }) or kw.eql(.{ .unsigned = 1, .long = 1, .int = 1 }))
        return ZigTag.type.create(mt.t.arena, "c_ulong");

    if (kw.eql(.{ .long = 2 }) or kw.eql(.{ .signed = 1, .long = 2 }) or kw.eql(.{ .long = 2, .int = 1 }) or kw.eql(.{ .signed = 1, .long = 2, .int = 1 }))
        return ZigTag.type.create(mt.t.arena, "c_longlong");

    if (kw.eql(.{ .unsigned = 1, .long = 2 }) or kw.eql(.{ .unsigned = 1, .long = 2, .int = 1 }))
        return ZigTag.type.create(mt.t.arena, "c_ulonglong");

    if (kw.eql(.{ .signed = 1, .char = 1 }))
        return ZigTag.type.create(mt.t.arena, "i8");

    if (kw.eql(.{ .char = 1 }) or kw.eql(.{ .unsigned = 1, .char = 1 }))
        return ZigTag.type.create(mt.t.arena, "u8");

    if (kw.eql(.{ .short = 1 }) or kw.eql(.{ .signed = 1, .short = 1 }) or kw.eql(.{ .short = 1, .int = 1 }) or kw.eql(.{ .signed = 1, .short = 1, .int = 1 }))
        return ZigTag.type.create(mt.t.arena, "c_short");

    if (kw.eql(.{ .unsigned = 1, .short = 1 }) or kw.eql(.{ .unsigned = 1, .short = 1, .int = 1 }))
        return ZigTag.type.create(mt.t.arena, "c_ushort");

    if (kw.eql(.{ .float = 1 }))
        return ZigTag.type.create(mt.t.arena, "f32");

    if (kw.eql(.{ .double = 1 }))
        return ZigTag.type.create(mt.t.arena, "f64");

    if (kw.eql(.{ .long = 1, .double = 1 })) {
        try mt.fail("unable to translate: TODO long double", .{});
        return error.ParseError;
    }

    if (kw.eql(.{ .float = 1, .complex = 1 })) {
        try mt.fail("unable to translate: TODO _Complex", .{});
        return error.ParseError;
    }

    if (kw.eql(.{ .double = 1, .complex = 1 })) {
        try mt.fail("unable to translate: TODO _Complex", .{});
        return error.ParseError;
    }

    if (kw.eql(.{ .long = 1, .double = 1, .complex = 1 })) {
        try mt.fail("unable to translate: TODO _Complex", .{});
        return error.ParseError;
    }

    try mt.fail("unable to translate: invalid numeric type", .{});
    return error.ParseError;
}

fn parseCAbstractDeclarator(mt: *MacroTranslator, node: ZigNode) ParseError!ZigNode {
    if (mt.eat(.asterisk)) {
        if (node.castTag(.type)) |some| {
            if (std.mem.eql(u8, some.data, "anyopaque")) {
                const ptr = try ZigTag.single_pointer.create(mt.t.arena, .{
                    .is_const = false,
                    .is_volatile = false,
                    .is_allowzero = false,
                    .elem_type = node,
                });
                return ZigTag.optional_type.create(mt.t.arena, ptr);
            }
        }
        return ZigTag.c_pointer.create(mt.t.arena, .{
            .is_const = false,
            .is_volatile = false,
            .is_allowzero = false,
            .elem_type = node,
        });
    }
    return node;
}

fn parseCPostfixExpr(mt: *MacroTranslator, scope: *Scope, type_name: ?ZigNode) ParseError!ZigNode {
    var node = try mt.parseCPostfixExprInner(scope, type_name);
    // In C the preprocessor would handle concatting strings while expanding macros.
    // This should do approximately the same by concatting any strings and identifiers
    // after a primary or postfix expression.
    while (true) {
        switch (mt.peek()) {
            .string_literal,
            .string_literal_utf_16,
            .string_literal_utf_8,
            .string_literal_utf_32,
            .string_literal_wide,
            .macro_param,
            .macro_param_no_expand,
            => {},
            .identifier, .extended_identifier => {
                if (mt.t.global_scope.blank_macros.contains(mt.tokSlice())) {
                    mt.i += 1;
                    continue;
                }
            },
            else => break,
        }
        const rhs = try mt.parseCPostfixExprInner(scope, type_name);
        node = try ZigTag.array_cat.create(mt.t.arena, .{ .lhs = node, .rhs = rhs });
    }
    return node;
}

fn parseCPostfixExprInner(mt: *MacroTranslator, scope: *Scope, type_name: ?ZigNode) ParseError!ZigNode {
    const gpa = mt.t.gpa;
    const arena = mt.t.arena;
    var node = type_name orelse try mt.parseCPrimaryExpr(scope);
    while (true) {
        switch (mt.peek()) {
            .period => {
                mt.i += 1;
                const tok = mt.tokens[mt.i];
                if (tok.id == .macro_param or tok.id == .macro_param_no_expand) {
                    const param = mt.macro.params[tok.end];
                    mt.i += 1;

                    const mangled_name = scope.getAlias(param) orelse param;
                    const field_name = try ZigTag.identifier.create(arena, mangled_name);
                    node = try ZigTag.field_builtin.create(arena, .{ .lhs = node, .rhs = field_name });
                    continue;
                }
                const field_name = mt.tokSlice();
                try mt.expect(.identifier);

                node = try ZigTag.field_access.create(arena, .{ .lhs = node, .field_name = field_name });
            },
            .arrow => {
                mt.i += 1;
                const tok = mt.tokens[mt.i];
                if (tok.id == .macro_param or tok.id == .macro_param_no_expand) {
                    const param = mt.macro.params[tok.end];
                    mt.i += 1;

                    const mangled_name = scope.getAlias(param) orelse param;
                    const field_name = try ZigTag.identifier.create(arena, mangled_name);
                    node = try ZigTag.field_builtin.create(arena, .{ .lhs = node, .rhs = field_name });
                    continue;
                }
                const field_name = mt.tokSlice();
                try mt.expect(.identifier);

                const deref = try ZigTag.deref.create(arena, node);
                node = try ZigTag.field_access.create(arena, .{ .lhs = deref, .field_name = field_name });
            },
            .l_bracket => {
                mt.i += 1;

                const index_val = try mt.macroIntFromBool(try mt.parseCExpr(scope));
                const index = try ZigTag.as.create(arena, .{
                    .lhs = try ZigTag.type.create(arena, "usize"),
                    .rhs = try ZigTag.int_cast.create(arena, index_val),
                });
                node = try ZigTag.array_access.create(arena, .{ .lhs = node, .rhs = index });
                try mt.expect(.r_bracket);
            },
            .l_paren => {
                mt.i += 1;

                if (mt.eat(.r_paren)) {
                    node = try ZigTag.call.create(arena, .{ .lhs = node, .args = &.{} });
                } else {
                    var args: std.ArrayList(ZigNode) = .empty;
                    defer args.deinit(gpa);

                    while (true) {
                        const arg = try mt.parseCCondExpr(scope);
                        try args.append(gpa, arg);

                        const next_id = mt.peek();
                        switch (next_id) {
                            .comma => {
                                mt.i += 1;
                            },
                            .r_paren => {
                                mt.i += 1;
                                break;
                            },
                            else => {
                                try mt.fail("unable to translate C expr: expected ',' or ')' instead got '{s}'", .{next_id.symbol()});
                                return error.ParseError;
                            },
                        }
                    }
                    node = try ZigTag.call.create(arena, .{ .lhs = node, .args = try arena.dupe(ZigNode, args.items) });
                }
            },
            .l_brace => {
                mt.i += 1;

                // Check for designated field initializers
                if (mt.peek() == .period) {
                    var init_vals: std.ArrayList(ast.Payload.ContainerInitDot.Initializer) = .empty;
                    defer init_vals.deinit(gpa);

                    while (true) {
                        try mt.expect(.period);
                        const name = mt.tokSlice();
                        try mt.expect(.identifier);
                        try mt.expect(.equal);

                        const val = try mt.parseCCondExpr(scope);
                        try init_vals.append(gpa, .{ .name = name, .value = val });

                        const next_id = mt.peek();
                        switch (next_id) {
                            .comma => {
                                mt.i += 1;
                            },
                            .r_brace => {
                                mt.i += 1;
                                break;
                            },
                            else => {
                                try mt.fail("unable to translate C expr: expected ',' or '}}' instead got '{s}'", .{next_id.symbol()});
                                return error.ParseError;
                            },
                        }
                    }
                    const tuple_node = try ZigTag.container_init_dot.create(arena, try arena.dupe(ast.Payload.ContainerInitDot.Initializer, init_vals.items));
                    node = try ZigTag.std_mem_zeroinit.create(arena, .{ .lhs = node, .rhs = tuple_node });
                    continue;
                }

                var init_vals: std.ArrayList(ZigNode) = .empty;
                defer init_vals.deinit(gpa);

                while (true) {
                    const val = try mt.parseCCondExpr(scope);
                    try init_vals.append(gpa, val);

                    const next_id = mt.peek();
                    switch (next_id) {
                        .comma => {
                            mt.i += 1;
                        },
                        .r_brace => {
                            mt.i += 1;
                            break;
                        },
                        else => {
                            try mt.fail("unable to translate C expr: expected ',' or '}}' instead got '{s}'", .{next_id.symbol()});
                            return error.ParseError;
                        },
                    }
                }
                const tuple_node = try ZigTag.tuple.create(arena, try arena.dupe(ZigNode, init_vals.items));
                node = try ZigTag.std_mem_zeroinit.create(arena, .{ .lhs = node, .rhs = tuple_node });
            },
            .plus_plus, .minus_minus => {
                try mt.fail("TODO postfix inc/dec expr", .{});
                return error.ParseError;
            },
            else => return node,
        }
    }
}

fn parseCUnaryExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode {
    switch (mt.peek()) {
        .bang => {
            mt.i += 1;
            const operand = try mt.macroIntToBool(try mt.parseCCastExpr(scope));
            return ZigTag.not.create(mt.t.arena, operand);
        },
        .minus => {
            mt.i += 1;
            const operand = try mt.macroIntFromBool(try mt.parseCCastExpr(scope));
            return ZigTag.negate.create(mt.t.arena, operand);
        },
        .plus => {
            mt.i += 1;
            return try mt.parseCCastExpr(scope);
        },
        .tilde => {
            mt.i += 1;
            const operand = try mt.macroIntFromBool(try mt.parseCCastExpr(scope));
            return ZigTag.bit_not.create(mt.t.arena, operand);
        },
        .asterisk => {
            mt.i += 1;
            const operand = try mt.parseCCastExpr(scope);
            return ZigTag.deref.create(mt.t.arena, operand);
        },
        .ampersand => {
            mt.i += 1;
            const operand = try mt.parseCCastExpr(scope);
            return ZigTag.address_of.create(mt.t.arena, operand);
        },
        .keyword_sizeof => {
            mt.i += 1;
            const operand = if (mt.eat(.l_paren)) blk: {
                const inner = (try mt.parseCTypeName(scope)) orelse try mt.parseCUnaryExpr(scope);
                try mt.expect(.r_paren);
                break :blk inner;
            } else try mt.parseCUnaryExpr(scope);

            return mt.t.createHelperCallNode(.sizeof, &.{operand});
        },
        .keyword_alignof => {
            mt.i += 1;
            // TODO this won't work if using <stdalign.h>'s
            // #define alignof _Alignof
            try mt.expect(.l_paren);
            const operand = (try mt.parseCTypeName(scope)) orelse try mt.parseCUnaryExpr(scope);
            try mt.expect(.r_paren);

            return ZigTag.alignof.create(mt.t.arena, operand);
        },
        .plus_plus, .minus_minus => {
            try mt.fail("TODO unary inc/dec expr", .{});
            return error.ParseError;
        },
        else => {},
    }

    return try mt.parseCPostfixExpr(scope, null);
}
