Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: block expression #258

Merged
merged 1 commit into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,19 @@
- REPL (https://github.com/buzz-language/buzz/issues/17) available by running buzz without any argument
- Function argument names and object property names can be ommitted if the provided value is a named variable with the same name (https://github.com/buzz-language/buzz/issues/204)
- Sandboxing build options `memory_limit` and `cycle_limit` (https://github.com/buzz-language/buzz/issues/182)
- Block expression (https://github.com/buzz-language/buzz/issues/105):
```buzz
var value = <{
| ...

out result;
}
```

## Changed
- Map type notation has changed from `{K, V}` to `{K: V}`. Similarly map expression with specified typed went from `{<K, V>, ...}` to `{<K: V>, ...}` (https://github.com/buzz-language/buzz/issues/253)
- `File.readLine`, `File.readAll`, `Socket.readLine`, `Socket.readAll` have now an optional `maxSize` argument
- Overriding variable from upper scope is not allowed anymore

## Fixed

Expand Down
6 changes: 6 additions & 0 deletions src/Ast.zig
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ pub const Node = struct {
AsyncCall,
Binary,
Block,
BlockExpression,
Boolean,
Break,
Call,
Expand Down Expand Up @@ -112,6 +113,7 @@ pub const Node = struct {
Null,
ObjectDeclaration,
ObjectInit,
Out,
Pattern,
ProtocolDeclaration,
Range,
Expand Down Expand Up @@ -142,6 +144,7 @@ pub const Node = struct {
AsyncCall: Node.Index,
Binary: Binary,
Block: []Node.Index,
BlockExpression: []Node.Index,
Boolean: bool,
Break: void,
Call: Call,
Expand Down Expand Up @@ -175,6 +178,7 @@ pub const Node = struct {
Null: void,
ObjectDeclaration: ObjectDeclaration,
ObjectInit: ObjectInit,
Out: Node.Index,
Pattern: *obj.ObjPattern,
ProtocolDeclaration: ProtocolDeclaration,
Range: Range,
Expand Down Expand Up @@ -585,6 +589,8 @@ pub fn isConstant(self: Self, node: Node.Index) bool {
.While,
.Yield,
.Zdef,
.BlockExpression,
.Out,
=> false,
.As => self.isConstant(self.nodes.items(.components)[node].As.left),
.Is => self.isConstant(self.nodes.items(.components)[node].Is.left),
Expand Down
22 changes: 22 additions & 0 deletions src/Codegen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const generators = [_]NodeGen{
generateAsyncCall, // AsyncCall,
generateBinary, // Binary,
generateBlock, // Block,
generateBlockExpression, // BlockExpression,
generateBoolean, // Boolean,
generateBreak, // Break,
generateCall, // Call,
Expand Down Expand Up @@ -95,6 +96,7 @@ const generators = [_]NodeGen{
generateNull, // Null,
generateObjectDeclaration, // ObjectDeclaration,
generateObjectInit, // ObjectInit,
generateOut, // Out,
generatePattern, // Pattern,
generateProtocolDeclaration, // ProtocolDeclaration,
generateRange, // Range,
Expand Down Expand Up @@ -836,6 +838,26 @@ fn generateBlock(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usiz
return null;
}

fn generateBlockExpression(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction {
for (self.ast.nodes.items(.components)[node].BlockExpression) |statement| {
_ = try self.generateNode(statement, breaks);
}

try self.patchOptJumps(node);
try self.endScope(node);

return null;
}

fn generateOut(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction {
_ = try self.generateNode(self.ast.nodes.items(.components)[node].Out, breaks);

try self.patchOptJumps(node);
try self.endScope(node);

return null;
}

fn generateBoolean(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction {
try self.emitOpCode(
self.ast.nodes.items(.location)[node],
Expand Down
16 changes: 16 additions & 0 deletions src/Jit.zig
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@ fn generateNode(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t {
.FunDeclaration => try self.generateFunDeclaration(node),
.VarDeclaration => try self.generateVarDeclaration(node),
.Block => try self.generateBlock(node),
.BlockExpression => try self.generateBlockExpression(node),
.Out => try self.generateNode(components[node].Out),
.Call => try self.generateCall(node),
.NamedVariable => try self.generateNamedVariable(node),
.Return => try self.generateReturn(node),
Expand Down Expand Up @@ -4032,6 +4034,20 @@ fn generateBlock(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t {
return null;
}

fn generateBlockExpression(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t {
const statements = self.state.?.ast.nodes.items(.components)[node].BlockExpression;

var out_statement: ?m.MIR_op_t = null;
for (statements) |statement| {
out_statement = try self.generateNode(statement);
}

return if (statements.len > 0 and self.state.?.ast.nodes.items(.tag)[statements[statements.len - 1]] == .Out)
out_statement.?
else
null;
}

fn generateFunDeclaration(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t {
return try self.generateFunction(
self.state.?.ast.nodes.items(.components)[node].FunDeclaration.function,
Expand Down
92 changes: 91 additions & 1 deletion src/Parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ pub const Frame = struct {
constants: std.ArrayList(Value),

in_try: bool = false,
in_block_expression: bool = false,

pub fn resolveGeneric(self: Frame, name: *obj.ObjString) ?*obj.ObjTypeDef {
if (self.generics) |generics| {
Expand Down Expand Up @@ -342,6 +343,8 @@ const rules = [_]ParseRule{
.{ .prefix = null, .infix = null, .precedence = .None }, // zdef
.{ .prefix = typeOfExpression, .infix = null, .precedence = .Unary }, // typeof
.{ .prefix = null, .infix = null, .precedence = .None }, // var
.{ .prefix = blockExpression, .infix = null, .precedence = .None }, // <{
.{ .prefix = null, .infix = null, .precedence = .None }, // out
};

ast: Ast,
Expand Down Expand Up @@ -1310,6 +1313,9 @@ fn statement(self: *Self, hanging: bool, loop_scope: ?LoopScope) !?Ast.Node.Inde
} else if (try self.match(.Import)) {
std.debug.assert(!hanging);
return try self.importStatement();
} else if (try self.match(.Out)) {
std.debug.assert(!hanging);
return try self.outStatement();
} else if (try self.match(.Throw)) {
const start_location = self.current_token.? - 1;
// For now we don't care about the type. Later if we have `Error` type of data, we'll type check this
Expand Down Expand Up @@ -1948,7 +1954,7 @@ fn declareVariable(self: *Self, variable_type: *obj.ObjTypeDef, name_token: ?Ast
break;
}

if (!std.mem.eql(u8, name_lexeme, "_") and std.mem.eql(u8, name_lexeme, local.name.string)) {
if (!std.mem.eql(u8, name_lexeme, "_") and !std.mem.startsWith(u8, name_lexeme, "$") and std.mem.eql(u8, name_lexeme, local.name.string)) {
self.reporter.reportWithOrigin(
.variable_already_exists,
self.ast.tokens.get(name),
Expand Down Expand Up @@ -5378,6 +5384,63 @@ fn typeOfExpression(self: *Self, _: bool) Error!Ast.Node.Index {
);
}

fn blockExpression(self: *Self, _: bool) Error!Ast.Node.Index {
const start_location = self.current_token.? - 1;

self.current.?.in_block_expression = true;

self.beginScope();

var statements = std.ArrayList(Ast.Node.Index).init(self.gc.allocator);

var out: ?Ast.Node.Index = null;
while (!self.check(.RightBrace) and !self.check(.Eof)) {
if (try self.declarationOrStatement(null)) |stmt| {
try statements.append(stmt);

if (self.ast.nodes.items(.tag)[stmt] == .Out) {
if (out != null) {
self.reportError(
.multiple_out,
"Only one `out` statement is allowed in block expression",
);
}

out = stmt;
}
}
}

if (out != null and statements.getLastOrNull() != out) {
self.reportError(
.missing_out,
"Last block expression statement must be `out`",
);
}

try self.consume(.RightBrace, "Expected `}` at end of block expression");

self.current.?.in_block_expression = false;

statements.shrinkAndFree(statements.items.len);

return try self.ast.appendNode(
.{
.tag = .BlockExpression,
.location = start_location,
.end_location = self.current_token.? - 1,
.type_def = if (out) |o|
self.ast.nodes.items(.type_def)[o]
else
try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }),
.components = .{
.BlockExpression = statements.items,
},
.ends_scope = try self.endScope(),
},
);
}

fn binary(self: *Self, _: bool, left: Ast.Node.Index) Error!Ast.Node.Index {
const start_location = self.ast.nodes.items(.location)[left];

Expand Down Expand Up @@ -7633,6 +7696,33 @@ fn returnStatement(self: *Self) Error!Ast.Node.Index {
);
}

fn outStatement(self: *Self) Error!Ast.Node.Index {
const start_location = self.current_token.? - 1;

if (!self.current.?.in_block_expression) {
self.reportError(
.out_not_allowed,
"`out` statement is only allowed inside a block expression",
);
}

const expr = try self.expression(false);

try self.consume(.Semicolon, "Expected `;` after `out` statement.");

return try self.ast.appendNode(
.{
.tag = .Out,
.location = start_location,
.end_location = self.current_token.? - 1,
.type_def = self.ast.nodes.items(.type_def)[expr],
.components = .{
.Out = expr,
},
},
);
}

fn tryStatement(self: *Self) Error!Ast.Node.Index {
const start_location = self.current_token.? - 1;

Expand Down
3 changes: 3 additions & 0 deletions src/Reporter.zig
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ pub const Error = enum(u8) {
inferred_type = 90,
empty_import = 91,
import_already_exists = 92,
multiple_out = 93,
missing_out = 94,
out_not_allowed = 95,
};

// Inspired by https://github.com/zesterer/ariadne
Expand Down
3 changes: 3 additions & 0 deletions src/Token.zig
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ pub const Type = enum {
Zdef, // zdef
TypeOf, // typeof
Var, // var
Blk, // <{
Out, // out
};

pub const keywords = std.ComptimeStringMap(
Expand Down Expand Up @@ -232,5 +234,6 @@ pub const keywords = std.ComptimeStringMap(
.{ "type", .Type },
.{ "typeof", .TypeOf },
.{ "var", .Var },
.{ "out", .Out },
},
);
18 changes: 2 additions & 16 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -263,28 +263,14 @@ pub fn main() !void {
.Run;

if (flavor == .Repl) {
repl(allocator) catch |err| {
std.io.getStdErr().writer().print(
"Failed with error {s}\n",
.{@errorName(err)},
) catch unreachable;

std.os.exit(1);
};
repl(allocator) catch std.os.exit(1);
} else {
runFile(
allocator,
res.positionals[0],
positionals.items[1..],
flavor,
) catch |err| {
std.io.getStdErr().writer().print(
"Failed with error {s}\n",
.{@errorName(err)},
) catch unreachable;

std.os.exit(1);
};
) catch std.os.exit(1);
}

std.os.exit(0);
Expand Down
4 changes: 4 additions & 0 deletions src/scanner.zig
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ pub const Scanner = struct {
self.makeToken(.ShiftLeft, null, null, null)
else if (self.match('='))
self.makeToken(.LessEqual, null, null, null)
else if (self.match('{'))
self.makeToken(.Blk, null, null, null)
else
self.makeToken(.Less, null, null, null),
'~' => self.makeToken(.Bnot, null, null, null),
Expand Down Expand Up @@ -668,6 +670,7 @@ pub const Scanner = struct {
.Var,
.Question,
.AsQuestion,
.Out,
=> if (true_color) Color.keyword else "\x1b[94m",
// Punctuation
.LeftBracket,
Expand All @@ -685,6 +688,7 @@ pub const Scanner = struct {
.Arrow,
.Ampersand,
.Spread,
.Blk,
=> if (true_color) Color.punctuation else Color.magenta,
.IntegerValue,
.FloatValue,
Expand Down
12 changes: 12 additions & 0 deletions tests/070-block-expression.buzz
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import "std";

test "block expression" {
var value = <{
print("doing stuff in my block...");

out "my value";
};

assert(value == "my value", message: "Could use block expression");
assert(value is str, message: "Could infer block expression type properly");
}
4 changes: 4 additions & 0 deletions tests/compile_errors/024-misplaced-out.buzz
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
| `out` statement is only allowed inside a block expression
test "misplaced out statement" {
out "not allowed";
}
7 changes: 7 additions & 0 deletions tests/compile_errors/025-multiple-out.buzz
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
| Only one `out` statement is allowed in block expression
test "multiple out statements" {
<{
out "one";
out "two";
};
}
8 changes: 8 additions & 0 deletions tests/compile_errors/026-out-last-statement.buzz
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
| Last block expression statement must be `out`
test "out must be last statement" {
<{
out "i should be last";

"hello world";
};
}
Loading