Skip to content

Commit

Permalink
feat: Checked subscript
Browse files Browse the repository at this point in the history
closes #304

Left operand of binary operation was not checked for optional
  • Loading branch information
giann committed Jun 26, 2024
1 parent db013da commit fccb4e4
Show file tree
Hide file tree
Showing 13 changed files with 172 additions and 38 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ const tuple = .{ "john", "james" };
tuples.@"0" == "john";
```
- Checked subscript access to list and strings:
```buzz
var list = [1, 2, 3];
list[?10] == null;
"hello"[?20] == null;
```

## Modified
- Enum can now have `rg`, `ud`, `void`, `pat` has value type
Expand Down
9 changes: 9 additions & 0 deletions src/Ast.zig
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,7 @@ pub const Subscript = struct {
subscripted: Node.Index,
index: Node.Index,
value: ?Node.Index,
checked: bool,
};

pub const Throw = struct {
Expand Down Expand Up @@ -1203,6 +1204,10 @@ pub fn toValue(self: Self, node: Node.Index, gc: *GarbageCollector) Error!Value
const index: usize = @intCast(key.integer());

if (index < 0 or index >= list.items.items.len) {
if (components.checked) {
break :subscript Value.Null;
}

return Error.OutOfBound;
}

Expand All @@ -1218,6 +1223,10 @@ pub fn toValue(self: Self, node: Node.Index, gc: *GarbageCollector) Error!Value
const index: usize = @intCast(key.integer());

if (index < 0 or index >= str.string.len) {
if (components.checked) {
break :subscript Value.Null;
}

return Error.OutOfBound;
}

Expand Down
24 changes: 23 additions & 1 deletion src/Codegen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,24 @@ fn generateBinary(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*o
else => unreachable,
}

if (components.operator != .QuestionQuestion and components.operator != .EqualEqual and components.operator != .BangEqual) {
if (left_type.optional) {
self.reporter.reportErrorAt(
.binary_operand_type,
self.ast.tokens.get(locations[components.left]),
"Binary operand can't be optional",
);
}

if (right_type.optional) {
self.reporter.reportErrorAt(
.binary_operand_type,
self.ast.tokens.get(locations[components.right]),
"Binary operand can't be optional",
);
}
}

switch (components.operator) {
.QuestionQuestion => {
if (!left_type.optional) {
Expand Down Expand Up @@ -3569,7 +3587,11 @@ fn generateSubscript(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!

try self.emitOpCode(location, set_code);
} else {
try self.emitOpCode(location, get_code);
try self.emitCodeArg(
location,
get_code,
if (components.checked) 1 else 0,
);
}

try self.patchOptJumps(node);
Expand Down
8 changes: 8 additions & 0 deletions src/Jit.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3567,6 +3567,10 @@ fn generateSubscript(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t {
&[_]m.MIR_op_t{
subscripted,
index,
m.MIR_new_uint_op(
self.ctx,
if (components.checked) 1 else 0,
),
},
);

Expand All @@ -3585,6 +3589,10 @@ fn generateSubscript(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t {
m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?),
subscripted,
index_val,
m.MIR_new_uint_op(
self.ctx,
if (components.checked) 1 else 0,
),
},
);

Expand Down
17 changes: 12 additions & 5 deletions src/Parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3303,6 +3303,7 @@ fn subscript(self: *Self, can_assign: bool, subscripted: Ast.Node.Index) Error!A
const start_location = self.current_token.? - 1;

const subscript_type_def = self.ast.nodes.items(.type_def)[subscripted];
const checked = try self.match(.Question);
const index = try self.expression(false);
const index_type_def = self.ast.nodes.items(.type_def)[index];

Expand All @@ -3321,13 +3322,15 @@ fn subscript(self: *Self, can_assign: bool, subscripted: Ast.Node.Index) Error!A
if (!type_def.optional) {
switch (type_def.def_type) {
.Placeholder => {
const placeholder_resolved_type: obj.ObjTypeDef.TypeUnion = .{
.Placeholder = obj.PlaceholderDef.init(self.gc.allocator, self.current_token.? - 1),
};
const placeholder = try self.gc.type_registry.getTypeDef(
.{
.def_type = .Placeholder,
.resolved_type = placeholder_resolved_type,
.resolved_type = .{
.Placeholder = obj.PlaceholderDef.init(
self.gc.allocator,
self.current_token.? - 1,
),
},
},
);

Expand Down Expand Up @@ -3366,12 +3369,16 @@ fn subscript(self: *Self, can_assign: bool, subscripted: Ast.Node.Index) Error!A
.tag = .Subscript,
.location = start_location,
.end_location = self.current_token.? - 1,
.type_def = subscripted_type_def,
.type_def = if (subscripted_type_def != null and checked)
try subscripted_type_def.?.cloneOptional(&self.gc.type_registry)
else
subscripted_type_def,
.components = .{
.Subscript = .{
.index = index,
.value = value,
.subscripted = subscripted,
.checked = checked,
},
},
},
Expand Down
24 changes: 21 additions & 3 deletions src/buzz_api.zig
Original file line number Diff line number Diff line change
Expand Up @@ -426,11 +426,15 @@ export fn bz_objStringConcat(vm: *VM, obj_string: Value, other: Value) Value {
) catch @panic("Could not concat strings")).toValue();
}

export fn bz_objStringSubscript(vm: *VM, obj_string: Value, index_value: Value) Value {
export fn bz_objStringSubscript(vm: *VM, obj_string: Value, index_value: Value, checked: bool) Value {
const str = ObjString.cast(obj_string.obj()).?;
const index = index_value.integer();

if (index < 0) {
if (checked) {
return Value.Null;
}

bz_throw(
vm,
(vm.gc.copyString("Out of bound string access.") catch unreachable).toValue(),
Expand All @@ -444,6 +448,10 @@ export fn bz_objStringSubscript(vm: *VM, obj_string: Value, index_value: Value)
if (str_index < str.string.len) {
return (vm.gc.copyString(&([_]u8{str.string[str_index]})) catch unreachable).toValue();
} else {
if (checked) {
return Value.Null;
}

bz_throw(
vm,
(vm.gc.copyString("Out of bound str access.") catch unreachable).toValue(),
Expand Down Expand Up @@ -564,8 +572,18 @@ export fn bz_valueToList(value: Value) *ObjList {
return ObjList.cast(value.obj()).?;
}

export fn bz_listGet(self: Value, index: usize) Value {
return ObjList.cast(self.obj()).?.items.items[index];
export fn bz_listGet(self: Value, index: i32, checked: bool) Value {
const list = ObjList.cast(self.obj()).?;

if (index < 0 or index >= list.items.items.len) {
if (checked) {
return Value.Null;
} else {
@panic("Out of bound list access.");
}
}

return list.items.items[@intCast(index)];
}

export fn bz_listSet(vm: *VM, self: Value, index: usize, value: Value) void {
Expand Down
6 changes: 3 additions & 3 deletions src/disassembler.zig
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,6 @@ pub fn disassembleInstruction(chunk: *Chunk, offset: usize) usize {
.OP_MOD,
.OP_UNWRAP,
.OP_GET_ENUM_CASE_VALUE,
.OP_GET_LIST_SUBSCRIPT,
.OP_GET_MAP_SUBSCRIPT,
.OP_GET_STRING_SUBSCRIPT,
.OP_SET_LIST_SUBSCRIPT,
.OP_SET_MAP_SUBSCRIPT,
.OP_THROW,
Expand Down Expand Up @@ -306,6 +303,9 @@ pub fn disassembleInstruction(chunk: *Chunk, offset: usize) usize {
.OP_GET_STRING_PROPERTY,
.OP_GET_PATTERN_PROPERTY,
.OP_GET_OBJECT_PROPERTY,
.OP_GET_LIST_SUBSCRIPT,
.OP_GET_STRING_SUBSCRIPT,
.OP_GET_MAP_SUBSCRIPT,
=> byteInstruction(instruction, chunk, offset),

.OP_OBJECT,
Expand Down
33 changes: 31 additions & 2 deletions src/jit_extern_api.zig
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ pub const ExternApi = enum {
self.pname(),
1,
&[_]m.MIR_type_t{m.MIR_T_U64},
3,
4,
&[_]m.MIR_var_t{
.{
.type = m.MIR_T_P,
Expand All @@ -120,6 +120,11 @@ pub const ExternApi = enum {
.name = "index_value",
.size = undefined,
},
.{
.type = m.MIR_T_U8,
.name = "checked",
.size = undefined,
},
},
),
.bz_closure => m.MIR_new_proto_arr(
Expand Down Expand Up @@ -218,7 +223,31 @@ pub const ExternApi = enum {
},
},
),
.bz_listGet, .bz_mapGet => m.MIR_new_proto_arr(
.bz_listGet => 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_U64,
.name = "list",
.size = undefined,
},
.{
.type = m.MIR_T_I32,
.name = "index",
.size = undefined,
},
.{
.type = m.MIR_T_U8,
.name = "checked",
.size = undefined,
},
},
),
.bz_mapGet => m.MIR_new_proto_arr(
ctx,
self.pname(),
1,
Expand Down
4 changes: 2 additions & 2 deletions src/lib/buzz_api.zig
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ pub const ObjString = opaque {
pub extern fn bz_objStringToString(obj_string: *ObjString, len: *usize) ?[*]const u8;
pub extern fn bz_objStringToValue(obj_string: *ObjString) Value;
pub extern fn bz_objStringConcat(vm: *VM, obj_string: Value, other: Value) Value;
pub extern fn bz_objStringSubscript(vm: *VM, obj_string: Value, index_value: Value) Value;
pub extern fn bz_objStringSubscript(vm: *VM, obj_string: Value, index_value: Value, checked: bool) Value;
pub extern fn bz_toString(vm: *VM, value: Value) Value;
pub extern fn bz_getStringProperty(vm: *VM, method_idx: usize) Value;
pub extern fn bz_stringNext(vm: *VM, string_value: Value, index: *Value) Value;
Expand All @@ -340,7 +340,7 @@ 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;
pub extern fn bz_valueToList(value: Value) *ObjList;
pub extern fn bz_listGet(self: Value, index: usize) Value;
pub extern fn bz_listGet(self: Value, index: i32, checked: bool) Value;
pub extern fn bz_listSet(vm: *VM, self: Value, index: usize, value: Value) void;
pub extern fn bz_listLen(self: *ObjList) usize;
pub extern fn bz_listConcat(vm: *VM, list: Value, other_list: Value) Value;
Expand Down
12 changes: 10 additions & 2 deletions src/lib/buzz_buffer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,11 @@ inline fn rawWriteZ(

var index = at;
for (0..values.bz_listLen()) |i| {
const value = api.ObjList.bz_listGet(values_value, i);
const value = api.ObjList.bz_listGet(
values_value,
@intCast(i),
false,
);

if (!ctx.vm.bz_checkBuzzType(value, zig_type.?, obj_typedef)) {
return false;
Expand Down Expand Up @@ -591,7 +595,11 @@ inline fn rawWriteStruct(

var index = at;
for (0..values.bz_listLen()) |i| {
const value = api.ObjList.bz_listGet(values_value, i);
const value = api.ObjList.bz_listGet(
values_value,
@intCast(i),
false,
);

if (!value.bz_valueIs(type_def_value).boolean()) {
var msg = std.ArrayList(u8).init(api.VM.allocator);
Expand Down
6 changes: 5 additions & 1 deletion src/lib/buzz_os.zig
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,11 @@ pub export fn execute(ctx: *api.NativeCtx) c_int {
const len = argv.bz_listLen();
var i: usize = 0;
while (i < len) : (i += 1) {
const arg = api.ObjList.bz_listGet(argv_value, i);
const arg = api.ObjList.bz_listGet(
argv_value,
@intCast(i),
false,
);
var arg_len: usize = 0;
var arg_str = arg.bz_valueToString(&arg_len);

Expand Down
Loading

0 comments on commit fccb4e4

Please sign in to comment.