diff --git a/samples/indices.lox b/samples/indices.lox new file mode 100644 index 0000000..0dbe73b --- /dev/null +++ b/samples/indices.lox @@ -0,0 +1,3 @@ +var alphabet = "abcdefghijklmnopqrstuvwxyz"; var X = alphabet; print "hello " + X[3] + X[0] + X[10]; + +var s = " "; s[0] = "d"; s[1] = "a"; s[2] = "k"; var hey = "hello " + s + "!"; print hey; diff --git a/src/chunk.zig b/src/chunk.zig index bfbbddf..1a9fe77 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -152,6 +152,8 @@ pub const Chunk = struct { @intFromEnum(OpCode.OP_GET_PROPERTY) => return utils.constant_instruction("OP_GET_PROPERTY", self, offset), @intFromEnum(OpCode.OP_SET_PROPERTY) => return utils.constant_instruction("OP_SET_PROPERTY", self, offset), @intFromEnum(OpCode.OP_METHOD) => return utils.constant_instruction("OP_METHOD", self, offset), + @intFromEnum(OpCode.OP_INDEX_GET) => return utils.simple_instruction("OP_INDEX_GET", offset), + @intFromEnum(OpCode.OP_INDEX_SET) => return utils.simple_instruction("OP_INDEX_SET", offset), else => { debug.print("unknown opcode {d}\n", .{instruction}); return offset + 1; diff --git a/src/compile.zig b/src/compile.zig index 97a7705..1fb5cf0 100644 --- a/src/compile.zig +++ b/src/compile.zig @@ -29,6 +29,7 @@ const Precedence = enum { Term, Factor, Unary, + Index, Call, Primary, }; @@ -206,6 +207,18 @@ pub const Parser = struct { self.consume(TokenType.RIGHT_PAREN, "Expect ')' after expression."); } + fn index_(self: *Parser, can_assign: bool) ParsingError!void { + try self.expression(); + self.consume(TokenType.RIGHT_BRACKET, "Expecting ']"); + + if (can_assign and self.match(TokenType.EQUAL)) { + try self.expression(); + try self.emit_byte(@intFromEnum(OpCode.OP_INDEX_SET)); + } else { + try self.emit_byte(@intFromEnum(OpCode.OP_INDEX_GET)); + } + } + fn unary(self: *Parser, can_assign: bool) ParsingError!void { _ = can_assign; @@ -267,6 +280,8 @@ pub const Parser = struct { TokenType.DOT => ParserRule{ .prefix = null, .infix = dot, .precedence = Precedence.Call }, TokenType.MINUS => ParserRule{ .prefix = unary, .infix = binary, .precedence = Precedence.Term }, TokenType.PLUS => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Term }, + TokenType.LEFT_BRACKET => ParserRule{ .prefix = null, .infix = index_, .precedence = Precedence.Unary }, + TokenType.RIGHT_BRACKET => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.SEMICOLON => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.SLASH => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Factor }, TokenType.STAR => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Factor }, @@ -369,6 +384,17 @@ pub const Parser = struct { set_op = OpCode.OP_SET_GLOBAL; } + // handle assignments by index: + // - push value to the stack + // - modify it (through a OP_INDEX_SET) + // - update the variable + if (can_assign and self.match(TokenType.LEFT_BRACKET)) { + try self.emit_bytes(@intFromEnum(get_op), @intCast(arg)); + try self.index_(can_assign); + try self.emit_bytes(@intFromEnum(set_op), @intCast(arg)); + return; + } + if (can_assign and self.match(TokenType.EQUAL)) { try self.expression(); try self.emit_bytes(@intFromEnum(set_op), @intCast(arg)); diff --git a/src/opcode.zig b/src/opcode.zig index de2cc10..0960094 100644 --- a/src/opcode.zig +++ b/src/opcode.zig @@ -32,4 +32,6 @@ pub const OpCode = enum(u8) { OP_RETURN, OP_CLASS, OP_METHOD, + OP_INDEX_SET, + OP_INDEX_GET, }; diff --git a/src/scanner.zig b/src/scanner.zig index f60d114..0c10fb8 100644 --- a/src/scanner.zig +++ b/src/scanner.zig @@ -6,6 +6,8 @@ pub const TokenType = enum { RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, + LEFT_BRACKET, + RIGHT_BRACKET, COMMA, DOT, MINUS, @@ -55,6 +57,8 @@ pub const TokenType = enum { TokenType.RIGHT_PAREN => "RIGHT_PAREN", TokenType.LEFT_BRACE => "LEFT_BRACE", TokenType.RIGHT_BRACE => "RIGHT_BRACE", + TokenType.LEFT_BRACKET => "LEFT_BRACKET", + TokenType.RIGHT_BRACKET => "RIGHT_BRACKET", TokenType.COMMA => "COMMA", TokenType.DOT => "DOT", TokenType.MINUS => "MINUS", @@ -140,6 +144,8 @@ pub const Scanner = struct { ')' => self.make_token(TokenType.RIGHT_PAREN), '{' => self.make_token(TokenType.LEFT_BRACE), '}' => self.make_token(TokenType.RIGHT_BRACE), + '[' => self.make_token(TokenType.LEFT_BRACKET), + ']' => self.make_token(TokenType.RIGHT_BRACKET), ';' => self.make_token(TokenType.SEMICOLON), ',' => self.make_token(TokenType.COMMA), '.' => self.make_token(TokenType.DOT), diff --git a/src/vm.zig b/src/vm.zig index 57cd6e7..f8b16b9 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -338,6 +338,40 @@ pub const VM = struct { @intFromEnum(OpCode.OP_METHOD) => { self.define_method(self.read_constant().as_string()); }, + @intFromEnum(OpCode.OP_INDEX_GET) => { + if (!self.peek(0).is_number() or !self.peek(1).is_string()) { + self.runtime_error("A number and a string are required for indexes."); + return InterpretResult.RUNTIME_ERROR; + } + const index_val = self.pop(); + const value = self.pop(); + + const index: usize = @as(usize, @intFromFloat(index_val.as_number())); + if (index >= value.as_cstring().len) { + self.runtime_error("The index must be set between 0 and string len."); + return InterpretResult.RUNTIME_ERROR; + } + const c = value.as_cstring()[index .. index + 1]; + + _ = try self.push(Value.obj_val(&self.copy_string(c).obj)); + }, + @intFromEnum(OpCode.OP_INDEX_SET) => { + const value = self.pop(); + const index_val = self.pop(); + const origin = self.pop(); + + if (!value.is_string() or value.as_cstring().len != 1) { + self.runtime_error("Value to assign must be one byte."); + return InterpretResult.RUNTIME_ERROR; + } + + const index: usize = @as(usize, @intFromFloat(index_val.as_number())); + + var str = self.allocator.dupe(u8, origin.as_cstring()) catch unreachable; + str[index] = value.as_cstring()[0]; + + _ = try self.push(Value.obj_val(&self.take_string(str).obj)); + }, else => { debug.print("Invalid instruction: {d}\n", .{instruction}); return InterpretResult.RUNTIME_ERROR;