From d2640a44ac84a8359b5cca47aa5e1efcb85ffd76 Mon Sep 17 00:00:00 2001 From: Auguste Rame <19855629+SuperAuguste@users.noreply.github.com> Date: Thu, 27 Oct 2022 23:59:24 -0400 Subject: [PATCH 01/12] comptime interpreter moment --- src/ComptimeInterpreter.zig | 937 ++++++++++++++++++ src/analysis.zig | 2 +- src/zls.zig | 1 + .../comptime_interpreter.zig | 26 + tests/tests.zig | 23 +- 5 files changed, 977 insertions(+), 12 deletions(-) create mode 100644 src/ComptimeInterpreter.zig create mode 100644 tests/language_features/comptime_interpreter.zig diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig new file mode 100644 index 0000000..902d842 --- /dev/null +++ b/src/ComptimeInterpreter.zig @@ -0,0 +1,937 @@ +//! Hacky comptime interpreter, courtesy of midnight code run fuelled by spite; +//! hope that one day this can use async... <33 + +// TODO: builtin work!! +// TODO: DODify +// TODO: Work with DocumentStore + +const std = @import("std"); +const ast = @import("ast.zig"); +const zig = std.zig; +const Ast = zig.Ast; +const analysis = @import("analysis.zig"); +const DocumentStore = @import("DocumentStore.zig"); +const ComptimeInterpreter = @This(); + +tree: Ast, +root_scope: *InterpreterScope = undefined, +allocator: std.mem.Allocator, + +type_info: std.ArrayListUnmanaged(TypeInfo) = .{}, +type_info_map: std.HashMapUnmanaged(TypeInfo, usize, TypeInfo.Context, std.hash_map.default_max_load_percentage) = .{}, + +pub fn deinit(interpreter: *ComptimeInterpreter) void { + interpreter.type_info.deinit(interpreter.allocator); + interpreter.type_info_map.deinit(interpreter.allocator); +} + +pub const TypeInfo = union(enum) { + pub const Context = struct { + interpreter: ComptimeInterpreter, + hasher: *std.hash.Wyhash, + + pub fn hash(self: @This(), s: TypeInfo) u64 { + TypeInfo.hash(self, s); + return self.hasher.final(); + } + pub fn eql(self: @This(), a: TypeInfo, b: TypeInfo) bool { + return TypeInfo.eql(self.interpreter, a, b); + } + }; + + pub const Signedness = enum { signed, unsigned }; + + pub const Struct = struct { + /// Declarations contained within + scope: *InterpreterScope, + fields: std.ArrayListUnmanaged(FieldDefinition) = .{}, + }; + + pub const Int = struct { + bits: u16, + signedness: Signedness, + }; + + pub const Pointer = struct { + size: Size, + is_const: bool, + is_volatile: bool, + child: Type, + is_allowzero: bool, + + sentinel: ?ValueData, + + pub const Size = enum { + one, + many, + slice, + c, + }; + }; + + pub const Fn = struct { + return_type: ?Type, + /// Index into interpreter.declarations + params: std.ArrayListUnmanaged(usize) = .{}, + }; + + /// Hack to get anytype working; only valid on fnparams + @"anytype", + @"type", + @"bool", + + @"struct": Struct, + pointer: Pointer, + + int: Int, + @"comptime_int", + float: u16, + @"comptime_float", + + pub fn eql(interpreter: ComptimeInterpreter, a: TypeInfo, b: TypeInfo) bool { + if (std.meta.activeTag(a) != std.meta.activeTag(b)) return false; + return switch (a) { + .@"struct" => false, // Struct declarations can never be equal + .pointer => p: { + const ap = a.pointer; + const bp = b.pointer; + break :p ap.size == bp.size and ap.is_const == bp.is_const and ap.is_volatile == bp.is_volatile and eql( + interpreter, + interpreter.typeToTypeInfo(ap.child), + interpreter.typeToTypeInfo(bp.child), + ) and ap.is_allowzero == bp.is_allowzero and ((ap.sentinel == null and bp.sentinel == null) or ((ap.sentinel != null and bp.sentinel != null) and ap.sentinel.?.eql(bp.sentinel.?))); + }, + .int => a.int.signedness == b.int.signedness and a.int.bits == b.int.bits, + .float => a.float == b.float, + else => return true, + }; + } + + pub fn hash(context: TypeInfo.Context, ti: TypeInfo) void { + context.hasher.update(&[_]u8{@enumToInt(ti)}); + return switch (ti) { + .@"struct" => |s| { + context.hasher.update(std.mem.sliceAsBytes(s.fields.items)); + // TODO: Fix + // context.hasher.update(std.mem.sliceAsBytes(s.declarations.items)); + }, + .pointer => |p| { + // const ap = a.pointer; + // const bp = b.pointer; + context.hasher.update(&[_]u8{ @enumToInt(p.size), @boolToInt(p.is_const), @boolToInt(p.is_volatile) }); + TypeInfo.hash(context, context.interpreter.typeToTypeInfo(p.child)); + context.hasher.update(&[_]u8{@boolToInt(p.is_allowzero)}); + // TODO: Hash Sentinel + // break :p ap.size == bp.size and ap.is_const == bp.is_const and ap.is_volatile == bp.is_volatile and eql( + // source_unit, + // source_interpreter.type_info.items[ap.child.info_idx], + // source_interpreter.type_info.items[bp.child.info_idx], + // ) and ap.is_allowzero == bp.is_allowzero and ((ap.sentinel == null and bp.sentinel == null) or ((ap.sentinel != null and bp.sentinel != null) and ap.sentinel.?.eql(bp.sentinel.?))); + }, + .int => |i| { + // a.int.signedness == b.int.signedness and a.int.bits == b.int.bits; + context.hasher.update(&[_]u8{@enumToInt(i.signedness)}); + context.hasher.update(&std.mem.toBytes(i.bits)); + }, + .float => |f| context.hasher.update(&std.mem.toBytes(f)), + else => {}, + }; + } +}; + +pub const Type = struct { + node_idx: Ast.Node.Index, + info_idx: usize, +}; + +pub const Value = struct { + node_idx: Ast.Node.Index, + @"type": Type, + value_data: ValueData, + + pub fn eql(value: Value, other_value: Value) bool { + return value.value_data.eql(other_value.value_data); + } +}; + +pub const ValueData = union(enum) { + // TODO: Support larger ints, floats; bigints? + + @"type": Type, + @"bool": bool, + + // @"struct": struct { + + // }, + // one_ptr: *anyopaque, + /// TODO: Optimize this with an ArrayList that uses anyopaque slice + slice_ptr: std.ArrayListUnmanaged(ValueData), + + @"comptime_int": std.math.big.int.Managed, + unsigned_int: u64, + signed_int: i64, + float: f64, + + pub fn eql(data: ValueData, other_data: ValueData) bool { + if (std.meta.activeTag(data) != std.meta.activeTag(other_data)) return false; + // std.enums. + // std.meta.activeTag(u: anytype) + switch (data) { + .@"bool" => return data.@"bool" == other_data.@"bool", + .@"comptime_int" => return data.@"comptime_int".eq(other_data.@"comptime_int"), + .unsigned_int => return data.unsigned_int == other_data.unsigned_int, + .signed_int => return data.signed_int == other_data.signed_int, + .float => return data.float == other_data.float, + else => @panic("Simple eql not implemented!"), + } + } +}; + +pub const FieldDefinition = struct { + node_idx: Ast.Node.Index, + /// Store name so tree doesn't need to be used to access field name + name: []const u8, + @"type": Type, + default_value: ?Value, +}; + +pub const Declaration = struct { + node_idx: Ast.Node.Index, + /// Store name so tree doesn't need to be used to access declaration name + name: []const u8, + @"type": Type, + value: Value, + + // TODO: figure this out + // pub const DeclarationKind = enum{variable, function}; + // pub fn declarationKind(declaration: Declaration, tree: Ast) DeclarationKind { + // return switch(tree.nodes.items(.tag)[declaration.node_idx]) { + // .fn_proto, + // .fn_proto_one, + // .fn_proto_simple, + // .fn_proto_multi, + // .fn_decl + // } + // } + + pub fn isConstant(declaration: Declaration, tree: Ast) bool { + return switch (tree.nodes.items(.tag)[declaration.node_idx]) { + .global_var_decl, + .local_var_decl, + .aligned_var_decl, + .simple_var_decl, + => { + return tree.tokenSlice(ast.varDecl(tree, declaration.node_idx).?.ast.mut_token).len == 3; + }, + else => false, + }; + } +}; + +pub fn createType(interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index, type_info: TypeInfo) std.mem.Allocator.Error!Type { + // TODO: Figure out dedup + var hasher = std.hash.Wyhash.init(0); + var gpr = try interpreter.type_info_map.getOrPutContext(interpreter.allocator, type_info, .{ .interpreter = interpreter.*, .hasher = &hasher }); + + if (gpr.found_existing) { + // std.log.info("Deduplicating type {d}", .{interpreter.formatTypeInfo(unit.type_info.items[gpr.value_ptr.*])}); + return Type{ .node_idx = node_idx, .info_idx = gpr.value_ptr.* }; + } else { + try interpreter.type_info.append(interpreter.allocator, type_info); + const info_idx = interpreter.type_info.items.len - 1; + gpr.value_ptr.* = info_idx; + return Type{ .node_idx = node_idx, .info_idx = info_idx }; + } +} + +pub fn typeToTypeInfo(interpreter: ComptimeInterpreter, @"type": Type) TypeInfo { + return interpreter.type_info.items[@"type".info_idx]; +} + +pub const TypeInfoFormatter = struct { + interpreter: *ComptimeInterpreter, + ti: TypeInfo, + + pub fn format(value: TypeInfoFormatter, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { + _ = fmt; + _ = options; + return switch (value.ti) { + .int => |ii| switch (ii.signedness) { + .signed => try writer.print("i{d}", .{ii.bits}), + .unsigned => try writer.print("u{d}", .{ii.bits}), + }, // TODO + .float => |f| try writer.print("f{d}", .{f}), + .@"comptime_int" => try writer.writeAll("comptime_int"), + .@"comptime_float" => try writer.writeAll("comptime_float"), + .@"type" => try writer.writeAll("type"), + .@"bool" => try writer.writeAll("bool"), + .@"struct" => |s| { + try writer.writeAll("struct {"); + var iterator = s.scope.declarations.iterator(); + while (iterator.next()) |di| { + const decl = di.value_ptr.*; + if (decl.isConstant(value.interpreter.tree)) { + try writer.print("const {s}: , {any}", .{ decl.name, value.interpreter.formatTypeInfo(value.interpreter.typeToTypeInfo(decl.@"type")) }); + } else { + try writer.print("var {s}: , {any}", .{ decl.name, value.interpreter.formatTypeInfo(value.interpreter.typeToTypeInfo(decl.@"type")) }); + } + } + try writer.writeAll("}"); + }, + else => try writer.print("UnimplementedTypeInfoPrint", .{}), + }; + } +}; + +pub fn formatTypeInfo(interpreter: *ComptimeInterpreter, ti: TypeInfo) TypeInfoFormatter { + return TypeInfoFormatter{ .interpreter = interpreter, .ti = ti }; +} + +pub const InterpreterScope = struct { + interpreter: *ComptimeInterpreter, + + parent: ?*InterpreterScope = null, + node_idx: Ast.Node.Index, + declarations: std.StringHashMapUnmanaged(Declaration) = .{}, + /// Resizes can modify element pointer locations, so we use a list of pointers + child_scopes: std.ArrayListUnmanaged(*InterpreterScope) = .{}, + + pub const ScopeKind = enum { container, block, function }; + pub fn scopeKind(scope: InterpreterScope, tree: Ast) ScopeKind { + return switch (tree.nodes.items(.tag)[scope.node_idx]) { + .container_decl, + .container_decl_trailing, + .container_decl_arg, + .container_decl_arg_trailing, + .container_decl_two, + .container_decl_two_trailing, + .tagged_union, + .tagged_union_trailing, + .tagged_union_two, + .tagged_union_two_trailing, + .tagged_union_enum_tag, + .tagged_union_enum_tag_trailing, + .root, + .error_set_decl, + => .container, + else => .block, + }; + } + + pub fn getLabel(scope: InterpreterScope, tree: Ast) ?Ast.TokenIndex { + const token_tags = tree.tokens.items(.tag); + + return switch (scope.scopeKind(tree)) { + .block => z: { + const lbrace = tree.nodes.items(.main_token)[scope.node_idx]; + break :z if (token_tags[lbrace - 1] == .colon and token_tags[lbrace - 2] == .identifier) + lbrace - 2 + else + null; + }, + else => null, + }; + } + + pub const ParentScopeIterator = struct { + maybe_scope: ?*InterpreterScope, + + pub fn next(psi: *ParentScopeIterator) ?*InterpreterScope { + if (psi.maybe_scope) |scope| { + const curr = scope; + psi.maybe_scope = scope.parent; + return curr; + } else return null; + } + }; + + pub fn parentScopeIterator(scope: *InterpreterScope) ParentScopeIterator { + return ParentScopeIterator{ .maybe_scope = scope }; + } + + pub fn deinit(scope: *InterpreterScope) void { + scope.declarations.deinit(scope.interpreter.allocator); + for (scope.child_scopes.items) |child| child.deinit(); + scope.child_scopes.deinit(scope.interpreter.allocator); + + scope.interpreter.allocator.destroy(scope); + } +}; + +pub fn newScope(interpreter: *ComptimeInterpreter, maybe_parent: ?*InterpreterScope, node_idx: Ast.Node.Index) std.mem.Allocator.Error!*InterpreterScope { + var ls = try interpreter.allocator.create(InterpreterScope); + if (maybe_parent) |parent| try parent.child_scopes.append(interpreter.allocator, ls); + ls.* = .{ + .interpreter = interpreter, + .parent = maybe_parent, + .node_idx = node_idx, + }; + return ls; +} + +pub const InterpretResult = union(enum) { + @"break": ?[]const u8, + break_with_value: struct { + label: ?[]const u8, + value: Value, + }, + value: Value, + @"return", + return_with_value: Value, + nothing, + + pub fn maybeGetValue(result: InterpretResult) ?Value { + return switch (result) { + .break_with_value => |v| v.value, + .value => |v| v, + else => null, + }; + } + + pub fn getValue(result: InterpretResult) Value { + return result.maybeGetValue() orelse @panic("Attempted to get value from non-value interpret result"); + } +}; + +// Might be useful in the future +pub const InterpretOptions = struct {}; + +pub const InterpretError = std.mem.Allocator.Error || std.fmt.ParseIntError || std.fmt.ParseFloatError || error{ InvalidCharacter, InvalidBase }; +pub fn interpret( + interpreter: *ComptimeInterpreter, + node_idx: Ast.Node.Index, + scope: ?*InterpreterScope, + options: InterpretOptions, +) InterpretError!InterpretResult { + // _ = unit; + // _ = node; + // _ = observe_values; + + const tree = interpreter.tree; + const tags = tree.nodes.items(.tag); + const data = tree.nodes.items(.data); + const main_tokens = tree.nodes.items(.main_token); + + std.log.info("{any}", .{tags[node_idx]}); + + switch (tags[node_idx]) { + .container_decl, + .container_decl_trailing, + .container_decl_arg, + .container_decl_arg_trailing, + .container_decl_two, + .container_decl_two_trailing, + .tagged_union, + .tagged_union_trailing, + .tagged_union_two, + .tagged_union_two_trailing, + .tagged_union_enum_tag, + .tagged_union_enum_tag_trailing, + .root, + .error_set_decl, + => { + var container_scope = try interpreter.newScope(scope, node_idx); + var type_info = TypeInfo{ + .@"struct" = .{ + .scope = container_scope, + }, + }; + + if (node_idx == 0) interpreter.root_scope = container_scope; + + var buffer: [2]Ast.Node.Index = undefined; + const members = ast.declMembers(tree, node_idx, &buffer); + + for (members) |member| { + const maybe_container_field: ?zig.Ast.full.ContainerField = switch (tags[member]) { + .container_field => tree.containerField(member), + .container_field_align => tree.containerFieldAlign(member), + .container_field_init => tree.containerFieldInit(member), + else => null, + }; + + if (maybe_container_field) |field_info| { + var init_type = try interpreter.interpret(field_info.ast.type_expr, container_scope, .{}); + var default_value = if (field_info.ast.value_expr == 0) + null + else + (try interpreter.interpret(field_info.ast.value_expr, container_scope, .{})).getValue(); + + const name = tree.tokenSlice(field_info.ast.name_token); + const field = FieldDefinition{ + .node_idx = member, + .name = name, + .@"type" = init_type.getValue().value_data.@"type", + .default_value = default_value, + // TODO: Default values + // .@"type" = T: { + // var value = (try interpreter.interpret(field_info.ast.type_expr, scope_idx, true)).?.value; + // break :T @ptrCast(*Type, @alignCast(@alignOf(*Type), value)).*; + // }, + // .value = null, + }; + + try type_info.@"struct".fields.append(interpreter.allocator, field); + } else { + _ = try interpreter.interpret(member, container_scope, options); + } + } + + return InterpretResult{ .value = Value{ + .node_idx = node_idx, + .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }), + .value_data = .{ .@"type" = try interpreter.createType(node_idx, type_info) }, + } }; + }, + .global_var_decl, + .local_var_decl, + .aligned_var_decl, + .simple_var_decl, + => { + const decl = ast.varDecl(tree, node_idx).?; + var value = (try interpreter.interpret(decl.ast.init_node, scope, options)).getValue(); + var @"type" = if (decl.ast.type_node == 0) Value{ + .node_idx = std.math.maxInt(Ast.Node.Index), + .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }), + .value_data = .{ .@"type" = value.@"type" }, + } else (try interpreter.interpret(decl.ast.type_node, scope, options)).getValue(); + + const name = analysis.getDeclName(tree, node_idx).?; + try scope.?.declarations.put(interpreter.allocator, name, .{ + .node_idx = node_idx, + .name = name, + .@"type" = @"type".value_data.@"type", + .@"value" = value, + }); + + return InterpretResult{ .nothing = .{} }; + }, + .block, + .block_semicolon, + .block_two, + .block_two_semicolon, + => { + // try interpreter.scopes.append(interpreter.allocator, .{ + // .node_idx = node_idx, + // .parent_scope = parent_scope_idx orelse std.math.maxInt(usize), + // }); + // const scope_idx = interpreter.scopes.items.len - 1; + + var block_scope = try interpreter.newScope(scope, node_idx); + + var buffer: [2]Ast.Node.Index = undefined; + const statements = ast.blockStatements(tree, node_idx, &buffer).?; + + for (statements) |idx| { + const ret = try interpreter.interpret(idx, block_scope, options); + switch (ret) { + .@"break" => |lllll| { + const maybe_block_label_string = if (scope.?.getLabel(tree)) |i| tree.tokenSlice(i) else null; + if (lllll) |l| { + if (maybe_block_label_string) |ls| { + if (std.mem.eql(u8, l, ls)) { + return InterpretResult{ .nothing = .{} }; + } else return ret; + } else return ret; + } else { + return InterpretResult{ .nothing = .{} }; + } + }, + .break_with_value => |bwv| { + const maybe_block_label_string = if (scope.?.getLabel(tree)) |i| tree.tokenSlice(i) else null; + + if (bwv.label) |l| { + if (maybe_block_label_string) |ls| { + if (std.mem.eql(u8, l, ls)) { + return InterpretResult{ .value = bwv.value }; + } else return ret; + } else return ret; + } else { + return InterpretResult{ .value = bwv.value }; + } + }, + .@"return", .return_with_value => return ret, + else => {}, + } + } + + return InterpretResult{ .nothing = .{} }; + }, + .identifier => { + var value = tree.getNodeSource(node_idx); + + if (std.mem.eql(u8, "type", value)) { + return InterpretResult{ .value = Value{ + .node_idx = node_idx, + .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }), + .value_data = .{ .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }) }, + } }; + } else if (value.len >= 2 and (value[0] == 'u' or value[0] == 'i')) int: { + return InterpretResult{ .value = Value{ + .node_idx = node_idx, + .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }), + .value_data = .{ .@"type" = try interpreter.createType(node_idx, .{ + .int = .{ + .signedness = if (value[0] == 'u') .unsigned else .signed, + .bits = std.fmt.parseInt(u16, value[1..], 10) catch break :int, + }, + }) }, + } }; + } + + // Logic to find identifiers in accessible scopes + + var psi = scope.?.parentScopeIterator(); + while (psi.next()) |pscope| { + return InterpretResult{ .value = (pscope.declarations.get(value) orelse continue).value }; + } + + std.log.err("Identifier not found: {s}", .{value}); + @panic("Could not find identifier"); + }, + .grouped_expression => { + return try interpreter.interpret(data[node_idx].lhs, scope, options); + }, + .@"break" => { + const label = if (data[node_idx].lhs == 0) null else tree.tokenSlice(data[node_idx].lhs); + return if (data[node_idx].rhs == 0) + InterpretResult{ .@"break" = label } + else + InterpretResult{ .break_with_value = .{ .label = label, .value = (try interpreter.interpret(data[node_idx].rhs, scope, options)).getValue() } }; + }, + .@"return" => { + return if (data[node_idx].lhs == 0) + InterpretResult{ .@"return" = {} } + else + InterpretResult{ .return_with_value = (try interpreter.interpret(data[node_idx].lhs, scope, options)).getValue() }; + }, + .@"if", .if_simple => { + const iff = ast.ifFull(tree, node_idx); + // TODO: Don't evaluate runtime ifs + // if (options.observe_values) { + const ir = try interpreter.interpret(iff.ast.cond_expr, scope, options); + if (ir.getValue().value_data.@"bool") { + return try interpreter.interpret(iff.ast.then_expr, scope, options); + } else { + if (iff.ast.else_expr != 0) { + return try interpreter.interpret(iff.ast.else_expr, scope, options); + } else return InterpretResult{ .nothing = .{} }; + } + // } else { + // _ = try interpreter.interpret(iff.ast.cond_expr, scope, options); + // _ = try interpreter.interpret(iff.ast.then_expr, scope, options); + // _ = try interpreter.interpret(iff.ast.else_expr, scope, options); + // } + @panic("bruh"); + }, + .equal_equal => { + var a = try interpreter.interpret(data[node_idx].lhs, scope, options); + var b = try interpreter.interpret(data[node_idx].rhs, scope, options); + return InterpretResult{ .value = Value{ + .node_idx = node_idx, + .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = .{} }), + .value_data = .{ .@"bool" = a.getValue().eql(b.getValue()) }, + } }; + // a.getValue().eql(b.getValue()) + }, + .number_literal => { + const s = tree.getNodeSource(node_idx); + const nl = std.zig.parseNumberLiteral(s); + // if (nl == .failure) ; + return InterpretResult{ .value = Value{ + .node_idx = node_idx, + .@"type" = try interpreter.createType(node_idx, .{ .@"comptime_int" = .{} }), + .value_data = switch (nl) { + .float => .{ .float = try std.fmt.parseFloat(f64, s) }, + .int => if (s[0] == '-') ValueData{ .signed_int = try std.fmt.parseInt(i64, s, 0) } else ValueData{ .unsigned_int = try std.fmt.parseInt(u64, s, 0) }, + .big_int => |bii| ppp: { + var bi = try std.math.big.int.Managed.init(interpreter.allocator); + try bi.setString(@enumToInt(bii), s[if (bii != .decimal) @as(usize, 2) else @as(usize, 0)..]); + break :ppp .{ .@"comptime_int" = bi }; + }, + .failure => @panic("Failed to parse number literal"), + }, + } }; + }, + .assign, + .assign_bit_and, + .assign_bit_or, + .assign_shl, + .assign_shr, + .assign_bit_xor, + .assign_div, + .assign_sub, + .assign_sub_wrap, + .assign_mod, + .assign_add, + .assign_add_wrap, + .assign_mul, + .assign_mul_wrap, + => { + // TODO: Make this work with non identifiers + // TODO: Actually consider operators + + const value = tree.getNodeSource(data[node_idx].lhs); + + var psi = scope.?.parentScopeIterator(); + while (psi.next()) |pscope| { + if (pscope.declarations.getEntry(value)) |decl| + decl.value_ptr.value = (try interpreter.interpret(data[node_idx].rhs, scope.?, options)).getValue(); + } + + return InterpretResult{ .nothing = .{} }; + }, + // .@"switch", + // .switch_comma, + // => { + // const cond = data[node_idx].lhs; + // const extra = tree.extraData(data[node_idx].rhs, Ast.Node.SubRange); + // const cases = tree.extra_data[extra.start..extra.end]; + + // for (cases) |case| { + // const switch_case: Ast.full.SwitchCase = switch (tags[case]) { + // .switch_case => tree.switchCase(case), + // .switch_case_one => tree.switchCaseOne(case), + // else => continue, + // }; + // } + // }, + .builtin_call, + .builtin_call_comma, + .builtin_call_two, + .builtin_call_two_comma, + => { + var buffer: [2]Ast.Node.Index = undefined; + const params = ast.builtinCallParams(tree, node_idx, &buffer).?; + const call_name = tree.tokenSlice(main_tokens[node_idx]); + + if (std.mem.eql(u8, call_name, "@compileLog")) { + pp: for (params) |param| { + const res = (try interpreter.interpret(param, scope, .{})).getValue(); + const ti = interpreter.type_info.items[res.@"type".info_idx]; + switch (ti) { + .pointer => |ptr| { + const child = interpreter.type_info.items[ptr.child.info_idx]; + if (ptr.size == .slice and child == .int and child.int.bits == 8 and child.int.signedness == .unsigned) { + + // TODO: Fix once I optimize slices + std.debug.print("@compileLog output: ", .{}); + for (res.value_data.slice_ptr.items) |i| std.debug.print("{c}", .{@truncate(u8, i.unsigned_int)}); + std.debug.print("\n", .{}); + + break :pp; + } + }, + else => {}, + } + + @panic("compileLog argument type not printable!"); + } + + return InterpretResult{ .nothing = .{} }; + } + + std.log.info("Builtin not implemented: {s}", .{call_name}); + @panic("Builtin not implemented"); + }, + .string_literal => { + const value = tree.getNodeSource(node_idx)[1 .. tree.getNodeSource(node_idx).len - 1]; + var val = Value{ + .node_idx = node_idx, + .@"type" = try interpreter.createType(node_idx, .{ + .pointer = .{ + .size = .slice, + .is_const = true, + .is_volatile = false, + .child = try interpreter.createType(0, .{ .int = .{ + .bits = 8, + .signedness = .unsigned, + } }), + .is_allowzero = false, + + .sentinel = .{ .unsigned_int = 0 }, + }, + }), + .value_data = .{ .slice_ptr = .{} }, + }; + + for (value) |z| { + try val.value_data.slice_ptr.append(interpreter.allocator, .{ .unsigned_int = z }); + } + try val.value_data.slice_ptr.append(interpreter.allocator, .{ .unsigned_int = 0 }); + + return InterpretResult{ .value = val }; + }, + // TODO: Add comptime autodetection; e.g. const MyArrayList = std.ArrayList(u8) + .@"comptime" => { + return try interpreter.interpret(data[node_idx].lhs, scope, .{}); + }, + // .fn_proto, + // .fn_proto_multi, + // .fn_proto_one, + // .fn_proto_simple, + .fn_decl => { + // var buf: [1]Ast.Node.Index = undefined; + // const func = ast.fnProto(tree, node_idx, &buf).?; + + // TODO: Add params + + // var type_info = TypeInfo{ + // .@"fn" = .{ + // .definition_scope = scope.?, + // .node_idx = node_idx, + // }, + // }; + + // var it = func.iterate(&tree); + // while (ast.nextFnParam(&it)) |param| { + // // Add parameter decls + // if (param.name_token) |name_token| { + // // TODO: Think of new method for functions + // if ((try interpreter.interpret(param.type_expr, func_scope_idx, .{ .observe_values = true, .is_comptime = true })).maybeGetValue()) |value| { + // try interpreter.addDeclaration(func_scope_idx, value.value_data.@"type"); + // try fnd.params.append(interpreter.allocator, interpreter.declarations.items.len - 1); + // } else { + // try interpreter.addDeclaration(parent_scope_idx.?, .{ + // .node_idx = node_idx, + // .name = tree.tokenSlice(name_token), + // .scope_idx = func_scope_idx, // orelse std.math.maxInt(usize), + // .@"value" = undefined, + // .@"type" = interpreter.createType(0, .{ .@"anytype" = .{} }), + // }); + // try fnd.params.append(interpreter.allocator, interpreter.declarations.items.len - 1); + // } + // } + // } + + // if ((try interpreter.interpret(func.ast.return_type, func_scope_idx, .{ .observe_values = true, .is_comptime = true })).maybeGetValue()) |value| + // fnd.return_type = value.value_data.@"type"; + + // var value = Value{ + // .node_idx = node_idx, + // .@"type" = try interpreter.createType(node_idx, type_info), + // .value_data = .{ .@"fn" = .{} }, + // }; + + // const name = ast.getDeclName(tree, node_idx).?; + // try scope.?.declarations.put(interpreter.allocator, name, .{ + // .node_idx = node_idx, + // .name = name, + // .@"type" = value.@"type", + // .@"value" = value, + // }); + + return InterpretResult{ .nothing = .{} }; + }, + .call, + .call_comma, + .async_call, + .async_call_comma, + .call_one, + .call_one_comma, + .async_call_one, + .async_call_one_comma, + => { + // var params: [1]Ast.Node.Index = undefined; + // const call = ast.callFull(tree, node_idx, ¶ms) orelse unreachable; + + // const callee = .{ .node = call.ast.fn_expr, .handle = handle }; + // const decl = (try resolveTypeOfNodeInternal(store, arena, callee, bound_type_params)) orelse + // return null; + + // if (decl.type.is_type_val) return null; + // const decl_node = switch (decl.type.data) { + // .other => |n| n, + // else => return null, + // }; + // var buf: [1]Ast.Node.Index = undefined; + // const func_maybe = ast.fnProto(decl.handle.tree, decl_node, &buf); + + // if (func_maybe) |fn_decl| { + // var expected_params = fn_decl.ast.params.len; + // // If we call as method, the first parameter should be skipped + // // TODO: Back-parse to extract the self argument? + // var it = fn_decl.iterate(&decl.handle.tree); + // if (token_tags[call.ast.lparen - 2] == .period) { + // if (try hasSelfParam(arena, store, decl.handle, fn_decl)) { + // _ = ast.nextFnParam(&it); + // expected_params -= 1; + // } + // } + + // // Bind type params to the arguments passed in the call. + // const param_len = std.math.min(call.ast.params.len, expected_params); + // var i: usize = 0; + // while (ast.nextFnParam(&it)) |decl_param| : (i += 1) { + // if (i >= param_len) break; + // if (!isMetaType(decl.handle.tree, decl_param.type_expr)) + // continue; + + // const argument = .{ .node = call.ast.params[i], .handle = handle }; + // const argument_type = (try resolveTypeOfNodeInternal( + // store, + // arena, + // argument, + // bound_type_params, + // )) orelse + // continue; + // if (!argument_type.type.is_type_val) continue; + + // try bound_type_params.put(arena.allocator(), decl_param, argument_type); + // } + + // const has_body = decl.handle.tree.nodes.items(.tag)[decl_node] == .fn_decl; + // const body = decl.handle.tree.nodes.items(.data)[decl_node].rhs; + // return try resolveReturnType(store, arena, fn_decl, decl.handle, bound_type_params, if (has_body) body else null); + // } + // return null; + return InterpretResult{ .nothing = .{} }; + }, + else => { + std.log.err("Unhandled {any}", .{tags[node_idx]}); + return InterpretResult{ .nothing = .{} }; + }, + } +} + +pub const CallResult = struct { + scope: *InterpreterScope, + result: union(enum) { + value: Value, + nothing, + }, +}; + +pub fn call( + interpreter: *ComptimeInterpreter, + func_node_idx: Ast.Node.Index, + arguments: []const Value, + options: InterpretOptions, +) InterpretError!CallResult { + // TODO: Eval, check parameter types + + // TODO: Arguments + _ = options; + _ = arguments; + + const tree = interpreter.tree; + const tags = tree.nodes.items(.tag); + + std.debug.assert(tags[func_node_idx] == .fn_decl); + + // TODO: Parent sc]ope exploration (consts, typefuncs, etc.) + var fn_scope = try interpreter.newScope(null, func_node_idx); + + const body = tree.nodes.items(.data)[func_node_idx].rhs; + const result = try interpreter.interpret(body, fn_scope, .{}); + + // TODO: Defers + return CallResult{ + .scope = fn_scope, + .result = switch (result) { + .@"return" => .{ .nothing = {} }, + .@"return_with_value" => |v| .{ .value = v }, + else => @panic("bruh"), + }, + }; +} diff --git a/src/analysis.zig b/src/analysis.zig index 7da2747..2e35540 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -306,7 +306,7 @@ pub fn getDeclNameToken(tree: Ast, node: Ast.Node.Index) ?Ast.TokenIndex { }; } -fn getDeclName(tree: Ast, node: Ast.Node.Index) ?[]const u8 { +pub fn getDeclName(tree: Ast, node: Ast.Node.Index) ?[]const u8 { const name = tree.tokenSlice(getDeclNameToken(tree, node) orelse return null); return switch (tree.nodes.items(.tag)[node]) { .test_decl => name[1 .. name.len - 1], diff --git a/src/zls.zig b/src/zls.zig index c896309..40920b2 100644 --- a/src/zls.zig +++ b/src/zls.zig @@ -8,3 +8,4 @@ pub const Server = @import("Server.zig"); pub const translate_c = @import("translate_c.zig"); pub const types = @import("types.zig"); pub const URI = @import("uri.zig"); +pub const ComptimeInterpreter = @import("ComptimeInterpreter.zig"); diff --git a/tests/language_features/comptime_interpreter.zig b/tests/language_features/comptime_interpreter.zig new file mode 100644 index 0000000..d08d777 --- /dev/null +++ b/tests/language_features/comptime_interpreter.zig @@ -0,0 +1,26 @@ +const std = @import("std"); +const zls = @import("zls"); + +const Ast = std.zig.Ast; + +const ComptimeInterpreter = zls.ComptimeInterpreter; + +const allocator: std.mem.Allocator = std.testing.allocator; + +test "ComptimeInterpreter - basic test" { + var tree = try std.zig.parse(allocator, + \\pub fn ReturnMyType() type { + \\ if (1 == 1) return u69; + \\ return u8; + \\} + ); + defer tree.deinit(allocator); + + var interpreter = ComptimeInterpreter{ .tree = tree, .allocator = allocator }; + defer interpreter.deinit(); + + const z = try interpreter.call(tree.rootDecls()[0], &.{}, .{}); + defer z.scope.deinit(); + + try std.testing.expectFmt("u69", "{any}", .{interpreter.formatTypeInfo(interpreter.typeToTypeInfo(z.result.value.value_data.@"type"))}); +} diff --git a/tests/tests.zig b/tests/tests.zig index c1772cb..93efef6 100644 --- a/tests/tests.zig +++ b/tests/tests.zig @@ -1,20 +1,21 @@ comptime { _ = @import("helper.zig"); - _ = @import("utility/offsets.zig"); - _ = @import("utility/position_context.zig"); - _ = @import("utility/uri.zig"); + // _ = @import("utility/offsets.zig"); + // _ = @import("utility/position_context.zig"); + // _ = @import("utility/uri.zig"); - // TODO Lifecycle Messages + // // TODO Lifecycle Messages - // TODO Document Synchronization + // // TODO Document Synchronization - // LSP features - _ = @import("lsp_features/semantic_tokens.zig"); - _ = @import("lsp_features/inlay_hints.zig"); - _ = @import("lsp_features/references.zig"); - _ = @import("lsp_features/completion.zig"); + // // LSP features + // _ = @import("lsp_features/semantic_tokens.zig"); + // _ = @import("lsp_features/inlay_hints.zig"); + // _ = @import("lsp_features/references.zig"); + // _ = @import("lsp_features/completion.zig"); // Language features - _ = @import("language_features/cimport.zig"); + // _ = @import("language_features/cimport.zig"); + _ = @import("language_features/comptime_interpreter.zig"); } From 06e875684935f1434b41ecf8961dc6ead3c11f43 Mon Sep 17 00:00:00 2001 From: Auguste Rame <19855629+SuperAuguste@users.noreply.github.com> Date: Fri, 28 Oct 2022 01:22:03 -0400 Subject: [PATCH 02/12] Add struct test --- src/ComptimeInterpreter.zig | 87 ++++++++++++++----- .../comptime_interpreter.zig | 23 ++++- 2 files changed, 87 insertions(+), 23 deletions(-) diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig index 902d842..585e3ff 100644 --- a/src/ComptimeInterpreter.zig +++ b/src/ComptimeInterpreter.zig @@ -21,6 +21,7 @@ type_info: std.ArrayListUnmanaged(TypeInfo) = .{}, type_info_map: std.HashMapUnmanaged(TypeInfo, usize, TypeInfo.Context, std.hash_map.default_max_load_percentage) = .{}, pub fn deinit(interpreter: *ComptimeInterpreter) void { + for (interpreter.type_info.items) |*ti| ti.deinit(interpreter.allocator); interpreter.type_info.deinit(interpreter.allocator); interpreter.type_info_map.deinit(interpreter.allocator); } @@ -137,6 +138,13 @@ pub const TypeInfo = union(enum) { else => {}, }; } + + pub fn deinit(ti: *TypeInfo, allocator: std.mem.Allocator) void { + switch (ti.*) { + .@"struct" => |*s| s.fields.deinit(allocator), + else => {}, + } + } }; pub const Type = struct { @@ -267,13 +275,16 @@ pub const TypeInfoFormatter = struct { .@"bool" => try writer.writeAll("bool"), .@"struct" => |s| { try writer.writeAll("struct {"); + for (s.fields.items) |field| { + try writer.print("{s}: {s}, ", .{ field.name, value.interpreter.formatTypeInfo(value.interpreter.typeToTypeInfo(field.@"type")) }); + } var iterator = s.scope.declarations.iterator(); while (iterator.next()) |di| { const decl = di.value_ptr.*; if (decl.isConstant(value.interpreter.tree)) { - try writer.print("const {s}: , {any}", .{ decl.name, value.interpreter.formatTypeInfo(value.interpreter.typeToTypeInfo(decl.@"type")) }); + try writer.print("const {s}: {any} = TODO_PRINT_VALUES, ", .{ decl.name, value.interpreter.formatTypeInfo(value.interpreter.typeToTypeInfo(decl.@"type")) }); } else { - try writer.print("var {s}: , {any}", .{ decl.name, value.interpreter.formatTypeInfo(value.interpreter.typeToTypeInfo(decl.@"type")) }); + try writer.print("var {s}: {any}, ", .{ decl.name, value.interpreter.formatTypeInfo(value.interpreter.typeToTypeInfo(decl.@"type")) }); } } try writer.writeAll("}"); @@ -388,15 +399,22 @@ pub const InterpretResult = union(enum) { }; } - pub fn getValue(result: InterpretResult) Value { - return result.maybeGetValue() orelse @panic("Attempted to get value from non-value interpret result"); + pub fn getValue(result: InterpretResult) error{ExpectedValue}!Value { + return result.maybeGetValue() orelse error.ExpectedValue; } }; // Might be useful in the future pub const InterpretOptions = struct {}; -pub const InterpretError = std.mem.Allocator.Error || std.fmt.ParseIntError || std.fmt.ParseFloatError || error{ InvalidCharacter, InvalidBase }; +pub const InterpretError = std.mem.Allocator.Error || std.fmt.ParseIntError || std.fmt.ParseFloatError || error{ + InvalidCharacter, + InvalidBase, + ExpectedValue, + InvalidOperation, + CriticalAstFailure, + InvalidBuiltin, +}; pub fn interpret( interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index, @@ -455,13 +473,13 @@ pub fn interpret( var default_value = if (field_info.ast.value_expr == 0) null else - (try interpreter.interpret(field_info.ast.value_expr, container_scope, .{})).getValue(); + try (try interpreter.interpret(field_info.ast.value_expr, container_scope, .{})).getValue(); const name = tree.tokenSlice(field_info.ast.name_token); const field = FieldDefinition{ .node_idx = member, .name = name, - .@"type" = init_type.getValue().value_data.@"type", + .@"type" = (try init_type.getValue()).value_data.@"type", .default_value = default_value, // TODO: Default values // .@"type" = T: { @@ -489,12 +507,12 @@ pub fn interpret( .simple_var_decl, => { const decl = ast.varDecl(tree, node_idx).?; - var value = (try interpreter.interpret(decl.ast.init_node, scope, options)).getValue(); + var value = try (try interpreter.interpret(decl.ast.init_node, scope, options)).getValue(); var @"type" = if (decl.ast.type_node == 0) Value{ .node_idx = std.math.maxInt(Ast.Node.Index), .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }), .value_data = .{ .@"type" = value.@"type" }, - } else (try interpreter.interpret(decl.ast.type_node, scope, options)).getValue(); + } else try (try interpreter.interpret(decl.ast.type_node, scope, options)).getValue(); const name = analysis.getDeclName(tree, node_idx).?; try scope.?.declarations.put(interpreter.allocator, name, .{ @@ -560,6 +578,22 @@ pub fn interpret( .identifier => { var value = tree.getNodeSource(node_idx); + if (std.mem.eql(u8, "bool", value)) return InterpretResult{ .value = Value{ + .node_idx = node_idx, + .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }), + .value_data = .{ .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = .{} }) }, + } }; + if (std.mem.eql(u8, "true", value)) return InterpretResult{ .value = Value{ + .node_idx = node_idx, + .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = .{} }), + .value_data = .{ .@"bool" = true }, + } }; + if (std.mem.eql(u8, "false", value)) return InterpretResult{ .value = Value{ + .node_idx = node_idx, + .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = .{} }), + .value_data = .{ .@"bool" = false }, + } }; + if (std.mem.eql(u8, "type", value)) { return InterpretResult{ .value = Value{ .node_idx = node_idx, @@ -579,6 +613,8 @@ pub fn interpret( } }; } + // TODO: Floats + // Logic to find identifiers in accessible scopes var psi = scope.?.parentScopeIterator(); @@ -597,32 +633,26 @@ pub fn interpret( return if (data[node_idx].rhs == 0) InterpretResult{ .@"break" = label } else - InterpretResult{ .break_with_value = .{ .label = label, .value = (try interpreter.interpret(data[node_idx].rhs, scope, options)).getValue() } }; + InterpretResult{ .break_with_value = .{ .label = label, .value = try (try interpreter.interpret(data[node_idx].rhs, scope, options)).getValue() } }; }, .@"return" => { return if (data[node_idx].lhs == 0) InterpretResult{ .@"return" = {} } else - InterpretResult{ .return_with_value = (try interpreter.interpret(data[node_idx].lhs, scope, options)).getValue() }; + InterpretResult{ .return_with_value = try (try interpreter.interpret(data[node_idx].lhs, scope, options)).getValue() }; }, .@"if", .if_simple => { const iff = ast.ifFull(tree, node_idx); // TODO: Don't evaluate runtime ifs // if (options.observe_values) { const ir = try interpreter.interpret(iff.ast.cond_expr, scope, options); - if (ir.getValue().value_data.@"bool") { + if ((try ir.getValue()).value_data.@"bool") { return try interpreter.interpret(iff.ast.then_expr, scope, options); } else { if (iff.ast.else_expr != 0) { return try interpreter.interpret(iff.ast.else_expr, scope, options); } else return InterpretResult{ .nothing = .{} }; } - // } else { - // _ = try interpreter.interpret(iff.ast.cond_expr, scope, options); - // _ = try interpreter.interpret(iff.ast.then_expr, scope, options); - // _ = try interpreter.interpret(iff.ast.else_expr, scope, options); - // } - @panic("bruh"); }, .equal_equal => { var a = try interpreter.interpret(data[node_idx].lhs, scope, options); @@ -630,7 +660,7 @@ pub fn interpret( return InterpretResult{ .value = Value{ .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = .{} }), - .value_data = .{ .@"bool" = a.getValue().eql(b.getValue()) }, + .value_data = .{ .@"bool" = (try a.getValue()).eql(try b.getValue()) }, } }; // a.getValue().eql(b.getValue()) }, @@ -649,7 +679,7 @@ pub fn interpret( try bi.setString(@enumToInt(bii), s[if (bii != .decimal) @as(usize, 2) else @as(usize, 0)..]); break :ppp .{ .@"comptime_int" = bi }; }, - .failure => @panic("Failed to parse number literal"), + .failure => return error.CriticalAstFailure, }, } }; }, @@ -676,7 +706,7 @@ pub fn interpret( var psi = scope.?.parentScopeIterator(); while (psi.next()) |pscope| { if (pscope.declarations.getEntry(value)) |decl| - decl.value_ptr.value = (try interpreter.interpret(data[node_idx].rhs, scope.?, options)).getValue(); + decl.value_ptr.value = try (try interpreter.interpret(data[node_idx].rhs, scope.?, options)).getValue(); } return InterpretResult{ .nothing = .{} }; @@ -707,7 +737,7 @@ pub fn interpret( if (std.mem.eql(u8, call_name, "@compileLog")) { pp: for (params) |param| { - const res = (try interpreter.interpret(param, scope, .{})).getValue(); + const res = try (try interpreter.interpret(param, scope, .{})).getValue(); const ti = interpreter.type_info.items[res.@"type".info_idx]; switch (ti) { .pointer => |ptr| { @@ -733,6 +763,7 @@ pub fn interpret( std.log.info("Builtin not implemented: {s}", .{call_name}); @panic("Builtin not implemented"); + // return error.InvalidBuiltin; }, .string_literal => { const value = tree.getNodeSource(node_idx)[1 .. tree.getNodeSource(node_idx).len - 1]; @@ -887,6 +918,18 @@ pub fn interpret( // return null; return InterpretResult{ .nothing = .{} }; }, + .bool_not => { + const result = try interpreter.interpret(data[node_idx].lhs, scope, .{}); + const value = (try result.getValue()); + if (value.value_data != .@"bool") return error.InvalidOperation; + return InterpretResult{ + .value = .{ + .node_idx = node_idx, + .@"type" = value.@"type", + .value_data = .{ .@"bool" = !value.value_data.@"bool" }, + }, + }; + }, else => { std.log.err("Unhandled {any}", .{tags[node_idx]}); return InterpretResult{ .nothing = .{} }; diff --git a/tests/language_features/comptime_interpreter.zig b/tests/language_features/comptime_interpreter.zig index d08d777..9a7e3ef 100644 --- a/tests/language_features/comptime_interpreter.zig +++ b/tests/language_features/comptime_interpreter.zig @@ -10,7 +10,8 @@ const allocator: std.mem.Allocator = std.testing.allocator; test "ComptimeInterpreter - basic test" { var tree = try std.zig.parse(allocator, \\pub fn ReturnMyType() type { - \\ if (1 == 1) return u69; + \\ var abc = z: {break :z if (!false) 123 else 0;}; + \\ if (abc == 123) return u69; \\ return u8; \\} ); @@ -24,3 +25,23 @@ test "ComptimeInterpreter - basic test" { try std.testing.expectFmt("u69", "{any}", .{interpreter.formatTypeInfo(interpreter.typeToTypeInfo(z.result.value.value_data.@"type"))}); } + +test "ComptimeInterpreter - struct" { + var tree = try std.zig.parse(allocator, + \\pub fn ReturnMyType() type { + \\ return struct { + \\ slay: bool, + \\ var abc = 123; + \\ }; + \\} + ); + defer tree.deinit(allocator); + + var interpreter = ComptimeInterpreter{ .tree = tree, .allocator = allocator }; + defer interpreter.deinit(); + + const z = try interpreter.call(tree.rootDecls()[0], &.{}, .{}); + defer z.scope.deinit(); + + try std.testing.expectFmt("struct {slay: bool, const abc: comptime_int = TODO_PRINT_VALUES, }", "{any}", .{interpreter.formatTypeInfo(interpreter.typeToTypeInfo(z.result.value.value_data.@"type"))}); +} From 779c3c0710524570e9ad8292c2b5049f74c56e67 Mon Sep 17 00:00:00 2001 From: Auguste Rame <19855629+SuperAuguste@users.noreply.github.com> Date: Fri, 28 Oct 2022 14:24:38 -0400 Subject: [PATCH 03/12] Hacky mess but it works (only if your function is the first root decl tho :P) --- src/ComptimeInterpreter.zig | 131 +++++++----------- src/Server.zig | 50 +++---- src/analysis.zig | 39 +++++- .../comptime_interpreter.zig | 29 +++- 4 files changed, 133 insertions(+), 116 deletions(-) diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig index 585e3ff..26d8c37 100644 --- a/src/ComptimeInterpreter.zig +++ b/src/ComptimeInterpreter.zig @@ -180,6 +180,9 @@ pub const ValueData = union(enum) { signed_int: i64, float: f64, + runtime, + comptime_undetermined, + pub fn eql(data: ValueData, other_data: ValueData) bool { if (std.meta.activeTag(data) != std.meta.activeTag(other_data)) return false; // std.enums. @@ -190,7 +193,8 @@ pub const ValueData = union(enum) { .unsigned_int => return data.unsigned_int == other_data.unsigned_int, .signed_int => return data.signed_int == other_data.signed_int, .float => return data.float == other_data.float, - else => @panic("Simple eql not implemented!"), + + else => return false, } } }; @@ -395,6 +399,7 @@ pub const InterpretResult = union(enum) { return switch (result) { .break_with_value => |v| v.value, .value => |v| v, + .return_with_value => |v| v, else => null, }; } @@ -414,6 +419,7 @@ pub const InterpretError = std.mem.Allocator.Error || std.fmt.ParseIntError || s InvalidOperation, CriticalAstFailure, InvalidBuiltin, + IdentifierNotFound, }; pub fn interpret( interpreter: *ComptimeInterpreter, @@ -623,7 +629,7 @@ pub fn interpret( } std.log.err("Identifier not found: {s}", .{value}); - @panic("Could not find identifier"); + return error.IdentifierNotFound; }, .grouped_expression => { return try interpreter.interpret(data[node_idx].lhs, scope, options); @@ -733,34 +739,17 @@ pub fn interpret( => { var buffer: [2]Ast.Node.Index = undefined; const params = ast.builtinCallParams(tree, node_idx, &buffer).?; + _ = params; const call_name = tree.tokenSlice(main_tokens[node_idx]); if (std.mem.eql(u8, call_name, "@compileLog")) { - pp: for (params) |param| { - const res = try (try interpreter.interpret(param, scope, .{})).getValue(); - const ti = interpreter.type_info.items[res.@"type".info_idx]; - switch (ti) { - .pointer => |ptr| { - const child = interpreter.type_info.items[ptr.child.info_idx]; - if (ptr.size == .slice and child == .int and child.int.bits == 8 and child.int.signedness == .unsigned) { - - // TODO: Fix once I optimize slices - std.debug.print("@compileLog output: ", .{}); - for (res.value_data.slice_ptr.items) |i| std.debug.print("{c}", .{@truncate(u8, i.unsigned_int)}); - std.debug.print("\n", .{}); - - break :pp; - } - }, - else => {}, - } - - @panic("compileLog argument type not printable!"); - } - return InterpretResult{ .nothing = .{} }; } + if (std.mem.eql(u8, call_name, "@compileError")) { + return InterpretResult{ .@"return" = .{} }; + } + std.log.info("Builtin not implemented: {s}", .{call_name}); @panic("Builtin not implemented"); // return error.InvalidBuiltin; @@ -844,12 +833,13 @@ pub fn interpret( // .value_data = .{ .@"fn" = .{} }, // }; - // const name = ast.getDeclName(tree, node_idx).?; + // const name = analysis.getDeclName(tree, node_idx).?; + // // TODO: DANGER DANGER DANGER // try scope.?.declarations.put(interpreter.allocator, name, .{ // .node_idx = node_idx, // .name = name, - // .@"type" = value.@"type", - // .@"value" = value, + // .@"type" = undefined, + // .@"value" = undefined, // }); return InterpretResult{ .nothing = .{} }; @@ -863,60 +853,28 @@ pub fn interpret( .async_call_one, .async_call_one_comma, => { - // var params: [1]Ast.Node.Index = undefined; - // const call = ast.callFull(tree, node_idx, ¶ms) orelse unreachable; + var params: [1]Ast.Node.Index = undefined; + const call_full = ast.callFull(tree, node_idx, ¶ms) orelse unreachable; - // const callee = .{ .node = call.ast.fn_expr, .handle = handle }; - // const decl = (try resolveTypeOfNodeInternal(store, arena, callee, bound_type_params)) orelse - // return null; + var args = try std.ArrayListUnmanaged(Value).initCapacity(interpreter.allocator, call_full.ast.params.len); + defer args.deinit(interpreter.allocator); - // if (decl.type.is_type_val) return null; - // const decl_node = switch (decl.type.data) { - // .other => |n| n, - // else => return null, - // }; - // var buf: [1]Ast.Node.Index = undefined; - // const func_maybe = ast.fnProto(decl.handle.tree, decl_node, &buf); + for (call_full.ast.params) |param| { + try args.append(interpreter.allocator, try (try interpreter.interpret(param, scope, .{})).getValue()); + } - // if (func_maybe) |fn_decl| { - // var expected_params = fn_decl.ast.params.len; - // // If we call as method, the first parameter should be skipped - // // TODO: Back-parse to extract the self argument? - // var it = fn_decl.iterate(&decl.handle.tree); - // if (token_tags[call.ast.lparen - 2] == .period) { - // if (try hasSelfParam(arena, store, decl.handle, fn_decl)) { - // _ = ast.nextFnParam(&it); - // expected_params -= 1; - // } - // } + // TODO: Make this actually resolve function; requires interpreting whole file + // const res = try interpreter.interpret(call_full.ast.fn_expr, scope, .{}); + // const value = try res.getValue(); - // // Bind type params to the arguments passed in the call. - // const param_len = std.math.min(call.ast.params.len, expected_params); - // var i: usize = 0; - // while (ast.nextFnParam(&it)) |decl_param| : (i += 1) { - // if (i >= param_len) break; - // if (!isMetaType(decl.handle.tree, decl_param.type_expr)) - // continue; + const call_res = try interpreter.call(tree.rootDecls()[0], args.items, options); + // defer call_res.scope.deinit(); + // TODO: Figure out call result memory model - // const argument = .{ .node = call.ast.params[i], .handle = handle }; - // const argument_type = (try resolveTypeOfNodeInternal( - // store, - // arena, - // argument, - // bound_type_params, - // )) orelse - // continue; - // if (!argument_type.type.is_type_val) continue; - - // try bound_type_params.put(arena.allocator(), decl_param, argument_type); - // } - - // const has_body = decl.handle.tree.nodes.items(.tag)[decl_node] == .fn_decl; - // const body = decl.handle.tree.nodes.items(.data)[decl_node].rhs; - // return try resolveReturnType(store, arena, fn_decl, decl.handle, bound_type_params, if (has_body) body else null); - // } - // return null; - return InterpretResult{ .nothing = .{} }; + return switch (call_res.result) { + .value => |v| .{ .value = v }, + .nothing => .{ .nothing = {} }, + }; }, .bool_not => { const result = try interpreter.interpret(data[node_idx].lhs, scope, .{}); @@ -955,16 +913,33 @@ pub fn call( // TODO: Arguments _ = options; - _ = arguments; + // _ = arguments; const tree = interpreter.tree; const tags = tree.nodes.items(.tag); std.debug.assert(tags[func_node_idx] == .fn_decl); - // TODO: Parent sc]ope exploration (consts, typefuncs, etc.) var fn_scope = try interpreter.newScope(null, func_node_idx); + var buf: [1]Ast.Node.Index = undefined; + var proto = ast.fnProto(tree, func_node_idx, &buf).?; + + var arg_it = proto.iterate(&tree); + var arg_index: usize = 0; + while (ast.nextFnParam(&arg_it)) |param| { + if (param.name_token) |nt| { + const decl = Declaration{ + .node_idx = param.type_expr, + .name = tree.tokenSlice(nt), + .@"type" = arguments[arg_index].@"type", + .value = arguments[arg_index], + }; + try fn_scope.declarations.put(interpreter.allocator, tree.tokenSlice(nt), decl); + arg_index += 1; + } + } + const body = tree.nodes.items(.data)[func_node_idx].rhs; const result = try interpreter.interpret(body, fn_scope, .{}); diff --git a/src/Server.zig b/src/Server.zig index 1cb2f68..d563ede 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -484,6 +484,23 @@ fn typeToCompletion( null, ), .primitive, .array_index => {}, + .@"comptime" => |co| { + const ti = co.interpreter.typeToTypeInfo(co.type); + switch (ti) { + .@"struct" => |st| { + var it = st.scope.declarations.iterator(); + while (it.next()) |entry| { + try list.append(allocator, .{ + .label = entry.key_ptr.*, + .kind = if (entry.value_ptr.isConstant(co.interpreter.tree)) .Constant else .Variable, + .insertText = entry.key_ptr.*, + .insertTextFormat = .PlainText, + }); + } + }, + else => {}, + } + }, } } @@ -2663,8 +2680,7 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void if (s.len == 0) { if (field.field_type == ?[]const u8) { break :blk null; - } - else { + } else { break :blk s; } } @@ -2727,35 +2743,7 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void } } - const method_map = .{ - .{ "initialized", void, initializedHandler }, - .{"$/cancelRequest"}, - .{"textDocument/willSave"}, - .{ "initialize", requests.Initialize, initializeHandler }, - .{ "shutdown", void, shutdownHandler }, - .{ "exit", void, exitHandler }, - .{ "textDocument/didOpen", requests.OpenDocument, openDocumentHandler }, - .{ "textDocument/didChange", requests.ChangeDocument, changeDocumentHandler }, - .{ "textDocument/didSave", requests.SaveDocument, saveDocumentHandler }, - .{ "textDocument/didClose", requests.CloseDocument, closeDocumentHandler }, - .{ "textDocument/semanticTokens/full", requests.SemanticTokensFull, semanticTokensFullHandler }, - .{ "textDocument/inlayHint", requests.InlayHint, inlayHintHandler }, - .{ "textDocument/completion", requests.Completion, completionHandler }, - .{ "textDocument/signatureHelp", requests.SignatureHelp, signatureHelpHandler }, - .{ "textDocument/definition", requests.GotoDefinition, gotoDefinitionHandler }, - .{ "textDocument/typeDefinition", requests.GotoDefinition, gotoDefinitionHandler }, - .{ "textDocument/implementation", requests.GotoDefinition, gotoDefinitionHandler }, - .{ "textDocument/declaration", requests.GotoDeclaration, gotoDeclarationHandler }, - .{ "textDocument/hover", requests.Hover, hoverHandler }, - .{ "textDocument/documentSymbol", requests.DocumentSymbols, documentSymbolsHandler }, - .{ "textDocument/formatting", requests.Formatting, formattingHandler }, - .{ "textDocument/rename", requests.Rename, renameHandler }, - .{ "textDocument/references", requests.References, referencesHandler }, - .{ "textDocument/documentHighlight", requests.DocumentHighlight, documentHighlightHandler }, - .{ "textDocument/codeAction", requests.CodeAction, codeActionHandler }, - .{ "workspace/didChangeConfiguration", Config.DidChangeConfigurationParams, didChangeConfigurationHandler }, - .{ "textDocument/foldingRange", requests.FoldingRange, foldingRangeHandler }, - }; + const method_map = .{ .{ "initialized", void, initializedHandler }, .{"$/cancelRequest"}, .{"textDocument/willSave"}, .{ "initialize", requests.Initialize, initializeHandler }, .{ "shutdown", void, shutdownHandler }, .{ "exit", void, exitHandler }, .{ "textDocument/didOpen", requests.OpenDocument, openDocumentHandler }, .{ "textDocument/didChange", requests.ChangeDocument, changeDocumentHandler }, .{ "textDocument/didSave", requests.SaveDocument, saveDocumentHandler }, .{ "textDocument/didClose", requests.CloseDocument, closeDocumentHandler }, .{ "textDocument/semanticTokens/full", requests.SemanticTokensFull, semanticTokensFullHandler }, .{ "textDocument/inlayHint", requests.InlayHint, inlayHintHandler }, .{ "textDocument/completion", requests.Completion, completionHandler }, .{ "textDocument/signatureHelp", requests.SignatureHelp, signatureHelpHandler }, .{ "textDocument/definition", requests.GotoDefinition, gotoDefinitionHandler }, .{ "textDocument/typeDefinition", requests.GotoDefinition, gotoDefinitionHandler }, .{ "textDocument/implementation", requests.GotoDefinition, gotoDefinitionHandler }, .{ "textDocument/declaration", requests.GotoDeclaration, gotoDeclarationHandler }, .{ "textDocument/hover", requests.Hover, hoverHandler }, .{ "textDocument/documentSymbol", requests.DocumentSymbols, documentSymbolsHandler }, .{ "textDocument/formatting", requests.Formatting, formattingHandler }, .{ "textDocument/rename", requests.Rename, renameHandler }, .{ "textDocument/references", requests.References, referencesHandler }, .{ "textDocument/documentHighlight", requests.DocumentHighlight, documentHighlightHandler }, .{ "textDocument/codeAction", requests.CodeAction, codeActionHandler }, .{ "workspace/didChangeConfiguration", Config.DidChangeConfigurationParams, didChangeConfigurationHandler }, .{ "textDocument/foldingRange", requests.FoldingRange, foldingRangeHandler } }; if (zig_builtin.zig_backend == .stage1) { // Hack to avoid `return`ing in the inline for, which causes bugs. diff --git a/src/analysis.zig b/src/analysis.zig index 2e35540..e9a1143 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -5,6 +5,7 @@ const types = @import("types.zig"); const offsets = @import("offsets.zig"); const log = std.log.scoped(.analysis); const ast = @import("ast.zig"); +const ComptimeInterpreter = @import("ComptimeInterpreter.zig"); var using_trail: std.ArrayList([*]const u8) = undefined; var resolve_trail: std.ArrayList(NodeWithHandle) = undefined; @@ -491,7 +492,7 @@ fn resolveUnwrapErrorType(store: *DocumentStore, arena: *std.heap.ArenaAllocator .type = .{ .data = .{ .other = n }, .is_type_val = rhs.type.is_type_val }, .handle = rhs.handle, }, - .primitive, .slice, .pointer, .array_index => return null, + .primitive, .slice, .pointer, .array_index, .@"comptime" => return null, }; if (rhs.handle.tree.nodes.items(.tag)[rhs_node] == .error_union) { @@ -742,7 +743,37 @@ pub fn resolveTypeOfNodeInternal(store: *DocumentStore, arena: *std.heap.ArenaAl const has_body = decl.handle.tree.nodes.items(.tag)[decl_node] == .fn_decl; const body = decl.handle.tree.nodes.items(.data)[decl_node].rhs; - return try resolveReturnType(store, arena, fn_decl, decl.handle, bound_type_params, if (has_body) body else null); + if (try resolveReturnType(store, arena, fn_decl, decl.handle, bound_type_params, if (has_body) body else null)) |ret| { + return ret; + } else { + // TODO: Better case-by-case; we just use the ComptimeInterpreter when all else fails, + // probably better to use it more liberally + // TODO: Handle non-isolate args; e.g. `const T = u8; TypeFunc(T);` + var interpreter = ComptimeInterpreter{ .tree = tree, .allocator = arena.allocator() }; + + const result = interpreter.interpret(node, null, .{}) catch |err| { + std.log.err("Interpreter error: {s}", .{@errorName(err)}); + return null; + }; + const val = result.getValue() catch |err| { + std.log.err("Interpreter error: {s}", .{@errorName(err)}); + return null; + }; + + const ti = interpreter.typeToTypeInfo(val.@"type"); + if (ti != .@"type") { + std.log.err("Not a type: { }", .{interpreter.formatTypeInfo(ti)}); + return null; + } + + return TypeWithHandle{ + .type = .{ + .data = .{ .@"comptime" = .{ .interpreter = interpreter, .type = val.value_data.@"type" } }, + .is_type_val = true, + }, + .handle = node_handle.handle, + }; + } } return null; }, @@ -965,6 +996,10 @@ pub const Type = struct { other: Ast.Node.Index, primitive: Ast.Node.Index, array_index, + @"comptime": struct { + interpreter: ComptimeInterpreter, + type: ComptimeInterpreter.Type, + }, }, /// If true, the type `type`, the attached data is the value of the type value. is_type_val: bool, diff --git a/tests/language_features/comptime_interpreter.zig b/tests/language_features/comptime_interpreter.zig index 9a7e3ef..3d16ee3 100644 --- a/tests/language_features/comptime_interpreter.zig +++ b/tests/language_features/comptime_interpreter.zig @@ -9,8 +9,8 @@ const allocator: std.mem.Allocator = std.testing.allocator; test "ComptimeInterpreter - basic test" { var tree = try std.zig.parse(allocator, - \\pub fn ReturnMyType() type { - \\ var abc = z: {break :z if (!false) 123 else 0;}; + \\pub fn ReturnMyType(comptime my_arg: bool) type { + \\ var abc = z: {break :z if (!my_arg) 123 else 0;}; \\ if (abc == 123) return u69; \\ return u8; \\} @@ -20,10 +20,29 @@ test "ComptimeInterpreter - basic test" { var interpreter = ComptimeInterpreter{ .tree = tree, .allocator = allocator }; defer interpreter.deinit(); - const z = try interpreter.call(tree.rootDecls()[0], &.{}, .{}); - defer z.scope.deinit(); + var bool_type = try interpreter.createType(std.math.maxInt(std.zig.Ast.Node.Index), .{ .@"bool" = .{} }); + var arg_false = ComptimeInterpreter.Value{ + .node_idx = std.math.maxInt(std.zig.Ast.Node.Index), + .@"type" = bool_type, + .value_data = .{ .@"bool" = false }, + }; + var arg_true = ComptimeInterpreter.Value{ + .node_idx = std.math.maxInt(std.zig.Ast.Node.Index), + .@"type" = bool_type, + .value_data = .{ .@"bool" = true }, + }; - try std.testing.expectFmt("u69", "{any}", .{interpreter.formatTypeInfo(interpreter.typeToTypeInfo(z.result.value.value_data.@"type"))}); + const call_with_false = try interpreter.call(tree.rootDecls()[0], &.{ + arg_false, + }, .{}); + defer call_with_false.scope.deinit(); + const call_with_true = try interpreter.call(tree.rootDecls()[0], &.{ + arg_true, + }, .{}); + defer call_with_true.scope.deinit(); + + try std.testing.expectFmt("u69", "{any}", .{interpreter.formatTypeInfo(interpreter.typeToTypeInfo(call_with_false.result.value.value_data.@"type"))}); + try std.testing.expectFmt("u8", "{any}", .{interpreter.formatTypeInfo(interpreter.typeToTypeInfo(call_with_true.result.value.value_data.@"type"))}); } test "ComptimeInterpreter - struct" { From da00751726dd53492660e5bcdbdc495640bff0dd Mon Sep 17 00:00:00 2001 From: Auguste Rame <19855629+SuperAuguste@users.noreply.github.com> Date: Sat, 29 Oct 2022 01:46:22 -0400 Subject: [PATCH 04/12] Field access, function calls based on function value --- src/ComptimeInterpreter.zig | 114 ++++++++++++++++++++++-------------- src/DocumentStore.zig | 23 ++++++++ src/Server.zig | 8 ++- src/analysis.zig | 24 +++++++- 4 files changed, 121 insertions(+), 48 deletions(-) diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig index 26d8c37..6ba572c 100644 --- a/src/ComptimeInterpreter.zig +++ b/src/ComptimeInterpreter.zig @@ -13,14 +13,16 @@ const analysis = @import("analysis.zig"); const DocumentStore = @import("DocumentStore.zig"); const ComptimeInterpreter = @This(); -tree: Ast, -root_scope: *InterpreterScope = undefined, allocator: std.mem.Allocator, +document_store: *DocumentStore, +handle: *const DocumentStore.Handle, +root_scope: ?*InterpreterScope = null, type_info: std.ArrayListUnmanaged(TypeInfo) = .{}, type_info_map: std.HashMapUnmanaged(TypeInfo, usize, TypeInfo.Context, std.hash_map.default_max_load_percentage) = .{}, pub fn deinit(interpreter: *ComptimeInterpreter) void { + if (interpreter.root_scope) |rs| rs.deinit(); for (interpreter.type_info.items) |*ti| ti.deinit(interpreter.allocator); interpreter.type_info.deinit(interpreter.allocator); interpreter.type_info_map.deinit(interpreter.allocator); @@ -83,6 +85,7 @@ pub const TypeInfo = union(enum) { @"struct": Struct, pointer: Pointer, + @"fn": Fn, int: Int, @"comptime_int", @@ -145,6 +148,13 @@ pub const TypeInfo = union(enum) { else => {}, } } + + pub fn getScopeOfType(ti: TypeInfo) ?*InterpreterScope { + return switch (ti) { + .@"struct" => |s| s.scope, + else => null, + }; + } }; pub const Type = struct { @@ -180,6 +190,7 @@ pub const ValueData = union(enum) { signed_int: i64, float: f64, + @"fn", runtime, comptime_undetermined, @@ -233,7 +244,7 @@ pub const Declaration = struct { .aligned_var_decl, .simple_var_decl, => { - return tree.tokenSlice(ast.varDecl(tree, declaration.node_idx).?.ast.mut_token).len == 3; + return tree.tokenSlice(ast.varDecl(tree, declaration.node_idx).?.ast.mut_token).len != 3; }, else => false, }; @@ -261,7 +272,7 @@ pub fn typeToTypeInfo(interpreter: ComptimeInterpreter, @"type": Type) TypeInfo } pub const TypeInfoFormatter = struct { - interpreter: *ComptimeInterpreter, + interpreter: *const ComptimeInterpreter, ti: TypeInfo, pub fn format(value: TypeInfoFormatter, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { @@ -285,7 +296,7 @@ pub const TypeInfoFormatter = struct { var iterator = s.scope.declarations.iterator(); while (iterator.next()) |di| { const decl = di.value_ptr.*; - if (decl.isConstant(value.interpreter.tree)) { + if (decl.isConstant(value.interpreter.handle.tree)) { try writer.print("const {s}: {any} = TODO_PRINT_VALUES, ", .{ decl.name, value.interpreter.formatTypeInfo(value.interpreter.typeToTypeInfo(decl.@"type")) }); } else { try writer.print("var {s}: {any}, ", .{ decl.name, value.interpreter.formatTypeInfo(value.interpreter.typeToTypeInfo(decl.@"type")) }); @@ -298,7 +309,7 @@ pub const TypeInfoFormatter = struct { } }; -pub fn formatTypeInfo(interpreter: *ComptimeInterpreter, ti: TypeInfo) TypeInfoFormatter { +pub fn formatTypeInfo(interpreter: *const ComptimeInterpreter, ti: TypeInfo) TypeInfoFormatter { return TypeInfoFormatter{ .interpreter = interpreter, .ti = ti }; } @@ -365,11 +376,13 @@ pub const InterpreterScope = struct { } pub fn deinit(scope: *InterpreterScope) void { - scope.declarations.deinit(scope.interpreter.allocator); - for (scope.child_scopes.items) |child| child.deinit(); - scope.child_scopes.deinit(scope.interpreter.allocator); + const allocator = scope.interpreter.allocator; - scope.interpreter.allocator.destroy(scope); + scope.declarations.deinit(allocator); + for (scope.child_scopes.items) |child| child.deinit(); + scope.child_scopes.deinit(allocator); + + allocator.destroy(scope); } }; @@ -431,7 +444,7 @@ pub fn interpret( // _ = node; // _ = observe_values; - const tree = interpreter.tree; + const tree = interpreter.handle.tree; const tags = tree.nodes.items(.tag); const data = tree.nodes.items(.data); const main_tokens = tree.nodes.items(.main_token); @@ -445,12 +458,12 @@ pub fn interpret( .container_decl_arg_trailing, .container_decl_two, .container_decl_two_trailing, - .tagged_union, - .tagged_union_trailing, - .tagged_union_two, - .tagged_union_two_trailing, - .tagged_union_enum_tag, - .tagged_union_enum_tag_trailing, + // .tagged_union, // TODO: Fix these + // .tagged_union_trailing, + // .tagged_union_two, + // .tagged_union_two_trailing, + // .tagged_union_enum_tag, + // .tagged_union_enum_tag_trailing, .root, .error_set_decl, => { @@ -631,6 +644,20 @@ pub fn interpret( std.log.err("Identifier not found: {s}", .{value}); return error.IdentifierNotFound; }, + .field_access => { + if (data[node_idx].rhs == 0) return error.CriticalAstFailure; + const rhs_str = ast.tokenSlice(tree, data[node_idx].rhs) catch return error.CriticalAstFailure; + + var ir = try interpreter.interpret(data[node_idx].lhs, scope, options); + var irv = try ir.getValue(); + + var sub_scope = interpreter.typeToTypeInfo(irv.value_data.@"type").getScopeOfType() orelse return error.IdentifierNotFound; + var scope_sub_decl = sub_scope.declarations.get(rhs_str) orelse return error.IdentifierNotFound; + + return InterpretResult{ + .value = scope_sub_decl.value, + }; + }, .grouped_expression => { return try interpreter.interpret(data[node_idx].lhs, scope, options); }, @@ -796,12 +823,11 @@ pub fn interpret( // TODO: Add params - // var type_info = TypeInfo{ - // .@"fn" = .{ - // .definition_scope = scope.?, - // .node_idx = node_idx, - // }, - // }; + var type_info = TypeInfo{ + .@"fn" = .{ + .return_type = null, + }, + }; // var it = func.iterate(&tree); // while (ast.nextFnParam(&it)) |param| { @@ -827,20 +853,19 @@ pub fn interpret( // if ((try interpreter.interpret(func.ast.return_type, func_scope_idx, .{ .observe_values = true, .is_comptime = true })).maybeGetValue()) |value| // fnd.return_type = value.value_data.@"type"; - // var value = Value{ - // .node_idx = node_idx, - // .@"type" = try interpreter.createType(node_idx, type_info), - // .value_data = .{ .@"fn" = .{} }, - // }; + var value = Value{ + .node_idx = node_idx, + .@"type" = try interpreter.createType(node_idx, type_info), + .value_data = .{ .@"fn" = .{} }, + }; - // const name = analysis.getDeclName(tree, node_idx).?; - // // TODO: DANGER DANGER DANGER - // try scope.?.declarations.put(interpreter.allocator, name, .{ - // .node_idx = node_idx, - // .name = name, - // .@"type" = undefined, - // .@"value" = undefined, - // }); + const name = analysis.getDeclName(tree, node_idx).?; + try scope.?.declarations.put(interpreter.allocator, name, .{ + .node_idx = node_idx, + .name = name, + .@"type" = value.@"type", + .@"value" = value, + }); return InterpretResult{ .nothing = .{} }; }, @@ -863,13 +888,15 @@ pub fn interpret( try args.append(interpreter.allocator, try (try interpreter.interpret(param, scope, .{})).getValue()); } - // TODO: Make this actually resolve function; requires interpreting whole file - // const res = try interpreter.interpret(call_full.ast.fn_expr, scope, .{}); - // const value = try res.getValue(); + std.log.err("AEWEWEWE: {s}", .{tree.getNodeSource(call_full.ast.fn_expr)}); - const call_res = try interpreter.call(tree.rootDecls()[0], args.items, options); + const func_id_result = try interpreter.interpret(call_full.ast.fn_expr, interpreter.root_scope, .{}); + const func_id_val = try func_id_result.getValue(); + + const call_res = try interpreter.call(interpreter.root_scope, func_id_val.node_idx, args.items, options); // defer call_res.scope.deinit(); - // TODO: Figure out call result memory model + // TODO: Figure out call result memory model; this is actually fine because newScope + // makes this a child of the decl scope which is freed on refresh... in theory return switch (call_res.result) { .value => |v| .{ .value = v }, @@ -905,6 +932,7 @@ pub const CallResult = struct { pub fn call( interpreter: *ComptimeInterpreter, + scope: ?*InterpreterScope, func_node_idx: Ast.Node.Index, arguments: []const Value, options: InterpretOptions, @@ -915,12 +943,12 @@ pub fn call( _ = options; // _ = arguments; - const tree = interpreter.tree; + const tree = interpreter.handle.tree; const tags = tree.nodes.items(.tag); std.debug.assert(tags[func_node_idx] == .fn_decl); - var fn_scope = try interpreter.newScope(null, func_node_idx); + var fn_scope = try interpreter.newScope(scope, func_node_idx); var buf: [1]Ast.Node.Index = undefined; var proto = ast.fnProto(tree, func_node_idx, &buf).?; diff --git a/src/DocumentStore.zig b/src/DocumentStore.zig index 2144a29..a10c7f5 100644 --- a/src/DocumentStore.zig +++ b/src/DocumentStore.zig @@ -12,6 +12,7 @@ const BuildConfig = @import("special/build_runner.zig").BuildConfig; const tracy = @import("tracy.zig"); const Config = @import("Config.zig"); const translate_c = @import("translate_c.zig"); +const ComptimeInterpreter = @import("ComptimeInterpreter.zig"); const DocumentStore = @This(); @@ -55,6 +56,8 @@ pub const Handle = struct { uri: Uri, text: [:0]const u8, tree: Ast, + /// Not null if a ComptimeInterpreter is actually used + interpreter: ?*ComptimeInterpreter = null, document_scope: analysis.DocumentScope, /// Contains one entry for every import in the document import_uris: std.ArrayListUnmanaged(Uri) = .{}, @@ -189,6 +192,11 @@ pub fn refreshDocument(self: *DocumentStore, uri: Uri, new_text: [:0]const u8) ! const handle = self.handles.get(uri) orelse unreachable; + if (handle.interpreter) |int| { + int.deinit(); + handle.interpreter = null; + } + self.allocator.free(handle.text); handle.text = new_text; @@ -927,3 +935,18 @@ pub fn errorCompletionItems(self: DocumentStore, arena: std.mem.Allocator, handl pub fn enumCompletionItems(self: DocumentStore, arena: std.mem.Allocator, handle: Handle) ![]types.CompletionItem { return try self.tagStoreCompletionItems(arena, handle, "enum_completions"); } + +pub fn ensureInterpreterExists(self: *DocumentStore, uri: Uri) !void { + var handle = self.handles.get(uri) orelse unreachable; + if (handle.interpreter == null) { + var int = try self.allocator.create(ComptimeInterpreter); + int.* = ComptimeInterpreter{ + .allocator = self.allocator, + .document_store = self, + .handle = handle, + }; + _ = try int.interpret(0, null, .{}); + + handle.interpreter = int; + } +} diff --git a/src/Server.zig b/src/Server.zig index d563ede..68dab07 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -18,6 +18,7 @@ const Ast = std.zig.Ast; const tracy = @import("tracy.zig"); const uri_utils = @import("uri.zig"); const diff = @import("diff.zig"); +const ComptimeInterpreter = @import("ComptimeInterpreter.zig"); const data = @import("data/data.zig"); const snipped_data = @import("data/snippets.zig"); @@ -492,7 +493,7 @@ fn typeToCompletion( while (it.next()) |entry| { try list.append(allocator, .{ .label = entry.key_ptr.*, - .kind = if (entry.value_ptr.isConstant(co.interpreter.tree)) .Constant else .Variable, + .kind = if (entry.value_ptr.isConstant(co.interpreter.handle.tree)) .Constant else .Variable, .insertText = entry.key_ptr.*, .insertTextFormat = .PlainText, }); @@ -819,7 +820,10 @@ fn hoverSymbol(server: *Server, decl_handle: analysis.DeclWithHandle) error{OutO const resolved_type = try decl_handle.resolveType(&server.document_store, &server.arena, &bound_type_params); const resolved_type_str = if (resolved_type) |rt| - if (rt.type.is_type_val) "type" else switch (rt.type.data) { // TODO: Investigate random weird numbers like 897 that cause index of bounds + if (rt.type.is_type_val) switch (rt.type.data) { + .@"comptime" => |*co| try std.fmt.allocPrint(server.arena.allocator(), "{ }", .{co.interpreter.formatTypeInfo(co.interpreter.typeToTypeInfo(co.type))}), + else => "type", + } else switch (rt.type.data) { // TODO: Investigate random weird numbers like 897 that cause index of bounds .pointer, .slice, .error_union, diff --git a/src/analysis.zig b/src/analysis.zig index e9a1143..aa490db 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -749,14 +749,32 @@ pub fn resolveTypeOfNodeInternal(store: *DocumentStore, arena: *std.heap.ArenaAl // TODO: Better case-by-case; we just use the ComptimeInterpreter when all else fails, // probably better to use it more liberally // TODO: Handle non-isolate args; e.g. `const T = u8; TypeFunc(T);` - var interpreter = ComptimeInterpreter{ .tree = tree, .allocator = arena.allocator() }; + // var interpreter = ComptimeInterpreter{ .tree = tree, .allocator = arena.allocator() }; - const result = interpreter.interpret(node, null, .{}) catch |err| { + // var top_decl = try (try interpreter.interpret(0, null, .{})).getValue(); + // var top_scope = interpreter.typeToTypeInfo(top_decl.@"type".info_idx).@"struct".scope; + + // var fn_decl_scope = top_scope.getParentScopeFromNode(node); + + store.ensureInterpreterExists(handle.uri) catch |err| { std.log.err("Interpreter error: {s}", .{@errorName(err)}); return null; }; + var interpreter = handle.interpreter.?; + + // TODO: Start from current/nearest-current scope + const result = interpreter.interpret(node, interpreter.root_scope, .{}) catch |err| { + std.log.err("Interpreter error: {s}", .{@errorName(err)}); + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace.*); + } + return null; + }; const val = result.getValue() catch |err| { std.log.err("Interpreter error: {s}", .{@errorName(err)}); + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace.*); + } return null; }; @@ -997,7 +1015,7 @@ pub const Type = struct { primitive: Ast.Node.Index, array_index, @"comptime": struct { - interpreter: ComptimeInterpreter, + interpreter: *ComptimeInterpreter, type: ComptimeInterpreter.Type, }, }, From 599c134593088bc6d12faf0d54d72cd21018081a Mon Sep 17 00:00:00 2001 From: Auguste Rame <19855629+SuperAuguste@users.noreply.github.com> Date: Sat, 29 Oct 2022 17:28:44 -0400 Subject: [PATCH 05/12] Imports, cross-boundary resolution; can import std but dies on missing builtins / lang features --- src/ComptimeInterpreter.zig | 181 ++++++++++++++---- src/DocumentStore.zig | 6 +- src/Server.zig | 4 +- src/analysis.zig | 17 +- .../comptime_interpreter.zig | 4 +- tests/tests.zig | 24 +-- 6 files changed, 175 insertions(+), 61 deletions(-) diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig index 6ba572c..64a4e64 100644 --- a/src/ComptimeInterpreter.zig +++ b/src/ComptimeInterpreter.zig @@ -16,13 +16,14 @@ const ComptimeInterpreter = @This(); allocator: std.mem.Allocator, document_store: *DocumentStore, handle: *const DocumentStore.Handle, -root_scope: ?*InterpreterScope = null, +root_type: ?Type = null, +// TODO: Deduplicate typeinfo across different interpreters type_info: std.ArrayListUnmanaged(TypeInfo) = .{}, type_info_map: std.HashMapUnmanaged(TypeInfo, usize, TypeInfo.Context, std.hash_map.default_max_load_percentage) = .{}, pub fn deinit(interpreter: *ComptimeInterpreter) void { - if (interpreter.root_scope) |rs| rs.deinit(); + if (interpreter.root_type) |rt| rt.getTypeInfo().getScopeOfType().?.deinit(); for (interpreter.type_info.items) |*ti| ti.deinit(interpreter.allocator); interpreter.type_info.deinit(interpreter.allocator); interpreter.type_info_map.deinit(interpreter.allocator); @@ -101,8 +102,8 @@ pub const TypeInfo = union(enum) { const bp = b.pointer; break :p ap.size == bp.size and ap.is_const == bp.is_const and ap.is_volatile == bp.is_volatile and eql( interpreter, - interpreter.typeToTypeInfo(ap.child), - interpreter.typeToTypeInfo(bp.child), + ap.child.getTypeInfo(), + bp.child.getTypeInfo(), ) and ap.is_allowzero == bp.is_allowzero and ((ap.sentinel == null and bp.sentinel == null) or ((ap.sentinel != null and bp.sentinel != null) and ap.sentinel.?.eql(bp.sentinel.?))); }, .int => a.int.signedness == b.int.signedness and a.int.bits == b.int.bits, @@ -123,7 +124,7 @@ pub const TypeInfo = union(enum) { // const ap = a.pointer; // const bp = b.pointer; context.hasher.update(&[_]u8{ @enumToInt(p.size), @boolToInt(p.is_const), @boolToInt(p.is_volatile) }); - TypeInfo.hash(context, context.interpreter.typeToTypeInfo(p.child)); + TypeInfo.hash(context, p.child.getTypeInfo()); context.hasher.update(&[_]u8{@boolToInt(p.is_allowzero)}); // TODO: Hash Sentinel // break :p ap.size == bp.size and ap.is_const == bp.is_const and ap.is_volatile == bp.is_volatile and eql( @@ -158,11 +159,19 @@ pub const TypeInfo = union(enum) { }; pub const Type = struct { + handle: *const DocumentStore.Handle, + node_idx: Ast.Node.Index, info_idx: usize, + + pub fn getTypeInfo(@"type": Type) TypeInfo { + return @"type".handle.interpreter.?.type_info.items[@"type".info_idx]; + } }; pub const Value = struct { + handle: *const DocumentStore.Handle, + node_idx: Ast.Node.Index, @"type": Type, value_data: ValueData, @@ -258,19 +267,15 @@ pub fn createType(interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index, t if (gpr.found_existing) { // std.log.info("Deduplicating type {d}", .{interpreter.formatTypeInfo(unit.type_info.items[gpr.value_ptr.*])}); - return Type{ .node_idx = node_idx, .info_idx = gpr.value_ptr.* }; + return Type{ .handle = interpreter.handle, .node_idx = node_idx, .info_idx = gpr.value_ptr.* }; } else { try interpreter.type_info.append(interpreter.allocator, type_info); const info_idx = interpreter.type_info.items.len - 1; gpr.value_ptr.* = info_idx; - return Type{ .node_idx = node_idx, .info_idx = info_idx }; + return Type{ .handle = interpreter.handle, .node_idx = node_idx, .info_idx = info_idx }; } } -pub fn typeToTypeInfo(interpreter: ComptimeInterpreter, @"type": Type) TypeInfo { - return interpreter.type_info.items[@"type".info_idx]; -} - pub const TypeInfoFormatter = struct { interpreter: *const ComptimeInterpreter, ti: TypeInfo, @@ -291,15 +296,15 @@ pub const TypeInfoFormatter = struct { .@"struct" => |s| { try writer.writeAll("struct {"); for (s.fields.items) |field| { - try writer.print("{s}: {s}, ", .{ field.name, value.interpreter.formatTypeInfo(value.interpreter.typeToTypeInfo(field.@"type")) }); + try writer.print("{s}: {s}, ", .{ field.name, value.interpreter.formatTypeInfo(field.@"type".getTypeInfo()) }); } var iterator = s.scope.declarations.iterator(); while (iterator.next()) |di| { const decl = di.value_ptr.*; if (decl.isConstant(value.interpreter.handle.tree)) { - try writer.print("const {s}: {any} = TODO_PRINT_VALUES, ", .{ decl.name, value.interpreter.formatTypeInfo(value.interpreter.typeToTypeInfo(decl.@"type")) }); + try writer.print("const {s}: {any} = TODO_PRINT_VALUES, ", .{ decl.name, value.interpreter.formatTypeInfo(decl.@"type".getTypeInfo()) }); } else { - try writer.print("var {s}: {any}, ", .{ decl.name, value.interpreter.formatTypeInfo(value.interpreter.typeToTypeInfo(decl.@"type")) }); + try writer.print("var {s}: {any}, ", .{ decl.name, value.interpreter.formatTypeInfo(decl.@"type".getTypeInfo()) }); } } try writer.writeAll("}"); @@ -422,6 +427,70 @@ pub const InterpretResult = union(enum) { } }; +fn getDeclCount(tree: Ast, node_idx: Ast.Node.Index) usize { + var buffer: [2]Ast.Node.Index = undefined; + const members = ast.declMembers(tree, node_idx, &buffer); + + var count: usize = 0; + + for (members) |member| { + switch (tree.nodes.items(.tag)[member]) { + .global_var_decl, + .local_var_decl, + .aligned_var_decl, + .simple_var_decl, + => count += 1, + else => {}, + } + } + + return count; +} + +pub fn huntItDown( + interpreter: *ComptimeInterpreter, + scope: *InterpreterScope, + decl_name: []const u8, + options: InterpretOptions, +) InterpretError!Declaration { + const tree = interpreter.handle.tree; + const tags = tree.nodes.items(.tag); + + var psi = scope.parentScopeIterator(); + while (psi.next()) |pscope| { + const known_decl = pscope.declarations.get(decl_name); + if (pscope.scopeKind(tree) == .container and + known_decl == null and + pscope.declarations.count() != getDeclCount(tree, pscope.node_idx)) + { + std.log.info("Order-independent evaluating {s}...", .{decl_name}); + + var buffer: [2]Ast.Node.Index = undefined; + const members = ast.declMembers(tree, pscope.node_idx, &buffer); + + for (members) |member| { + switch (tags[member]) { + .global_var_decl, + .local_var_decl, + .aligned_var_decl, + .simple_var_decl, + => { + if (std.mem.eql(u8, analysis.getDeclName(tree, member).?, decl_name)) { + _ = try interpreter.interpret(member, pscope, options); + return pscope.declarations.get(decl_name).?; + } + }, + else => {}, + } + } + } + return known_decl orelse continue; + } + + std.log.err("Identifier not found: {s}", .{decl_name}); + return error.IdentifierNotFound; +} + // Might be useful in the future pub const InterpretOptions = struct {}; @@ -433,6 +502,8 @@ pub const InterpretError = std.mem.Allocator.Error || std.fmt.ParseIntError || s CriticalAstFailure, InvalidBuiltin, IdentifierNotFound, + MissingArguments, + ImportFailure, }; pub fn interpret( interpreter: *ComptimeInterpreter, @@ -449,7 +520,7 @@ pub fn interpret( const data = tree.nodes.items(.data); const main_tokens = tree.nodes.items(.main_token); - std.log.info("{any}", .{tags[node_idx]}); + std.log.info("{s}: {any}", .{ interpreter.handle.uri, tags[node_idx] }); switch (tags[node_idx]) { .container_decl, @@ -473,8 +544,9 @@ pub fn interpret( .scope = container_scope, }, }; + var cont_type = try interpreter.createType(node_idx, type_info); - if (node_idx == 0) interpreter.root_scope = container_scope; + if (node_idx == 0) interpreter.root_type = cont_type; var buffer: [2]Ast.Node.Index = undefined; const members = ast.declMembers(tree, node_idx, &buffer); @@ -515,9 +587,10 @@ pub fn interpret( } return InterpretResult{ .value = Value{ + .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }), - .value_data = .{ .@"type" = try interpreter.createType(node_idx, type_info) }, + .value_data = .{ .@"type" = cont_type }, } }; }, .global_var_decl, @@ -525,15 +598,27 @@ pub fn interpret( .aligned_var_decl, .simple_var_decl, => { + // TODO: Add 0 check + const name = analysis.getDeclName(tree, node_idx).?; + if (scope.?.declarations.contains(name)) + return InterpretResult{ .nothing = .{} }; + const decl = ast.varDecl(tree, node_idx).?; - var value = try (try interpreter.interpret(decl.ast.init_node, scope, options)).getValue(); + if (decl.ast.init_node == 0) + return InterpretResult{ .nothing = .{} }; + + // We should have a value when a var is defined + // var value = try (try interpreter.interpret(decl.ast.init_node, scope, options)).getValue(); + var value = (try interpreter.interpret(decl.ast.init_node, scope, options)).maybeGetValue() orelse return InterpretResult{ .nothing = .{} }; + + // Is this redundant? im too afraid to change it rn tbh var @"type" = if (decl.ast.type_node == 0) Value{ + .handle = interpreter.handle, .node_idx = std.math.maxInt(Ast.Node.Index), .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }), .value_data = .{ .@"type" = value.@"type" }, } else try (try interpreter.interpret(decl.ast.type_node, scope, options)).getValue(); - const name = analysis.getDeclName(tree, node_idx).?; try scope.?.declarations.put(interpreter.allocator, name, .{ .node_idx = node_idx, .name = name, @@ -598,16 +683,19 @@ pub fn interpret( var value = tree.getNodeSource(node_idx); if (std.mem.eql(u8, "bool", value)) return InterpretResult{ .value = Value{ + .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }), .value_data = .{ .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = .{} }) }, } }; if (std.mem.eql(u8, "true", value)) return InterpretResult{ .value = Value{ + .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = .{} }), .value_data = .{ .@"bool" = true }, } }; if (std.mem.eql(u8, "false", value)) return InterpretResult{ .value = Value{ + .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = .{} }), .value_data = .{ .@"bool" = false }, @@ -615,12 +703,14 @@ pub fn interpret( if (std.mem.eql(u8, "type", value)) { return InterpretResult{ .value = Value{ + .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }), .value_data = .{ .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }) }, } }; } else if (value.len >= 2 and (value[0] == 'u' or value[0] == 'i')) int: { return InterpretResult{ .value = Value{ + .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }), .value_data = .{ .@"type" = try interpreter.createType(node_idx, .{ @@ -635,14 +725,7 @@ pub fn interpret( // TODO: Floats // Logic to find identifiers in accessible scopes - - var psi = scope.?.parentScopeIterator(); - while (psi.next()) |pscope| { - return InterpretResult{ .value = (pscope.declarations.get(value) orelse continue).value }; - } - - std.log.err("Identifier not found: {s}", .{value}); - return error.IdentifierNotFound; + return InterpretResult{ .value = (try interpreter.huntItDown(scope.?, value, options)).value }; }, .field_access => { if (data[node_idx].rhs == 0) return error.CriticalAstFailure; @@ -651,8 +734,9 @@ pub fn interpret( var ir = try interpreter.interpret(data[node_idx].lhs, scope, options); var irv = try ir.getValue(); - var sub_scope = interpreter.typeToTypeInfo(irv.value_data.@"type").getScopeOfType() orelse return error.IdentifierNotFound; - var scope_sub_decl = sub_scope.declarations.get(rhs_str) orelse return error.IdentifierNotFound; + var sub_scope = irv.value_data.@"type".getTypeInfo().getScopeOfType() orelse return error.IdentifierNotFound; + // var scope_sub_decl = sub_scope.declarations.get(rhs_str) orelse return error.IdentifierNotFound; + var scope_sub_decl = try irv.value_data.@"type".handle.interpreter.?.huntItDown(sub_scope, rhs_str, options); return InterpretResult{ .value = scope_sub_decl.value, @@ -691,6 +775,7 @@ pub fn interpret( var a = try interpreter.interpret(data[node_idx].lhs, scope, options); var b = try interpreter.interpret(data[node_idx].rhs, scope, options); return InterpretResult{ .value = Value{ + .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = .{} }), .value_data = .{ .@"bool" = (try a.getValue()).eql(try b.getValue()) }, @@ -702,6 +787,7 @@ pub fn interpret( const nl = std.zig.parseNumberLiteral(s); // if (nl == .failure) ; return InterpretResult{ .value = Value{ + .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"comptime_int" = .{} }), .value_data = switch (nl) { @@ -766,7 +852,6 @@ pub fn interpret( => { var buffer: [2]Ast.Node.Index = undefined; const params = ast.builtinCallParams(tree, node_idx, &buffer).?; - _ = params; const call_name = tree.tokenSlice(main_tokens[node_idx]); if (std.mem.eql(u8, call_name, "@compileLog")) { @@ -777,6 +862,28 @@ pub fn interpret( return InterpretResult{ .@"return" = .{} }; } + if (std.mem.eql(u8, call_name, "@import")) { + if (params.len == 0) return error.InvalidBuiltin; + const import_param = params[0]; + if (tags[import_param] != .string_literal) return error.InvalidBuiltin; + + const import_str = tree.tokenSlice(main_tokens[import_param]); + + std.log.info("Resolving {s} from {s}", .{ import_str[1 .. import_str.len - 1], interpreter.handle.uri }); + var import_uri = (try interpreter.document_store.uriFromImportStr(interpreter.allocator, interpreter.handle.*, import_str[1 .. import_str.len - 1])) orelse return error.ImportFailure; + defer interpreter.allocator.free(import_uri); + + var handle = interpreter.document_store.getOrLoadHandle(import_uri) orelse return error.ImportFailure; + try interpreter.document_store.ensureInterpreterExists(handle.uri); + + return InterpretResult{ .value = Value{ + .handle = interpreter.handle, + .node_idx = node_idx, + .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }), + .value_data = .{ .@"type" = handle.interpreter.?.root_type.? }, + } }; + } + std.log.info("Builtin not implemented: {s}", .{call_name}); @panic("Builtin not implemented"); // return error.InvalidBuiltin; @@ -784,6 +891,7 @@ pub fn interpret( .string_literal => { const value = tree.getNodeSource(node_idx)[1 .. tree.getNodeSource(node_idx).len - 1]; var val = Value{ + .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .pointer = .{ @@ -854,6 +962,7 @@ pub fn interpret( // fnd.return_type = value.value_data.@"type"; var value = Value{ + .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, type_info), .value_data = .{ .@"fn" = .{} }, @@ -890,10 +999,10 @@ pub fn interpret( std.log.err("AEWEWEWE: {s}", .{tree.getNodeSource(call_full.ast.fn_expr)}); - const func_id_result = try interpreter.interpret(call_full.ast.fn_expr, interpreter.root_scope, .{}); + const func_id_result = try interpreter.interpret(call_full.ast.fn_expr, interpreter.root_type.?.getTypeInfo().getScopeOfType().?, .{}); const func_id_val = try func_id_result.getValue(); - const call_res = try interpreter.call(interpreter.root_scope, func_id_val.node_idx, args.items, options); + const call_res = try interpreter.call(interpreter.root_type.?.getTypeInfo().getScopeOfType().?, func_id_val.node_idx, args.items, options); // defer call_res.scope.deinit(); // TODO: Figure out call result memory model; this is actually fine because newScope // makes this a child of the decl scope which is freed on refresh... in theory @@ -909,6 +1018,7 @@ pub fn interpret( if (value.value_data != .@"bool") return error.InvalidOperation; return InterpretResult{ .value = .{ + .handle = interpreter.handle, .node_idx = node_idx, .@"type" = value.@"type", .value_data = .{ .@"bool" = !value.value_data.@"bool" }, @@ -937,11 +1047,9 @@ pub fn call( arguments: []const Value, options: InterpretOptions, ) InterpretError!CallResult { - // TODO: Eval, check parameter types - - // TODO: Arguments _ = options; - // _ = arguments; + + // TODO: type check args const tree = interpreter.handle.tree; const tags = tree.nodes.items(.tag); @@ -956,6 +1064,7 @@ pub fn call( var arg_it = proto.iterate(&tree); var arg_index: usize = 0; while (ast.nextFnParam(&arg_it)) |param| { + if (arg_index >= arguments.len) return error.MissingArguments; if (param.name_token) |nt| { const decl = Declaration{ .node_idx = param.type_expr, diff --git a/src/DocumentStore.zig b/src/DocumentStore.zig index a10c7f5..ccd01f1 100644 --- a/src/DocumentStore.zig +++ b/src/DocumentStore.zig @@ -192,6 +192,7 @@ pub fn refreshDocument(self: *DocumentStore, uri: Uri, new_text: [:0]const u8) ! const handle = self.handles.get(uri) orelse unreachable; + // TODO: Handle interpreter cross reference if (handle.interpreter) |int| { int.deinit(); handle.interpreter = null; @@ -937,7 +938,7 @@ pub fn enumCompletionItems(self: DocumentStore, arena: std.mem.Allocator, handle } pub fn ensureInterpreterExists(self: *DocumentStore, uri: Uri) !void { - var handle = self.handles.get(uri) orelse unreachable; + var handle = self.handles.get(uri).?; if (handle.interpreter == null) { var int = try self.allocator.create(ComptimeInterpreter); int.* = ComptimeInterpreter{ @@ -945,8 +946,7 @@ pub fn ensureInterpreterExists(self: *DocumentStore, uri: Uri) !void { .document_store = self, .handle = handle, }; - _ = try int.interpret(0, null, .{}); - handle.interpreter = int; + _ = try int.interpret(0, null, .{}); } } diff --git a/src/Server.zig b/src/Server.zig index 68dab07..21612a6 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -486,7 +486,7 @@ fn typeToCompletion( ), .primitive, .array_index => {}, .@"comptime" => |co| { - const ti = co.interpreter.typeToTypeInfo(co.type); + const ti = co.type.getTypeInfo(); switch (ti) { .@"struct" => |st| { var it = st.scope.declarations.iterator(); @@ -821,7 +821,7 @@ fn hoverSymbol(server: *Server, decl_handle: analysis.DeclWithHandle) error{OutO const resolved_type_str = if (resolved_type) |rt| if (rt.type.is_type_val) switch (rt.type.data) { - .@"comptime" => |*co| try std.fmt.allocPrint(server.arena.allocator(), "{ }", .{co.interpreter.formatTypeInfo(co.interpreter.typeToTypeInfo(co.type))}), + .@"comptime" => |*co| try std.fmt.allocPrint(server.arena.allocator(), "{ }", .{co.interpreter.formatTypeInfo(co.type.getTypeInfo())}), else => "type", } else switch (rt.type.data) { // TODO: Investigate random weird numbers like 897 that cause index of bounds .pointer, diff --git a/src/analysis.zig b/src/analysis.zig index aa490db..acd19c0 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -756,31 +756,36 @@ pub fn resolveTypeOfNodeInternal(store: *DocumentStore, arena: *std.heap.ArenaAl // var fn_decl_scope = top_scope.getParentScopeFromNode(node); + log.info("Invoking interpreter!", .{}); + store.ensureInterpreterExists(handle.uri) catch |err| { - std.log.err("Interpreter error: {s}", .{@errorName(err)}); + log.err("Interpreter error: {s}", .{@errorName(err)}); + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace.*); + } return null; }; var interpreter = handle.interpreter.?; // TODO: Start from current/nearest-current scope - const result = interpreter.interpret(node, interpreter.root_scope, .{}) catch |err| { - std.log.err("Interpreter error: {s}", .{@errorName(err)}); + const result = interpreter.interpret(node, interpreter.root_type.?.getTypeInfo().getScopeOfType().?, .{}) catch |err| { + log.err("Interpreter error: {s}", .{@errorName(err)}); if (@errorReturnTrace()) |trace| { std.debug.dumpStackTrace(trace.*); } return null; }; const val = result.getValue() catch |err| { - std.log.err("Interpreter error: {s}", .{@errorName(err)}); + log.err("Interpreter error: {s}", .{@errorName(err)}); if (@errorReturnTrace()) |trace| { std.debug.dumpStackTrace(trace.*); } return null; }; - const ti = interpreter.typeToTypeInfo(val.@"type"); + const ti = val.@"type".getTypeInfo(); if (ti != .@"type") { - std.log.err("Not a type: { }", .{interpreter.formatTypeInfo(ti)}); + log.err("Not a type: { }", .{interpreter.formatTypeInfo(ti)}); return null; } diff --git a/tests/language_features/comptime_interpreter.zig b/tests/language_features/comptime_interpreter.zig index 3d16ee3..79e6743 100644 --- a/tests/language_features/comptime_interpreter.zig +++ b/tests/language_features/comptime_interpreter.zig @@ -41,8 +41,8 @@ test "ComptimeInterpreter - basic test" { }, .{}); defer call_with_true.scope.deinit(); - try std.testing.expectFmt("u69", "{any}", .{interpreter.formatTypeInfo(interpreter.typeToTypeInfo(call_with_false.result.value.value_data.@"type"))}); - try std.testing.expectFmt("u8", "{any}", .{interpreter.formatTypeInfo(interpreter.typeToTypeInfo(call_with_true.result.value.value_data.@"type"))}); + try std.testing.expectFmt("u69", "{any}", .{interpreter.formatTypeInfo(call_with_false.result.value.value_data.@"type".getTypeInfo())}); + try std.testing.expectFmt("u8", "{any}", .{interpreter.formatTypeInfo(call_with_true.result.value.value_data.@"type".getTypeInfo())}); } test "ComptimeInterpreter - struct" { diff --git a/tests/tests.zig b/tests/tests.zig index 93efef6..e1480d7 100644 --- a/tests/tests.zig +++ b/tests/tests.zig @@ -1,21 +1,21 @@ comptime { _ = @import("helper.zig"); - // _ = @import("utility/offsets.zig"); - // _ = @import("utility/position_context.zig"); - // _ = @import("utility/uri.zig"); + _ = @import("utility/offsets.zig"); + _ = @import("utility/position_context.zig"); + _ = @import("utility/uri.zig"); - // // TODO Lifecycle Messages + // TODO Lifecycle Messages - // // TODO Document Synchronization + // TODO Document Synchronization - // // LSP features - // _ = @import("lsp_features/semantic_tokens.zig"); - // _ = @import("lsp_features/inlay_hints.zig"); - // _ = @import("lsp_features/references.zig"); - // _ = @import("lsp_features/completion.zig"); + // LSP features + _ = @import("lsp_features/semantic_tokens.zig"); + _ = @import("lsp_features/inlay_hints.zig"); + _ = @import("lsp_features/references.zig"); + _ = @import("lsp_features/completion.zig"); // Language features - // _ = @import("language_features/cimport.zig"); - _ = @import("language_features/comptime_interpreter.zig"); + _ = @import("language_features/cimport.zig"); + // _ = @import("language_features/comptime_interpreter.zig"); } From d2e166bb0bbc2930d2517945c0398cfcae6f2228 Mon Sep 17 00:00:00 2001 From: Auguste Rame <19855629+SuperAuguste@users.noreply.github.com> Date: Sun, 30 Oct 2022 04:07:49 -0400 Subject: [PATCH 06/12] Some builtins, rudimentary hacky diagnostics; need to nerf global evaluation --- src/ComptimeInterpreter.zig | 132 +++++++++++++++++++++++++++++++----- src/Server.zig | 29 ++++++++ 2 files changed, 143 insertions(+), 18 deletions(-) diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig index 64a4e64..5f26c51 100644 --- a/src/ComptimeInterpreter.zig +++ b/src/ComptimeInterpreter.zig @@ -18,10 +18,25 @@ document_store: *DocumentStore, handle: *const DocumentStore.Handle, root_type: ?Type = null, +/// Interpreter diagnostic errors +errors: std.AutoArrayHashMapUnmanaged(Ast.Node.Index, InterpreterError) = .{}, + // TODO: Deduplicate typeinfo across different interpreters type_info: std.ArrayListUnmanaged(TypeInfo) = .{}, type_info_map: std.HashMapUnmanaged(TypeInfo, usize, TypeInfo.Context, std.hash_map.default_max_load_percentage) = .{}, +pub const InterpreterError = struct { + code: []const u8, + message: []const u8, +}; + +pub fn recordError(interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index, code: []const u8, message: []const u8) !void { + try interpreter.errors.put(interpreter.allocator, node_idx, .{ + .code = code, + .message = message, + }); +} + pub fn deinit(interpreter: *ComptimeInterpreter) void { if (interpreter.root_type) |rt| rt.getTypeInfo().getScopeOfType().?.deinit(); for (interpreter.type_info.items) |*ti| ti.deinit(interpreter.allocator); @@ -48,7 +63,7 @@ pub const TypeInfo = union(enum) { pub const Struct = struct { /// Declarations contained within scope: *InterpreterScope, - fields: std.ArrayListUnmanaged(FieldDefinition) = .{}, + fields: std.StringHashMapUnmanaged(FieldDefinition) = .{}, }; pub const Int = struct { @@ -116,7 +131,9 @@ pub const TypeInfo = union(enum) { context.hasher.update(&[_]u8{@enumToInt(ti)}); return switch (ti) { .@"struct" => |s| { - context.hasher.update(std.mem.sliceAsBytes(s.fields.items)); + _ = s; + // TODO: Fix + // context.hasher.update(std.mem.sliceAsBytes(s.fields.items)); // TODO: Fix // context.hasher.update(std.mem.sliceAsBytes(s.declarations.items)); }, @@ -167,6 +184,11 @@ pub const Type = struct { pub fn getTypeInfo(@"type": Type) TypeInfo { return @"type".handle.interpreter.?.type_info.items[@"type".info_idx]; } + + /// Be careful with this; typeinfo resizes reassign pointers! + pub fn getTypeInfoMutable(@"type": Type) *TypeInfo { + return &@"type".handle.interpreter.?.type_info.items[@"type".info_idx]; + } }; pub const Value = struct { @@ -191,8 +213,8 @@ pub const ValueData = union(enum) { // }, // one_ptr: *anyopaque, - /// TODO: Optimize this with an ArrayList that uses anyopaque slice - slice_ptr: std.ArrayListUnmanaged(ValueData), + /// Special case slice; this is extremely common at comptime so it makes sense + string: []const u8, @"comptime_int": std.math.big.int.Managed, unsigned_int: u64, @@ -295,9 +317,11 @@ pub const TypeInfoFormatter = struct { .@"bool" => try writer.writeAll("bool"), .@"struct" => |s| { try writer.writeAll("struct {"); - for (s.fields.items) |field| { - try writer.print("{s}: {s}, ", .{ field.name, value.interpreter.formatTypeInfo(field.@"type".getTypeInfo()) }); + var field_iterator = s.fields.iterator(); + while (field_iterator.next()) |di| { + try writer.print("{s}: {s}, ", .{ di.key_ptr.*, value.interpreter.formatTypeInfo(di.value_ptr.*.@"type".getTypeInfo()) }); } + var iterator = s.scope.declarations.iterator(); while (iterator.next()) |di| { const decl = di.value_ptr.*; @@ -560,17 +584,26 @@ pub fn interpret( }; if (maybe_container_field) |field_info| { - var init_type = try interpreter.interpret(field_info.ast.type_expr, container_scope, .{}); + var init_type_value = try (try interpreter.interpret(field_info.ast.type_expr, container_scope, .{})).getValue(); var default_value = if (field_info.ast.value_expr == 0) null else try (try interpreter.interpret(field_info.ast.value_expr, container_scope, .{})).getValue(); + if (init_type_value.@"type".getTypeInfo() != .@"type") { + try interpreter.recordError( + field_info.ast.type_expr, + "invalid_field_type_value", + "Field type should be a type!", + ); + continue; + } + const name = tree.tokenSlice(field_info.ast.name_token); const field = FieldDefinition{ .node_idx = member, .name = name, - .@"type" = (try init_type.getValue()).value_data.@"type", + .@"type" = init_type_value.value_data.@"type", .default_value = default_value, // TODO: Default values // .@"type" = T: { @@ -580,7 +613,9 @@ pub fn interpret( // .value = null, }; - try type_info.@"struct".fields.append(interpreter.allocator, field); + // std.log.info("FIELD: {s}", .{name}); + + try cont_type.getTypeInfoMutable().@"struct".fields.put(interpreter.allocator, name, field); } else { _ = try interpreter.interpret(member, container_scope, options); } @@ -701,6 +736,22 @@ pub fn interpret( .value_data = .{ .@"bool" = false }, } }; + if (value.len == 5 and (value[0] == 'u' or value[0] == 'i') and std.mem.eql(u8, "size", value[1..])) return InterpretResult{ + .value = Value{ + .handle = interpreter.handle, + .node_idx = node_idx, + .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }), + .value_data = .{ + .@"type" = try interpreter.createType(node_idx, .{ + .int = .{ + .signedness = if (value[0] == 'u') .unsigned else .signed, + .bits = 64, // TODO: Platform specific + }, + }), + }, + }, + }; + if (std.mem.eql(u8, "type", value)) { return InterpretResult{ .value = Value{ .handle = interpreter.handle, @@ -725,7 +776,14 @@ pub fn interpret( // TODO: Floats // Logic to find identifiers in accessible scopes - return InterpretResult{ .value = (try interpreter.huntItDown(scope.?, value, options)).value }; + return InterpretResult{ .value = (interpreter.huntItDown(scope.?, value, options) catch |err| { + if (err == error.IdentifierNotFound) try interpreter.recordError( + node_idx, + "identifier_not_found", + try std.fmt.allocPrint(interpreter.allocator, "Couldn't find identifier \"{s}\"", .{value}), + ); + return err; + }).value }; }, .field_access => { if (data[node_idx].rhs == 0) return error.CriticalAstFailure; @@ -736,7 +794,14 @@ pub fn interpret( var sub_scope = irv.value_data.@"type".getTypeInfo().getScopeOfType() orelse return error.IdentifierNotFound; // var scope_sub_decl = sub_scope.declarations.get(rhs_str) orelse return error.IdentifierNotFound; - var scope_sub_decl = try irv.value_data.@"type".handle.interpreter.?.huntItDown(sub_scope, rhs_str, options); + var scope_sub_decl = irv.value_data.@"type".handle.interpreter.?.huntItDown(sub_scope, rhs_str, options) catch |err| { + if (err == error.IdentifierNotFound) try interpreter.recordError( + node_idx, + "identifier_not_found", + try std.fmt.allocPrint(interpreter.allocator, "Couldn't find identifier \"{s}\"", .{rhs_str}), + ); + return err; + }; return InterpretResult{ .value = scope_sub_decl.value, @@ -884,9 +949,41 @@ pub fn interpret( } }; } + if (std.mem.eql(u8, call_name, "@TypeOf")) { + if (params.len != 1) return error.InvalidBuiltin; + + const value = try (try interpreter.interpret(params[0], scope, options)).getValue(); + return InterpretResult{ .value = Value{ + .handle = interpreter.handle, + .node_idx = node_idx, + .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }), + .value_data = .{ .@"type" = value.@"type" }, + } }; + } + + if (std.mem.eql(u8, call_name, "@hasDecl")) { + if (params.len != 2) return error.InvalidBuiltin; + + const value = try (try interpreter.interpret(params[0], scope, options)).getValue(); + const field_name = try (try interpreter.interpret(params[1], scope, options)).getValue(); + + if (value.@"type".getTypeInfo() != .@"type") return error.InvalidBuiltin; + if (field_name.@"type".getTypeInfo() != .@"pointer") return error.InvalidBuiltin; // Check if it's a []const u8 + + const ti = value.value_data.@"type".getTypeInfo(); + if (ti.getScopeOfType() == null) return error.InvalidBuiltin; + + return InterpretResult{ .value = Value{ + .handle = interpreter.handle, + .node_idx = node_idx, + .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = .{} }), + .value_data = .{ .@"bool" = ti.getScopeOfType().?.declarations.contains(field_name.value_data.string) }, + } }; + } + std.log.info("Builtin not implemented: {s}", .{call_name}); - @panic("Builtin not implemented"); - // return error.InvalidBuiltin; + // @panic("Builtin not implemented"); + return error.InvalidBuiltin; }, .string_literal => { const value = tree.getNodeSource(node_idx)[1 .. tree.getNodeSource(node_idx).len - 1]; @@ -907,13 +1004,12 @@ pub fn interpret( .sentinel = .{ .unsigned_int = 0 }, }, }), - .value_data = .{ .slice_ptr = .{} }, + .value_data = .{ .string = value }, }; - for (value) |z| { - try val.value_data.slice_ptr.append(interpreter.allocator, .{ .unsigned_int = z }); - } - try val.value_data.slice_ptr.append(interpreter.allocator, .{ .unsigned_int = 0 }); + // TODO: Add type casting, sentinel + // TODO: Should this be a `*const [len:0]u8`? + // try val.value_data.slice_ptr.append(interpreter.allocator, .{ .unsigned_int = 0 }); return InterpretResult{ .value = val }; }, diff --git a/src/Server.zig b/src/Server.zig index 21612a6..9837ab3 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -322,6 +322,23 @@ fn publishDiagnostics(server: *Server, writer: anytype, handle: DocumentStore.Ha } } + if (handle.interpreter) |int| { + try diagnostics.ensureUnusedCapacity(allocator, int.errors.count()); + + var err_it = int.errors.iterator(); + + while (err_it.next()) |err| { + try diagnostics.append(allocator, .{ + .range = offsets.nodeToRange(tree, err.key_ptr.*, server.offset_encoding), + .severity = .Error, + .code = err.value_ptr.code, + .source = "zls", + .message = err.value_ptr.message, + }); + } + } + // try diagnostics.appendSlice(allocator, handle.interpreter.?.diagnostics.items); + try send(writer, server.arena.allocator(), types.Notification{ .method = "textDocument/publishDiagnostics", .params = .{ @@ -489,6 +506,16 @@ fn typeToCompletion( const ti = co.type.getTypeInfo(); switch (ti) { .@"struct" => |st| { + var fit = st.fields.iterator(); + while (fit.next()) |entry| { + try list.append(allocator, .{ + .label = entry.key_ptr.*, + .kind = .Field, + .insertText = entry.key_ptr.*, + .insertTextFormat = .PlainText, + }); + } + var it = st.scope.declarations.iterator(); while (it.next()) |entry| { try list.append(allocator, .{ @@ -2045,6 +2072,8 @@ fn hoverHandler(server: *Server, writer: anytype, id: types.RequestId, req: requ }; const hover = maybe_hover orelse return try respondGeneric(writer, id, null_result_response); + // TODO: Figure out a better solution for comptime interpreter diags + try server.publishDiagnostics(writer, handle.*); try send(writer, server.arena.allocator(), types.Response{ .id = id, From e6b691e44721e6c119ad4787e328f1b0ae803efd Mon Sep 17 00:00:00 2001 From: Auguste Rame <19855629+SuperAuguste@users.noreply.github.com> Date: Mon, 31 Oct 2022 01:51:51 -0400 Subject: [PATCH 07/12] More builtins, use stage2 because we can --- build.zig | 12 +-- src/ComptimeInterpreter.zig | 174 ++++++++++++++++++++++++++---------- 2 files changed, 131 insertions(+), 55 deletions(-) diff --git a/build.zig b/build.zig index 7b09246..24e452f 100644 --- a/build.zig +++ b/build.zig @@ -7,19 +7,19 @@ const zls_version = std.builtin.Version{ .major = 0, .minor = 11, .patch = 0 }; pub fn build(b: *std.build.Builder) !void { const current_zig = builtin.zig_version; const min_zig = std.SemanticVersion.parse("0.10.0-dev.4458+b120c819d") catch return; // builtins changed to @min / @max - if (current_zig.order(min_zig).compare(.lt)) @panic(b.fmt("Your Zig version v{} does not meet the minimum build requirement of v{}", .{current_zig, min_zig})); + if (current_zig.order(min_zig).compare(.lt)) @panic(b.fmt("Your Zig version v{} does not meet the minimum build requirement of v{}", .{ current_zig, min_zig })); const target = b.standardTargetOptions(.{}); const mode = b.standardReleaseOptions(); const exe = b.addExecutable("zls", "src/main.zig"); - exe.use_stage1 = true; + // exe.use_stage1 = true; const exe_options = b.addOptions(); exe.addOptions("build_options", exe_options); const enable_tracy = b.option(bool, "enable_tracy", "Whether tracy should be enabled.") orelse false; const coverage = b.option(bool, "generate_coverage", "Generate coverage data with kcov") orelse false; - const coverage_output_dir = b.option([]const u8, "coverage_output_dir", "Output directory for coverage data") orelse b.pathJoin(&.{b.install_prefix, "kcov"}); + const coverage_output_dir = b.option([]const u8, "coverage_output_dir", "Output directory for coverage data") orelse b.pathJoin(&.{ b.install_prefix, "kcov" }); exe_options.addOption( shared.ZigVersion, @@ -122,10 +122,10 @@ pub fn build(b: *std.build.Builder) !void { var tests = b.addTest("tests/tests.zig"); - if(coverage) { - const src_dir = b.pathJoin(&.{b.build_root, "src"}); + if (coverage) { + const src_dir = b.pathJoin(&.{ b.build_root, "src" }); const include_pattern = b.fmt("--include-pattern={s}", .{src_dir}); - + tests.setExecCmd(&[_]?[]const u8{ "kcov", include_pattern, diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig index 5f26c51..7865d6e 100644 --- a/src/ComptimeInterpreter.zig +++ b/src/ComptimeInterpreter.zig @@ -30,7 +30,7 @@ pub const InterpreterError = struct { message: []const u8, }; -pub fn recordError(interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index, code: []const u8, message: []const u8) !void { +pub fn recordError(interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index, code: []const u8, message: []const u8) error{OutOfMemory}!void { try interpreter.errors.put(interpreter.allocator, node_idx, .{ .code = code, .message = message, @@ -212,13 +212,19 @@ pub const ValueData = union(enum) { // @"struct": struct { // }, - // one_ptr: *anyopaque, + /// This is what a pointer is; we don't need to map + /// this to anything because @ptrToInt is comptime-illegal + /// Pointer equality scares me though :( (but that's for later) + one_ptr: *ValueData, /// Special case slice; this is extremely common at comptime so it makes sense - string: []const u8, + slice_of_const_u8: []const u8, - @"comptime_int": std.math.big.int.Managed, unsigned_int: u64, signed_int: i64, + /// If the int does not fit into the previous respective slots, + /// use a bit int to store it + big_int: std.math.big.int.Managed, + float: f64, @"fn", @@ -231,7 +237,7 @@ pub const ValueData = union(enum) { // std.meta.activeTag(u: anytype) switch (data) { .@"bool" => return data.@"bool" == other_data.@"bool", - .@"comptime_int" => return data.@"comptime_int".eq(other_data.@"comptime_int"), + .big_int => return data.big_int.eq(other_data.big_int), .unsigned_int => return data.unsigned_int == other_data.unsigned_int, .signed_int => return data.signed_int == other_data.signed_int, .float => return data.float == other_data.float, @@ -239,6 +245,16 @@ pub const ValueData = union(enum) { else => return false, } } + + /// Get the bit count required to store a certain integer + pub fn bitCount(data: ValueData) ?u16 { + return switch (data) { + // TODO: Implement for signed ints + .unsigned_int => |i| std.math.log2_int(@TypeOf(i), i), + .big_int => |bi| @intCast(u16, bi.bitCountAbs()), + else => null, + }; + } }; pub const FieldDefinition = struct { @@ -515,6 +531,49 @@ pub fn huntItDown( return error.IdentifierNotFound; } +pub fn cast( + interpreter: *ComptimeInterpreter, + node_idx: Ast.Node.Index, + dest_type: Type, + value: Value, +) error{ OutOfMemory, InvalidCast }!Value { + const value_data = value.value_data; + + const to_type_info = dest_type.getTypeInfo(); + const from_type_info = value.@"type".getTypeInfo(); + + const err = switch (from_type_info) { + .@"comptime_int" => switch (to_type_info) { + .int => { + if (value_data.bitCount().? > to_type_info.int.bits) { + switch (value_data) { + inline .unsigned_int, .signed_int, .big_int => |bi| { + try interpreter.recordError(node_idx, "invalid_cast", try std.fmt.allocPrint(interpreter.allocator, "integer value {d} cannot be coerced to type '{s}'", .{ bi, interpreter.formatTypeInfo(to_type_info) })); + }, + else => unreachable, + } + return error.InvalidCast; + } + }, + else => error.InvalidCast, + }, + else => error.InvalidCast, + }; + + err catch |e| { + try interpreter.recordError(node_idx, "invalid_cast", "invalid cast"); + return e; + }; + + return Value{ + .handle = interpreter.handle, + + .node_idx = node_idx, + .@"type" = dest_type, + .value_data = value.value_data, + }; +} + // Might be useful in the future pub const InterpretOptions = struct {}; @@ -528,6 +587,7 @@ pub const InterpretError = std.mem.Allocator.Error || std.fmt.ParseIntError || s IdentifierNotFound, MissingArguments, ImportFailure, + InvalidCast, }; pub fn interpret( interpreter: *ComptimeInterpreter, @@ -624,7 +684,7 @@ pub fn interpret( return InterpretResult{ .value = Value{ .handle = interpreter.handle, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }), + .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), .value_data = .{ .@"type" = cont_type }, } }; }, @@ -636,21 +696,21 @@ pub fn interpret( // TODO: Add 0 check const name = analysis.getDeclName(tree, node_idx).?; if (scope.?.declarations.contains(name)) - return InterpretResult{ .nothing = .{} }; + return InterpretResult{ .nothing = {} }; const decl = ast.varDecl(tree, node_idx).?; if (decl.ast.init_node == 0) - return InterpretResult{ .nothing = .{} }; + return InterpretResult{ .nothing = {} }; // We should have a value when a var is defined // var value = try (try interpreter.interpret(decl.ast.init_node, scope, options)).getValue(); - var value = (try interpreter.interpret(decl.ast.init_node, scope, options)).maybeGetValue() orelse return InterpretResult{ .nothing = .{} }; + var value = (try interpreter.interpret(decl.ast.init_node, scope, options)).maybeGetValue() orelse return InterpretResult{ .nothing = {} }; // Is this redundant? im too afraid to change it rn tbh var @"type" = if (decl.ast.type_node == 0) Value{ .handle = interpreter.handle, .node_idx = std.math.maxInt(Ast.Node.Index), - .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }), + .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), .value_data = .{ .@"type" = value.@"type" }, } else try (try interpreter.interpret(decl.ast.type_node, scope, options)).getValue(); @@ -661,7 +721,7 @@ pub fn interpret( .@"value" = value, }); - return InterpretResult{ .nothing = .{} }; + return InterpretResult{ .nothing = {} }; }, .block, .block_semicolon, @@ -687,11 +747,11 @@ pub fn interpret( if (lllll) |l| { if (maybe_block_label_string) |ls| { if (std.mem.eql(u8, l, ls)) { - return InterpretResult{ .nothing = .{} }; + return InterpretResult{ .nothing = {} }; } else return ret; } else return ret; } else { - return InterpretResult{ .nothing = .{} }; + return InterpretResult{ .nothing = {} }; } }, .break_with_value => |bwv| { @@ -712,7 +772,7 @@ pub fn interpret( } } - return InterpretResult{ .nothing = .{} }; + return InterpretResult{ .nothing = {} }; }, .identifier => { var value = tree.getNodeSource(node_idx); @@ -720,19 +780,19 @@ pub fn interpret( if (std.mem.eql(u8, "bool", value)) return InterpretResult{ .value = Value{ .handle = interpreter.handle, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }), - .value_data = .{ .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = .{} }) }, + .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), + .value_data = .{ .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }) }, } }; if (std.mem.eql(u8, "true", value)) return InterpretResult{ .value = Value{ .handle = interpreter.handle, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = .{} }), + .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }), .value_data = .{ .@"bool" = true }, } }; if (std.mem.eql(u8, "false", value)) return InterpretResult{ .value = Value{ .handle = interpreter.handle, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = .{} }), + .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }), .value_data = .{ .@"bool" = false }, } }; @@ -740,7 +800,7 @@ pub fn interpret( .value = Value{ .handle = interpreter.handle, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }), + .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), .value_data = .{ .@"type" = try interpreter.createType(node_idx, .{ .int = .{ @@ -756,14 +816,14 @@ pub fn interpret( return InterpretResult{ .value = Value{ .handle = interpreter.handle, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }), - .value_data = .{ .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }) }, + .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), + .value_data = .{ .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }) }, } }; } else if (value.len >= 2 and (value[0] == 'u' or value[0] == 'i')) int: { return InterpretResult{ .value = Value{ .handle = interpreter.handle, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }), + .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), .value_data = .{ .@"type" = try interpreter.createType(node_idx, .{ .int = .{ .signedness = if (value[0] == 'u') .unsigned else .signed, @@ -833,7 +893,7 @@ pub fn interpret( } else { if (iff.ast.else_expr != 0) { return try interpreter.interpret(iff.ast.else_expr, scope, options); - } else return InterpretResult{ .nothing = .{} }; + } else return InterpretResult{ .nothing = {} }; } }, .equal_equal => { @@ -842,7 +902,7 @@ pub fn interpret( return InterpretResult{ .value = Value{ .handle = interpreter.handle, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = .{} }), + .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }), .value_data = .{ .@"bool" = (try a.getValue()).eql(try b.getValue()) }, } }; // a.getValue().eql(b.getValue()) @@ -850,22 +910,24 @@ pub fn interpret( .number_literal => { const s = tree.getNodeSource(node_idx); const nl = std.zig.parseNumberLiteral(s); - // if (nl == .failure) ; - return InterpretResult{ .value = Value{ - .handle = interpreter.handle, - .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"comptime_int" = .{} }), - .value_data = switch (nl) { - .float => .{ .float = try std.fmt.parseFloat(f64, s) }, - .int => if (s[0] == '-') ValueData{ .signed_int = try std.fmt.parseInt(i64, s, 0) } else ValueData{ .unsigned_int = try std.fmt.parseInt(u64, s, 0) }, - .big_int => |bii| ppp: { - var bi = try std.math.big.int.Managed.init(interpreter.allocator); - try bi.setString(@enumToInt(bii), s[if (bii != .decimal) @as(usize, 2) else @as(usize, 0)..]); - break :ppp .{ .@"comptime_int" = bi }; + + return InterpretResult{ + .value = Value{ + .handle = interpreter.handle, + .node_idx = node_idx, + .@"type" = try interpreter.createType(node_idx, .{ .@"comptime_int" = {} }), + .value_data = switch (nl) { + .float => .{ .float = try std.fmt.parseFloat(f64, s) }, + .int => if (s[0] == '-') ValueData{ .signed_int = try std.fmt.parseInt(i64, s, 0) } else ValueData{ .unsigned_int = try std.fmt.parseInt(u64, s, 0) }, + .big_int => |bii| ppp: { + var bi = try std.math.big.int.Managed.init(interpreter.allocator); + try bi.setString(@enumToInt(bii), s[if (bii != .decimal) @as(usize, 2) else @as(usize, 0)..]); + break :ppp .{ .big_int = bi }; + }, + .failure => return error.CriticalAstFailure, }, - .failure => return error.CriticalAstFailure, }, - } }; + }; }, .assign, .assign_bit_and, @@ -893,7 +955,7 @@ pub fn interpret( decl.value_ptr.value = try (try interpreter.interpret(data[node_idx].rhs, scope.?, options)).getValue(); } - return InterpretResult{ .nothing = .{} }; + return InterpretResult{ .nothing = {} }; }, // .@"switch", // .switch_comma, @@ -920,11 +982,11 @@ pub fn interpret( const call_name = tree.tokenSlice(main_tokens[node_idx]); if (std.mem.eql(u8, call_name, "@compileLog")) { - return InterpretResult{ .nothing = .{} }; + return InterpretResult{ .nothing = {} }; } if (std.mem.eql(u8, call_name, "@compileError")) { - return InterpretResult{ .@"return" = .{} }; + return InterpretResult{ .@"return" = {} }; } if (std.mem.eql(u8, call_name, "@import")) { @@ -944,7 +1006,7 @@ pub fn interpret( return InterpretResult{ .value = Value{ .handle = interpreter.handle, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }), + .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), .value_data = .{ .@"type" = handle.interpreter.?.root_type.? }, } }; } @@ -956,7 +1018,7 @@ pub fn interpret( return InterpretResult{ .value = Value{ .handle = interpreter.handle, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }), + .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), .value_data = .{ .@"type" = value.@"type" }, } }; } @@ -976,11 +1038,22 @@ pub fn interpret( return InterpretResult{ .value = Value{ .handle = interpreter.handle, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = .{} }), - .value_data = .{ .@"bool" = ti.getScopeOfType().?.declarations.contains(field_name.value_data.string) }, + .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }), + .value_data = .{ .@"bool" = ti.getScopeOfType().?.declarations.contains(field_name.value_data.slice_of_const_u8) }, } }; } + if (std.mem.eql(u8, call_name, "@as")) { + if (params.len != 2) return error.InvalidBuiltin; + + const as_type = try (try interpreter.interpret(params[0], scope, options)).getValue(); + const value = try (try interpreter.interpret(params[1], scope, options)).getValue(); + + if (as_type.@"type".getTypeInfo() != .@"type") return error.InvalidBuiltin; + + return InterpretResult{ .value = try interpreter.cast(node_idx, as_type.value_data.@"type", value) }; + } + std.log.info("Builtin not implemented: {s}", .{call_name}); // @panic("Builtin not implemented"); return error.InvalidBuiltin; @@ -990,6 +1063,9 @@ pub fn interpret( var val = Value{ .handle = interpreter.handle, .node_idx = node_idx, + // TODO: This is literally the wrong type lmao + // the actual type is *[len:0]u8 because we're pointing + // to a fixed size value in the data(?) section (when we're compilign zig code) .@"type" = try interpreter.createType(node_idx, .{ .pointer = .{ .size = .slice, @@ -1004,7 +1080,7 @@ pub fn interpret( .sentinel = .{ .unsigned_int = 0 }, }, }), - .value_data = .{ .string = value }, + .value_data = .{ .slice_of_const_u8 = value }, }; // TODO: Add type casting, sentinel @@ -1061,7 +1137,7 @@ pub fn interpret( .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, type_info), - .value_data = .{ .@"fn" = .{} }, + .value_data = .{ .@"fn" = {} }, }; const name = analysis.getDeclName(tree, node_idx).?; @@ -1072,7 +1148,7 @@ pub fn interpret( .@"value" = value, }); - return InterpretResult{ .nothing = .{} }; + return InterpretResult{ .nothing = {} }; }, .call, .call_comma, @@ -1123,7 +1199,7 @@ pub fn interpret( }, else => { std.log.err("Unhandled {any}", .{tags[node_idx]}); - return InterpretResult{ .nothing = .{} }; + return InterpretResult{ .nothing = {} }; }, } } From c6ab7e8a0fb2680d3784c3d943ca73a1ce4eb825 Mon Sep 17 00:00:00 2001 From: Auguste Rame <19855629+SuperAuguste@users.noreply.github.com> Date: Mon, 31 Oct 2022 16:00:02 -0400 Subject: [PATCH 08/12] Casts++, compileLog, pointers --- build.zig | 2 +- src/ComptimeInterpreter.zig | 205 +++++++++++++++++++++++++++++------- 2 files changed, 167 insertions(+), 40 deletions(-) diff --git a/build.zig b/build.zig index 24e452f..fef43a9 100644 --- a/build.zig +++ b/build.zig @@ -134,7 +134,7 @@ pub fn build(b: *std.build.Builder) !void { }); } - tests.use_stage1 = true; + // tests.use_stage1 = true; tests.addPackage(.{ .name = "zls", .source = .{ .path = "src/zls.zig" }, .dependencies = exe.packages.items }); tests.setBuildMode(.Debug); tests.setTarget(target); diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig index 7865d6e..9894c27 100644 --- a/src/ComptimeInterpreter.zig +++ b/src/ComptimeInterpreter.zig @@ -13,6 +13,8 @@ const analysis = @import("analysis.zig"); const DocumentStore = @import("DocumentStore.zig"); const ComptimeInterpreter = @This(); +// TODO: Investigate arena + allocator: std.mem.Allocator, document_store: *DocumentStore, handle: *const DocumentStore.Handle, @@ -25,11 +27,15 @@ errors: std.AutoArrayHashMapUnmanaged(Ast.Node.Index, InterpreterError) = .{}, type_info: std.ArrayListUnmanaged(TypeInfo) = .{}, type_info_map: std.HashMapUnmanaged(TypeInfo, usize, TypeInfo.Context, std.hash_map.default_max_load_percentage) = .{}, +// TODO: Use DOD +value_data_list: std.ArrayListUnmanaged(*ValueData) = .{}, + pub const InterpreterError = struct { code: []const u8, message: []const u8, }; +/// `message` must be allocated with interpreter allocator pub fn recordError(interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index, code: []const u8, message: []const u8) error{OutOfMemory}!void { try interpreter.errors.put(interpreter.allocator, node_idx, .{ .code = code, @@ -38,10 +44,17 @@ pub fn recordError(interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index, } pub fn deinit(interpreter: *ComptimeInterpreter) void { + var err_it = interpreter.errors.iterator(); + while (err_it.next()) |entry| interpreter.allocator.free(entry.value_ptr.message); + if (interpreter.root_type) |rt| rt.getTypeInfo().getScopeOfType().?.deinit(); for (interpreter.type_info.items) |*ti| ti.deinit(interpreter.allocator); + for (interpreter.value_data_list.items) |ti| interpreter.allocator.destroy(ti); + + interpreter.errors.deinit(interpreter.allocator); interpreter.type_info.deinit(interpreter.allocator); interpreter.type_info_map.deinit(interpreter.allocator); + interpreter.value_data_list.deinit(interpreter.allocator); } pub const TypeInfo = union(enum) { @@ -78,7 +91,7 @@ pub const TypeInfo = union(enum) { child: Type, is_allowzero: bool, - sentinel: ?ValueData, + sentinel: ?*ValueData, pub const Size = enum { one, @@ -94,6 +107,13 @@ pub const TypeInfo = union(enum) { params: std.ArrayListUnmanaged(usize) = .{}, }; + pub const Array = struct { + len: usize, + child: Type, + + sentinel: ?*ValueData, + }; + /// Hack to get anytype working; only valid on fnparams @"anytype", @"type", @@ -108,6 +128,8 @@ pub const TypeInfo = union(enum) { float: u16, @"comptime_float", + array: Array, + pub fn eql(interpreter: ComptimeInterpreter, a: TypeInfo, b: TypeInfo) bool { if (std.meta.activeTag(a) != std.meta.activeTag(b)) return false; return switch (a) { @@ -196,7 +218,7 @@ pub const Value = struct { node_idx: Ast.Node.Index, @"type": Type, - value_data: ValueData, + value_data: *ValueData, pub fn eql(value: Value, other_value: Value) bool { return value.value_data.eql(other_value.value_data); @@ -231,11 +253,11 @@ pub const ValueData = union(enum) { runtime, comptime_undetermined, - pub fn eql(data: ValueData, other_data: ValueData) bool { - if (std.meta.activeTag(data) != std.meta.activeTag(other_data)) return false; + pub fn eql(data: *ValueData, other_data: *ValueData) bool { + if (std.meta.activeTag(data.*) != std.meta.activeTag(other_data.*)) return false; // std.enums. // std.meta.activeTag(u: anytype) - switch (data) { + switch (data.*) { .@"bool" => return data.@"bool" == other_data.@"bool", .big_int => return data.big_int.eq(other_data.big_int), .unsigned_int => return data.unsigned_int == other_data.unsigned_int, @@ -250,7 +272,7 @@ pub const ValueData = union(enum) { pub fn bitCount(data: ValueData) ?u16 { return switch (data) { // TODO: Implement for signed ints - .unsigned_int => |i| std.math.log2_int(@TypeOf(i), i), + .unsigned_int => |i| if (i == 0) 0 else std.math.log2_int(@TypeOf(i), i), .big_int => |bi| @intCast(u16, bi.bitCountAbs()), else => null, }; @@ -314,6 +336,13 @@ pub fn createType(interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index, t } } +pub fn createValueData(interpreter: *ComptimeInterpreter, data: ValueData) error{OutOfMemory}!*ValueData { + var vd = try interpreter.allocator.create(ValueData); + try interpreter.value_data_list.append(interpreter.allocator, vd); + vd.* = data; + return vd; +} + pub const TypeInfoFormatter = struct { interpreter: *const ComptimeInterpreter, ti: TypeInfo, @@ -358,9 +387,39 @@ pub fn formatTypeInfo(interpreter: *const ComptimeInterpreter, ti: TypeInfo) Typ return TypeInfoFormatter{ .interpreter = interpreter, .ti = ti }; } +pub const ValueFormatter = struct { + interpreter: *const ComptimeInterpreter, + val: Value, + + pub fn format(form: ValueFormatter, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { + _ = fmt; + _ = options; + + var value = form.val; + var ti = value.@"type".getTypeInfo(); + + return switch (ti) { + .int, .@"comptime_int" => switch (value.value_data.*) { + inline .unsigned_int, .signed_int, .big_int => |a| try writer.print("{d}", .{a}), + else => unreachable, + }, + else => try writer.print("UnimplementedValuePrint", .{}), + }; + } +}; + +pub fn formatValue(interpreter: *const ComptimeInterpreter, value: Value) ValueFormatter { + return ValueFormatter{ .interpreter = interpreter, .val = value }; +} + +// pub const Comptimeness = enum { @"comptime", runtime }; + pub const InterpreterScope = struct { interpreter: *ComptimeInterpreter, + // TODO: Actually use this value + // comptimeness: Comptimeness, + parent: ?*InterpreterScope = null, node_idx: Ast.Node.Index, declarations: std.StringHashMapUnmanaged(Declaration) = .{}, @@ -431,7 +490,11 @@ pub const InterpreterScope = struct { } }; -pub fn newScope(interpreter: *ComptimeInterpreter, maybe_parent: ?*InterpreterScope, node_idx: Ast.Node.Index) std.mem.Allocator.Error!*InterpreterScope { +pub fn newScope( + interpreter: *ComptimeInterpreter, + maybe_parent: ?*InterpreterScope, + node_idx: Ast.Node.Index, +) std.mem.Allocator.Error!*InterpreterScope { var ls = try interpreter.allocator.create(InterpreterScope); if (maybe_parent) |parent| try parent.child_scopes.append(interpreter.allocator, ls); ls.* = .{ @@ -546,7 +609,7 @@ pub fn cast( .@"comptime_int" => switch (to_type_info) { .int => { if (value_data.bitCount().? > to_type_info.int.bits) { - switch (value_data) { + switch (value_data.*) { inline .unsigned_int, .signed_int, .big_int => |bi| { try interpreter.recordError(node_idx, "invalid_cast", try std.fmt.allocPrint(interpreter.allocator, "integer value {d} cannot be coerced to type '{s}'", .{ bi, interpreter.formatTypeInfo(to_type_info) })); }, @@ -561,7 +624,7 @@ pub fn cast( }; err catch |e| { - try interpreter.recordError(node_idx, "invalid_cast", "invalid cast"); + try interpreter.recordError(node_idx, "invalid_cast", try std.fmt.allocPrint(interpreter.allocator, "invalid cast from '{s}' to '{s}'", .{ interpreter.formatTypeInfo(from_type_info), interpreter.formatTypeInfo(to_type_info) })); return e; }; @@ -654,7 +717,7 @@ pub fn interpret( try interpreter.recordError( field_info.ast.type_expr, "invalid_field_type_value", - "Field type should be a type!", + try std.fmt.allocPrint(interpreter.allocator, "expected type 'type', found '{s}'", .{interpreter.formatTypeInfo(init_type_value.@"type".getTypeInfo())}), ); continue; } @@ -685,7 +748,7 @@ pub fn interpret( .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), - .value_data = .{ .@"type" = cont_type }, + .value_data = try interpreter.createValueData(.{ .@"type" = cont_type }), } }; }, .global_var_decl, @@ -711,7 +774,7 @@ pub fn interpret( .handle = interpreter.handle, .node_idx = std.math.maxInt(Ast.Node.Index), .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), - .value_data = .{ .@"type" = value.@"type" }, + .value_data = try interpreter.createValueData(.{ .@"type" = value.@"type" }), } else try (try interpreter.interpret(decl.ast.type_node, scope, options)).getValue(); try scope.?.declarations.put(interpreter.allocator, name, .{ @@ -781,19 +844,19 @@ pub fn interpret( .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), - .value_data = .{ .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }) }, + .value_data = try interpreter.createValueData(.{ .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }) }), } }; if (std.mem.eql(u8, "true", value)) return InterpretResult{ .value = Value{ .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }), - .value_data = .{ .@"bool" = true }, + .value_data = try interpreter.createValueData(.{ .@"bool" = true }), } }; if (std.mem.eql(u8, "false", value)) return InterpretResult{ .value = Value{ .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }), - .value_data = .{ .@"bool" = false }, + .value_data = try interpreter.createValueData(.{ .@"bool" = false }), } }; if (value.len == 5 and (value[0] == 'u' or value[0] == 'i') and std.mem.eql(u8, "size", value[1..])) return InterpretResult{ @@ -801,14 +864,14 @@ pub fn interpret( .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), - .value_data = .{ + .value_data = try interpreter.createValueData(.{ .@"type" = try interpreter.createType(node_idx, .{ .int = .{ .signedness = if (value[0] == 'u') .unsigned else .signed, .bits = 64, // TODO: Platform specific }, }), - }, + }), }, }; @@ -817,19 +880,19 @@ pub fn interpret( .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), - .value_data = .{ .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }) }, + .value_data = try interpreter.createValueData(.{ .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }) }), } }; } else if (value.len >= 2 and (value[0] == 'u' or value[0] == 'i')) int: { return InterpretResult{ .value = Value{ .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), - .value_data = .{ .@"type" = try interpreter.createType(node_idx, .{ + .value_data = try interpreter.createValueData(.{ .@"type" = try interpreter.createType(node_idx, .{ .int = .{ .signedness = if (value[0] == 'u') .unsigned else .signed, .bits = std.fmt.parseInt(u16, value[1..], 10) catch break :int, }, - }) }, + }) }), } }; } @@ -903,7 +966,7 @@ pub fn interpret( .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }), - .value_data = .{ .@"bool" = (try a.getValue()).eql(try b.getValue()) }, + .value_data = try interpreter.createValueData(.{ .@"bool" = (try a.getValue()).eql(try b.getValue()) }), } }; // a.getValue().eql(b.getValue()) }, @@ -916,7 +979,7 @@ pub fn interpret( .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"comptime_int" = {} }), - .value_data = switch (nl) { + .value_data = try interpreter.createValueData(switch (nl) { .float => .{ .float = try std.fmt.parseFloat(f64, s) }, .int => if (s[0] == '-') ValueData{ .signed_int = try std.fmt.parseInt(i64, s, 0) } else ValueData{ .unsigned_int = try std.fmt.parseInt(u64, s, 0) }, .big_int => |bii| ppp: { @@ -925,7 +988,7 @@ pub fn interpret( break :ppp .{ .big_int = bi }; }, .failure => return error.CriticalAstFailure, - }, + }), }, }; }, @@ -944,16 +1007,15 @@ pub fn interpret( .assign_mul, .assign_mul_wrap, => { - // TODO: Make this work with non identifiers // TODO: Actually consider operators - const value = tree.getNodeSource(data[node_idx].lhs); + if (std.mem.eql(u8, tree.getNodeSource(data[node_idx].lhs), "_")) return InterpretResult{ .nothing = {} }; - var psi = scope.?.parentScopeIterator(); - while (psi.next()) |pscope| { - if (pscope.declarations.getEntry(value)) |decl| - decl.value_ptr.value = try (try interpreter.interpret(data[node_idx].rhs, scope.?, options)).getValue(); - } + var ir = try interpreter.interpret(data[node_idx].lhs, scope, options); + var to_value = try ir.getValue(); + var from_value = (try (try interpreter.interpret(data[node_idx].rhs, scope.?, options)).getValue()); + + to_value.value_data.* = (try interpreter.cast(node_idx, to_value.@"type", from_value)).value_data.*; return InterpretResult{ .nothing = {} }; }, @@ -982,10 +1044,27 @@ pub fn interpret( const call_name = tree.tokenSlice(main_tokens[node_idx]); if (std.mem.eql(u8, call_name, "@compileLog")) { + var final = std.ArrayList(u8).init(interpreter.allocator); + var writer = final.writer(); + try writer.writeAll("log: "); + + for (params) |param, index| { + var value = (try interpreter.interpret(param, scope, options)).maybeGetValue() orelse { + try writer.writeAll("indeterminate"); + continue; + }; + try writer.print("@as({s}, {s})", .{ interpreter.formatTypeInfo(value.@"type".getTypeInfo()), interpreter.formatValue(value) }); + if (index != params.len - 1) + try writer.writeAll(", "); + } + try interpreter.recordError(node_idx, "compile_log", final.toOwnedSlice()); + return InterpretResult{ .nothing = {} }; } if (std.mem.eql(u8, call_name, "@compileError")) { + // TODO: Add message + try interpreter.recordError(node_idx, "compile_error", try std.fmt.allocPrint(interpreter.allocator, "compile error", .{})); return InterpretResult{ .@"return" = {} }; } @@ -1007,7 +1086,7 @@ pub fn interpret( .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), - .value_data = .{ .@"type" = handle.interpreter.?.root_type.? }, + .value_data = try interpreter.createValueData(.{ .@"type" = handle.interpreter.?.root_type.? }), } }; } @@ -1019,7 +1098,7 @@ pub fn interpret( .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), - .value_data = .{ .@"type" = value.@"type" }, + .value_data = try interpreter.createValueData(.{ .@"type" = value.@"type" }), } }; } @@ -1039,7 +1118,7 @@ pub fn interpret( .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }), - .value_data = .{ .@"bool" = ti.getScopeOfType().?.declarations.contains(field_name.value_data.slice_of_const_u8) }, + .value_data = try interpreter.createValueData(.{ .@"bool" = ti.getScopeOfType().?.declarations.contains(field_name.value_data.slice_of_const_u8) }), } }; } @@ -1068,7 +1147,7 @@ pub fn interpret( // to a fixed size value in the data(?) section (when we're compilign zig code) .@"type" = try interpreter.createType(node_idx, .{ .pointer = .{ - .size = .slice, + .size = .one, .is_const = true, .is_volatile = false, .child = try interpreter.createType(0, .{ .int = .{ @@ -1077,10 +1156,10 @@ pub fn interpret( } }), .is_allowzero = false, - .sentinel = .{ .unsigned_int = 0 }, + .sentinel = null, }, }), - .value_data = .{ .slice_of_const_u8 = value }, + .value_data = try interpreter.createValueData(.{ .slice_of_const_u8 = value }), }; // TODO: Add type casting, sentinel @@ -1137,7 +1216,7 @@ pub fn interpret( .handle = interpreter.handle, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, type_info), - .value_data = .{ .@"fn" = {} }, + .value_data = try interpreter.createValueData(.{ .@"fn" = {} }), }; const name = analysis.getDeclName(tree, node_idx).?; @@ -1187,13 +1266,61 @@ pub fn interpret( .bool_not => { const result = try interpreter.interpret(data[node_idx].lhs, scope, .{}); const value = (try result.getValue()); - if (value.value_data != .@"bool") return error.InvalidOperation; + if (value.value_data.* != .@"bool") return error.InvalidOperation; return InterpretResult{ .value = .{ .handle = interpreter.handle, .node_idx = node_idx, .@"type" = value.@"type", - .value_data = .{ .@"bool" = !value.value_data.@"bool" }, + .value_data = try interpreter.createValueData(.{ .@"bool" = !value.value_data.@"bool" }), + }, + }; + }, + .address_of => { + // TODO: Make const pointers if we're drawing from a const; + // variables are the only non-const(?) + + const result = try interpreter.interpret(data[node_idx].lhs, scope, .{}); + const value = (try result.getValue()); + + return InterpretResult{ + .value = .{ + .handle = interpreter.handle, + .node_idx = node_idx, + .@"type" = try interpreter.createType(node_idx, .{ + .pointer = .{ + .size = .one, + .is_const = false, + .is_volatile = false, + .child = value.@"type", + .is_allowzero = false, + + .sentinel = null, + }, + }), + .value_data = try interpreter.createValueData(.{ .@"one_ptr" = value.value_data }), + }, + }; + }, + .deref => { + const result = try interpreter.interpret(data[node_idx].lhs, scope, .{}); + const value = (try result.getValue()); + + const ti = value.@"type".getTypeInfo(); + + if (ti != .pointer) { + try interpreter.recordError(node_idx, "invalid_deref", try std.fmt.allocPrint(interpreter.allocator, "cannot deference non-pointer", .{})); + return error.InvalidOperation; + } + + // TODO: Check if this is a one_ptr or not + + return InterpretResult{ + .value = .{ + .handle = interpreter.handle, + .node_idx = node_idx, + .@"type" = ti.pointer.child, + .value_data = value.value_data.one_ptr, }, }; }, From b91a193d043972d184876011311e2fcb076a1575 Mon Sep 17 00:00:00 2001 From: Auguste Rame <19855629+SuperAuguste@users.noreply.github.com> Date: Mon, 31 Oct 2022 23:36:13 -0400 Subject: [PATCH 09/12] We can interpret std now because of proper tree shaking! --- src/ComptimeInterpreter.zig | 181 +++++++++++++++++++++++------------- src/Server.zig | 2 +- 2 files changed, 119 insertions(+), 64 deletions(-) diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig index 9894c27..5b1a3ca 100644 --- a/src/ComptimeInterpreter.zig +++ b/src/ComptimeInterpreter.zig @@ -198,23 +198,23 @@ pub const TypeInfo = union(enum) { }; pub const Type = struct { - handle: *const DocumentStore.Handle, + interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index, info_idx: usize, pub fn getTypeInfo(@"type": Type) TypeInfo { - return @"type".handle.interpreter.?.type_info.items[@"type".info_idx]; + return @"type".interpreter.type_info.items[@"type".info_idx]; } /// Be careful with this; typeinfo resizes reassign pointers! pub fn getTypeInfoMutable(@"type": Type) *TypeInfo { - return &@"type".handle.interpreter.?.type_info.items[@"type".info_idx]; + return &@"type".interpreter.type_info.items[@"type".info_idx]; } }; pub const Value = struct { - handle: *const DocumentStore.Handle, + interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index, @"type": Type, @@ -272,7 +272,7 @@ pub const ValueData = union(enum) { pub fn bitCount(data: ValueData) ?u16 { return switch (data) { // TODO: Implement for signed ints - .unsigned_int => |i| if (i == 0) 0 else std.math.log2_int(@TypeOf(i), i), + .unsigned_int => |i| if (i == 0) 0 else std.math.log2_int_ceil(@TypeOf(i), i + 1), .big_int => |bi| @intCast(u16, bi.bitCountAbs()), else => null, }; @@ -288,11 +288,14 @@ pub const FieldDefinition = struct { }; pub const Declaration = struct { + scope: *InterpreterScope, + node_idx: Ast.Node.Index, /// Store name so tree doesn't need to be used to access declaration name name: []const u8, - @"type": Type, - value: Value, + + /// If value is null, declaration has not been interpreted yet + value: ?Value = null, // TODO: figure this out // pub const DeclarationKind = enum{variable, function}; @@ -306,7 +309,48 @@ pub const Declaration = struct { // } // } - pub fn isConstant(declaration: Declaration, tree: Ast) bool { + pub fn getValue(decl: *Declaration) InterpretError!Value { + var interpreter = decl.scope.interpreter; + const tree = decl.scope.interpreter.handle.tree; + const tags = tree.nodes.items(.tag); + + if (decl.value == null) { + switch (tags[decl.node_idx]) { + .global_var_decl, + .local_var_decl, + .aligned_var_decl, + .simple_var_decl, + => { + const var_decl = ast.varDecl(tree, decl.node_idx).?; + if (var_decl.ast.init_node == 0) + return error.CriticalAstFailure; + + var value = try (try interpreter.interpret(var_decl.ast.init_node, decl.scope, .{})).getValue(); + + if (var_decl.ast.type_node != 0) { + var type_val = try (try interpreter.interpret(var_decl.ast.type_node, decl.scope, .{})).getValue(); + if (type_val.@"type".getTypeInfo() != .@"type") { + try interpreter.recordError( + decl.node_idx, + "expected_type", + std.fmt.allocPrint(interpreter.allocator, "expected type 'type', found '{s}'", .{interpreter.formatTypeInfo(type_val.@"type".getTypeInfo())}) catch return error.CriticalAstFailure, + ); + return error.InvalidCast; + } + value = try interpreter.cast(var_decl.ast.type_node, type_val.value_data.@"type", value); + } + + decl.value = value; + }, + else => @panic("No other case supported for lazy declaration evaluation"), + } + } + + return decl.value.?; + } + + pub fn isConstant(declaration: Declaration) bool { + const tree = declaration.scope.interpreter.handle.tree; return switch (tree.nodes.items(.tag)[declaration.node_idx]) { .global_var_decl, .local_var_decl, @@ -327,12 +371,12 @@ pub fn createType(interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index, t if (gpr.found_existing) { // std.log.info("Deduplicating type {d}", .{interpreter.formatTypeInfo(unit.type_info.items[gpr.value_ptr.*])}); - return Type{ .handle = interpreter.handle, .node_idx = node_idx, .info_idx = gpr.value_ptr.* }; + return Type{ .interpreter = interpreter, .node_idx = node_idx, .info_idx = gpr.value_ptr.* }; } else { try interpreter.type_info.append(interpreter.allocator, type_info); const info_idx = interpreter.type_info.items.len - 1; gpr.value_ptr.* = info_idx; - return Type{ .handle = interpreter.handle, .node_idx = node_idx, .info_idx = info_idx }; + return Type{ .interpreter = interpreter, .node_idx = node_idx, .info_idx = info_idx }; } } @@ -369,11 +413,27 @@ pub const TypeInfoFormatter = struct { var iterator = s.scope.declarations.iterator(); while (iterator.next()) |di| { - const decl = di.value_ptr.*; - if (decl.isConstant(value.interpreter.handle.tree)) { - try writer.print("const {s}: {any} = TODO_PRINT_VALUES, ", .{ decl.name, value.interpreter.formatTypeInfo(decl.@"type".getTypeInfo()) }); + const decl = di.value_ptr; + if (decl.isConstant()) { + if (decl.value) |sv| { + try writer.print("const {s}: {any} = { }, ", .{ + decl.name, + value.interpreter.formatTypeInfo(sv.@"type".getTypeInfo()), + value.interpreter.formatValue(sv), + }); + } else { + try writer.print("const {s} (not analyzed), ", .{decl.name}); + } } else { - try writer.print("var {s}: {any}, ", .{ decl.name, value.interpreter.formatTypeInfo(decl.@"type".getTypeInfo()) }); + if (decl.value) |sv| { + try writer.print("var {s}: {any} = { }, ", .{ + decl.name, + value.interpreter.formatTypeInfo(sv.@"type".getTypeInfo()), + value.interpreter.formatValue(sv), + }); + } else { + try writer.print("var {s} (not analyzed), ", .{decl.name}); + } } } try writer.writeAll("}"); @@ -403,6 +463,7 @@ pub const ValueFormatter = struct { inline .unsigned_int, .signed_int, .big_int => |a| try writer.print("{d}", .{a}), else => unreachable, }, + .@"type" => try writer.print("{ }", .{form.interpreter.formatTypeInfo(value.value_data.@"type".getTypeInfo())}), else => try writer.print("UnimplementedValuePrint", .{}), }; } @@ -427,7 +488,8 @@ pub const InterpreterScope = struct { child_scopes: std.ArrayListUnmanaged(*InterpreterScope) = .{}, pub const ScopeKind = enum { container, block, function }; - pub fn scopeKind(scope: InterpreterScope, tree: Ast) ScopeKind { + pub fn scopeKind(scope: InterpreterScope) ScopeKind { + const tree = scope.interpreter.handle.tree; return switch (tree.nodes.items(.tag)[scope.node_idx]) { .container_decl, .container_decl_trailing, @@ -448,10 +510,11 @@ pub const InterpreterScope = struct { }; } - pub fn getLabel(scope: InterpreterScope, tree: Ast) ?Ast.TokenIndex { + pub fn getLabel(scope: InterpreterScope) ?Ast.TokenIndex { + const tree = scope.interpreter.handle.tree; const token_tags = tree.tokens.items(.tag); - return switch (scope.scopeKind(tree)) { + return switch (scope.scopeKind()) { .block => z: { const lbrace = tree.nodes.items(.main_token)[scope.node_idx]; break :z if (token_tags[lbrace - 1] == .colon and token_tags[lbrace - 2] == .identifier) @@ -555,14 +618,14 @@ pub fn huntItDown( scope: *InterpreterScope, decl_name: []const u8, options: InterpretOptions, -) InterpretError!Declaration { +) InterpretError!*Declaration { const tree = interpreter.handle.tree; const tags = tree.nodes.items(.tag); var psi = scope.parentScopeIterator(); while (psi.next()) |pscope| { - const known_decl = pscope.declarations.get(decl_name); - if (pscope.scopeKind(tree) == .container and + const known_decl = pscope.declarations.getEntry(decl_name); + if (pscope.scopeKind() == .container and known_decl == null and pscope.declarations.count() != getDeclCount(tree, pscope.node_idx)) { @@ -580,14 +643,14 @@ pub fn huntItDown( => { if (std.mem.eql(u8, analysis.getDeclName(tree, member).?, decl_name)) { _ = try interpreter.interpret(member, pscope, options); - return pscope.declarations.get(decl_name).?; + return pscope.declarations.getEntry(decl_name).?.value_ptr; } }, else => {}, } } } - return known_decl orelse continue; + return (known_decl orelse continue).value_ptr; } std.log.err("Identifier not found: {s}", .{decl_name}); @@ -629,7 +692,7 @@ pub fn cast( }; return Value{ - .handle = interpreter.handle, + .interpreter = interpreter, .node_idx = node_idx, .@"type" = dest_type, @@ -716,7 +779,7 @@ pub fn interpret( if (init_type_value.@"type".getTypeInfo() != .@"type") { try interpreter.recordError( field_info.ast.type_expr, - "invalid_field_type_value", + "expected_type", try std.fmt.allocPrint(interpreter.allocator, "expected type 'type', found '{s}'", .{interpreter.formatTypeInfo(init_type_value.@"type".getTypeInfo())}), ); continue; @@ -745,7 +808,7 @@ pub fn interpret( } return InterpretResult{ .value = Value{ - .handle = interpreter.handle, + .interpreter = interpreter, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), .value_data = try interpreter.createValueData(.{ .@"type" = cont_type }), @@ -765,25 +828,16 @@ pub fn interpret( if (decl.ast.init_node == 0) return InterpretResult{ .nothing = {} }; - // We should have a value when a var is defined - // var value = try (try interpreter.interpret(decl.ast.init_node, scope, options)).getValue(); - var value = (try interpreter.interpret(decl.ast.init_node, scope, options)).maybeGetValue() orelse return InterpretResult{ .nothing = {} }; - - // Is this redundant? im too afraid to change it rn tbh - var @"type" = if (decl.ast.type_node == 0) Value{ - .handle = interpreter.handle, - .node_idx = std.math.maxInt(Ast.Node.Index), - .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), - .value_data = try interpreter.createValueData(.{ .@"type" = value.@"type" }), - } else try (try interpreter.interpret(decl.ast.type_node, scope, options)).getValue(); - try scope.?.declarations.put(interpreter.allocator, name, .{ + .scope = scope.?, .node_idx = node_idx, .name = name, - .@"type" = @"type".value_data.@"type", - .@"value" = value, }); + if (scope.?.scopeKind() != .container) { + _ = try scope.?.declarations.getPtr(name).?.getValue(); + } + return InterpretResult{ .nothing = {} }; }, .block, @@ -806,7 +860,7 @@ pub fn interpret( const ret = try interpreter.interpret(idx, block_scope, options); switch (ret) { .@"break" => |lllll| { - const maybe_block_label_string = if (scope.?.getLabel(tree)) |i| tree.tokenSlice(i) else null; + const maybe_block_label_string = if (scope.?.getLabel()) |i| tree.tokenSlice(i) else null; if (lllll) |l| { if (maybe_block_label_string) |ls| { if (std.mem.eql(u8, l, ls)) { @@ -818,7 +872,7 @@ pub fn interpret( } }, .break_with_value => |bwv| { - const maybe_block_label_string = if (scope.?.getLabel(tree)) |i| tree.tokenSlice(i) else null; + const maybe_block_label_string = if (scope.?.getLabel()) |i| tree.tokenSlice(i) else null; if (bwv.label) |l| { if (maybe_block_label_string) |ls| { @@ -841,19 +895,19 @@ pub fn interpret( var value = tree.getNodeSource(node_idx); if (std.mem.eql(u8, "bool", value)) return InterpretResult{ .value = Value{ - .handle = interpreter.handle, + .interpreter = interpreter, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), .value_data = try interpreter.createValueData(.{ .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }) }), } }; if (std.mem.eql(u8, "true", value)) return InterpretResult{ .value = Value{ - .handle = interpreter.handle, + .interpreter = interpreter, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }), .value_data = try interpreter.createValueData(.{ .@"bool" = true }), } }; if (std.mem.eql(u8, "false", value)) return InterpretResult{ .value = Value{ - .handle = interpreter.handle, + .interpreter = interpreter, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }), .value_data = try interpreter.createValueData(.{ .@"bool" = false }), @@ -861,7 +915,7 @@ pub fn interpret( if (value.len == 5 and (value[0] == 'u' or value[0] == 'i') and std.mem.eql(u8, "size", value[1..])) return InterpretResult{ .value = Value{ - .handle = interpreter.handle, + .interpreter = interpreter, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), .value_data = try interpreter.createValueData(.{ @@ -877,14 +931,14 @@ pub fn interpret( if (std.mem.eql(u8, "type", value)) { return InterpretResult{ .value = Value{ - .handle = interpreter.handle, + .interpreter = interpreter, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), .value_data = try interpreter.createValueData(.{ .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }) }), } }; } else if (value.len >= 2 and (value[0] == 'u' or value[0] == 'i')) int: { return InterpretResult{ .value = Value{ - .handle = interpreter.handle, + .interpreter = interpreter, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), .value_data = try interpreter.createValueData(.{ .@"type" = try interpreter.createType(node_idx, .{ @@ -899,14 +953,14 @@ pub fn interpret( // TODO: Floats // Logic to find identifiers in accessible scopes - return InterpretResult{ .value = (interpreter.huntItDown(scope.?, value, options) catch |err| { + return InterpretResult{ .value = try (interpreter.huntItDown(scope.?, value, options) catch |err| { if (err == error.IdentifierNotFound) try interpreter.recordError( node_idx, "identifier_not_found", try std.fmt.allocPrint(interpreter.allocator, "Couldn't find identifier \"{s}\"", .{value}), ); return err; - }).value }; + }).getValue() }; }, .field_access => { if (data[node_idx].rhs == 0) return error.CriticalAstFailure; @@ -917,7 +971,7 @@ pub fn interpret( var sub_scope = irv.value_data.@"type".getTypeInfo().getScopeOfType() orelse return error.IdentifierNotFound; // var scope_sub_decl = sub_scope.declarations.get(rhs_str) orelse return error.IdentifierNotFound; - var scope_sub_decl = irv.value_data.@"type".handle.interpreter.?.huntItDown(sub_scope, rhs_str, options) catch |err| { + var scope_sub_decl = irv.value_data.@"type".interpreter.huntItDown(sub_scope, rhs_str, options) catch |err| { if (err == error.IdentifierNotFound) try interpreter.recordError( node_idx, "identifier_not_found", @@ -927,7 +981,7 @@ pub fn interpret( }; return InterpretResult{ - .value = scope_sub_decl.value, + .value = try scope_sub_decl.getValue(), }; }, .grouped_expression => { @@ -963,7 +1017,7 @@ pub fn interpret( var a = try interpreter.interpret(data[node_idx].lhs, scope, options); var b = try interpreter.interpret(data[node_idx].rhs, scope, options); return InterpretResult{ .value = Value{ - .handle = interpreter.handle, + .interpreter = interpreter, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }), .value_data = try interpreter.createValueData(.{ .@"bool" = (try a.getValue()).eql(try b.getValue()) }), @@ -976,7 +1030,7 @@ pub fn interpret( return InterpretResult{ .value = Value{ - .handle = interpreter.handle, + .interpreter = interpreter, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"comptime_int" = {} }), .value_data = try interpreter.createValueData(switch (nl) { @@ -1083,7 +1137,7 @@ pub fn interpret( try interpreter.document_store.ensureInterpreterExists(handle.uri); return InterpretResult{ .value = Value{ - .handle = interpreter.handle, + .interpreter = interpreter, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), .value_data = try interpreter.createValueData(.{ .@"type" = handle.interpreter.?.root_type.? }), @@ -1095,7 +1149,7 @@ pub fn interpret( const value = try (try interpreter.interpret(params[0], scope, options)).getValue(); return InterpretResult{ .value = Value{ - .handle = interpreter.handle, + .interpreter = interpreter, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), .value_data = try interpreter.createValueData(.{ .@"type" = value.@"type" }), @@ -1115,7 +1169,7 @@ pub fn interpret( if (ti.getScopeOfType() == null) return error.InvalidBuiltin; return InterpretResult{ .value = Value{ - .handle = interpreter.handle, + .interpreter = interpreter, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }), .value_data = try interpreter.createValueData(.{ .@"bool" = ti.getScopeOfType().?.declarations.contains(field_name.value_data.slice_of_const_u8) }), @@ -1140,7 +1194,7 @@ pub fn interpret( .string_literal => { const value = tree.getNodeSource(node_idx)[1 .. tree.getNodeSource(node_idx).len - 1]; var val = Value{ - .handle = interpreter.handle, + .interpreter = interpreter, .node_idx = node_idx, // TODO: This is literally the wrong type lmao // the actual type is *[len:0]u8 because we're pointing @@ -1213,7 +1267,7 @@ pub fn interpret( // fnd.return_type = value.value_data.@"type"; var value = Value{ - .handle = interpreter.handle, + .interpreter = interpreter, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, type_info), .value_data = try interpreter.createValueData(.{ .@"fn" = {} }), @@ -1221,9 +1275,9 @@ pub fn interpret( const name = analysis.getDeclName(tree, node_idx).?; try scope.?.declarations.put(interpreter.allocator, name, .{ + .scope = scope.?, .node_idx = node_idx, .name = name, - .@"type" = value.@"type", .@"value" = value, }); @@ -1269,7 +1323,7 @@ pub fn interpret( if (value.value_data.* != .@"bool") return error.InvalidOperation; return InterpretResult{ .value = .{ - .handle = interpreter.handle, + .interpreter = interpreter, .node_idx = node_idx, .@"type" = value.@"type", .value_data = try interpreter.createValueData(.{ .@"bool" = !value.value_data.@"bool" }), @@ -1285,7 +1339,7 @@ pub fn interpret( return InterpretResult{ .value = .{ - .handle = interpreter.handle, + .interpreter = interpreter, .node_idx = node_idx, .@"type" = try interpreter.createType(node_idx, .{ .pointer = .{ @@ -1317,7 +1371,7 @@ pub fn interpret( return InterpretResult{ .value = .{ - .handle = interpreter.handle, + .interpreter = interpreter, .node_idx = node_idx, .@"type" = ti.pointer.child, .value_data = value.value_data.one_ptr, @@ -1355,6 +1409,7 @@ pub fn call( std.debug.assert(tags[func_node_idx] == .fn_decl); + // TODO: Make argument scope to evaluate arguments in var fn_scope = try interpreter.newScope(scope, func_node_idx); var buf: [1]Ast.Node.Index = undefined; @@ -1366,9 +1421,9 @@ pub fn call( if (arg_index >= arguments.len) return error.MissingArguments; if (param.name_token) |nt| { const decl = Declaration{ + .scope = fn_scope, .node_idx = param.type_expr, .name = tree.tokenSlice(nt), - .@"type" = arguments[arg_index].@"type", .value = arguments[arg_index], }; try fn_scope.declarations.put(interpreter.allocator, tree.tokenSlice(nt), decl); diff --git a/src/Server.zig b/src/Server.zig index 9837ab3..f4e3ac4 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -520,7 +520,7 @@ fn typeToCompletion( while (it.next()) |entry| { try list.append(allocator, .{ .label = entry.key_ptr.*, - .kind = if (entry.value_ptr.isConstant(co.interpreter.handle.tree)) .Constant else .Variable, + .kind = if (entry.value_ptr.isConstant()) .Constant else .Variable, .insertText = entry.key_ptr.*, .insertTextFormat = .PlainText, }); From 411e74d364def3d24686c819b3ce2a8ce4b271db Mon Sep 17 00:00:00 2001 From: Auguste Rame <19855629+SuperAuguste@users.noreply.github.com> Date: Tue, 8 Nov 2022 15:54:30 -0500 Subject: [PATCH 10/12] Add param type resolution & stop using stage2, still a bit broken :( --- build.zig | 4 +-- src/ComptimeInterpreter.zig | 52 +++++++++++++++++++++++++------------ 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/build.zig b/build.zig index fef43a9..be88bfb 100644 --- a/build.zig +++ b/build.zig @@ -13,7 +13,7 @@ pub fn build(b: *std.build.Builder) !void { const mode = b.standardReleaseOptions(); const exe = b.addExecutable("zls", "src/main.zig"); - // exe.use_stage1 = true; + exe.use_stage1 = true; const exe_options = b.addOptions(); exe.addOptions("build_options", exe_options); @@ -134,7 +134,7 @@ pub fn build(b: *std.build.Builder) !void { }); } - // tests.use_stage1 = true; + tests.use_stage1 = true; tests.addPackage(.{ .name = "zls", .source = .{ .path = "src/zls.zig" }, .dependencies = exe.packages.items }); tests.setBuildMode(.Debug); tests.setTarget(target); diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig index 5b1a3ca..124daf1 100644 --- a/src/ComptimeInterpreter.zig +++ b/src/ComptimeInterpreter.zig @@ -67,7 +67,8 @@ pub const TypeInfo = union(enum) { return self.hasher.final(); } pub fn eql(self: @This(), a: TypeInfo, b: TypeInfo) bool { - return TypeInfo.eql(self.interpreter, a, b); + _ = self; + return TypeInfo.eql(a, b); } }; @@ -130,15 +131,14 @@ pub const TypeInfo = union(enum) { array: Array, - pub fn eql(interpreter: ComptimeInterpreter, a: TypeInfo, b: TypeInfo) bool { + pub fn eql(a: TypeInfo, b: TypeInfo) bool { if (std.meta.activeTag(a) != std.meta.activeTag(b)) return false; return switch (a) { - .@"struct" => false, // Struct declarations can never be equal + .@"struct" => false, // Struct declarations can never be equal (this is a lie, gotta fix this) .pointer => p: { const ap = a.pointer; const bp = b.pointer; break :p ap.size == bp.size and ap.is_const == bp.is_const and ap.is_volatile == bp.is_volatile and eql( - interpreter, ap.child.getTypeInfo(), bp.child.getTypeInfo(), ) and ap.is_allowzero == bp.is_allowzero and ((ap.sentinel == null and bp.sentinel == null) or ((ap.sentinel != null and bp.sentinel != null) and ap.sentinel.?.eql(bp.sentinel.?))); @@ -460,7 +460,10 @@ pub const ValueFormatter = struct { return switch (ti) { .int, .@"comptime_int" => switch (value.value_data.*) { - inline .unsigned_int, .signed_int, .big_int => |a| try writer.print("{d}", .{a}), + .unsigned_int => |a| try writer.print("{d}", .{a}), + .signed_int => |a| try writer.print("{d}", .{a}), + .big_int => |a| try writer.print("{d}", .{a}), + else => unreachable, }, .@"type" => try writer.print("{ }", .{form.interpreter.formatTypeInfo(value.value_data.@"type".getTypeInfo())}), @@ -668,14 +671,19 @@ pub fn cast( const to_type_info = dest_type.getTypeInfo(); const from_type_info = value.@"type".getTypeInfo(); + // TODO: Implement more implicit casts + + if (from_type_info.eql(to_type_info)) return value; + const err = switch (from_type_info) { .@"comptime_int" => switch (to_type_info) { .int => { if (value_data.bitCount().? > to_type_info.int.bits) { switch (value_data.*) { - inline .unsigned_int, .signed_int, .big_int => |bi| { - try interpreter.recordError(node_idx, "invalid_cast", try std.fmt.allocPrint(interpreter.allocator, "integer value {d} cannot be coerced to type '{s}'", .{ bi, interpreter.formatTypeInfo(to_type_info) })); - }, + .unsigned_int => |bi| try interpreter.recordError(node_idx, "invalid_cast", try std.fmt.allocPrint(interpreter.allocator, "integer value {d} cannot be coerced to type '{s}'", .{ bi, interpreter.formatTypeInfo(to_type_info) })), + .signed_int => |bi| try interpreter.recordError(node_idx, "invalid_cast", try std.fmt.allocPrint(interpreter.allocator, "integer value {d} cannot be coerced to type '{s}'", .{ bi, interpreter.formatTypeInfo(to_type_info) })), + .big_int => |bi| try interpreter.recordError(node_idx, "invalid_cast", try std.fmt.allocPrint(interpreter.allocator, "integer value {d} cannot be coerced to type '{s}'", .{ bi, interpreter.formatTypeInfo(to_type_info) })), + else => unreachable, } return error.InvalidCast; @@ -956,8 +964,8 @@ pub fn interpret( return InterpretResult{ .value = try (interpreter.huntItDown(scope.?, value, options) catch |err| { if (err == error.IdentifierNotFound) try interpreter.recordError( node_idx, - "identifier_not_found", - try std.fmt.allocPrint(interpreter.allocator, "Couldn't find identifier \"{s}\"", .{value}), + "undeclared_identifier", + try std.fmt.allocPrint(interpreter.allocator, "use of undeclared identifier '{s}'", .{value}), ); return err; }).getValue() }; @@ -974,8 +982,8 @@ pub fn interpret( var scope_sub_decl = irv.value_data.@"type".interpreter.huntItDown(sub_scope, rhs_str, options) catch |err| { if (err == error.IdentifierNotFound) try interpreter.recordError( node_idx, - "identifier_not_found", - try std.fmt.allocPrint(interpreter.allocator, "Couldn't find identifier \"{s}\"", .{rhs_str}), + "undeclared_identifier", + try std.fmt.allocPrint(interpreter.allocator, "use of undeclared identifier '{s}'", .{rhs_str}), ); return err; }; @@ -1063,7 +1071,10 @@ pub fn interpret( => { // TODO: Actually consider operators - if (std.mem.eql(u8, tree.getNodeSource(data[node_idx].lhs), "_")) return InterpretResult{ .nothing = {} }; + if (std.mem.eql(u8, tree.getNodeSource(data[node_idx].lhs), "_")) { + _ = try interpreter.interpret(data[node_idx].rhs, scope.?, options); + return InterpretResult{ .nothing = {} }; + } var ir = try interpreter.interpret(data[node_idx].lhs, scope, options); var to_value = try ir.getValue(); @@ -1400,7 +1411,7 @@ pub fn call( arguments: []const Value, options: InterpretOptions, ) InterpretError!CallResult { - _ = options; + // _ = options; // TODO: type check args @@ -1419,12 +1430,21 @@ pub fn call( var arg_index: usize = 0; while (ast.nextFnParam(&arg_it)) |param| { if (arg_index >= arguments.len) return error.MissingArguments; + var tex = try (try interpreter.interpret(param.type_expr, fn_scope, options)).getValue(); + if (tex.@"type".getTypeInfo() != .@"type") { + try interpreter.recordError( + param.type_expr, + "expected_type", + std.fmt.allocPrint(interpreter.allocator, "expected type 'type', found '{s}'", .{interpreter.formatTypeInfo(tex.@"type".getTypeInfo())}) catch return error.CriticalAstFailure, + ); + return error.InvalidCast; + } if (param.name_token) |nt| { const decl = Declaration{ .scope = fn_scope, .node_idx = param.type_expr, .name = tree.tokenSlice(nt), - .value = arguments[arg_index], + .value = try interpreter.cast(arguments[arg_index].node_idx, tex.value_data.@"type", arguments[arg_index]), }; try fn_scope.declarations.put(interpreter.allocator, tree.tokenSlice(nt), decl); arg_index += 1; @@ -1438,7 +1458,7 @@ pub fn call( return CallResult{ .scope = fn_scope, .result = switch (result) { - .@"return" => .{ .nothing = {} }, + .@"return", .nothing => .{ .nothing = {} }, // nothing could be due to an error .@"return_with_value" => |v| .{ .value = v }, else => @panic("bruh"), }, From c803a5de3f63dbaa5b86ea6774e5770d2516203f Mon Sep 17 00:00:00 2001 From: Auguste Rame <19855629+SuperAuguste@users.noreply.github.com> Date: Wed, 9 Nov 2022 23:46:23 -0500 Subject: [PATCH 11/12] Quick cleanup --- src/ComptimeInterpreter.zig | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig index 124daf1..be300db 100644 --- a/src/ComptimeInterpreter.zig +++ b/src/ComptimeInterpreter.zig @@ -1,9 +1,7 @@ //! Hacky comptime interpreter, courtesy of midnight code run fuelled by spite; //! hope that one day this can use async... <33 -// TODO: builtin work!! // TODO: DODify -// TODO: Work with DocumentStore const std = @import("std"); const ast = @import("ast.zig"); @@ -13,6 +11,8 @@ const analysis = @import("analysis.zig"); const DocumentStore = @import("DocumentStore.zig"); const ComptimeInterpreter = @This(); +const log = std.log.scoped(.comptime_interpreter); + // TODO: Investigate arena allocator: std.mem.Allocator, @@ -370,7 +370,6 @@ pub fn createType(interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index, t var gpr = try interpreter.type_info_map.getOrPutContext(interpreter.allocator, type_info, .{ .interpreter = interpreter.*, .hasher = &hasher }); if (gpr.found_existing) { - // std.log.info("Deduplicating type {d}", .{interpreter.formatTypeInfo(unit.type_info.items[gpr.value_ptr.*])}); return Type{ .interpreter = interpreter, .node_idx = node_idx, .info_idx = gpr.value_ptr.* }; } else { try interpreter.type_info.append(interpreter.allocator, type_info); @@ -632,7 +631,7 @@ pub fn huntItDown( known_decl == null and pscope.declarations.count() != getDeclCount(tree, pscope.node_idx)) { - std.log.info("Order-independent evaluating {s}...", .{decl_name}); + log.info("Order-independent evaluating {s}...", .{decl_name}); var buffer: [2]Ast.Node.Index = undefined; const members = ast.declMembers(tree, pscope.node_idx, &buffer); @@ -656,7 +655,7 @@ pub fn huntItDown( return (known_decl orelse continue).value_ptr; } - std.log.err("Identifier not found: {s}", .{decl_name}); + log.err("Identifier not found: {s}", .{decl_name}); return error.IdentifierNotFound; } @@ -729,17 +728,11 @@ pub fn interpret( scope: ?*InterpreterScope, options: InterpretOptions, ) InterpretError!InterpretResult { - // _ = unit; - // _ = node; - // _ = observe_values; - const tree = interpreter.handle.tree; const tags = tree.nodes.items(.tag); const data = tree.nodes.items(.data); const main_tokens = tree.nodes.items(.main_token); - std.log.info("{s}: {any}", .{ interpreter.handle.uri, tags[node_idx] }); - switch (tags[node_idx]) { .container_decl, .container_decl_trailing, @@ -807,8 +800,6 @@ pub fn interpret( // .value = null, }; - // std.log.info("FIELD: {s}", .{name}); - try cont_type.getTypeInfoMutable().@"struct".fields.put(interpreter.allocator, name, field); } else { _ = try interpreter.interpret(member, container_scope, options); @@ -1140,7 +1131,7 @@ pub fn interpret( const import_str = tree.tokenSlice(main_tokens[import_param]); - std.log.info("Resolving {s} from {s}", .{ import_str[1 .. import_str.len - 1], interpreter.handle.uri }); + log.info("Resolving {s} from {s}", .{ import_str[1 .. import_str.len - 1], interpreter.handle.uri }); var import_uri = (try interpreter.document_store.uriFromImportStr(interpreter.allocator, interpreter.handle.*, import_str[1 .. import_str.len - 1])) orelse return error.ImportFailure; defer interpreter.allocator.free(import_uri); @@ -1198,8 +1189,7 @@ pub fn interpret( return InterpretResult{ .value = try interpreter.cast(node_idx, as_type.value_data.@"type", value) }; } - std.log.info("Builtin not implemented: {s}", .{call_name}); - // @panic("Builtin not implemented"); + log.err("Builtin not implemented: {s}", .{call_name}); return error.InvalidBuiltin; }, .string_literal => { @@ -1313,8 +1303,6 @@ pub fn interpret( try args.append(interpreter.allocator, try (try interpreter.interpret(param, scope, .{})).getValue()); } - std.log.err("AEWEWEWE: {s}", .{tree.getNodeSource(call_full.ast.fn_expr)}); - const func_id_result = try interpreter.interpret(call_full.ast.fn_expr, interpreter.root_type.?.getTypeInfo().getScopeOfType().?, .{}); const func_id_val = try func_id_result.getValue(); @@ -1390,7 +1378,7 @@ pub fn interpret( }; }, else => { - std.log.err("Unhandled {any}", .{tags[node_idx]}); + log.err("Unhandled {any}", .{tags[node_idx]}); return InterpretResult{ .nothing = {} }; }, } From c8dffc1f9b1d92acf6fc6476dfd868255674221f Mon Sep 17 00:00:00 2001 From: Auguste Rame <19855629+SuperAuguste@users.noreply.github.com> Date: Thu, 10 Nov 2022 20:51:02 -0500 Subject: [PATCH 12/12] Some comptime interpreter fixes --- src/ComptimeInterpreter.zig | 49 +++++++++++------ src/DocumentStore.zig | 2 +- src/Server.zig | 30 ++++++++++- src/zls.zig | 5 +- .../comptime_interpreter.zig | 54 ++++++++++++++----- tests/tests.zig | 2 +- 6 files changed, 109 insertions(+), 33 deletions(-) diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig index be300db..619c3cc 100644 --- a/src/ComptimeInterpreter.zig +++ b/src/ComptimeInterpreter.zig @@ -17,7 +17,7 @@ const log = std.log.scoped(.comptime_interpreter); allocator: std.mem.Allocator, document_store: *DocumentStore, -handle: *const DocumentStore.Handle, +uri: DocumentStore.Uri, root_type: ?Type = null, /// Interpreter diagnostic errors @@ -30,6 +30,11 @@ type_info_map: std.HashMapUnmanaged(TypeInfo, usize, TypeInfo.Context, std.hash_ // TODO: Use DOD value_data_list: std.ArrayListUnmanaged(*ValueData) = .{}, +pub fn getHandle(interpreter: *ComptimeInterpreter) *const DocumentStore.Handle { + // This interpreter is loaded from a known-valid handle so a valid handle must exist + return interpreter.document_store.getOrLoadHandle(interpreter.uri).?; +} + pub const InterpreterError = struct { code: []const u8, message: []const u8, @@ -231,9 +236,7 @@ pub const ValueData = union(enum) { @"type": Type, @"bool": bool, - // @"struct": struct { - - // }, + @"struct": struct {}, /// This is what a pointer is; we don't need to map /// this to anything because @ptrToInt is comptime-illegal /// Pointer equality scares me though :( (but that's for later) @@ -311,7 +314,7 @@ pub const Declaration = struct { pub fn getValue(decl: *Declaration) InterpretError!Value { var interpreter = decl.scope.interpreter; - const tree = decl.scope.interpreter.handle.tree; + const tree = decl.scope.interpreter.getHandle().tree; const tags = tree.nodes.items(.tag); if (decl.value == null) { @@ -350,7 +353,7 @@ pub const Declaration = struct { } pub fn isConstant(declaration: Declaration) bool { - const tree = declaration.scope.interpreter.handle.tree; + const tree = declaration.scope.interpreter.getHandle().tree; return switch (tree.nodes.items(.tag)[declaration.node_idx]) { .global_var_decl, .local_var_decl, @@ -491,7 +494,7 @@ pub const InterpreterScope = struct { pub const ScopeKind = enum { container, block, function }; pub fn scopeKind(scope: InterpreterScope) ScopeKind { - const tree = scope.interpreter.handle.tree; + const tree = scope.interpreter.getHandle().tree; return switch (tree.nodes.items(.tag)[scope.node_idx]) { .container_decl, .container_decl_trailing, @@ -513,7 +516,7 @@ pub const InterpreterScope = struct { } pub fn getLabel(scope: InterpreterScope) ?Ast.TokenIndex { - const tree = scope.interpreter.handle.tree; + const tree = scope.interpreter.getHandle().tree; const token_tags = tree.tokens.items(.tag); return switch (scope.scopeKind()) { @@ -621,7 +624,7 @@ pub fn huntItDown( decl_name: []const u8, options: InterpretOptions, ) InterpretError!*Declaration { - const tree = interpreter.handle.tree; + const tree = interpreter.getHandle().tree; const tags = tree.nodes.items(.tag); var psi = scope.parentScopeIterator(); @@ -728,7 +731,7 @@ pub fn interpret( scope: ?*InterpreterScope, options: InterpretOptions, ) InterpretError!InterpretResult { - const tree = interpreter.handle.tree; + const tree = interpreter.getHandle().tree; const tags = tree.nodes.items(.tag); const data = tree.nodes.items(.data); const main_tokens = tree.nodes.items(.main_token); @@ -833,9 +836,11 @@ pub fn interpret( .name = name, }); - if (scope.?.scopeKind() != .container) { + // TODO: Am I a dumbo shrimp? (e.g. is this tree shaking correct? works on my machine so like...) + + // if (scope.?.scopeKind() != .container) { + if (scope.?.node_idx != 0) _ = try scope.?.declarations.getPtr(name).?.getValue(); - } return InterpretResult{ .nothing = {} }; }, @@ -969,8 +974,7 @@ pub fn interpret( var irv = try ir.getValue(); var sub_scope = irv.value_data.@"type".getTypeInfo().getScopeOfType() orelse return error.IdentifierNotFound; - // var scope_sub_decl = sub_scope.declarations.get(rhs_str) orelse return error.IdentifierNotFound; - var scope_sub_decl = irv.value_data.@"type".interpreter.huntItDown(sub_scope, rhs_str, options) catch |err| { + var scope_sub_decl = sub_scope.interpreter.huntItDown(sub_scope, rhs_str, options) catch |err| { if (err == error.IdentifierNotFound) try interpreter.recordError( node_idx, "undeclared_identifier", @@ -1131,8 +1135,19 @@ pub fn interpret( const import_str = tree.tokenSlice(main_tokens[import_param]); - log.info("Resolving {s} from {s}", .{ import_str[1 .. import_str.len - 1], interpreter.handle.uri }); - var import_uri = (try interpreter.document_store.uriFromImportStr(interpreter.allocator, interpreter.handle.*, import_str[1 .. import_str.len - 1])) orelse return error.ImportFailure; + log.info("Resolving {s} from {s}", .{ import_str[1 .. import_str.len - 1], interpreter.uri }); + + // TODO: Implement root support + if (std.mem.eql(u8, import_str[1 .. import_str.len - 1], "root")) { + return InterpretResult{ .value = Value{ + .interpreter = interpreter, + .node_idx = node_idx, + .@"type" = try interpreter.createType(node_idx, .{ .@"struct" = .{ .scope = try interpreter.newScope(null, 0) } }), + .value_data = try interpreter.createValueData(.{ .@"struct" = .{} }), + } }; + } + + var import_uri = (try interpreter.document_store.uriFromImportStr(interpreter.allocator, interpreter.getHandle().*, import_str[1 .. import_str.len - 1])) orelse return error.ImportFailure; defer interpreter.allocator.free(import_uri); var handle = interpreter.document_store.getOrLoadHandle(import_uri) orelse return error.ImportFailure; @@ -1403,7 +1418,7 @@ pub fn call( // TODO: type check args - const tree = interpreter.handle.tree; + const tree = interpreter.getHandle().tree; const tags = tree.nodes.items(.tag); std.debug.assert(tags[func_node_idx] == .fn_decl); diff --git a/src/DocumentStore.zig b/src/DocumentStore.zig index ccd01f1..2b82bfe 100644 --- a/src/DocumentStore.zig +++ b/src/DocumentStore.zig @@ -944,7 +944,7 @@ pub fn ensureInterpreterExists(self: *DocumentStore, uri: Uri) !void { int.* = ComptimeInterpreter{ .allocator = self.allocator, .document_store = self, - .handle = handle, + .uri = uri, }; handle.interpreter = int; _ = try int.interpret(0, null, .{}); diff --git a/src/Server.zig b/src/Server.zig index f4e3ac4..c759c7d 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -2776,7 +2776,35 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void } } - const method_map = .{ .{ "initialized", void, initializedHandler }, .{"$/cancelRequest"}, .{"textDocument/willSave"}, .{ "initialize", requests.Initialize, initializeHandler }, .{ "shutdown", void, shutdownHandler }, .{ "exit", void, exitHandler }, .{ "textDocument/didOpen", requests.OpenDocument, openDocumentHandler }, .{ "textDocument/didChange", requests.ChangeDocument, changeDocumentHandler }, .{ "textDocument/didSave", requests.SaveDocument, saveDocumentHandler }, .{ "textDocument/didClose", requests.CloseDocument, closeDocumentHandler }, .{ "textDocument/semanticTokens/full", requests.SemanticTokensFull, semanticTokensFullHandler }, .{ "textDocument/inlayHint", requests.InlayHint, inlayHintHandler }, .{ "textDocument/completion", requests.Completion, completionHandler }, .{ "textDocument/signatureHelp", requests.SignatureHelp, signatureHelpHandler }, .{ "textDocument/definition", requests.GotoDefinition, gotoDefinitionHandler }, .{ "textDocument/typeDefinition", requests.GotoDefinition, gotoDefinitionHandler }, .{ "textDocument/implementation", requests.GotoDefinition, gotoDefinitionHandler }, .{ "textDocument/declaration", requests.GotoDeclaration, gotoDeclarationHandler }, .{ "textDocument/hover", requests.Hover, hoverHandler }, .{ "textDocument/documentSymbol", requests.DocumentSymbols, documentSymbolsHandler }, .{ "textDocument/formatting", requests.Formatting, formattingHandler }, .{ "textDocument/rename", requests.Rename, renameHandler }, .{ "textDocument/references", requests.References, referencesHandler }, .{ "textDocument/documentHighlight", requests.DocumentHighlight, documentHighlightHandler }, .{ "textDocument/codeAction", requests.CodeAction, codeActionHandler }, .{ "workspace/didChangeConfiguration", Config.DidChangeConfigurationParams, didChangeConfigurationHandler }, .{ "textDocument/foldingRange", requests.FoldingRange, foldingRangeHandler } }; + const method_map = .{ + .{ "initialized", void, initializedHandler }, + .{"$/cancelRequest"}, + .{"textDocument/willSave"}, + .{ "initialize", requests.Initialize, initializeHandler }, + .{ "shutdown", void, shutdownHandler }, + .{ "exit", void, exitHandler }, + .{ "textDocument/didOpen", requests.OpenDocument, openDocumentHandler }, + .{ "textDocument/didChange", requests.ChangeDocument, changeDocumentHandler }, + .{ "textDocument/didSave", requests.SaveDocument, saveDocumentHandler }, + .{ "textDocument/didClose", requests.CloseDocument, closeDocumentHandler }, + .{ "textDocument/semanticTokens/full", requests.SemanticTokensFull, semanticTokensFullHandler }, + .{ "textDocument/inlayHint", requests.InlayHint, inlayHintHandler }, + .{ "textDocument/completion", requests.Completion, completionHandler }, + .{ "textDocument/signatureHelp", requests.SignatureHelp, signatureHelpHandler }, + .{ "textDocument/definition", requests.GotoDefinition, gotoDefinitionHandler }, + .{ "textDocument/typeDefinition", requests.GotoDefinition, gotoDefinitionHandler }, + .{ "textDocument/implementation", requests.GotoDefinition, gotoDefinitionHandler }, + .{ "textDocument/declaration", requests.GotoDeclaration, gotoDeclarationHandler }, + .{ "textDocument/hover", requests.Hover, hoverHandler }, + .{ "textDocument/documentSymbol", requests.DocumentSymbols, documentSymbolsHandler }, + .{ "textDocument/formatting", requests.Formatting, formattingHandler }, + .{ "textDocument/rename", requests.Rename, renameHandler }, + .{ "textDocument/references", requests.References, referencesHandler }, + .{ "textDocument/documentHighlight", requests.DocumentHighlight, documentHighlightHandler }, + .{ "textDocument/codeAction", requests.CodeAction, codeActionHandler }, + .{ "workspace/didChangeConfiguration", Config.DidChangeConfigurationParams, didChangeConfigurationHandler }, + .{ "textDocument/foldingRange", requests.FoldingRange, foldingRangeHandler }, + }; if (zig_builtin.zig_backend == .stage1) { // Hack to avoid `return`ing in the inline for, which causes bugs. diff --git a/src/zls.zig b/src/zls.zig index 40920b2..34290c1 100644 --- a/src/zls.zig +++ b/src/zls.zig @@ -1,4 +1,6 @@ -// used by tests as a package +// Used by tests as a package, can be used by tools such as +// zigbot9001 to take advantage of zls' tools + pub const analysis = @import("analysis.zig"); pub const header = @import("header.zig"); pub const offsets = @import("offsets.zig"); @@ -8,4 +10,5 @@ pub const Server = @import("Server.zig"); pub const translate_c = @import("translate_c.zig"); pub const types = @import("types.zig"); pub const URI = @import("uri.zig"); +pub const DocumentStore = @import("DocumentStore.zig"); pub const ComptimeInterpreter = @import("ComptimeInterpreter.zig"); diff --git a/tests/language_features/comptime_interpreter.zig b/tests/language_features/comptime_interpreter.zig index 79e6743..ae9311e 100644 --- a/tests/language_features/comptime_interpreter.zig +++ b/tests/language_features/comptime_interpreter.zig @@ -8,35 +8,51 @@ const ComptimeInterpreter = zls.ComptimeInterpreter; const allocator: std.mem.Allocator = std.testing.allocator; test "ComptimeInterpreter - basic test" { - var tree = try std.zig.parse(allocator, + var config = zls.Config{}; + var doc_store = zls.DocumentStore{ + .allocator = allocator, + .config = &config, + }; + defer doc_store.deinit(); + + _ = try doc_store.openDocument("file:///file.zig", \\pub fn ReturnMyType(comptime my_arg: bool) type { \\ var abc = z: {break :z if (!my_arg) 123 else 0;}; \\ if (abc == 123) return u69; \\ return u8; \\} ); - defer tree.deinit(allocator); - var interpreter = ComptimeInterpreter{ .tree = tree, .allocator = allocator }; + var interpreter = ComptimeInterpreter{ + .allocator = allocator, + .document_store = &doc_store, + .uri = "file:///file.zig", + }; defer interpreter.deinit(); + _ = try interpreter.interpret(0, null, .{}); + var bool_type = try interpreter.createType(std.math.maxInt(std.zig.Ast.Node.Index), .{ .@"bool" = .{} }); var arg_false = ComptimeInterpreter.Value{ + .interpreter = &interpreter, .node_idx = std.math.maxInt(std.zig.Ast.Node.Index), .@"type" = bool_type, - .value_data = .{ .@"bool" = false }, + .value_data = try interpreter.createValueData(.{ .@"bool" = false }), }; var arg_true = ComptimeInterpreter.Value{ + .interpreter = &interpreter, .node_idx = std.math.maxInt(std.zig.Ast.Node.Index), .@"type" = bool_type, - .value_data = .{ .@"bool" = true }, + .value_data = try interpreter.createValueData(.{ .@"bool" = true }), }; - const call_with_false = try interpreter.call(tree.rootDecls()[0], &.{ + const rmt = interpreter.root_type.?.getTypeInfo().@"struct".scope.declarations.get("ReturnMyType").?; + + const call_with_false = try interpreter.call(null, rmt.node_idx, &.{ arg_false, }, .{}); defer call_with_false.scope.deinit(); - const call_with_true = try interpreter.call(tree.rootDecls()[0], &.{ + const call_with_true = try interpreter.call(null, rmt.node_idx, &.{ arg_true, }, .{}); defer call_with_true.scope.deinit(); @@ -46,7 +62,14 @@ test "ComptimeInterpreter - basic test" { } test "ComptimeInterpreter - struct" { - var tree = try std.zig.parse(allocator, + var config = zls.Config{}; + var doc_store = zls.DocumentStore{ + .allocator = allocator, + .config = &config, + }; + defer doc_store.deinit(); + + _ = try doc_store.openDocument("file:///file.zig", \\pub fn ReturnMyType() type { \\ return struct { \\ slay: bool, @@ -54,13 +77,20 @@ test "ComptimeInterpreter - struct" { \\ }; \\} ); - defer tree.deinit(allocator); - var interpreter = ComptimeInterpreter{ .tree = tree, .allocator = allocator }; + var interpreter = ComptimeInterpreter{ + .allocator = allocator, + .document_store = &doc_store, + .uri = "file:///file.zig", + }; defer interpreter.deinit(); - const z = try interpreter.call(tree.rootDecls()[0], &.{}, .{}); + _ = try interpreter.interpret(0, null, .{}); + + const rmt = interpreter.root_type.?.getTypeInfo().@"struct".scope.declarations.get("ReturnMyType").?; + + const z = try interpreter.call(null, rmt.node_idx, &.{}, .{}); defer z.scope.deinit(); - try std.testing.expectFmt("struct {slay: bool, const abc: comptime_int = TODO_PRINT_VALUES, }", "{any}", .{interpreter.formatTypeInfo(interpreter.typeToTypeInfo(z.result.value.value_data.@"type"))}); + try std.testing.expectFmt("struct {slay: bool, var abc: comptime_int = 123, }", "{any}", .{interpreter.formatTypeInfo(z.result.value.value_data.@"type".getTypeInfo())}); } diff --git a/tests/tests.zig b/tests/tests.zig index e1480d7..f6a97a9 100644 --- a/tests/tests.zig +++ b/tests/tests.zig @@ -17,5 +17,5 @@ comptime { // Language features _ = @import("language_features/cimport.zig"); - // _ = @import("language_features/comptime_interpreter.zig"); + _ = @import("language_features/comptime_interpreter.zig"); }