Skip to content

Commit

Permalink
Vm returns a Zig value by default. (#9)
Browse files Browse the repository at this point in the history
* Return a fizz.Val from Vm.evalStr

* Label new objects as unreachable when created.

* Makes it so that recursive structures have their children marked.

* Simplify evalStr call by removing temp allocator.

* Remove 0.2.0 label on map.
  • Loading branch information
wmedrano authored Aug 27, 2024
1 parent a347d8e commit b48ab92
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 117 deletions.
2 changes: 2 additions & 0 deletions site/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ Will focus on:
- Incorporating user feedback.
- Performance & API tweaks.

- `Vm.evalStr` returns a Zig type by default.

### 0.1.1 (Current)

- Add `map` and `filter` functions.
Expand Down
4 changes: 2 additions & 2 deletions site/fizz-language-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ or equal to the length of the list.
$1 = 2
```

### map (0.2.0 candidate)
### map

`(map <function> <list>)`

Expand All @@ -315,7 +315,7 @@ Returns a list by applying `<function>` to each element in `<list>`.
$1 = (2 3 4)
```

### filter (0.2.0 candidate)
### filter

`(filter <function> <list>)`

Expand Down
3 changes: 1 addition & 2 deletions site/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ const fizz = @import("fizz");
var vm = try fizz.Vm.init(std.testing.allocator);
defer vm.deinit();
const v = try vm.evalStr(std.testing.allocator, "(list 1 2 3 4)");
const actual = try vm.env.toZig([]i64, std.testing.allocator, v);
const actual = try vm.evalStr([]i64, std.testing.allocator, "(list 1 2 3 4)");
defer std.testing.allocator.free(actual);
try std.testing.expectEqualDeep(&[_]i64{ 1, 2, 3, 4 }, actual);
```
45 changes: 22 additions & 23 deletions site/zig-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ nav_order: 1
var vm = try fizz.Vm.init(allocator);
defer vm.deinit();
```
1. Run some code within the virtual machine.
1. Write some code within the virtual machine.
```zig
const src = \\
\\ (define magic-numbers (list 1 2 3 4))
Expand All @@ -41,17 +41,12 @@ nav_order: 1
\\ 'numbers-sum (apply + magic-numbers)
\\ )
;
const val = try vm.EvalStr(allocator, src);
```
1. Convert VM values into Zig.
1. Run the code in the VM.
```zig
const ResultType = struct { numbers: []const i64, numbers_sum: i64 };
const result = try vm.env.toZig(
ResultType,
allocator,
val,
);
defer allocator.free(result.numbers);
const result = try vm.evalStr(ResultType, std.testing.allocator, src);
defer std.testing.allocator.free(result.numbers);
```
1. Run the garbage collector from time to time.
```zig
Expand All @@ -60,25 +55,29 @@ nav_order: 1

## Evaluating Expressions

Expressions can be evaluated with `vm.evalStr`. The value of the returned
`fizz.Val` is guaranteed to exist until the next garbage collection run. See
[Extracting Values](#extracting-values) to extend the lifetime of the returned
values.
Expressions can be evaluated with `vm.evalStr`. The first argument is the type
that should be returned. If you do not care about the return value, `fizz.Val`
can be used to hold any value returned by the VM.

{: .warning}
> Any `fizz.Val` that is returned is guaranteed to exist until the next garbage
> collection run. Use a concrete Zig type, (e.g. []u8) to ensure that the
> allocator is used to extend the lifetime.
```zig
fn evalStr(self: *fizz.Vm, tmp_allocator: std.mem.Allocator, expr: []const u8) fizz.Val
fn evalStr(self: *fizz.Vm, T: type, allocator: std.mem.allocator, expr: []const u8) !T
```

- `self`: Pointer to the virtual machine.
- `tmp_allocator`: Allocator used for temporary memory for AST and ByteCode
compiler.
- `allocator`: Allocator used to allocate any slices or strings in `T`.
- `expr`: Lisp expression to evaluate. If multiple expressions are provided, the
returned `fizz.Val` will contain the value of the final expression.
return value will contain the value of the final expression.


## Extracting Values

Values are extracted using `vm.env.toZig`.
Values from the Fizz VM are extracted from `fizz.Vm.evalStr`. They can also manually
be extracted from a `fizz.Val` by calling `vm.env.toZig`.

```zig
fn toZig(self: *fizz.Env, comptime T: type, allocator: std.mem.Allocator, val: fizz.Val) !T
Expand All @@ -93,7 +92,7 @@ fn toZig(self: *fizz.Env, comptime T: type, allocator: std.mem.Allocator, val: f

```zig
fn buildStringInFizz(allocator: std.mem.allocator, vm: *fizz.Vm) ![]const u8 {
const val = try vm.evalStr(allocator, "(str-concat (list \"hello\" \" \" \"world\"))");
const val = try vm.evalStr(fizz.Val, allocator, "(str-concat (list \"hello\" \" \" \"world\"))");
const string_val = try vm.env.toZig([]const u8, val);
return string_val;
}
Expand All @@ -105,7 +104,8 @@ fn buildStringInFizz(allocator: std.mem.allocator, vm: *fizz.Vm) ![]const u8 {
- `bool`: Either `true` or `false`.
- `i64`: Fizz integers.
- `f64`: Can convert from either a Fizz int or a Fizz float.
- `void`: Converts from none.
- `void`: Converts from none. None is often returned by expressions that don't
typically return values like `(define x 1)`.
- `[]u8` or `[]const u8`: Converts from Fizz strings.
- `[]T` or `[]const T`: Converts from a Fizz list where `T` is a supported conversion.
- Supports nested lists (e.g., `[][]f64`).
Expand All @@ -132,8 +132,7 @@ const src = \\(struct
\\ 'my-list (list 1 2 3 4)
\\ 'nested (struct 'a 1 'b 2.0)
\\)
const complex_val = try vm.evalStr(allocator, src);
const result = try vm.env.toZig(TestType, allocator, complex_val);
const result = try vm.evalStr(TestType, std.testing.allocator, src);
defer allocator.free(result.my_string);
defer allocator.free(result.my_list);
```
Expand Down Expand Up @@ -179,7 +178,7 @@ fn beep(env: *Vm.Environment, args: []const Vm.Val) Vm.NativeFnError!Vm.Val {
}
try vm.registerGlobalFn("beep!", beep)
_ = try vm.evalStr(allocator, "(beep!)")
_ = try vm.evalStr(fizz.Val, allocator, "(beep!)")
```

For some examples, see
Expand Down
36 changes: 16 additions & 20 deletions src/Environment.zig
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ pub fn allocator(self: *const Environment) std.mem.Allocator {
/// alloc - Allocator used to create strings and slices.
/// val - The value to convert from.
pub fn toZig(self: *const Environment, T: type, alloc: std.mem.Allocator, val: Val) !T {
if (T == Val) return val;
if (T == void) {
if (!val.isNone()) return error.TypeError;
return;
Expand Down Expand Up @@ -283,7 +284,8 @@ pub fn deleteModule(self: *Environment, module: *Module) !void {
/// Compared to the `eval` function present in `Vm`, this function can be called from a native
/// function as it does not reset the function call and data stacks after execution.
///
/// Note: The returned Val is only valid until the next runGc call.
/// Note: The returned Val is only valid until the next runGc call. Use `self.toZig` to extend the
/// lifetime if needed.
pub fn evalNoReset(self: *Environment, func: Val, args: []const Val) Error!Val {
self.runtime_stats.function_calls += 1;
const stack_start = self.stack.items.len;
Expand Down Expand Up @@ -501,20 +503,20 @@ test "can convert to zig struct" {
var vm = try @import("Vm.zig").init(std.testing.allocator);
defer vm.deinit();

_ = try vm.evalStr(std.testing.allocator, "(define string \"string\")");
_ = try vm.evalStr(std.testing.allocator, "(define lst (list 0 1 2))");
_ = try vm.evalStr(std.testing.allocator, "(define strct (struct 'a-val 1 'b-val 2.0))");
const v = try vm.evalStr(
std.testing.allocator,
"(struct 'string string 'list lst 'strct strct)",
);
_ = try vm.evalStr(void, std.testing.allocator, "(define string \"string\")");
_ = try vm.evalStr(void, std.testing.allocator, "(define lst (list 0 1 2))");
_ = try vm.evalStr(void, std.testing.allocator, "(define strct (struct 'a-val 1 'b-val 2.0))");

const TestType = struct {
string: []const u8,
list: []const i64,
strct: struct { a_val: i64, b_val: f64 },
};
const actual = try vm.env.toZig(TestType, std.testing.allocator, v);
const actual = try vm.evalStr(
TestType,
std.testing.allocator,
"(struct 'string string 'list lst 'strct strct)",
);
defer std.testing.allocator.free(actual.string);
defer std.testing.allocator.free(actual.list);
try std.testing.expectEqualDeep(
Expand All @@ -531,11 +533,10 @@ test "can convert to zig slice of structs" {
var vm = try @import("Vm.zig").init(std.testing.allocator);
defer vm.deinit();

_ = try vm.evalStr(std.testing.allocator, "(define x (struct 'a 1 'b 2))");
const v = try vm.evalStr(std.testing.allocator, "(list x x x)");
try vm.evalStr(void, std.testing.allocator, "(define x (struct 'a 1 'b 2))");

const TestType = struct { a: i64, b: i64 };
const actual = try vm.env.toZig([]const TestType, std.testing.allocator, v);
const actual = try vm.evalStr([]TestType, std.testing.allocator, "(list x x x)");
defer std.testing.allocator.free(actual);
try std.testing.expectEqualDeep(
&[_]TestType{
Expand All @@ -551,19 +552,14 @@ test "partially built struct cleans up" {
var vm = try @import("Vm.zig").init(std.testing.allocator);
defer vm.deinit();

_ = try vm.evalStr(std.testing.allocator, "(define string \"string\")");
_ = try vm.evalStr(std.testing.allocator, "(define bad-list (list 0 1 2 \"bad\"))");
const v = try vm.evalStr(
std.testing.allocator,
"(struct 'string string 'list bad-list)",
);

try vm.evalStr(void, std.testing.allocator, "(define string \"string\")");
try vm.evalStr(void, std.testing.allocator, "(define bad-list (list 0 1 2 \"bad\"))");
const TestType = struct {
string: []const u8,
list: []const i64,
};
try std.testing.expectError(
error.TypeError,
vm.env.toZig(TestType, std.testing.allocator, v),
vm.evalStr(TestType, std.testing.allocator, "(struct 'string string 'list bad-list)"),
);
}
23 changes: 18 additions & 5 deletions src/MemoryManager.zig
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,11 @@ pub fn allocateString(self: *MemoryManager, str: []const u8) ![]const u8 {
return entry.key_ptr.*;
}
const str_copy = try self.allocator.dupe(u8, str);
try self.strings.putNoClobber(self.allocator, str_copy, self.reachable_color);
try self.strings.put(
self.allocator,
str_copy,
self.reachable_color.swap(),
);
return str_copy;
}

Expand All @@ -89,7 +93,11 @@ pub fn allocateUninitializedList(self: *MemoryManager, len: usize) ![]Val {
return &[0]Val{};
}
const lst = try self.allocator.alloc(Val, len);
try self.lists.put(self.allocator, lst.ptr, .{ .len = len, .color = self.reachable_color });
try self.lists.put(
self.allocator,
lst.ptr,
.{ .len = len, .color = self.reachable_color.swap() },
);
return lst;
}

Expand All @@ -111,7 +119,7 @@ pub fn allocateListVal(self: *MemoryManager, contents: []const Val) !Val {
/// Allocate a new bytecode object.
pub fn allocateByteCode(self: *MemoryManager, module: *Module) !*ByteCode {
const bc = try self.allocator.create(ByteCode);
try self.bytecode.put(self.allocator, bc, self.reachable_color);
try self.bytecode.put(self.allocator, bc, self.reachable_color.swap());
bc.* = ByteCode{
.name = "",
.arg_count = 0,
Expand All @@ -126,7 +134,7 @@ pub fn allocateStruct(self: *MemoryManager) !*std.StringHashMapUnmanaged(Val) {
const m = try self.allocator.create(std.StringHashMapUnmanaged(Val));
errdefer self.allocator.destroy(m);
m.* = .{};
try self.structs.put(self.allocator, m, self.reachable_color);
try self.structs.put(self.allocator, m, self.reachable_color.swap());
return m;
}

Expand All @@ -136,7 +144,12 @@ pub fn markVal(self: *MemoryManager, v: Val) !void {
.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| {
try self.structs.put(self.allocator, s, self.reachable_color);
if (self.structs.getEntry(s)) |entry| {
if (entry.value_ptr.* == self.reachable_color) return;
entry.value_ptr.* = self.reachable_color;
} else {
try self.structs.put(self.allocator, s, self.reachable_color);
}
var iter = s.valueIterator();
while (iter.next()) |structVal| {
try self.markVal(structVal.*);
Expand Down
Loading

0 comments on commit b48ab92

Please sign in to comment.