From 3497e6c0a8d4406601d229f28ae30d70a1029d12 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Tue, 12 Mar 2024 17:01:33 +0100 Subject: [PATCH] feat: New value ObjRange Range are now a special value that can be transformed into an [int] with toList. This allows to use them in a foreach statement without instanciating a list. closes #170 --- CHANGELOG.md | 5 ++ src/Ast.zig | 13 +++- src/Chunk.zig | 3 + src/Codegen.zig | 37 ++++++++++ src/Jit.zig | 159 +++++++++++++--------------------------- src/Parser.zig | 73 +++++++++++++++++- src/Token.zig | 2 + src/builtin.zig | 1 + src/builtin/range.zig | 34 +++++++++ src/buzz_api.zig | 78 +++++++++++++++++--- src/disassembler.zig | 9 +++ src/jit_extern_api.zig | 56 ++++++++++++++ src/lib/buzz_api.zig | 6 ++ src/memory.zig | 28 +++++++ src/obj.zig | 147 +++++++++++++++++++++++++++++++++++++ src/scanner.zig | 1 + src/vm.zig | 147 ++++++++++++++++++++++++++++++------- tests/041-iterator.buzz | 4 +- tests/053-range.buzz | 18 ++++- 19 files changed, 668 insertions(+), 153 deletions(-) create mode 100644 src/builtin/range.zig diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bee95cf..092c02a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,11 @@ var value = from { - `namespace` (https://github.com/buzz-language/buzz/issues/271): if a script exports at least one symbol, it has to define a namespace for the script with `namespace mynamespace` - By default, imported symbols from another script will be under `libprefix.XXXX` - When importing something, you can still redefine its namespace prefix with `import "..." as mynewnamespace` or remove it altogether with `import "..." _` +- Ranges are now an actual buzz value (https://github.com/buzz-language/buzz/issues/170) + - new `range` type + - `myrange.toList()` transforms a range into a list of integers + - `myrange.low` and `myrange.high` to get a range bounds + - works with `foreach` ## Changed - Map type notation has changed from `{K, V}` to `{K: V}`. Similarly map expression with specified typed went from `{, ...}` to `{, ...}` (https://github.com/buzz-language/buzz/issues/253) diff --git a/src/Ast.zig b/src/Ast.zig index 5398902d..bbcd321e 100644 --- a/src/Ast.zig +++ b/src/Ast.zig @@ -583,7 +583,6 @@ pub fn isConstant(self: Self, node: Node.Index) bool { .ObjectDeclaration, .ObjectInit, .ProtocolDeclaration, - .Range, .Resolve, .Resume, .Return, @@ -598,6 +597,7 @@ pub fn isConstant(self: Self, node: Node.Index) bool { => false, .As => self.isConstant(self.nodes.items(.components)[node].As.left), .Is => self.isConstant(self.nodes.items(.components)[node].Is.left), + .Range => self.isConstant(self.nodes.items(.components)[node].Range.low) and self.isConstant(self.nodes.items(.components)[node].Range.high), .Binary => { const components = self.nodes.items(.components)[node].Binary; @@ -946,6 +946,17 @@ pub fn toValue(self: Self, node: Node.Index, gc: *GarbageCollector) Error!Value else try self.toValue(components.else_branch.?, gc); }, + .Range => range: { + const components = self.nodes.items(.components)[node].Range; + + break :range (try gc.allocateObject( + obj.ObjRange, + .{ + .low = (try self.toValue(components.low, gc)).integer(), + .high = (try self.toValue(components.high, gc)).integer(), + }, + )).toValue(); + }, .List => list: { const components = self.nodes.items(.components)[node].List; const type_def = self.nodes.items(.type_def)[node]; diff --git a/src/Chunk.zig b/src/Chunk.zig index cf5f47e9..b756b3b4 100644 --- a/src/Chunk.zig +++ b/src/Chunk.zig @@ -60,6 +60,7 @@ pub const OpCode = enum(u8) { OP_LOOP, OP_STRING_FOREACH, OP_LIST_FOREACH, + OP_RANGE_FOREACH, OP_ENUM_FOREACH, OP_MAP_FOREACH, OP_FIBER_FOREACH, @@ -73,6 +74,7 @@ pub const OpCode = enum(u8) { OP_FIBER_INVOKE, OP_LIST_INVOKE, OP_MAP_INVOKE, + OP_RANGE_INVOKE, OP_CLOSURE, OP_CLOSE_UPVALUE, @@ -102,6 +104,7 @@ pub const OpCode = enum(u8) { OP_GET_STRING_PROPERTY, OP_GET_PATTERN_PROPERTY, OP_GET_FIBER_PROPERTY, + OP_GET_RANGE_PROPERTY, OP_SET_OBJECT_PROPERTY, OP_SET_INSTANCE_PROPERTY, OP_SET_FCONTAINER_INSTANCE_PROPERTY, diff --git a/src/Codegen.zig b/src/Codegen.zig index 2880252a..cdb484c0 100644 --- a/src/Codegen.zig +++ b/src/Codegen.zig @@ -1346,6 +1346,7 @@ fn generateCall(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize .Fiber => .OP_FIBER_INVOKE, .List => .OP_LIST_INVOKE, .Map => .OP_MAP_INVOKE, + .Range => .OP_RANGE_INVOKE, else => unexpected: { std.debug.assert(self.reporter.had_error); break :unexpected if (components.tail_call) @@ -1425,6 +1426,7 @@ fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize) .Pattern, .Fiber, .ForeignContainer, + .Range, => {}, else => self.reporter.reportErrorAt( .field_access, @@ -1450,6 +1452,7 @@ fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize) .String => .OP_GET_STRING_PROPERTY, .Pattern => .OP_GET_PATTERN_PROPERTY, .Fiber => .OP_GET_FIBER_PROPERTY, + .Range => .OP_GET_RANGE_PROPERTY, else => null, }; @@ -1535,6 +1538,21 @@ fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize) @intCast(components.value_or_call_or_enum.EnumCase), ); }, + .Range => { + if (components.member_kind == .Call) { + try self.emitOpCode(locations[node], .OP_COPY); + + _ = try self.generateNode(components.value_or_call_or_enum.Call, breaks); + } else { + std.debug.assert(components.member_kind != .Value); + + try self.emitCodeArg( + locations[node], + get_code.?, + try self.identifierConstant(identifier_lexeme), + ); + } + }, .EnumInstance => { std.debug.assert(std.mem.eql(u8, identifier_lexeme, "value")); @@ -1887,6 +1905,11 @@ fn generateForEach(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(us self.ast.tokens.get(locations[components.key]), "No key available when iterating over enum.", ), + .Range => self.reporter.reportErrorAt( + .foreach_key_type, + self.ast.tokens.get(locations[components.key]), + "No key available when iterating over range.", + ), else => self.reporter.reportErrorAt( .foreach_iterable, self.ast.tokens.get(locations[components.iterable]), @@ -1931,6 +1954,18 @@ fn generateForEach(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(us ); } }, + .Range => { + if (value_type_def.def_type != .Integer or value_type_def.optional) { + self.reporter.reportTypeCheck( + .foreach_value_type, + self.ast.tokens.get(locations[components.iterable]), + try self.gc.type_registry.getTypeDef(.{ .def_type = .Integer }), + self.ast.tokens.get(locations[components.value]), + value_type_def, + "Bad value type", + ); + } + }, .String => { if (value_type_def.def_type != .String) { self.reporter.reportErrorAt( @@ -1986,6 +2021,7 @@ fn generateForEach(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(us .Map => obj.ObjMap.cast(iterable).?.map.count() == 0, .String => obj.ObjString.cast(iterable).?.string.len == 0, .Enum => obj.ObjEnum.cast(iterable).?.cases.len == 0, + .Range => obj.ObjRange.cast(iterable).?.high == obj.ObjRange.cast(iterable).?.low, else => self.reporter.had_error, }) { try self.patchOptJumps(node); @@ -2008,6 +2044,7 @@ fn generateForEach(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(us .Enum => .OP_ENUM_FOREACH, .Map => .OP_MAP_FOREACH, .Fiber => .OP_FIBER_FOREACH, + .Range => .OP_RANGE_FOREACH, else => unexpected: { std.debug.assert(self.reporter.had_error); break :unexpected .OP_STRING_FOREACH; diff --git a/src/Jit.zig b/src/Jit.zig index f9ecfd47..d4e5fd1b 100644 --- a/src/Jit.zig +++ b/src/Jit.zig @@ -1412,6 +1412,7 @@ fn unwrap(self: *Self, def_type: o.ObjTypeDef.Type, value: m.MIR_op_t, dest: m.M .Type, .Fiber, .UserData, + .Range, => self.buildValueToObj(value, dest), .ForeignContainer => try self.buildValueToForeignContainer(value, dest), .Placeholder, @@ -1442,6 +1443,7 @@ fn wrap(self: *Self, def_type: o.ObjTypeDef.Type, value: m.MIR_op_t, dest: m.MIR .Type, .Fiber, .UserData, + .Range, => self.buildValueFromObj(value, dest), .ForeignContainer, .Placeholder, @@ -1682,6 +1684,7 @@ fn generateCall(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { .Fiber, .List, .Map, + .Range, => try self.buildExternApiCall( switch (invoked_on.?) { .ObjectInstance, .ProtocolInstance => .bz_getInstanceField, @@ -1690,6 +1693,7 @@ fn generateCall(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { .Fiber => .bz_getFiberField, .List => .bz_getListField, .Map => .bz_getMapField, + .Range => .bz_getRangeField, else => unreachable, }, callee, @@ -2825,123 +2829,23 @@ fn generateList(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { fn generateRange(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { const components = self.state.?.ast.nodes.items(.components)[node].Range; - const type_def = self.state.?.ast.nodes.items(.type_def)[node]; - - const new_list = m.MIR_new_reg_op( - self.ctx, - try self.REG("new_list", m.MIR_T_I64), - ); - - try self.buildExternApiCall( - .bz_newList, - new_list, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - m.MIR_new_uint_op(self.ctx, type_def.?.resolved_type.?.List.item_type.toValue().val), - }, - ); - - // Prevent collection - try self.buildPush(new_list); - - const low = (try self.generateNode(components.low)).?; - const high = (try self.generateNode(components.high)).?; - - const current = m.MIR_new_reg_op( - self.ctx, - try self.REG("current", m.MIR_T_I64), - ); - self.MOV(current, low); - const reached_limit = m.MIR_new_reg_op( - self.ctx, - try self.REG("reached_limit", m.MIR_T_I64), - ); - const unwrapped_low = m.MIR_new_reg_op( - self.ctx, - try self.REG("unwrapped_low", m.MIR_T_I64), - ); - try self.unwrap(.Integer, low, unwrapped_low); - - const unwrapped_high = m.MIR_new_reg_op( - self.ctx, - try self.REG("unwrapped_high", m.MIR_T_I64), - ); - try self.unwrap(.Integer, high, unwrapped_high); - // Select increment - const is_negative_delta = m.MIR_new_reg_op( + const new_range = m.MIR_new_reg_op( self.ctx, - try self.REG("is_negative_delta", m.MIR_T_I64), - ); - self.GES(is_negative_delta, unwrapped_low, unwrapped_high); - - const neg_loop_label = m.MIR_new_label(self.ctx); - self.BEQ( - m.MIR_new_label_op(self.ctx, neg_loop_label), - is_negative_delta, - m.MIR_new_uint_op(self.ctx, 1), - ); - - const exit_label = m.MIR_new_label(self.ctx); - - const pos_loop_label = m.MIR_new_label(self.ctx); - self.append(pos_loop_label); - - self.GES(reached_limit, unwrapped_low, unwrapped_high); - self.BEQ( - m.MIR_new_label_op(self.ctx, exit_label), - reached_limit, - m.MIR_new_uint_op(self.ctx, 1), + try self.REG("new_range", m.MIR_T_I64), ); - // Add new element try self.buildExternApiCall( - .bz_listAppend, - null, + .bz_newRange, + new_range, &[_]m.MIR_op_t{ m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - new_list, - current, + (try self.generateNode(components.low)).?, + (try self.generateNode(components.high)).?, }, ); - // Increment - self.ADDS(unwrapped_low, unwrapped_low, m.MIR_new_uint_op(self.ctx, 1)); - self.wrap(.Integer, unwrapped_low, current); - - self.JMP(pos_loop_label); - - self.append(neg_loop_label); - - self.LES(reached_limit, unwrapped_low, unwrapped_high); - self.BEQ( - m.MIR_new_label_op(self.ctx, exit_label), - reached_limit, - m.MIR_new_uint_op(self.ctx, 1), - ); - - // Add new element - try self.buildExternApiCall( - .bz_listAppend, - null, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - new_list, - current, - }, - ); - - // Increment - self.SUBS(unwrapped_low, unwrapped_low, m.MIR_new_uint_op(self.ctx, 1)); - self.wrap(.Integer, unwrapped_low, current); - - self.JMP(neg_loop_label); - - self.append(exit_label); - - try self.buildPop(null); // Pop list - - return new_list; + return new_range; } fn generateMap(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { @@ -3067,6 +2971,30 @@ fn generateDot(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { } }, + .Range => { + switch (components.member_kind) { + .Call => return try self.generateCall(components.value_or_call_or_enum.Call), + else => { + const res = m.MIR_new_reg_op( + self.ctx, + try self.REG("res", m.MIR_T_I64), + ); + try self.buildExternApiCall( + .bz_getRangeField, + res, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + (try self.generateNode(components.callee)).?, + m.MIR_new_uint_op(self.ctx, member_identifier), + m.MIR_new_uint_op(self.ctx, 1), + }, + ); + + return res; + }, + } + }, + .Object => { switch (components.member_kind) { .Call => return try self.generateCall(components.value_or_call_or_enum.Call), @@ -3940,6 +3868,7 @@ fn generateForEach(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { .Map => o.ObjMap.cast(iterable.obj()).?.map.count() == 0, .String => o.ObjString.cast(iterable.obj()).?.string.len == 0, .Enum => o.ObjEnum.cast(iterable.obj()).?.cases.len == 0, + .Range => o.ObjRange.cast(iterable.obj()).?.high == o.ObjRange.cast(iterable.obj()).?.low, else => unreachable, }) { return null; @@ -3982,6 +3911,22 @@ fn generateForEach(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { }, ); + // If next key is null stop, otherwise do loop + self.BEQ( + m.MIR_new_label_op(self.ctx, out_label), + try self.LOAD(value_ptr), + m.MIR_new_uint_op(self.ctx, Value.Null.val), + ); + } else if (iterable_type_def.?.def_type == .Range) { + try self.buildExternApiCall( + .bz_rangeNext, + try self.LOAD(value_ptr), + &[_]m.MIR_op_t{ + iterable, + try self.LOAD(value_ptr), + }, + ); + // If next key is null stop, otherwise do loop self.BEQ( m.MIR_new_label_op(self.ctx, out_label), diff --git a/src/Parser.zig b/src/Parser.zig index 77ec928c..09fe6482 100644 --- a/src/Parser.zig +++ b/src/Parser.zig @@ -467,6 +467,7 @@ const rules = [_]ParseRule{ .{ .prefix = null, .infix = null, .precedence = .None }, // var .{ .prefix = null, .infix = null, .precedence = .None }, // out .{ .prefix = null, .infix = null, .precedence = .None }, // namespace + .{ .prefix = null, .infix = null, .precedence = .None }, // range }; ast: Ast, @@ -1296,6 +1297,14 @@ fn declaration(self: *Self) Error!?Ast.Node.Index { constant, true, ) + else if (try self.match(.Range)) + try self.varDeclaration( + false, + try self.simpleType(.Range), + .Semicolon, + constant, + true, + ) else if (try self.match(.Type)) try self.varDeclaration( false, @@ -2419,6 +2428,25 @@ fn parseTypeDef( }, }, ); + } else if (try self.match(.Range)) { + const optional = try self.match(.Question); + + return self.ast.appendNode( + .{ + .tag = .SimpleType, + .location = self.current_token.? - 1, + .end_location = self.current_token.? - 1, + .type_def = try self.gc.type_registry.getTypeDef( + .{ + .optional = optional, + .def_type = .Range, + }, + ), + .components = .{ + .SimpleType = {}, + }, + }, + ); } else if (try self.match(.Any)) { const optional = try self.match(.Question); @@ -3919,6 +3947,49 @@ fn dot(self: *Self, can_assign: bool, callee: Ast.Node.Index) Error!Ast.Node.Ind self.reportError(.property_does_not_exists, "String property doesn't exist."); } }, + .Range => { + if (try obj.ObjRange.memberDef(self, member_name)) |member_type_def| { + const generic_resolve = if (try self.match(.DoubleColon)) + try self.parseGenericResolve(member_type_def, null) + else + null; + + self.ast.nodes.items(.components)[dot_node].Dot.generic_resolve = generic_resolve; + + const member = if (generic_resolve) |gr| + self.ast.nodes.items(.type_def)[gr] + else + member_type_def; + + if (try self.match(.LeftParen)) { + // `call` will look to the parent node for the function definition + self.ast.nodes.items(.type_def)[dot_node] = member; + var components = self.ast.nodes.items(.components); + components[dot_node].Dot.member_type_def = member.?; + components[dot_node].Dot.member_kind = .Call; + const dot_call = try self.call( + can_assign, + dot_node, + ); + components = self.ast.nodes.items(.components); + components[dot_node].Dot.value_or_call_or_enum = .{ + .Call = dot_call, + }; + + // Node type is the return type of the call + self.ast.nodes.items(.type_def)[dot_node] = self.ast.nodes.items(.type_def)[components[dot_node].Dot.value_or_call_or_enum.Call]; + } else { + // Range has only native functions + self.ast.nodes.items(.components)[dot_node].Dot.member_kind = .Ref; + self.ast.nodes.items(.type_def)[dot_node] = member; + } + } else if (std.mem.eql(u8, member_name, "high") or std.mem.eql(u8, member_name, "low")) { + self.ast.nodes.items(.components)[dot_node].Dot.member_kind = .Ref; + self.ast.nodes.items(.type_def)[dot_node] = try self.gc.type_registry.getTypeDef(.{ .def_type = .Integer }); + } else { + self.reportError(.property_does_not_exists, "Range property doesn't exist."); + } + }, .Pattern => { if (try obj.ObjPattern.memberDef(self, member_name)) |member_type_def| { const generic_resolve = if (try self.match(.DoubleColon)) @@ -5564,7 +5635,7 @@ fn range(self: *Self, _: bool, low: Ast.Node.Index) Error!Ast.Node.Index { .type_def = try self.gc.type_registry.getTypeDef( .{ .optional = false, - .def_type = .List, + .def_type = .Range, .resolved_type = resolved_type, }, ), diff --git a/src/Token.zig b/src/Token.zig index d9b69ade..f1f155fc 100644 --- a/src/Token.zig +++ b/src/Token.zig @@ -177,6 +177,7 @@ pub const Type = enum { Var, // var Out, // out Namespace, // namespace + Range, // range }; pub const keywords = std.ComptimeStringMap( @@ -217,6 +218,7 @@ pub const keywords = std.ComptimeStringMap( .{ "out", .Out }, .{ "pat", .Pat }, .{ "protocol", .Protocol }, + .{ "range", .Range }, .{ "resolve", .Resolve }, .{ "resume", .Resume }, .{ "return", .Return }, diff --git a/src/builtin.zig b/src/builtin.zig index 28e4cc8b..716c667e 100644 --- a/src/builtin.zig +++ b/src/builtin.zig @@ -3,3 +3,4 @@ pub const list = @import("builtin/list.zig"); pub const map = @import("builtin/map.zig"); pub const fiber = @import("builtin/fiber.zig"); pub const pattern = @import("builtin/pattern.zig"); +pub const range = @import("builtin/range.zig"); diff --git a/src/builtin/range.zig b/src/builtin/range.zig new file mode 100644 index 00000000..9bb98cbc --- /dev/null +++ b/src/builtin/range.zig @@ -0,0 +1,34 @@ +const obj = @import("../obj.zig"); +const Value = @import("../value.zig").Value; + +pub fn toList(ctx: *obj.NativeCtx) c_int { + const range = ctx.vm.peek(0).obj().access(obj.ObjRange, .Range, ctx.vm.gc).?; + + var list: *obj.ObjList = ctx.vm.gc.allocateObject( + obj.ObjList, + obj.ObjList.init( + ctx.vm.gc.allocator, + ctx.vm.gc.type_registry.getTypeDef( + .{ + .def_type = .Integer, + }, + ) catch @panic("Could not instanciate list"), + ), + ) catch @panic("Could not instanceiate range"); + + ctx.vm.push(Value.fromObj(list.toObj())); + + if (range.low < range.high) { + var i: i32 = range.low; + while (i < range.high) : (i += 1) { + list.rawAppend(ctx.vm.gc, Value.fromInteger(i)) catch @panic("Could not append to list"); + } + } else { + var i: i32 = range.low; + while (i > range.high) : (i -= 1) { + list.rawAppend(ctx.vm.gc, Value.fromInteger(i)) catch @panic("Could not append to list"); + } + } + + return 1; +} diff --git a/src/buzz_api.zig b/src/buzz_api.zig index aa6a0798..1c3329b8 100644 --- a/src/buzz_api.zig +++ b/src/buzz_api.zig @@ -5,7 +5,10 @@ const _vm = @import("vm.zig"); const VM = _vm.VM; const TryCtx = _vm.TryCtx; const _obj = @import("obj.zig"); -const Value = @import("value.zig").Value; +const _value = @import("value.zig"); +const Integer = _value.Integer; +const Float = _value.Float; +const Value = _value.Value; const memory = @import("memory.zig"); const Parser = @import("Parser.zig"); const CodeGen = @import("Codegen.zig"); @@ -41,6 +44,7 @@ const ObjNative = _obj.ObjNative; const ObjBoundMethod = _obj.ObjBoundMethod; const ObjFiber = _obj.ObjFiber; const ObjForeignContainer = _obj.ObjForeignContainer; +const ObjRange = _obj.ObjRange; const NativeFn = _obj.NativeFn; const NativeCtx = _obj.NativeCtx; const TypeRegistry = memory.TypeRegistry; @@ -82,12 +86,12 @@ export fn bz_pushBool(self: *VM, value: bool) void { } /// Push a float value on the stack -export fn bz_pushFloat(self: *VM, value: f64) void { +export fn bz_pushFloat(self: *VM, value: Float) void { self.push(Value.fromFloat(value)); } /// Push a integer value on the stack -export fn bz_pushInteger(self: *VM, value: i32) void { +export fn bz_pushInteger(self: *VM, value: Integer) void { self.push(Value.fromInteger(value)); } @@ -198,6 +202,12 @@ fn valueDump(value: Value, vm: *VM, seen: *std.AutoHashMap(*_obj.Obj, void), dep std.debug.print("]", .{}); }, + .Range => { + const range = ObjRange.cast(value.obj()).?; + + std.debug.print("{}..{}", .{ range.low, range.high }); + }, + .Map => { const map = ObjMap.cast(value.obj()).?; @@ -497,6 +507,16 @@ export fn bz_collect(self: *VM) bool { return true; } +export fn bz_newRange(vm: *VM, low: Integer, high: Integer) Value { + return Value.fromObj((vm.gc.allocateObject( + ObjRange, + ObjRange{ + .low = low, + .high = high, + }, + ) catch @panic("Could not create range")).toObj()); +} + export fn bz_newList(vm: *VM, of_type: Value) Value { const list_def: ObjList.ListDef = ObjList.ListDef.init( vm.gc.allocator, @@ -1351,6 +1371,27 @@ export fn bz_getListField(vm: *VM, list_value: Value, field_name_value: Value, b method.toValue(); } +export fn bz_getRangeField(vm: *VM, range_value: Value, field_name_value: Value, bind: bool) Value { + const range = ObjRange.cast(range_value.obj()).?; + const member_name = ObjString.cast(field_name_value.obj()).?; + + if (ObjRange.member(vm, member_name) catch @panic("Could not get range method")) |method| { + return if (bind) + bz_bindMethod( + vm, + range.toValue(), + Value.Null, + method.toValue(), + ) + else + method.toValue(); + } else if (std.mem.eql(u8, member_name.string, "high")) { + return Value.fromInteger(range.high); + } + + return Value.fromInteger(range.low); +} + export fn bz_getMapField(vm: *VM, map_value: Value, field_name_value: Value, bind: bool) Value { const map = ObjMap.cast(map_value.obj()).?; const method = (map.member(vm, ObjString.cast(field_name_value.obj()).?) catch @panic("Could not get map method")).?; @@ -1397,6 +1438,26 @@ export fn bz_listNext(vm: *VM, list_value: Value, index: *Value) Value { return Value.Null; } +export fn bz_rangeNext(range_value: Value, index_slot: Value) Value { + const range = ObjRange.cast(range_value.obj()).?; + + if (index_slot.integerOrNull()) |index| { + if (range.low < range.high) { + return if (index + 1 >= range.high) + Value.Null + else + Value.fromInteger(index + 1); + } else { + return if (index - 1 <= range.high) + Value.Null + else + Value.fromInteger(index - 1); + } + } + + return Value.fromInteger(range.low); +} + export fn bz_mapNext(_: *VM, map_value: Value, key: *Value) Value { const map = ObjMap.cast(map_value.obj()).?; @@ -1544,7 +1605,7 @@ export fn bz_readZigValueFromBuffer( if (ztype.Int.signedness == .signed) { break :integer Value.fromInteger( std.mem.bytesToValue( - i32, + Integer, bytes[0..4], ), ); @@ -1597,17 +1658,14 @@ export fn bz_readZigValueFromBuffer( switch (ztype.Float.bits) { 32 => { break :float Value.fromFloat( - @as( - f64, - @floatCast( - std.mem.bytesToValue(f32, bytes[0..4]), - ), + @floatCast( + std.mem.bytesToValue(f32, bytes[0..4]), ), ); }, 64 => { break :float Value.fromFloat( - std.mem.bytesToValue(f64, bytes[0..8]), + std.mem.bytesToValue(Float, bytes[0..8]), ); }, else => break :float Value.Void, diff --git a/src/disassembler.zig b/src/disassembler.zig index 01215c17..a3479f3d 100644 --- a/src/disassembler.zig +++ b/src/disassembler.zig @@ -242,6 +242,7 @@ pub fn disassembleInstruction(chunk: *Chunk, offset: usize) !usize { .OP_ENUM_FOREACH, .OP_MAP_FOREACH, .OP_FIBER_FOREACH, + .OP_RANGE_FOREACH, .OP_RESUME, .OP_YIELD, .OP_RESOLVE, @@ -281,6 +282,7 @@ pub fn disassembleInstruction(chunk: *Chunk, offset: usize) !usize { .OP_GET_STRING_PROPERTY, .OP_GET_PATTERN_PROPERTY, .OP_GET_FIBER_PROPERTY, + .OP_GET_RANGE_PROPERTY, .OP_SET_OBJECT_PROPERTY, .OP_SET_INSTANCE_PROPERTY, .OP_SET_FCONTAINER_INSTANCE_PROPERTY, @@ -303,6 +305,7 @@ pub fn disassembleInstruction(chunk: *Chunk, offset: usize) !usize { .OP_FIBER_INVOKE, .OP_LIST_INVOKE, .OP_MAP_INVOKE, + .OP_RANGE_INVOKE, => try invokeInstruction(instruction, chunk, offset), .OP_CALL, @@ -433,6 +436,12 @@ pub const DumpState = struct { out.print("$\"{s}\"", .{pattern.source}) catch unreachable; }, + .Range => { + const range = obj.ObjRange.cast(value.obj()).?; + + out.print("{}..{}", .{ range.low, range.high }) catch unreachable; + }, + .List => { const list = obj.ObjList.cast(value.obj()).?; diff --git a/src/jit_extern_api.zig b/src/jit_extern_api.zig index f071e724..b494281c 100644 --- a/src/jit_extern_api.zig +++ b/src/jit_extern_api.zig @@ -17,6 +17,7 @@ pub const ExternApi = enum { bz_objStringSubscript, bz_toString, bz_newList, + bz_newRange, bz_listAppend, bz_listGet, bz_listSet, @@ -48,12 +49,14 @@ pub const ExternApi = enum { bz_getEnumCaseValue, bz_getListField, bz_getMapField, + bz_getRangeField, bz_getEnumCaseFromValue, bz_bindMethod, bz_stringNext, bz_listNext, bz_mapNext, bz_enumNext, + bz_rangeNext, bz_clone, bz_valueToCString, bz_valueToUserData, @@ -163,6 +166,30 @@ pub const ExternApi = enum { }, }, ), + .bz_newRange => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_U64}, + 3, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "vm", + .size = undefined, + }, + .{ + .type = m.MIR_T_I32, + .name = "low", + .size = undefined, + }, + .{ + .type = m.MIR_T_I32, + .name = "high", + .size = undefined, + }, + }, + ), .bz_listAppend => m.MIR_new_proto_arr( ctx, self.pname(), @@ -341,6 +368,7 @@ pub const ExternApi = enum { ), .bz_getListField, .bz_getMapField, + .bz_getRangeField, .bz_getStringField, .bz_getPatternField, .bz_getFiberField, @@ -639,6 +667,25 @@ pub const ExternApi = enum { }, }, ), + .bz_rangeNext => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_U64}, + 2, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_U64, + .name = "range_value", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "index", + .size = undefined, + }, + }, + ), .rawfn => m.MIR_new_proto_arr( ctx, self.pname(), @@ -944,6 +991,8 @@ pub const ExternApi = enum { .bz_objStringSubscript => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjString.bz_objStringSubscript))), .bz_stringNext => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjString.bz_stringNext))), .bz_newList => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjList.bz_newList))), + .bz_newRange => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjRange.bz_newRange))), + .bz_rangeNext => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjRange.bz_rangeNext))), .bz_listAppend => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjList.bz_listAppend))), .bz_listGet => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjList.bz_listGet))), .bz_listSet => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjList.bz_listSet))), @@ -977,6 +1026,7 @@ pub const ExternApi = enum { .bz_getObjectField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjObject.bz_getObjectField))), .bz_getListField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjList.bz_getListField))), .bz_getMapField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjMap.bz_getMapField))), + .bz_getRangeField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjRange.bz_getRangeField))), .bz_getStringField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjString.bz_getStringField))), .bz_getPatternField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjPattern.bz_getPatternField))), .bz_getFiberField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjFiber.bz_getFiberField))), @@ -1019,6 +1069,8 @@ pub const ExternApi = enum { .bz_objStringSubscript => "bz_objStringSubscript", .bz_toString => "bz_toString", .bz_newList => "bz_newList", + .bz_newRange => "bz_newRange", + .bz_rangeNext => "bz_rangeNext", .bz_listAppend => "bz_listAppend", .bz_listGet => "bz_listGet", .bz_listSet => "bz_listSet", @@ -1046,6 +1098,7 @@ pub const ExternApi = enum { .bz_getStringField => "bz_getStringField", .bz_getPatternField => "bz_getPatternField", .bz_getFiberField => "bz_getFiberField", + .bz_getRangeField => "bz_getRangeField", .bz_getEnumCase => "bz_getEnumCase", .bz_getEnumCaseValue => "bz_getEnumCaseValue", .bz_getListField => "bz_getListField", @@ -1088,6 +1141,8 @@ pub const ExternApi = enum { .bz_objStringSubscript => "p_bz_objStringSubscript", .bz_toString => "p_bz_toString", .bz_newList => "p_bz_newList", + .bz_newRange => "p_bz_newRange", + .bz_rangeNext => "p_bz_rangeNext", .bz_listAppend => "p_bz_listAppend", .bz_listGet => "p_bz_listGet", .bz_listSet => "p_bz_listSet", @@ -1115,6 +1170,7 @@ pub const ExternApi = enum { .bz_getStringField => "p_bz_getStringField", .bz_getPatternField => "p_bz_getPatternField", .bz_getFiberField => "p_bz_getFiberField", + .bz_getRangeField => "p_bz_getRangeField", .bz_getEnumCase => "p_bz_getEnumCase", .bz_getEnumCaseValue => "p_bz_getEnumCaseValue", .bz_getListField => "p_bz_getListField", diff --git a/src/lib/buzz_api.zig b/src/lib/buzz_api.zig index 91698e6a..584b6e4f 100644 --- a/src/lib/buzz_api.zig +++ b/src/lib/buzz_api.zig @@ -329,6 +329,12 @@ pub const ObjString = opaque { pub extern fn bz_stringNext(vm: *VM, string_value: Value, index: *Value) Value; }; +pub const ObjRange = opaque { + pub extern fn bz_newRange(vm: *VM, low: i32, high: i32) Value; + pub extern fn bz_rangeNext(range_value: Value, index_slot: Value) Value; + pub extern fn bz_getRangeField(vm: *VM, range_value: Value, field_name_value: Value, bind: bool) Value; +}; + pub const ObjList = opaque { pub extern fn bz_newList(vm: *VM, of_type: Value) Value; pub extern fn bz_listAppend(vm: *VM, list: Value, value: Value) void; diff --git a/src/memory.zig b/src/memory.zig index 5ed757ca..13f74e73 100644 --- a/src/memory.zig +++ b/src/memory.zig @@ -33,6 +33,7 @@ const ObjUserData = _obj.ObjUserData; const ObjPattern = _obj.ObjPattern; const ObjFiber = _obj.ObjFiber; const ObjForeignContainer = _obj.ObjForeignContainer; +const ObjRange = _obj.ObjRange; pub const TypeRegistry = struct { const Self = @This(); @@ -154,6 +155,8 @@ pub const GarbageCollector = struct { objpattern_memberDefs: std.StringHashMap(*ObjTypeDef), objstring_members: std.AutoHashMap(*ObjString, *ObjNative), objstring_memberDefs: std.StringHashMap(*ObjTypeDef), + objrange_memberDefs: std.StringHashMap(*ObjTypeDef), + objrange_members: std.AutoHashMap(*ObjString, *ObjNative), full_collection_count: usize = 0, light_collection_count: usize = 0, @@ -176,6 +179,8 @@ pub const GarbageCollector = struct { .objpattern_memberDefs = std.StringHashMap(*ObjTypeDef).init(allocator), .objstring_members = std.AutoHashMap(*ObjString, *ObjNative).init(allocator), .objstring_memberDefs = std.StringHashMap(*ObjTypeDef).init(allocator), + .objrange_members = std.AutoHashMap(*ObjString, *ObjNative).init(allocator), + .objrange_memberDefs = std.StringHashMap(*ObjTypeDef).init(allocator), }; } @@ -201,6 +206,8 @@ pub const GarbageCollector = struct { self.objpattern_memberDefs.deinit(); self.objstring_members.deinit(); self.objstring_memberDefs.deinit(); + self.objrange_members.deinit(); + self.objrange_memberDefs.deinit(); } pub fn allocate(self: *Self, comptime T: type) !*T { @@ -277,6 +284,7 @@ pub const GarbageCollector = struct { ObjPattern => ObjPattern.toObj(obj), ObjFiber => ObjFiber.toObj(obj), ObjForeignContainer => ObjForeignContainer.toObj(obj), + ObjRange => ObjRange.toObj(obj), else => {}, }; @@ -310,6 +318,7 @@ pub const GarbageCollector = struct { ObjPattern => .Pattern, ObjFiber => .Fiber, ObjForeignContainer => .ForeignContainer, + ObjRange => .Range, else => {}, }, ); @@ -494,6 +503,7 @@ pub const GarbageCollector = struct { .Pattern => obj.access(ObjPattern, .Pattern, self).?.mark(self), .Fiber => obj.access(ObjFiber, .Fiber, self).?.mark(self), .ForeignContainer => obj.access(ObjForeignContainer, .ForeignContainer, self).?.mark(self), + .Range => obj.access(ObjRange, .Range, self).?.mark(self), }; if (BuildOptions.gc_debug) { @@ -665,6 +675,9 @@ pub const GarbageCollector = struct { free(self, ObjForeignContainer, obj_foreignstruct); }, + .Range => { + free(self, ObjRange, ObjRange.cast(obj).?); + }, } self.obj_collected = null; @@ -777,6 +790,21 @@ pub const GarbageCollector = struct { } } + { + var it = self.objrange_members.iterator(); + while (it.next()) |kv| { + try self.markObj(kv.key_ptr.*.toObj()); + try self.markObj(kv.value_ptr.*.toObj()); + } + } + + { + var it = self.objrange_memberDefs.iterator(); + while (it.next()) |kv| { + try self.markObj(@constCast(kv.value_ptr.*.toObj())); + } + } + if (BuildOptions.gc_debug) { std.debug.print("DONE MARKING BASIC TYPES METHOD\n", .{}); } diff --git a/src/obj.zig b/src/obj.zig index 1841857e..c9791845 100644 --- a/src/obj.zig +++ b/src/obj.zig @@ -12,6 +12,7 @@ const Parser = @import("Parser.zig"); const _memory = @import("memory.zig"); const GarbageCollector = _memory.GarbageCollector; const TypeRegistry = _memory.TypeRegistry; +const Integer = @import("value.zig").Integer; const Value = @import("value.zig").Value; const Token = @import("Token.zig"); const is_wasm = builtin.cpu.arch.isWasm(); @@ -48,6 +49,7 @@ pub const ObjType = enum { Pattern, Fiber, ForeignContainer, + Range, }; pub const Obj = struct { @@ -124,6 +126,48 @@ pub const Obj = struct { return try enum_instance.enum_ref.cases[enum_instance.case].serialize(vm, seen); }, + .Range => { + const range = self.access(ObjRange, .Range, vm.gc).?; + + const map_type = vm.gc.type_registry.getTypeDef( + .{ + .optional = false, + .def_type = .Map, + .resolved_type = .{ + .Map = ObjMap.MapDef.init( + vm.gc.allocator, + vm.gc.type_registry.getTypeDef( + .{ .def_type = .String }, + ) catch return error.OutOfMemory, + vm.gc.type_registry.getTypeDef( + .{ .def_type = .Integer }, + ) catch return error.OutOfMemory, + ), + }, + }, + ) catch return error.OutOfMemory; + + const serialized_range = vm.gc.allocateObject( + ObjMap, + ObjMap.init( + vm.gc.allocator, + map_type, + ), + ) catch return error.OutOfMemory; + + serialized_range.map.put( + (vm.gc.copyString("low") catch return error.OutOfMemory).toValue(), + Value.fromInteger(range.low), + ) catch return error.OutOfMemory; + + serialized_range.map.put( + (vm.gc.copyString("high") catch return error.OutOfMemory).toValue(), + Value.fromInteger(range.high), + ) catch return error.OutOfMemory; + + return serialized_range.toValue(); + }, + .List => { const list = self.access(ObjList, .List, vm.gc).?; @@ -300,6 +344,7 @@ pub const Obj = struct { pub fn typeOf(self: *Self, gc: *GarbageCollector) error{ OutOfMemory, NoSpaceLeft, ReachedMaximumMemoryUsage }!*ObjTypeDef { return switch (self.obj_type) { + .Range => try gc.type_registry.getTypeDef(.{ .def_type = .Range }), .String => try gc.type_registry.getTypeDef(.{ .def_type = .String }), .Pattern => try gc.type_registry.getTypeDef(.{ .def_type = .Pattern }), .Fiber => try gc.type_registry.getTypeDef(.{ .def_type = .Fiber }), @@ -335,6 +380,7 @@ pub const Obj = struct { } return switch (self.obj_type) { + .Range => type_def.def_type == .Range, .String => type_def.def_type == .String, .Pattern => type_def.def_type == .Pattern, .Fiber => ObjFiber.cast(self).?.is(type_def), @@ -450,6 +496,12 @@ pub const Obj = struct { return self_fiber.fiber == other_fiber.fiber; }, + .Range => { + const self_range = ObjRange.cast(self).?; + const other_range = ObjRange.cast(other).?; + + return self_range.low == other_range.low and self_range.high == other_range.high; + }, .Bound, .Closure, .Function, @@ -2386,6 +2438,83 @@ pub const ObjList = struct { }; }; +pub const ObjRange = struct { + const Self = @This(); + + obj: Obj = .{ .obj_type = .Range }, + + low: Integer, + high: Integer, + + pub fn mark(_: *Self, _: *GarbageCollector) void {} + + pub inline fn toObj(self: *Self) *Obj { + return &self.obj; + } + + pub inline fn toValue(self: *Self) Value { + return Value.fromObj(self.toObj()); + } + + pub inline fn cast(obj: *Obj) ?*Self { + return obj.cast(Self, .Range); + } + + const members = std.ComptimeStringMap( + NativeFn, + .{ + .{ "toList", buzz_builtin.range.toList }, + }, + ); + + const members_typedef = std.ComptimeStringMap( + []const u8, + .{ + .{ "toList", "extern Function toList() > [int]" }, + }, + ); + + pub fn member(vm: *VM, method: *ObjString) !?*ObjNative { + if (vm.gc.objrange_members.get(method)) |native| { + return native; + } + + if (members.get(method.string)) |nativeFn| { + var native: *ObjNative = try vm.gc.allocateObject( + ObjNative, + .{ + // Complains about const qualifier discard otherwise + .native = @as(*anyopaque, @ptrFromInt(@intFromPtr(nativeFn))), + }, + ); + + try vm.gc.objrange_members.put(method, native); + + // We need to mark it otherwise it could be collected by a Young gc and then badly accessed by a Full gc + vm.gc.markObj(native.toObj()) catch @panic("Could not mark obj"); + + return native; + } + + return null; + } + pub fn memberDef(parser: *Parser, method: []const u8) !?*ObjTypeDef { + if (parser.gc.objrange_memberDefs.get(method)) |umethod| { + return umethod; + } + + if (members_typedef.get(method)) |member_typedef| { + const native_type = try parser.parseTypeDefFrom(member_typedef); + + try parser.gc.objrange_memberDefs.put(method, native_type); + + return native_type; + } + + return null; + } +}; + /// Map pub const ObjMap = struct { const Self = @This(); @@ -3369,6 +3498,7 @@ pub const ObjTypeDef = struct { Type, // Something that holds a type, not an actual type UserData, Void, + Range, Enum, EnumInstance, @@ -3402,6 +3532,7 @@ pub const ObjTypeDef = struct { Type: void, UserData: void, Void: void, + Range: void, Enum: ObjEnum.EnumDef, EnumInstance: *ObjTypeDef, @@ -3499,6 +3630,7 @@ pub const ObjTypeDef = struct { .ProtocolInstance, .Any, .ForeignContainer, + .Range, => self, .Placeholder => placeholder: { @@ -3871,6 +4003,7 @@ pub const ObjTypeDef = struct { .String => try writer.writeAll("str"), .Pattern => try writer.writeAll("pat"), .Any => try writer.writeAll("any"), + .Range => try writer.writeAll("range"), .Fiber => { try writer.writeAll("fib<"); try self.resolved_type.?.Fiber.return_type.toStringRaw(writer, qualified); @@ -4239,6 +4372,7 @@ pub const ObjTypeDef = struct { .Pattern, .UserData, .Type, + .Range, .Any, => unreachable, @@ -4354,6 +4488,7 @@ pub fn cloneObject(obj: *Obj, vm: *VM) !Value { .Pattern, .Fiber, .ForeignContainer, + .Range, => return Value.fromObj(obj), .List => { @@ -4459,6 +4594,18 @@ pub fn objToString(writer: *const std.ArrayList(u8).Writer, obj: *Obj) (Allocato @intFromPtr(ObjObject.cast(obj).?), ObjObject.cast(obj).?.name.string, }), + .Range => { + const range = ObjRange.cast(obj).?; + + try writer.print( + "range: 0x{x} {}..{}", + .{ + @intFromPtr(range), + range.low, + range.high, + }, + ); + }, .List => { const list: *ObjList = ObjList.cast(obj).?; diff --git a/src/scanner.zig b/src/scanner.zig index 1e13eaff..47f614f9 100644 --- a/src/scanner.zig +++ b/src/scanner.zig @@ -670,6 +670,7 @@ pub const Scanner = struct { .AsQuestion, .Out, .Namespace, + .Range, => if (true_color) Color.keyword else Color.magenta, // Punctuation .LeftBracket, diff --git a/src/vm.zig b/src/vm.zig index f52bf388..d296e993 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -41,6 +41,7 @@ const ObjBoundMethod = _obj.ObjBoundMethod; const ObjTypeDef = _obj.ObjTypeDef; const ObjPattern = _obj.ObjPattern; const ObjForeignContainer = _obj.ObjForeignContainer; +const ObjRange = _obj.ObjRange; const cloneObject = _obj.cloneObject; const disassembleChunk = _disassembler.disassembleChunk; const dumpStack = _disassembler.dumpStack; @@ -639,6 +640,7 @@ pub const VM = struct { OP_LOOP, OP_STRING_FOREACH, OP_LIST_FOREACH, + OP_RANGE_FOREACH, OP_ENUM_FOREACH, OP_MAP_FOREACH, OP_FIBER_FOREACH, @@ -652,6 +654,7 @@ pub const VM = struct { OP_FIBER_INVOKE, OP_LIST_INVOKE, OP_MAP_INVOKE, + OP_RANGE_INVOKE, OP_CLOSURE, OP_CLOSE_UPVALUE, @@ -681,6 +684,7 @@ pub const VM = struct { OP_GET_STRING_PROPERTY, OP_GET_PATTERN_PROPERTY, OP_GET_FIBER_PROPERTY, + OP_GET_RANGE_PROPERTY, OP_SET_OBJECT_PROPERTY, OP_SET_INSTANCE_PROPERTY, OP_SET_FCONTAINER_INSTANCE_PROPERTY, @@ -1537,6 +1541,39 @@ pub const VM = struct { ); } + fn OP_RANGE_INVOKE(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { + const method: *ObjString = self.readString(arg); + const arg_instruction: u32 = self.readInstruction(); + const arg_count: u8 = @intCast(arg_instruction >> 24); + const catch_count: u24 = @intCast(0x00ffffff & arg_instruction); + const catch_value = if (catch_count > 0) self.pop() else null; + + const member = (ObjRange.member(self, method) catch |e| { + vmPanic(e); + unreachable; + }).?; + const member_value: Value = member.toValue(); + (self.current_fiber.stack_top - arg_count - 1)[0] = member_value; + + self.callValue(member_value, arg_count, catch_value, false) catch |e| { + vmPanic(e); + unreachable; + }; + + const next_full_instruction: u32 = self.readInstruction(); + @call( + dispatch_call_modifier, + dispatch, + .{ + self, + self.currentFrame().?, + next_full_instruction, + getCode(next_full_instruction), + getArg(next_full_instruction), + }, + ); + } + fn OP_PATTERN_INVOKE(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { const method: *ObjString = self.readString(arg); const arg_instruction: u32 = self.readInstruction(); @@ -1930,34 +1967,18 @@ pub const VM = struct { const high = self.pop().integer(); const low = self.pop().integer(); - var list: *ObjList = self.gc.allocateObject( - ObjList, - ObjList.init( - self.gc.allocator, - self.gc.type_registry.getTypeDef( - .{ - .def_type = .Integer, - }, - ) catch @panic("Could not instanciate list"), - ), - ) catch |e| { - vmPanic(e); - unreachable; - }; - - self.push(Value.fromObj(list.toObj())); - - if (low < high) { - var i: i32 = low; - while (i < high) : (i += 1) { - list.rawAppend(self.gc, Value.fromInteger(i)) catch @panic("Could not append to list"); - } - } else { - var i: i32 = low; - while (i > high) : (i -= 1) { - list.rawAppend(self.gc, Value.fromInteger(i)) catch @panic("Could not append to list"); - } - } + self.push( + Value.fromObj((self.gc.allocateObject( + ObjRange, + ObjRange{ + .high = high, + .low = low, + }, + ) catch |e| { + vmPanic(e); + unreachable; + }).toObj()), + ); const next_full_instruction: u32 = self.readInstruction(); @call( @@ -2769,6 +2790,42 @@ pub const VM = struct { vmPanic(e); unreachable; }; + } else { + unreachable; + } + + const next_full_instruction: u32 = self.readInstruction(); + @call( + dispatch_call_modifier, + dispatch, + .{ + self, + self.currentFrame().?, + next_full_instruction, + getCode(next_full_instruction), + getArg(next_full_instruction), + }, + ); + } + + fn OP_GET_RANGE_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { + const range = self.peek(0).obj().access(ObjRange, .Range, self.gc).?; + const name: *ObjString = self.readString(arg); + + if (ObjRange.member(self, name) catch |e| { + vmPanic(e); + unreachable; + }) |member| { + self.bindMethod(null, member) catch |e| { + vmPanic(e); + unreachable; + }; + } else if (std.mem.eql(u8, "high", name.string)) { + _ = self.pop(); // Pop range + self.push(Value.fromInteger(range.high)); + } else { + _ = self.pop(); // Pop range + self.push(Value.fromInteger(range.low)); } const next_full_instruction: u32 = self.readInstruction(); @@ -3585,6 +3642,40 @@ pub const VM = struct { ); } + fn OP_RANGE_FOREACH(self: *Self, _: *CallFrame, _: u32, _: OpCode, _: u24) void { + const value_slot: *Value = @ptrCast(self.current_fiber.stack_top - 2); + const range = self.peek(0).obj().access(ObjRange, .Range, self.gc).?; + + if (value_slot.integerOrNull()) |index| { + if (range.low < range.high) { + value_slot.* = if (index + 1 >= range.high) + Value.Null + else + Value.fromInteger(index + 1); + } else { + value_slot.* = if (index - 1 <= range.high) + Value.Null + else + Value.fromInteger(index - 1); + } + } else { + value_slot.* = Value.fromInteger(range.low); + } + + const next_full_instruction: u32 = self.readInstruction(); + @call( + dispatch_call_modifier, + dispatch, + .{ + self, + self.currentFrame().?, + next_full_instruction, + getCode(next_full_instruction), + getArg(next_full_instruction), + }, + ); + } + fn OP_ENUM_FOREACH(self: *Self, _: *CallFrame, _: u32, _: OpCode, _: u24) void { var value_slot: *Value = @ptrCast(self.current_fiber.stack_top - 2); const enum_case = if (value_slot.*.isNull()) diff --git a/tests/041-iterator.buzz b/tests/041-iterator.buzz index fad38b15..7ac309a5 100644 --- a/tests/041-iterator.buzz +++ b/tests/041-iterator.buzz @@ -24,7 +24,7 @@ test "finobacci generator" { } object Hello { - fun range() > [int] *> int? { + fun getRange() > [int] *> int? { var list = []; foreach (int i in 0..10) { _ = yield i; @@ -39,7 +39,7 @@ object Hello { test "dot async call" { var hello = Hello{}; - foreach (int i in &hello.range()) { + foreach (int i in &hello.getRange()) { std.print("i {i}"); } } \ No newline at end of file diff --git a/tests/053-range.buzz b/tests/053-range.buzz index a68bd668..a83ed0b8 100644 --- a/tests/053-range.buzz +++ b/tests/053-range.buzz @@ -2,8 +2,13 @@ import "std"; test "Range" { int limit = 10; - [int] range = 0..limit; - std.assert(range.len() == 10, message: "Could create list from range"); + range rg = 0..limit; + + std.assert(rg.low == 0, message: "Could get low limit of range"); + std.assert(rg.high == 10, message: "Could get high limit of range"); + + [int] list = rg.toList(); + std.assert(list.len() == 10, message: "Could create list from range"); int sum = 0; foreach (int n in 0..10) { @@ -14,8 +19,13 @@ test "Range" { test "Inverted range" { int limit = 0; - [int] range = 10..limit; - std.assert(range.len() == 10, message: "Could create list from inverted range"); + range rg = 10..limit; + + std.assert(rg.low == 10, message: "Could get low limit of range"); + std.assert(rg.high == 0, message: "Could get high limit of range"); + + [int] list = rg.toList(); + std.assert(list.len() == 10, message: "Could create list from inverted range"); int sum = 0; foreach (int n in 10..0) {