Wrong mem address access?

pub const Node = struct {
    kind: Kind,
    children: ?[]*const Node,
    props: ?Properties,
    // ...
}

fn evaluate(node: *const Node) Value {
    std.debug.print("{any}\n", .{node}); // 1
    // std.debug.print("{any}\n", .{node.children}); // 2
    // ...
}

when the line marked as one is active, the zig shows the object that I expect to consume, but when I try to access it as in the line marked as 2, it turns into another strange object. Why does this happen?

I don’t really know what you mean, can you make it into an example that can be executed? Or describe more what happens and what is printed vs what you expect?


Just a side note:
I usually avoid types like ?[]*const Node because they have two different ways to denote “no items”, one is null and the other is &.{}/empty slice, I think when you have a slice it is better to just use empty slice for no children.

1 Like

mmm, youre right. I’m using empty array now instead null.

// runtime
fn evaluate(node: *const Node) Value {
    std.debug.print("evaluate: {any}\n\n", .{node});
    return switch (node.kind) {
        .Program => {
            var last_evaluated: ?Value = null;

            for (node.children) |statement| {
                last_evaluated = evaluate(statement);
            }

            return last_evaluated orelse Value.mkNull();
        },
        .BinaryExpression => {
            const NodeProps = node.props.?.BinaryExpression;
            const left_node = evaluate(NodeProps.left);
            const right_node = evaluate(NodeProps.right);

            return eval_binary_expr(left_node, right_node, NodeProps.operator);
        },
        .Number => {
            return Value.mkNumber(node.props.?.Number.value);
        },
        else => @panic("?"),
    };
}

pub fn run(source: []const u8) Parser.Errors!Value {
    const AST = try Parser.init(source);
    std.debug.print("run: {any}\n\n", .{AST});
    return evaluate(&AST);
}

Look logs:

run: ast.Node{ .kind = ast.Node.Kind.Program, .props = null, .children = { ast.Node{ .kind = ast.Node.Kind.BinaryExpression, .props = ast.Node.Properties{ ... }, .children = { ... } } } }

evaluate: ast.Node{ .kind = ast.Node.Kind.Program, .props = null, .children = { ast.Node{ .kind = ast.Node.Kind.Program, .props = ast.Node.Properties{ ... }, .children = { ... } } } }

evaluate: ast.Node{ .kind = ast.Node.Kind.Program, .props = ast.Node.Properties{ .Program = void }, .children = { ast.Node{ .kind = ast.Node.Kind.thread 1872 panic: invalid enum value
C:\Users\esmer\zig\lib\std\fmt.zig:538:37: 0x843e8 in formatType__anon_8233 (main.exe.obj)
                try writer.writeAll(@tagName(value));
                                    ^
C:\Users\esmer\zig\lib\std\fmt.zig:607:31: 0x9150c in formatType__anon_8519 (main.exe.obj)
                try formatType(@field(value, f.name), ANY, options, writer, max_depth - 1);
                              ^
C:\Users\esmer\zig\lib\std\fmt.zig:614:38: 0x91211 in formatType__anon_8518 (main.exe.obj)
                    return formatType(value.*, actual_fmt, options, writer, max_depth);
                                     ^
C:\Users\esmer\zig\lib\std\fmt.zig:640:35: 0x8472d in formatType__anon_8235 (main.exe.obj)
                    try formatType(elem, actual_fmt, options, writer, max_depth - 1);
                                  ^
C:\Users\esmer\zig\lib\std\fmt.zig:607:31: 0x9181f in formatType__anon_8519 (main.exe.obj)
                try formatType(@field(value, f.name), ANY, options, writer, max_depth - 1);
                              ^
C:\Users\esmer\zig\lib\std\fmt.zig:614:38: 0x84861 in formatType__anon_8240 (main.exe.obj)
                    return formatType(value.*, actual_fmt, options, writer, max_depth);
                                     ^
C:\Users\esmer\zig\lib\std\fmt.zig:184:23: 0x6ea7c in format__anon_7250 (main.exe.obj)
        try formatType(
                      ^
C:\Users\esmer\zig\lib\std\io\Writer.zig:23:26: 0x36112 in print__anon_4361 (main.exe.obj)
    return std.fmt.format(self, format, args);
                         ^
C:\Users\esmer\zig\lib\std\debug.zig:88:27: 0x33560 in print__anon_4282 (main.exe.obj)
    nosuspend stderr.print(fmt, args) catch return;
                          ^
C:\Users\esmer\OneDrive\Documentos\vp\src\runtime.zig:49:20: 0x31abc in evaluate (main.exe.obj)
    std.debug.print("evaluate: {any}\n\n", .{node});
                   ^
C:\Users\esmer\OneDrive\Documentos\vp\src\runtime.zig:55:42: 0x31b9e in evaluate (main.exe.obj)
                last_evaluated = evaluate(statement);
                                         ^
C:\Users\esmer\OneDrive\Documentos\vp\src\runtime.zig:77:20: 0x31150 in run (main.exe.obj)
    return evaluate(&AST);
                   ^
C:\Users\esmer\OneDrive\Documentos\vp\src\main.zig:8:20: 0x3102d in main (main.exe.obj)
        Runtime.run("5 + 6"),
                   ^
C:\Users\esmer\zig\lib\std\start.zig:350:53: 0x313c1 in WinStartup (main.exe.obj)
    std.os.windows.ntdll.RtlExitUserProcess(callMain());
                                                    ^
???:?:?: 0x7ffaafb5257c in ??? (KERNEL32.DLL)
???:?:?: 0x7ffab0aeaf07 in ??? (ntdll.dll)

about the parser:

pub fn init(source: []const u8) Errors!Node {
    var tokens = try Lexer.init(source);
    defer tokens.deinit();

    var src = try Reader.init(&tokens);

    var program_children = std.ArrayList(*const Node).init(allocator);
    errdefer program_children.deinit();

    while (if (src.curr()) |c| c.tag != TokenTag.EOF else false) {
        const stmt = parse_stmt(&src);

        try program_children.append(&stmt);
    }

    const node = Node{
        .kind = .Program,
        .children = try program_children.toOwnedSlice(),
        .props = null,
    };

    return node;
}

...

You are taking Pointers to Temporary Memory
and saving those in your nodes, but the memory of the stmt node gets overridden with the next value and later on with other stack memory.

So when you are trying to use your nodes later they no longer exist and thus the pointers contain random garbage and you hit that error where you enum now contains values that aren’t valid.

Instead you need to allocate memory for the nodes.

I think something like this should work:

while (if (src.curr()) |c| c.tag != TokenTag.EOF else false) {
    const stmt = try allocator.create(Node);
    stmt.* = parse_stmt(&src);

    try program_children.append(stmt);
}

This doesn’t handle deallocation on error cases, but I would probably use an arena anyway.

1 Like