Skip to content

Commit

Permalink
Implements string indexing. Closes #9 (again)
Browse files Browse the repository at this point in the history
  • Loading branch information
ikskuh committed Oct 1, 2020
1 parent b11c913 commit dd3d234
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 158 deletions.
100 changes: 2 additions & 98 deletions develop.lola
Original file line number Diff line number Diff line change
@@ -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);
var x = "a";
x[0] = true;
2 changes: 1 addition & 1 deletion documentation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
11 changes: 10 additions & 1 deletion src/frontend/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
27 changes: 23 additions & 4 deletions src/library/compiler/analysis.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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| {
Expand Down Expand Up @@ -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| {
Expand Down
20 changes: 5 additions & 15 deletions src/library/runtime/value.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
90 changes: 52 additions & 38 deletions src/library/runtime/vm.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand Down
Loading

0 comments on commit dd3d234

Please sign in to comment.