Skip to content

Commit

Permalink
Use ids for symbols. (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
wmedrano authored Sep 1, 2024
1 parent 5f7b012 commit df032ae
Show file tree
Hide file tree
Showing 12 changed files with 326 additions and 199 deletions.
1 change: 1 addition & 0 deletions site/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ It should be easy to get started writing Fizz. Fizz supports the following:
> ```
- Common datatypes like ints, floats, strings, structs, and lists.
> ```lisp
> >> true
> >> 1
> >> 1.2
> >> "hello world"
Expand Down
17 changes: 16 additions & 1 deletion site/zig-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,9 @@ const other_val = try vm.evalStr(i64, allocator, "10");
```


## Errors
## Debugging

### Errors

Fizz uses the standard Zig error mechanism, but also stores error logs under
`vm.env.errors`. The current errors can be printed by:
Expand All @@ -184,6 +186,19 @@ fn printErrors(vm: *fizz.Vm) void {
}
```

### Printing Values

Fizz values may contain identifiers that are internal to the Vm. To properly
format a `fizz.Val`, call `fizz.Val.formatter` to provide a Vm.

```zig
const val = try vm.evalStr(fizz.Val, allocator, "(struct 'id 0 'name \"Will\")");
std.debug.print("Formatted value is: {any}", .{val.formatter(null)});
// (struct 'symbol-#43 0 'symbol-#44 "Will")
std.debug.print("Better formatted value is: {any}", .{val.formatter(&vm)});
// (struct 'id 0 'name "Will")
```


## Custom Functions

Expand Down
18 changes: 9 additions & 9 deletions src/ByteCode.zig
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const ByteCode = @This();
const Symbol = Val.Symbol;
const Val = @import("val.zig").Val;
const Ir = @import("ir.zig").Ir;
const MemoryManager = @import("MemoryManager.zig");
Expand All @@ -17,8 +18,7 @@ pub fn deinit(self: *ByteCode, allocator: std.mem.Allocator) void {
allocator.free(self.name);
for (self.instructions.items) |i| {
switch (i) {
.deref_local => |sym| allocator.free(sym),
.deref_global => |sym| allocator.free(sym),
.deref_local => |sym| allocator.free(sym.module),
.import_module => |path| allocator.free(path),
else => {},
}
Expand Down Expand Up @@ -56,7 +56,7 @@ const ByteCodeValuesIter = struct {
.deref_local => {},
.deref_global => {},
.get_arg => {},
.define => |v| return v,
.define => {},
.move => {},
.eval => {},
.jump => {},
Expand All @@ -81,13 +81,13 @@ pub const Instruction = union(enum) {
/// Push a constant onto the stack.
push_const: Val,
/// Dereference the symbol from the current module.
deref_local: []const u8,
deref_local: struct { module: []const u8, sym: Symbol },
/// Dereference the symbol from the global module.
deref_global: []const u8,
deref_global: Symbol,
/// Get the nth value (0-based index) from the base of the current function call stack.
get_arg: usize,
/// Define a symbol on the current module.
define: Val,
define: Symbol,
/// Move the top value of the stack into the given index.
move: usize,
/// Evaluate the top n elements of the stack. The deepmost value should be a function.
Expand Down Expand Up @@ -122,16 +122,16 @@ pub const Instruction = union(enum) {
) !void {
switch (self.*) {
.push_const => |v| try writer.print("push_const({any})", .{v}),
.deref_global => |sym| try writer.print("deref_global({s})", .{sym}),
.deref_local => |sym| try writer.print("deref_local({s})", .{sym}),
.deref_global => |sym| try writer.print("deref_global({d})", .{sym.id}),
.deref_local => |sym| try writer.print("deref_local({s}, {d})", .{ sym.module, sym.sym.id }),
.get_arg => |n| try writer.print("get_arg({d})", .{n}),
.move => |n| try writer.print("move({d})", .{n}),
.eval => |n| try writer.print("eval({d})", .{n}),
.jump => |n| try writer.print("jump({d})", .{n}),
.jump_if => |n| try writer.print("jump_if({d})", .{n}),
.import_module => |m| try writer.print("import({s})", .{m}),
.ret => try writer.print("ret()", .{}),
.define => |sym| try writer.print("define({s})", .{sym}),
.define => |sym| try writer.print("define({d})", .{sym.id}),
}
}
};
Expand Down
40 changes: 27 additions & 13 deletions src/Compiler.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const ByteCode = @import("ByteCode.zig");
const Ir = @import("ir.zig").Ir;
const MemoryManager = @import("MemoryManager.zig");
const Val = @import("val.zig").Val;
const Symbol = Val.Symbol;
const Compiler = @This();
const Env = @import("Env.zig");
const Vm = @import("Vm.zig");
Expand All @@ -26,13 +27,16 @@ module: *Module,
is_module: bool = false,

/// The values that are defined in `module`.
module_defined_vals: std.StringHashMap(void),
module_defined_vals: std.StringHashMap(Symbol),

/// Initialize a compiler for a function that lives inside `module`.
pub fn initFunction(allocator: std.mem.Allocator, env: *Env, module: *Module, args: []const []const u8) !Compiler {
var module_defined_vals = std.StringHashMap(void).init(allocator);
var module_defined_vals = std.StringHashMap(Symbol).init(allocator);
var module_definitions = module.values.keyIterator();
while (module_definitions.next()) |def| try module_defined_vals.put(def.*, {});
while (module_definitions.next()) |sym| {
if (env.memory_manager.symbols.getName(sym.*)) |sym_name|
try module_defined_vals.put(sym_name, sym.*);
}
const scopes = try ScopeManager.initWithArgs(allocator, args);
return .{
.env = env,
Expand All @@ -45,9 +49,12 @@ pub fn initFunction(allocator: std.mem.Allocator, env: *Env, module: *Module, ar

/// Initialize a compiler for a module definition.
pub fn initModule(allocator: std.mem.Allocator, env: *Env, module: *Module) !Compiler {
var module_defined_vals = std.StringHashMap(void).init(allocator);
var module_defined_vals = std.StringHashMap(Symbol).init(allocator);
var module_definitions = module.values.keyIterator();
while (module_definitions.next()) |def| try module_defined_vals.put(def.*, {});
while (module_definitions.next()) |sym| {
if (env.memory_manager.symbols.getName(sym.*)) |sym_name|
try module_defined_vals.put(sym_name, sym.*);
}
return .{
.env = env,
.scopes = try ScopeManager.init(allocator),
Expand All @@ -67,7 +74,7 @@ pub fn deinit(self: *Compiler) void {
pub fn compile(self: *Compiler, ir: *const Ir) !Val {
var irs = [1]*const Ir{ir};
if (self.is_module) {
try ir.populateDefinedVals(&self.module_defined_vals);
try ir.populateDefinedVals(&self.module_defined_vals, &self.env.memory_manager);
}
const bc = try self.compileImpl(&irs);
if (bc.instructions.items.len == 0 or bc.instructions.items[bc.instructions.items.len - 1].tag() != .ret) {
Expand Down Expand Up @@ -126,11 +133,11 @@ fn addIr(self: *Compiler, bc: *ByteCode, ir: *const Ir) Error!void {
.define => |def| {
if (self.computeStackAction(def.expr) != .push) return Error.BadSyntax;
if (is_module) {
const symbol_val = try self.env.memory_manager.allocateSymbolVal(def.name);
const sym = try self.env.memory_manager.allocateSymbol(def.name);
try self.addIr(bc, def.expr);
try bc.instructions.append(
self.env.memory_manager.allocator,
.{ .define = symbol_val },
.{ .define = sym },
);
} else {
const local_idx = try self.scopes.addVariable(def.name);
Expand All @@ -154,11 +161,16 @@ fn addIr(self: *Compiler, bc: *ByteCode, ir: *const Ir) Error!void {
if (self.scopes.variableIdx(s)) |var_idx| {
try bc.instructions.append(self.env.memory_manager.allocator, .{ .get_arg = var_idx });
} else {
const sym = try self.env.memory_manager.allocator.dupe(u8, s);
const parsed_sym = Module.parseModuleAndSymbol(sym);
const parsed_sym = try Module.parseModuleAndSymbol(s, &self.env.memory_manager);
if (self.module_defined_vals.contains(s) or parsed_sym.module_alias != null) {
try bc.instructions.append(self.env.memory_manager.allocator, .{ .deref_local = sym });
const module = try self.env.memory_manager.allocator.dupe(u8, parsed_sym.module_alias orelse "");
const local = .{
.module = module,
.sym = parsed_sym.sym,
};
try bc.instructions.append(self.env.memory_manager.allocator, .{ .deref_local = local });
} else {
const sym = try self.env.memory_manager.allocateSymbol(s);
try bc.instructions.append(self.env.memory_manager.allocator, .{ .deref_global = sym });
}
}
Expand Down Expand Up @@ -316,6 +328,8 @@ test "module with define expressions" {
var compiler = try Compiler.initModule(std.testing.allocator, &vm.env, try vm.getOrCreateModule(.{}));
defer compiler.deinit();
const actual = try compiler.compile(&ir);
const pi_symbol = try vm.env.memory_manager.allocateSymbol("pi");
const e_symbol = try vm.env.memory_manager.allocateSymbol("e");
try std.testing.expectEqualDeep(Val{
.bytecode = @constCast(&ByteCode{
.name = "",
Expand All @@ -324,9 +338,9 @@ test "module with define expressions" {
.instructions = std.ArrayListUnmanaged(ByteCode.Instruction){
.items = @constCast(&[_]ByteCode.Instruction{
.{ .push_const = .{ .float = 3.14 } },
.{ .define = .{ .symbol = "pi" } },
.{ .define = pi_symbol },
.{ .push_const = .{ .float = 2.718 } },
.{ .define = .{ .symbol = "e" } },
.{ .define = e_symbol },
.ret,
}),
.capacity = actual.bytecode.instructions.capacity,
Expand Down
38 changes: 27 additions & 11 deletions src/MemoryManager.zig
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const MemoryManager = @This();
const Val = @import("val.zig").Val;
const Symbol = Val.Symbol;
const ByteCode = @import("ByteCode.zig");
const Module = @import("Module.zig");
const StringInterner = @import("datastructures/string_interner.zig").StringInterner;

const std = @import("std");

Expand All @@ -11,17 +13,20 @@ allocator: std.mem.Allocator,
/// cleaned up for garbage collection.
reachable_color: Color = Color.red,

/// Contains all the symbols.
symbols: StringInterner(Symbol) = .{},

/// A map from an owned string to its reachable color.
strings: std.StringHashMapUnmanaged(Color) = .{},
/// A map from a struct pointer to its Color.
structs: std.AutoHashMapUnmanaged(*std.StringHashMapUnmanaged(Val), Color) = .{},
structs: std.AutoHashMapUnmanaged(*std.AutoHashMapUnmanaged(Symbol, Val), Color) = .{},
/// A map from an owned list pointer to its length and color.
lists: std.AutoHashMapUnmanaged([*]Val, LenAndColor) = .{},
/// A map from a bytecode pointer to its color.
bytecode: std.AutoHashMapUnmanaged(*ByteCode, Color) = .{},

keep_alive_strings: std.StringHashMapUnmanaged(usize) = .{},
keep_alive_structs: std.AutoHashMapUnmanaged(*std.StringHashMapUnmanaged(Val), usize) = .{},
keep_alive_structs: std.AutoHashMapUnmanaged(*std.AutoHashMapUnmanaged(Symbol, Val), usize) = .{},
keep_alive_lists: std.AutoHashMapUnmanaged([*]Val, usize) = .{},
keep_alive_bytecode: std.AutoHashMapUnmanaged(*ByteCode, usize) = .{},

Expand Down Expand Up @@ -56,6 +61,8 @@ pub fn init(allocator: std.mem.Allocator) MemoryManager {
pub fn deinit(self: *MemoryManager) void {
self.sweep() catch {};
self.sweep() catch {};

self.symbols.deinit(self.allocator);
self.strings.deinit(self.allocator);
self.structs.deinit(self.allocator);
self.lists.deinit(self.allocator);
Expand Down Expand Up @@ -87,9 +94,19 @@ pub fn allocateStringVal(self: *MemoryManager, str: []const u8) !Val {
return .{ .string = try self.allocateString(str) };
}

/// Create a new symbol and return its id. If the symbol already exists, its id is returned.
pub fn allocateSymbol(self: *MemoryManager, name: []const u8) !Symbol {
return try self.symbols.getOrMakeId(self.allocator, name);
}

/// Get the symbol from a name or `null` if it has not been allocated.
pub fn nameToSymbol(self: *const MemoryManager, name: []const u8) ?Symbol {
return self.name_to_symbol.get(name);
}

/// Create a new managed symbol `Val` from `sym`.
pub fn allocateSymbolVal(self: *MemoryManager, sym: []const u8) !Val {
return .{ .symbol = try self.allocateString(sym) };
return .{ .symbol = try self.allocateSymbol(sym) };
}

/// Allocate a new list of size `len`. All elements within the slice are uninitialized.
Expand Down Expand Up @@ -137,8 +154,8 @@ pub fn allocateByteCode(self: *MemoryManager, module: *Module) !*ByteCode {
}

/// Allocate a new empty struct.
pub fn allocateStruct(self: *MemoryManager) !*std.StringHashMapUnmanaged(Val) {
const m = try self.allocator.create(std.StringHashMapUnmanaged(Val));
pub fn allocateStruct(self: *MemoryManager) !*std.AutoHashMapUnmanaged(Symbol, Val) {
const m = try self.allocator.create(std.AutoHashMapUnmanaged(Symbol, Val));
errdefer self.allocator.destroy(m);
m.* = .{};
try self.structs.put(self.allocator, m, self.reachable_color.swap());
Expand All @@ -149,14 +166,17 @@ pub fn allocateStruct(self: *MemoryManager) !*std.StringHashMapUnmanaged(Val) {
pub fn markVal(self: *MemoryManager, v: Val) !void {
switch (v) {
.string => |s| try self.strings.put(self.allocator, s, self.reachable_color),
.symbol => |s| try self.strings.put(self.allocator, s, self.reachable_color),
.structV => |s| {
// Mark struct as reachable.
if (self.structs.getEntry(s)) |entry| {
if (entry.value_ptr.* == self.reachable_color) return;
entry.value_ptr.* = self.reachable_color;
} else {
// This should not be possible as all structs should be in self.structs. Consider
// returning an error instead.
try self.structs.put(self.allocator, s, self.reachable_color);
}
// Mark any child values.
var iter = s.valueIterator();
while (iter.next()) |structVal| {
try self.markVal(structVal.*);
Expand Down Expand Up @@ -219,15 +239,11 @@ pub fn sweep(self: *MemoryManager) !void {
for (list_free_targets.items) |t| _ = self.lists.remove(t);
_ = tmp_arena.reset(.retain_capacity);

var struct_free_targets = std.ArrayList(*std.StringHashMapUnmanaged(Val)).init(tmp_arena.allocator());
var struct_free_targets = std.ArrayList(*std.AutoHashMapUnmanaged(Symbol, Val)).init(tmp_arena.allocator());
var structsIter = self.structs.iterator();
while (structsIter.next()) |entry| {
if (entry.value_ptr.* != self.reachable_color) {
try struct_free_targets.append(entry.key_ptr.*);
var fieldsIter = entry.key_ptr.*.iterator();
while (fieldsIter.next()) |field| {
self.allocator.free(field.key_ptr.*);
}
}
}
for (struct_free_targets.items) |t| {
Expand Down
Loading

0 comments on commit df032ae

Please sign in to comment.