diff --git a/develop.lola b/develop.lola index ef5060d..9206653 100644 --- a/develop.lola +++ b/develop.lola @@ -1,99 +1,3 @@ -// Without optimizations: -// Number of runs: 100 -// Mean time: 74276.98325 µs -// Mean #instructions: 1122 -// Mean #stalls: 0 -// Mean instruction/s: 15105.621565480044 - -// With string-copy-removal: -// Number of runs: 100 -// Mean time: 19746.62485 µs -// Mean #instructions: 1122 -// Mean #stalls: 0 -// Mean instruction/s: 56819.83673275689 - -// Make clone() return a refcounted string instead of a flat clone: -// Number of runs: 100 -// Mean time: 17273.46292 µs -// Mean #instructions: 1122 -// Mean #stalls: 0 -// Mean instruction/s: 64955.128291090805 - -function dump(str) { } - -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); -dump("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); - -var deep_copy = [ - [ "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello" ], - [ "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello" ], - [ "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello" ], - [ "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello" ], - [ "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello" ], - [ "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello" ], - [ "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello" ], - [ "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello" ], - [ "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello" ], - [ "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello" ], - [ "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello" ], - [ "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello" ] -]; - -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); -dump(deep_copy, deep_copy, deep_copy, deep_copy, deep_copy, deep_copy); \ No newline at end of file +var x = "a"; +x[0] = true; \ No newline at end of file diff --git a/documentation/README.md b/documentation/README.md index 760431f..a44b498 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -144,7 +144,7 @@ LoLa provides several operators that execute arithmetic, logic or comparison ope | `a <= b` | `number` |Less-or-equal test|`(3 <= 2) == false`| | `a > b` | `number` |Greater-than test|`(3 > 2) == true`| | `a < b` | `number` |Less-than test|`(3 < 2) == false`| -| `a[i]` | `array` | Array index | `([1,2,3])[1] == 2` | +| `a[i]` | `array`, `string` | Array index, string index | `([1,2,3])[1] == 2` | ### Operator Precedence diff --git a/src/frontend/main.zig b/src/frontend/main.zig index f8afa7e..d216f8c 100644 --- a/src/frontend/main.zig +++ b/src/frontend/main.zig @@ -337,7 +337,16 @@ fn run(options: RunCLI, files: []const []const u8) !u8 { while (true) { var result = vm.execute(options.limit) catch |err| { var stderr = std.io.getStdErr().writer(); - try stderr.print("Panic during execution: {}\n", .{@errorName(err)}); + + if (std.builtin.mode == .Debug) { + if (@errorReturnTrace()) |err_trace| { + std.debug.dumpStackTrace(err_trace.*); + } else { + try stderr.print("Panic during execution: {}\n", .{@errorName(err)}); + } + } else { + try stderr.print("Panic during execution: {}\n", .{@errorName(err)}); + } try stderr.print("Call stack:\n", .{}); try vm.printStackTrace(stderr); diff --git a/src/library/compiler/analysis.zig b/src/library/compiler/analysis.zig index e07013c..7227713 100644 --- a/src/library/compiler/analysis.zig +++ b/src/library/compiler/analysis.zig @@ -21,6 +21,8 @@ const AnalysisState = struct { const ValidationError = error{OutOfMemory}; +const array_or_string = TypeSet.init(.{ .array, .string }); + fn expressionTypeToString(src: ast.Expression.Type) []const u8 { return switch (src) { .array_indexer => "array indexer", @@ -56,10 +58,20 @@ fn validateExpression(state: *AnalysisState, diagnostics: *Diagnostics, scope: * const array_type = try validateExpression(state, diagnostics, scope, indexer.value.*); const index_type = try validateExpression(state, diagnostics, scope, indexer.index.*); - try performTypeCheck(diagnostics, indexer.value.location, TypeSet.from(.array), array_type); + try performTypeCheck(diagnostics, indexer.value.location, array_or_string, array_type); try performTypeCheck(diagnostics, indexer.index.location, TypeSet.from(.number), index_type); - return TypeSet.any; + if (array_type.contains(.array)) { + // when we're possibly indexing an array, + // we return a value of type `any` + return TypeSet.any; + } else if (array_type.contains(.string)) { + // when we are not an array, but a string, + // we can only return a number. + return TypeSet.from(.number); + } else { + return TypeSet.empty; + } }, .variable_expr => |variable_name| { @@ -178,17 +190,24 @@ fn validateStore(state: *AnalysisState, diagnostics: *Diagnostics, scope: *Scope }); return; } + switch (expression.type) { .array_indexer => |indexer| { const array_val = try validateExpression(state, diagnostics, scope, indexer.value.*); const index_val = try validateExpression(state, diagnostics, scope, indexer.index.*); - try performTypeCheck(diagnostics, indexer.value.location, TypeSet.from(.array), array_val); + try performTypeCheck(diagnostics, indexer.value.location, array_or_string, array_val); try performTypeCheck(diagnostics, indexer.index.location, TypeSet.from(.number), index_val); + if (array_val.contains(.string) and !array_val.contains(.array)) { + // when we are sure we write into a string, but definitly not an array + // check if we're writing a number. + try performTypeCheck(diagnostics, expression.location, TypeSet.from(.number), type_hint); + } + // now propagate the store validation back to the lvalue. // Note that we can assume that the lvalue _is_ a array, as it would be a type mismatch otherwise. - try validateStore(state, diagnostics, scope, indexer.value.*, TypeSet.from(.array)); + try validateStore(state, diagnostics, scope, indexer.value.*, array_or_string.intersection(array_val)); }, .variable_expr => |variable_name| { diff --git a/src/library/runtime/value.zig b/src/library/runtime/value.zig index c1dcb09..bff9820 100644 --- a/src/library/runtime/value.zig +++ b/src/library/runtime/value.zig @@ -435,23 +435,13 @@ pub const String = struct { /// Clones `text` with the given parameter and stores the /// duplicated value. pub fn init(allocator: *std.mem.Allocator, text: []const u8) !Self { - const alignment = @alignOf(usize); - - const ptr_offset = std.mem.alignForward(text.len, alignment); - const buffer = try allocator.allocAdvanced( + var string = try initUninitialized(allocator, text.len); + std.mem.copy( u8, - alignment, - ptr_offset + @sizeOf(usize), - .exact, + string.obtainMutableStorage() catch unreachable, + text, ); - std.mem.copy(u8, buffer, text); - std.mem.writeIntNative(usize, buffer[ptr_offset..][0..@sizeOf(usize)], 1); - - return Self{ - .allocator = allocator, - .contents = buffer[0..text.len], - .refcount = @ptrCast(*usize, @alignCast(alignment, buffer.ptr + ptr_offset)), - }; + return string; } /// Returns a string that will take ownership of the passed `text` and diff --git a/src/library/runtime/vm.zig b/src/library/runtime/vm.zig index 463fbad..6fee8c5 100644 --- a/src/library/runtime/vm.zig +++ b/src/library/runtime/vm.zig @@ -302,40 +302,81 @@ pub const VM = struct { }, .array_load => { - var array_val = try self.pop(); - defer array_val.deinit(); + var indexed_val = try self.pop(); + defer indexed_val.deinit(); var index_val = try self.pop(); defer index_val.deinit(); - const item = try getArrayItem(&array_val, index_val); + const index = try index_val.toInteger(usize); + + var dupe: Value = switch (indexed_val) { + .array => |arr| blk: { + if (index >= arr.contents.len) + return error.IndexOutOfRange; + + break :blk try arr.contents[index].clone(); + }, + .string => |str| blk: { + if (index >= str.contents.len) + return error.IndexOutOfRange; + + break :blk Value.initInteger(u8, str.contents[index]); + }, + else => return error.TypeMismatch, + }; - var dupe = try item.clone(); errdefer dupe.deinit(); try self.push(dupe); }, .array_store => { - var array_val = try self.pop(); - errdefer array_val.deinit(); + var indexed_val = try self.pop(); + errdefer indexed_val.deinit(); var index_val = try self.pop(); defer index_val.deinit(); - var value = try self.pop(); - { + if (indexed_val == .array) { + var value = try self.pop(); // only destroy value when we fail to get the array item, // otherwise the value is stored in the array and must not // be deinitialized after that errdefer value.deinit(); - const item = try getArrayItem(&array_val, index_val); + const index = try index_val.toInteger(usize); + if (index >= indexed_val.array.contents.len) + return error.IndexOutOfRange; - item.replaceWith(value); + indexed_val.array.contents[index].replaceWith(value); + } else if (indexed_val == .string) { + var value = try self.pop(); + defer value.deinit(); + + const string = &indexed_val.string; + + const byte = try value.toInteger(u8); + + const index = try index_val.toInteger(usize); + if (index >= string.contents.len) + return error.IndexOutOfRange; + + if (string.refcount != null and string.refcount.?.* > 1) { + var new_string = try String.init(self.allocator, string.contents); + + string.deinit(); + string.* = new_string; + } + std.debug.assert(string.refcount == null or string.refcount.?.* == 1); + + const contents = try string.obtainMutableStorage(); + contents[index] = byte; + } else { + return error.TypeMismatch; } - try self.push(array_val); + try self.push(indexed_val); }, // Iterator Section: @@ -713,33 +754,6 @@ pub const VM = struct { )); } - fn getArrayItem(array_val: *Value, index_val: Value) !*Value { - const array = try array_val.getArray(); - const flt_index = try index_val.toNumber(); - - if (flt_index < 0) - return error.IndexOutOfRange; - - const index = try floatToInt(usize, std.math.trunc(flt_index)); - std.debug.assert(index >= 0); - if (index >= array.contents.len) - return error.IndexOutOfRange; - - return &array.contents[index]; - } - - fn floatToInt(comptime T: type, flt: anytype) error{Overflow}!T { - comptime std.debug.assert(@typeInfo(T) == .Int); // must pass an integer - comptime std.debug.assert(@typeInfo(@TypeOf(flt)) == .Float); // must pass a float - if (flt > std.math.maxInt(T)) { - return error.Overflow; - } else if (flt < std.math.minInt(T)) { - return error.Overflow; - } else { - return @floatToInt(T, flt); - } - } - const SingleResult = enum { /// The program has encountered an asynchronous function completed, diff --git a/src/test/behaviour.lola b/src/test/behaviour.lola index a8571e4..59f5094 100644 --- a/src/test/behaviour.lola +++ b/src/test/behaviour.lola @@ -259,4 +259,19 @@ ExpectEqual('💩', 0x1F4A9); ExpectEqual('\xF3', 0xF3); ExpectEqual('\xf0\x9f\x92\xa9', 0x1F4A9); // handcrafted utf8 escape sequence -Print("Behaviour test suite passed."); +// String indexing +ExpectEqual("a"[0], 'a'); +ExpectEqual("ab"[0], 'a'); +ExpectEqual("ab"[1], 'b'); +{ + var mut = "abc"; + ExpectEqual(mut, "abc"); + mut[0] = '1'; + ExpectEqual(mut, "1bc"); + mut[1] = '2'; + ExpectEqual(mut, "12c"); + mut[2] = '3'; + ExpectEqual(mut, "123"); +} + +Print("Behaviour test suite passed."); \ No newline at end of file