implementing string indexes

This commit is contained in:
Patrick MARIE 2024-08-31 22:07:12 +02:00
parent b839d67cea
commit 0bc5f495b9
6 changed files with 73 additions and 0 deletions

3
samples/indices.lox Normal file
View File

@ -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;

View File

@ -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_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_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_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 => { else => {
debug.print("unknown opcode {d}\n", .{instruction}); debug.print("unknown opcode {d}\n", .{instruction});
return offset + 1; return offset + 1;

View File

@ -29,6 +29,7 @@ const Precedence = enum {
Term, Term,
Factor, Factor,
Unary, Unary,
Index,
Call, Call,
Primary, Primary,
}; };
@ -206,6 +207,18 @@ pub const Parser = struct {
self.consume(TokenType.RIGHT_PAREN, "Expect ')' after expression."); 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 { fn unary(self: *Parser, can_assign: bool) ParsingError!void {
_ = can_assign; _ = can_assign;
@ -267,6 +280,8 @@ pub const Parser = struct {
TokenType.DOT => ParserRule{ .prefix = null, .infix = dot, .precedence = Precedence.Call }, TokenType.DOT => ParserRule{ .prefix = null, .infix = dot, .precedence = Precedence.Call },
TokenType.MINUS => ParserRule{ .prefix = unary, .infix = binary, .precedence = Precedence.Term }, TokenType.MINUS => ParserRule{ .prefix = unary, .infix = binary, .precedence = Precedence.Term },
TokenType.PLUS => ParserRule{ .prefix = null, .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.SEMICOLON => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
TokenType.SLASH => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Factor }, TokenType.SLASH => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Factor },
TokenType.STAR => 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; 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)) { if (can_assign and self.match(TokenType.EQUAL)) {
try self.expression(); try self.expression();
try self.emit_bytes(@intFromEnum(set_op), @intCast(arg)); try self.emit_bytes(@intFromEnum(set_op), @intCast(arg));

View File

@ -32,4 +32,6 @@ pub const OpCode = enum(u8) {
OP_RETURN, OP_RETURN,
OP_CLASS, OP_CLASS,
OP_METHOD, OP_METHOD,
OP_INDEX_SET,
OP_INDEX_GET,
}; };

View File

@ -6,6 +6,8 @@ pub const TokenType = enum {
RIGHT_PAREN, RIGHT_PAREN,
LEFT_BRACE, LEFT_BRACE,
RIGHT_BRACE, RIGHT_BRACE,
LEFT_BRACKET,
RIGHT_BRACKET,
COMMA, COMMA,
DOT, DOT,
MINUS, MINUS,
@ -55,6 +57,8 @@ pub const TokenType = enum {
TokenType.RIGHT_PAREN => "RIGHT_PAREN", TokenType.RIGHT_PAREN => "RIGHT_PAREN",
TokenType.LEFT_BRACE => "LEFT_BRACE", TokenType.LEFT_BRACE => "LEFT_BRACE",
TokenType.RIGHT_BRACE => "RIGHT_BRACE", TokenType.RIGHT_BRACE => "RIGHT_BRACE",
TokenType.LEFT_BRACKET => "LEFT_BRACKET",
TokenType.RIGHT_BRACKET => "RIGHT_BRACKET",
TokenType.COMMA => "COMMA", TokenType.COMMA => "COMMA",
TokenType.DOT => "DOT", TokenType.DOT => "DOT",
TokenType.MINUS => "MINUS", TokenType.MINUS => "MINUS",
@ -140,6 +144,8 @@ pub const Scanner = struct {
')' => self.make_token(TokenType.RIGHT_PAREN), ')' => self.make_token(TokenType.RIGHT_PAREN),
'{' => self.make_token(TokenType.LEFT_BRACE), '{' => self.make_token(TokenType.LEFT_BRACE),
'}' => self.make_token(TokenType.RIGHT_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.SEMICOLON),
',' => self.make_token(TokenType.COMMA), ',' => self.make_token(TokenType.COMMA),
'.' => self.make_token(TokenType.DOT), '.' => self.make_token(TokenType.DOT),

View File

@ -338,6 +338,40 @@ pub const VM = struct {
@intFromEnum(OpCode.OP_METHOD) => { @intFromEnum(OpCode.OP_METHOD) => {
self.define_method(self.read_constant().as_string()); 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 => { else => {
debug.print("Invalid instruction: {d}\n", .{instruction}); debug.print("Invalid instruction: {d}\n", .{instruction});
return InterpretResult.RUNTIME_ERROR; return InterpretResult.RUNTIME_ERROR;