From e23b93009cc36def66c153ff0f60f6fc7936abb0 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Wed, 29 Jan 2025 15:58:11 +0100 Subject: [PATCH] feat: Composite operators closes #78 --- src/Ast.zig | 1 + src/Codegen.zig | 85 +++++ src/Jit.zig | 706 +++++++++++++++++++++------------------ src/Parser.zig | 35 +- src/Scanner.zig | 65 +++- src/Token.zig | 14 +- tests/002-operators.buzz | 67 ++++ 7 files changed, 640 insertions(+), 333 deletions(-) diff --git a/src/Ast.zig b/src/Ast.zig index a036954c..fcad3e56 100644 --- a/src/Ast.zig +++ b/src/Ast.zig @@ -1154,6 +1154,7 @@ pub const Slot = u32; pub const NamedVariable = struct { name: []const TokenIndex, value: ?Node.Index, + assign_token: ?TokenIndex, slot: Slot, slot_type: SlotType, slot_final: bool, diff --git a/src/Codegen.zig b/src/Codegen.zig index 56479a40..38650904 100644 --- a/src/Codegen.zig +++ b/src/Codegen.zig @@ -2919,6 +2919,7 @@ fn generateNamedVariable(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Er const components = self.ast.nodes.items(.components)[node].NamedVariable; const locations = self.ast.nodes.items(.location); const type_defs = self.ast.nodes.items(.type_def); + const tags = self.ast.tokens.items(.tag); var get_op: Chunk.OpCode = undefined; var set_op: Chunk.OpCode = undefined; @@ -2955,8 +2956,92 @@ fn generateNamedVariable(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Er ); } + switch (tags[components.assign_token.?]) { + .PlusEqual, + .MinusEqual, + .StarEqual, + .SlashEqual, + .ShiftRightEqual, + .ShiftLeftEqual, + .XorEqual, + .BorEqual, + .BnotEqual, + .AmpersandEqual, + .PercentEqual, + => try self.emitCodeArg( + locations[node], + get_op, + @intCast(components.slot), + ), + else => {}, + } + + // Type check that operator is allowed + switch (tags[components.assign_token.?]) { + .PlusEqual => switch (type_defs[node].?.def_type) { + .Integer, + .Double, + .List, + .Map, + .String, + => {}, + else => self.reporter.report( + .arithmetic_operand_type, + self.ast.tokens.get(components.assign_token.?), + "Addition is only allowed for types `int`, `double`, list, map and `str`", + ), + }, + .MinusEqual, + .StarEqual, + .SlashEqual, + .PercentEqual, + => switch (type_defs[node].?.def_type) { + .Integer, .Double => {}, + else => self.reporter.report( + .arithmetic_operand_type, + self.ast.tokens.get(components.assign_token.?), + "Operator is only allowed for types `int`, `double`", + ), + }, + .ShiftRightEqual, + .ShiftLeftEqual, + .XorEqual, + .BorEqual, + .BnotEqual, + .AmpersandEqual, + => if (type_defs[node].?.def_type != .Integer) { + self.reporter.report( + .arithmetic_operand_type, + self.ast.tokens.get(components.assign_token.?), + "Operator is only allowed for `int`", + ); + }, + else => {}, + } + _ = try self.generateNode(value, breaks); + switch (tags[components.assign_token.?]) { + .PlusEqual => switch (type_defs[node].?.def_type) { + .Integer => try self.OP_ADD_I(locations[node]), + .Double => try self.OP_ADD_F(locations[node]), + .List => try self.OP_ADD_LIST(locations[node]), + .Map => try self.OP_ADD_MAP(locations[node]), + .String => try self.OP_ADD_STRING(locations[node]), + else => {}, + }, + .MinusEqual => try self.OP_SUBTRACT(locations[node]), + .StarEqual => try self.OP_MULTIPLY(locations[node]), + .SlashEqual => try self.OP_DIVIDE(locations[node]), + .ShiftRightEqual => try self.OP_SHR(locations[node]), + .ShiftLeftEqual => try self.OP_SHL(locations[node]), + .XorEqual => try self.OP_XOR(locations[node]), + .BorEqual => try self.OP_BOR(locations[node]), + .AmpersandEqual => try self.OP_BAND(locations[node]), + .PercentEqual => try self.OP_MOD(locations[node]), + else => {}, + } + try self.emitCodeArg( locations[node], set_op, diff --git a/src/Jit.zig b/src/Jit.zig index 7f699a52..0e87ebbe 100644 --- a/src/Jit.zig +++ b/src/Jit.zig @@ -12,6 +12,7 @@ const ExternApi = @import("jit_extern_api.zig").ExternApi; const api = @import("lib/buzz_api.zig"); const io = @import("io.zig"); const Chunk = @import("Chunk.zig"); +const Token = @import("Token.zig"); pub const Error = error{ CantCompile, @@ -1752,6 +1753,7 @@ fn generateString(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { fn generateNamedVariable(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { const components = self.state.?.ast.nodes.items(.components)[node].NamedVariable; const type_def = self.state.?.ast.nodes.items(.type_def)[node]; + const tags = self.state.?.ast.tokens.items(.tag); const function_type = if (type_def.?.def_type == .Function) type_def.?.resolved_type.?.Function.function_type @@ -1759,12 +1761,29 @@ fn generateNamedVariable(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { null; const is_constant_fn = function_type != null and function_type.? != .Extern and function_type.? != .Anonymous; + const value = if (components.value) |v| + try self.generateNode(v) + else + null; + switch (components.slot_type) { .Global => { - if (components.value) |value| { + if (value) |v| { std.debug.assert(!is_constant_fn); - try self.buildSetGlobal(components.slot, (try self.generateNode(value)).?); + if (tags[components.assign_token.?] != .Equal) { + // buildGetGlobal returns the actual address of the global so no need to buildSetGlobal after + const global = try self.buildGetGlobal(components.slot); + try self.buildBinary( + tags[components.assign_token.?], + type_def.?.def_type, + global, + v, + global, + ); + } else { + try self.buildSetGlobal(components.slot, v); + } return null; } else if (is_constant_fn) { @@ -1790,8 +1809,22 @@ fn generateNamedVariable(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { } }, .Local => { - if (components.value) |value| { - try self.buildSetLocal(components.slot, (try self.generateNode(value)).?); + if (value) |v| { + if (tags[components.assign_token.?] != .Equal) { + const local = try self.buildGetLocal(components.slot); + + try self.buildBinary( + tags[components.assign_token.?], + type_def.?.def_type, + local, + v, + local, + ); + + try self.buildSetLocal(components.slot, local); + } else { + try self.buildSetLocal(components.slot, v); + } return null; } @@ -1799,16 +1832,40 @@ fn generateNamedVariable(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { return try self.buildGetLocal(components.slot); }, .UpValue => { - if (components.value) |value| { - try self.buildExternApiCall( - .bz_setUpValue, - null, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.ctx_reg.?), - m.MIR_new_uint_op(self.ctx, components.slot), - (try self.generateNode(value)).?, - }, - ); + if (value) |v| { + if (tags[components.assign_token.?] != .Equal) { + const upvalue = m.MIR_new_reg_op( + self.ctx, + try self.REG("upvalue", m.MIR_T_I64), + ); + + try self.buildExternApiCall( + .bz_getUpValue, + upvalue, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.ctx_reg.?), + m.MIR_new_uint_op(self.ctx, components.slot), + }, + ); + + try self.buildBinary( + tags[components.assign_token.?], + type_def.?.def_type, + upvalue, + v, + upvalue, + ); + } else { + try self.buildExternApiCall( + .bz_setUpValue, + null, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.ctx_reg.?), + m.MIR_new_uint_op(self.ctx, components.slot), + v, + }, + ); + } return null; } @@ -1817,6 +1874,7 @@ fn generateNamedVariable(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { self.ctx, try self.REG("upvalue", m.MIR_T_I64), ); + try self.buildExternApiCall( .bz_getUpValue, upvalue, @@ -2515,26 +2573,200 @@ fn generateTypeOfExpression(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t return result; } +fn buildBinary( + self: *Self, + operator: Token.Type, + def_type: o.ObjTypeDef.Type, + left_value: m.MIR_op_t, + right_value: m.MIR_op_t, + dest: m.MIR_op_t, +) Error!void { + const left = m.MIR_new_reg_op( + self.ctx, + try self.REG( + "left", + if (def_type == .Double) m.MIR_T_D else m.MIR_T_I64, + ), + ); + const right = m.MIR_new_reg_op( + self.ctx, + try self.REG( + "right", + if (def_type == .Double) m.MIR_T_D else m.MIR_T_I64, + ), + ); + + if (def_type == .Integer) { + try self.unwrap(.Integer, left_value, left); + try self.unwrap(.Integer, right_value, right); + } else if (def_type == .Double) { + try self.unwrap(.Double, left_value, left); + try self.unwrap(.Double, right_value, right); + } else { + self.MOV(left, left_value); + self.MOV(right, right_value); + } + + // Avoid collection + if (def_type != .Integer and def_type != .Double) { + try self.buildPush(left_value); + try self.buildPush(right_value); + } + + switch (operator) { + .Plus, .PlusEqual => { + switch (def_type) { + .Integer => { + self.ADDS(dest, left, right); + try self.wrap(.Integer, dest, dest); + }, + .Double => { + self.DADD(left, left, right); + try self.wrap(.Double, left, dest); + }, + .String => { + try self.buildExternApiCall( + .bz_stringConcat, + dest, + &[_]m.MIR_op_t{ + left_value, + right_value, + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + }, + ); + }, + .List => { + try self.buildExternApiCall( + .bz_listConcat, + dest, + &[_]m.MIR_op_t{ + left_value, + right_value, + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + }, + ); + }, + .Map => { + try self.buildExternApiCall( + .bz_mapConcat, + dest, + &[_]m.MIR_op_t{ + left_value, + right_value, + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + }, + ); + }, + else => unreachable, + } + }, + .Minus, .MinusEqual => { + switch (def_type) { + .Integer => { + self.SUBS(dest, left, right); + try self.wrap(.Integer, dest, dest); + }, + .Double => { + self.DSUB(left, left, right); + try self.wrap(.Double, left, dest); + }, + else => unreachable, + } + }, + .Star, .StarEqual => { + switch (def_type) { + .Integer => { + self.MULS(dest, left, right); + try self.wrap(.Integer, dest, dest); + }, + .Double => { + self.DMUL(left, left, right); + try self.wrap(.Double, left, dest); + }, + else => unreachable, + } + }, + .Slash, .SlashEqual => { + switch (def_type) { + .Integer => { + self.DIVS(dest, left, right); + try self.wrap(.Integer, dest, dest); + }, + .Double => { + self.DDIV(left, left, right); + try self.wrap(.Double, left, dest); + }, + else => unreachable, + } + }, + .Percent, .PercentEqual => { + switch (def_type) { + .Integer => { + self.MODS(dest, left, right); + try self.wrap(.Integer, dest, dest); + }, + .Double => { + try self.buildExternApiCall( + .fmod, + dest, + &[_]m.MIR_op_t{ + left, + right, + }, + ); + }, + else => unreachable, + } + }, + .Ampersand, .AmpersandEqual => { + self.AND(dest, left, right); + try self.wrap(.Integer, dest, dest); + }, + .Bor, .BorEqual => { + self.OR(dest, left, right); + try self.wrap(.Integer, dest, dest); + }, + .Xor, .XorEqual => { + self.XOR(dest, left, right); + try self.wrap(.Integer, dest, dest); + }, + .ShiftLeft, .ShiftLeftEqual => { + self.SHL(dest, left, right); + try self.wrap(.Integer, dest, dest); + }, + .ShiftRight, .ShiftRightEqual => { + self.SHR(dest, left, right); + try self.wrap(.Integer, dest, dest); + }, + else => unreachable, + } + + if (def_type != .Integer and def_type != .Double) { + try self.buildPop(null); + try self.buildPop(null); + } +} + fn generateBinary(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { const node_components = self.state.?.ast.nodes.items(.components); const components = node_components[node].Binary; - const type_defs = self.state.?.ast.nodes.items(.type_def); - - const left_type_def = type_defs[components.left].?.def_type; - const right_type_def = type_defs[components.right].?.def_type; return switch (components.operator) { - .Ampersand, - .Bor, - .Xor, - .ShiftLeft, - .ShiftRight, - => try self.generateBitwise(components), .QuestionQuestion, .And, .Or, => try self.generateConditional(components), - else => { + .Less, + .Greater, + .GreaterEqual, + .LessEqual, + .EqualEqual, + .BangEqual, + => try self.generateComparison(components), + else => bin: { + const type_defs = self.state.?.ast.nodes.items(.type_def); + const type_def = type_defs[components.left].?.def_type; + const left_value = (try self.generateNode(components.left)).?; const right_value = (try self.generateNode(components.right)).?; @@ -2542,327 +2774,167 @@ fn generateBinary(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { self.ctx, try self.REG("res", m.MIR_T_I64), ); - var left = m.MIR_new_reg_op( - self.ctx, - try self.REG("left", if (left_type_def == .Double) m.MIR_T_D else m.MIR_T_I64), - ); - var right = m.MIR_new_reg_op( - self.ctx, - try self.REG("right", if (right_type_def == .Double) m.MIR_T_D else m.MIR_T_I64), - ); - if (left_type_def == .Integer) { - try self.unwrap(.Integer, left_value, left); - } else if (left_type_def == .Double) { - try self.unwrap(.Double, left_value, left); - } else { - self.MOV(left, left_value); - } - - if (right_type_def == .Integer) { - try self.unwrap(.Integer, right_value, right); - } else if (right_type_def == .Double) { - try self.unwrap(.Double, right_value, right); - } else { - self.MOV(right, right_value); - } - - // Avoid collection - if (left_type_def != .Integer and left_type_def != .Double) { - try self.buildPush(left_value); - } - - if (right_type_def != .Integer and right_type_def != .Double) { - try self.buildPush(right_value); - } - - switch (components.operator) { - .EqualEqual => try self.buildExternApiCall( - .bz_valueEqual, - res, - &[_]m.MIR_op_t{ - left_value, - right_value, - }, - ), - .BangEqual => { - try self.buildExternApiCall( - .bz_valueEqual, - res, - &[_]m.MIR_op_t{ - left_value, - right_value, - }, - ); - - try self.unwrap(.Bool, res, res); + try self.buildBinary( + components.operator, + type_def, + left_value, + right_value, + res, + ); - const true_label = m.MIR_new_label(self.ctx); - const out_label = m.MIR_new_label(self.ctx); + break :bin res; + }, + }; +} - self.BEQ( - m.MIR_new_label_op(self.ctx, true_label), - res, - m.MIR_new_uint_op(self.ctx, 1), - ); +fn generateComparison(self: *Self, components: Ast.Binary) Error!?m.MIR_op_t { + const type_defs = self.state.?.ast.nodes.items(.type_def); - self.MOV( - res, - m.MIR_new_uint_op(self.ctx, Value.True.val), - ); + const left_type_def = type_defs[components.left].?.def_type; + const right_type_def = type_defs[components.right].?.def_type; - self.JMP(out_label); + const left_value = (try self.generateNode(components.left)).?; + const right_value = (try self.generateNode(components.right)).?; - self.append(true_label); + const res = m.MIR_new_reg_op( + self.ctx, + try self.REG("res", m.MIR_T_I64), + ); - self.MOV( - res, - m.MIR_new_uint_op(self.ctx, Value.False.val), - ); + var left = m.MIR_new_reg_op( + self.ctx, + try self.REG("left", if (left_type_def == .Double) m.MIR_T_D else m.MIR_T_I64), + ); + var right = m.MIR_new_reg_op( + self.ctx, + try self.REG("right", if (right_type_def == .Double) m.MIR_T_D else m.MIR_T_I64), + ); - self.append(out_label); - }, - .Greater, .Less, .GreaterEqual, .LessEqual => { - if (left_type_def == .Double or right_type_def == .Double) { - if (left_type_def == .Integer) { - const left_f = m.MIR_new_reg_op( - self.ctx, - try self.REG("left_float", m.MIR_T_D), - ); - self.I2D(left_f, left); - left = left_f; - } + if (left_type_def == .Integer) { + try self.unwrap(.Integer, left_value, left); + } else if (left_type_def == .Double) { + try self.unwrap(.Double, left_value, left); + } else { + self.MOV(left, left_value); + } - if (right_type_def == .Integer) { - const right_f = m.MIR_new_reg_op( - self.ctx, - try self.REG("right_float", m.MIR_T_D), - ); - self.I2D(right_f, right); - right = right_f; - } + if (right_type_def == .Integer) { + try self.unwrap(.Integer, right_value, right); + } else if (right_type_def == .Double) { + try self.unwrap(.Double, right_value, right); + } else { + self.MOV(right, right_value); + } - switch (components.operator) { - .Greater => self.DGT(res, left, right), - .Less => self.DLT(res, left, right), - .GreaterEqual => self.DGE(res, left, right), - .LessEqual => self.DLE(res, left, right), - else => unreachable, - } + // Avoid collection + if (left_type_def != .Integer and left_type_def != .Double) { + try self.buildPush(left_value); + } - try self.wrap(.Bool, res, res); - } else { - switch (components.operator) { - .Greater => self.GTS(res, left, right), - .Less => self.LTS(res, left, right), - .GreaterEqual => self.GES(res, left, right), - .LessEqual => self.LES(res, left, right), - else => unreachable, - } + if (right_type_def != .Integer and right_type_def != .Double) { + try self.buildPush(right_value); + } - try self.wrap(.Bool, res, res); - } - }, - .Plus => { - switch (left_type_def) { - .Integer, .Double => { - if (left_type_def == .Double or right_type_def == .Double) { - if (left_type_def == .Integer) { - const left_f = m.MIR_new_reg_op( - self.ctx, - try self.REG("left_float", m.MIR_T_D), - ); - self.I2D(left_f, left); - left = left_f; - } - - if (right_type_def == .Integer) { - const right_f = m.MIR_new_reg_op( - self.ctx, - try self.REG("right_float", m.MIR_T_D), - ); - self.I2D(right_f, right); - right = right_f; - } - - const f_res = m.MIR_new_reg_op( - self.ctx, - try self.REG("f_res", m.MIR_T_D), - ); - self.DADD(f_res, left, right); - - try self.wrap(.Double, f_res, res); - } else { - self.ADDS(res, left, right); - - try self.wrap(.Integer, res, res); - } - }, - .String => { - try self.buildExternApiCall( - .bz_stringConcat, - res, - &[_]m.MIR_op_t{ - left, - right, - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - }, - ); - }, - .List => { - try self.buildExternApiCall( - .bz_listConcat, - res, - &[_]m.MIR_op_t{ - left, - right, - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - }, - ); - }, - .Map => { - try self.buildExternApiCall( - .bz_mapConcat, - res, - &[_]m.MIR_op_t{ - left, - right, - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - }, - ); - }, - else => unreachable, - } + switch (components.operator) { + .EqualEqual => try self.buildExternApiCall( + .bz_valueEqual, + res, + &[_]m.MIR_op_t{ + left_value, + right_value, + }, + ), + .BangEqual => { + try self.buildExternApiCall( + .bz_valueEqual, + res, + &[_]m.MIR_op_t{ + left_value, + right_value, }, - .Minus => { - if (left_type_def == .Double or right_type_def == .Double) { - if (left_type_def == .Integer) { - const left_f = m.MIR_new_reg_op( - self.ctx, - try self.REG("left_float", m.MIR_T_D), - ); - self.I2D(left_f, left); - left = left_f; - } - - if (right_type_def == .Integer) { - const right_f = m.MIR_new_reg_op( - self.ctx, - try self.REG("right_float", m.MIR_T_D), - ); - self.I2D(right_f, right); - right = right_f; - } + ); - const f_res = m.MIR_new_reg_op( - self.ctx, - try self.REG("f_res", m.MIR_T_D), - ); - self.DSUB(f_res, left, right); + try self.unwrap(.Bool, res, res); - try self.wrap(.Double, f_res, res); - } else { - self.SUBS(res, left, right); + const true_label = m.MIR_new_label(self.ctx); + const out_label = m.MIR_new_label(self.ctx); - try self.wrap(.Integer, res, res); - } - }, - .Star => { - if (left_type_def == .Double or right_type_def == .Double) { - if (left_type_def == .Integer) { - const left_f = m.MIR_new_reg_op( - self.ctx, - try self.REG("left_float", m.MIR_T_D), - ); - self.I2D(left_f, left); - left = left_f; - } + self.BEQ( + m.MIR_new_label_op(self.ctx, true_label), + res, + m.MIR_new_uint_op(self.ctx, 1), + ); - if (right_type_def == .Integer) { - const right_f = m.MIR_new_reg_op( - self.ctx, - try self.REG("right_float", m.MIR_T_D), - ); - self.I2D(right_f, right); - right = right_f; - } + self.MOV( + res, + m.MIR_new_uint_op(self.ctx, Value.True.val), + ); - const f_res = m.MIR_new_reg_op( - self.ctx, - try self.REG("f_res", m.MIR_T_D), - ); - self.DMUL(f_res, left, right); + self.JMP(out_label); - try self.wrap(.Double, f_res, res); - } else { - self.MULS(res, left, right); + self.append(true_label); - try self.wrap(.Integer, res, res); - } - }, - .Slash => { - if (left_type_def == .Double or right_type_def == .Double) { - if (left_type_def == .Integer) { - const left_f = m.MIR_new_reg_op( - self.ctx, - try self.REG("left_float", m.MIR_T_D), - ); - self.I2D(left_f, left); - left = left_f; - } + self.MOV( + res, + m.MIR_new_uint_op(self.ctx, Value.False.val), + ); - if (right_type_def == .Integer) { - const right_f = m.MIR_new_reg_op( - self.ctx, - try self.REG("right_float", m.MIR_T_D), - ); - self.I2D(right_f, right); - right = right_f; - } + self.append(out_label); + }, + .Greater, .Less, .GreaterEqual, .LessEqual => { + if (left_type_def == .Double or right_type_def == .Double) { + if (left_type_def == .Integer) { + const left_f = m.MIR_new_reg_op( + self.ctx, + try self.REG("left_float", m.MIR_T_D), + ); + self.I2D(left_f, left); + left = left_f; + } - const f_res = m.MIR_new_reg_op( - self.ctx, - try self.REG("f_res", m.MIR_T_D), - ); - self.DDIV(f_res, left, right); + if (right_type_def == .Integer) { + const right_f = m.MIR_new_reg_op( + self.ctx, + try self.REG("right_float", m.MIR_T_D), + ); + self.I2D(right_f, right); + right = right_f; + } - try self.wrap(.Double, f_res, res); - } else { - self.DIVS(res, left, right); + switch (components.operator) { + .Greater => self.DGT(res, left, right), + .Less => self.DLT(res, left, right), + .GreaterEqual => self.DGE(res, left, right), + .LessEqual => self.DLE(res, left, right), + else => unreachable, + } - try self.wrap(.Integer, res, res); - } - }, - .Percent => { - if (left_type_def == .Double or right_type_def == .Double) { - try self.buildExternApiCall( - .fmod, - res, - &[_]m.MIR_op_t{ - left, - right, - }, - ); - } else { - self.MODS(res, left, right); + try self.wrap(.Bool, res, res); + } else { + switch (components.operator) { + .Greater => self.GTS(res, left, right), + .Less => self.LTS(res, left, right), + .GreaterEqual => self.GES(res, left, right), + .LessEqual => self.LES(res, left, right), + else => unreachable, + } - try self.wrap(.Integer, res, res); - } - }, - else => unreachable, + try self.wrap(.Bool, res, res); } + }, + else => {}, + } - if (left_type_def != .Integer and left_type_def != .Double) { - try self.buildPop(null); - } + if (left_type_def != .Integer and left_type_def != .Double) { + try self.buildPop(null); + } - if (right_type_def != .Integer and right_type_def != .Double) { - try self.buildPop(null); - } + if (right_type_def != .Integer and right_type_def != .Double) { + try self.buildPop(null); + } - return res; - }, - }; + return res; } fn generateConditional(self: *Self, binary: Ast.Binary) Error!?m.MIR_op_t { diff --git a/src/Parser.zig b/src/Parser.zig index 826ef95e..be72e7d1 100644 --- a/src/Parser.zig +++ b/src/Parser.zig @@ -455,7 +455,6 @@ else }; const rules = [_]ParseRule{ - .{}, // Pipe .{ .prefix = list, .infix = subscript, .precedence = .Call }, // LeftBracket .{}, // RightBracket .{ .prefix = grouping, .infix = call, .precedence = .Call }, // LeftParen @@ -556,6 +555,17 @@ const rules = [_]ParseRule{ .{}, // namespace .{}, // rg .{ .prefix = mutableExpression }, // mut + .{}, // PlusEqual + .{}, // MinusEqual + .{}, // StarEqual + .{}, // SlashEqual + .{}, // ShiftRightEqual + .{}, // ShiftLeftEqual + .{}, // XorEqual + .{}, // BorEqual + .{}, // BnotEqual + .{}, // AmpersandEqual + .{}, // PercentEqual }; ast: Ast, @@ -802,6 +812,21 @@ fn match(self: *Self, tag: Token.Type) !bool { return true; } +fn matchOpEqual(self: *Self) !bool { + return try self.match(.Equal) or + try self.match(.PlusEqual) or + try self.match(.MinusEqual) or + try self.match(.StarEqual) or + try self.match(.SlashEqual) or + try self.match(.ShiftRightEqual) or + try self.match(.ShiftLeftEqual) or + try self.match(.XorEqual) or + try self.match(.BorEqual) or + try self.match(.BnotEqual) or + try self.match(.AmpersandEqual) or + try self.match(.PercentEqual); +} + /// Insert token in ast and advance over it to avoid confusing the parser fn insertUtilityToken(self: *Self, token: Token) !Ast.TokenIndex { const current_token = self.ast.tokens.get(self.current_token.?); @@ -5398,7 +5423,12 @@ fn namedVariable(self: *Self, name: []const Ast.TokenIndex, can_assign: bool) Er } } - const value = if (can_assign and try self.match(.Equal)) + const assign_token = if (can_assign and try self.matchOpEqual()) + self.current_token.? - 1 + else + null; + + const value = if (assign_token != null) try self.expression(false) else null; @@ -5423,6 +5453,7 @@ fn namedVariable(self: *Self, name: []const Ast.TokenIndex, can_assign: bool) Er .NamedVariable = .{ .name = name, .value = value, + .assign_token = assign_token, .slot = @intCast(slot), .slot_type = slot_type, .slot_final = slot_final, diff --git a/src/Scanner.zig b/src/Scanner.zig index 42905433..526e734e 100644 --- a/src/Scanner.zig +++ b/src/Scanner.zig @@ -79,32 +79,63 @@ pub fn scanToken(self: *Self) Allocator.Error!Token { else self.makeToken(.Dot, null, null, null), '>' => if (self.match('>')) - self.makeToken(.ShiftRight, null, null, null) + if (self.match('=')) + self.makeToken(.ShiftRightEqual, null, null, null) + else + self.makeToken(.ShiftRight, null, null, null) else if (self.match('=')) self.makeToken(.GreaterEqual, null, null, null) else self.makeToken(.Greater, null, null, null), '<' => if (self.match('<')) - self.makeToken(.ShiftLeft, null, null, null) + if (self.match('=')) + self.makeToken(.ShiftLeftEqual, null, null, null) + else + self.makeToken(.ShiftLeft, null, null, null) else if (self.match('=')) self.makeToken(.LessEqual, null, null, null) else self.makeToken(.Less, null, null, null), - '~' => self.makeToken(.Bnot, null, null, null), - '^' => self.makeToken(.Xor, null, null, null), - '|' => self.makeToken(.Bor, null, null, null), - '+' => self.makeToken(.Plus, null, null, null), - '-' => if (self.match('>')) + '~' => if (self.match('=')) + self.makeToken(.BnotEqual, null, null, null) + else + self.makeToken(.Bnot, null, null, null), + '^' => if (self.match('=')) + self.makeToken(.XorEqual, null, null, null) + else + self.makeToken(.Xor, null, null, null), + '|' => if (self.match('=')) + self.makeToken(.BorEqual, null, null, null) + else + self.makeToken(.Bor, null, null, null), + '+' => if (self.match('=')) + self.makeToken(.PlusEqual, null, null, null) + else + self.makeToken(.Plus, null, null, null), + '-' => if (self.match('=')) + self.makeToken(.MinusEqual, null, null, null) + else if (self.match('>')) self.makeToken(.Arrow, null, null, null) else self.makeToken(.Minus, null, null, null), - '&' => self.makeToken(.Ampersand, null, null, null), - '*' => self.makeToken(.Star, null, null, null), - '/' => if (self.match('/')) + '&' => if (self.match('=')) + self.makeToken(.AmpersandEqual, null, null, null) + else + self.makeToken(.Ampersand, null, null, null), + '*' => if (self.match('=')) + self.makeToken(.StarEqual, null, null, null) + else + self.makeToken(.Star, null, null, null), + '/' => if (self.match('=')) + self.makeToken(.SlashEqual, null, null, null) + else if (self.match('/')) try self.docblock() else self.makeToken(.Slash, null, null, null), - '%' => self.makeToken(.Percent, null, null, null), + '%' => if (self.match('=')) + self.makeToken(.PercentEqual, null, null, null) + else + self.makeToken(.Percent, null, null, null), '?' => self.makeToken(if (self.match('?')) .QuestionQuestion else .Question, null, null, null), '!' => if (self.match('=')) self.makeToken(.BangEqual, null, null, null) @@ -625,7 +656,6 @@ pub fn highlight(self: *Self, out: anytype, true_color: bool) void { .{ switch (token.tag) { // Operators - .Pipe, .Greater, .Less, .Plus, @@ -702,6 +732,17 @@ pub fn highlight(self: *Self, out: anytype, true_color: bool) void { .Namespace, .Range, .Mut, + .PlusEqual, + .MinusEqual, + .StarEqual, + .SlashEqual, + .ShiftRightEqual, + .ShiftLeftEqual, + .XorEqual, + .BorEqual, + .BnotEqual, + .PercentEqual, + .AmpersandEqual, => if (true_color) Color.keyword else Color.magenta, // Punctuation .LeftBracket, diff --git a/src/Token.zig b/src/Token.zig index 9e09a653..3820a7cc 100644 --- a/src/Token.zig +++ b/src/Token.zig @@ -75,7 +75,6 @@ pub fn getLines(self: Self, allocator: mem.Allocator, before: usize, after: usiz // WARNING: don't reorder without reordering `rules` in parser.zig pub const Type = enum { - Pipe, // | LeftBracket, // [ RightBracket, // ] LeftParen, // ( @@ -120,7 +119,7 @@ pub const Type = enum { ShiftRight, // >> ShiftLeft, // << Xor, // ^ - Bor, // \ + Bor, // | Bnot, // ~ Or, // or @@ -178,6 +177,17 @@ pub const Type = enum { Namespace, // namespace Range, // range Mut, // mut + PlusEqual, // += + MinusEqual, // -= + StarEqual, // *= + SlashEqual, // /= + ShiftRightEqual, // >>= + ShiftLeftEqual, // <<= + XorEqual, // ^= + BorEqual, // |= + BnotEqual, // ~= + AmpersandEqual, // &= + PercentEqual, // %= }; // FIXME if case had the same name as the actual token we could simply use @tagName diff --git a/tests/002-operators.buzz b/tests/002-operators.buzz index 4117002d..113fdaef 100644 --- a/tests/002-operators.buzz +++ b/tests/002-operators.buzz @@ -36,3 +36,70 @@ test "Unary operators" { std\assert(!b, message: "not operator"); std\assert(~c == -16, message: "~"); } + +test "Composite operators" { + var a = 1; + + a += 1; + std\assert(a == 2, message: "+= on int"); + + a -= 1; + std\assert(a == 1, message: "-= on int"); + + a *= 4; + std\assert(a == 4, message: "*= on int"); + + a /= 2; + std\assert(a == 2, message: "/= on int"); + + a %= 2; + std\assert(a == 0, message: "%= on int"); + + a = 3; + a <<= 1; + std\assert(a == 6, message: "<<="); + + a >>= 1; + std\assert(a == 3, message: ">>="); + + a ^= 2; + std\assert(a == 1, message: "^="); + + a |= 2; + std\assert(a == 3, message: "|="); + + a &= 2; + std\assert(a == 2, message: "&= 2"); + + var b = 1.0; + + b += 1.0; + std\assert(b == 2, message: "+= on double"); + + b -= 1.0; + std\assert(b == 1, message: "-= on double"); + + b *= 4.0; + std\assert(b == 4, message: "*= on double"); + + b /= 2.0; + std\assert(b == 2, message: "/= on double"); + + b %= 2.0; + std\assert(b == 0, message: "%= on double"); + + var l = [ 1, 2, 3 ]; + + l += [ 4, 5, 6 ]; + std\assert(l.len() == 6, message: "+= on list"); + + // var m = { "one": 1 }; + + // m += { "two": 2 }; + // std\assert(m.size() == 2, message: "+= on map"); + + // var s = "hello"; + + // s += " world"; + // std\assert(s == "hello world", message: "+= on string"); +}