From 7863f079b5ecaea2189cadbebf3da745be590496 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Wed, 15 May 2024 11:53:59 +0200 Subject: [PATCH 1/5] feat: Loop labels closes #199 --- CHANGELOG.md | 1 + src/Ast.zig | 7 +- src/Codegen.zig | 232 +++++++----- src/Jit.zig | 100 +++++- src/Parser.zig | 337 +++++++++++++----- src/Reporter.zig | 1 + tests/072-labels.buzz | 66 ++++ tests/compile_errors/029-label-not-found.buzz | 6 + 8 files changed, 551 insertions(+), 199 deletions(-) create mode 100644 tests/072-labels.buzz create mode 100644 tests/compile_errors/029-label-not-found.buzz diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b1ca908..173f69c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ var value = from { - Tracing JIT (https://github.com/buzz-language/buzz/issues/134): will look for hot loops and compile them - `list.fill` - `std.panic` will panic and print current stack trace +- Loop can have _labels_ that you can `break` or `continue` to (https://github.com/buzz-language/buzz/issues/199) ## Changed - Map type notation has changed from `{K, V}` to `{K: V}`. Similarly map expression with specified typed went from `{, ...}` to `{, ...}` (https://github.com/buzz-language/buzz/issues/253) diff --git a/src/Ast.zig b/src/Ast.zig index 6be6d79a..a8f3e8b3 100644 --- a/src/Ast.zig +++ b/src/Ast.zig @@ -157,9 +157,9 @@ pub const Node = struct { Block: []Node.Index, BlockExpression: []Node.Index, Boolean: bool, - Break: void, + Break: ?Node.Index, Call: Call, - Continue: void, + Continue: ?Node.Index, Dot: Dot, DoUntil: WhileDoUntil, Enum: Enum, @@ -458,6 +458,7 @@ pub const For = struct { condition: Node.Index, post_loop: []Node.Index, body: Node.Index, + label: ?TokenIndex, }; pub const ForEach = struct { @@ -466,6 +467,7 @@ pub const ForEach = struct { value: Node.Index, body: Node.Index, key_omitted: bool, + label: ?TokenIndex, }; pub const Function = struct { @@ -697,6 +699,7 @@ pub const VarDeclaration = struct { pub const WhileDoUntil = struct { condition: Node.Index, body: Node.Index, + label: ?TokenIndex, }; pub const Zdef = struct { diff --git a/src/Codegen.zig b/src/Codegen.zig index 9cfc4166..a0764639 100644 --- a/src/Codegen.zig +++ b/src/Codegen.zig @@ -38,9 +38,16 @@ pub const Frame = struct { const NodeGen = *const fn ( self: *Self, node: Ast.Node.Index, - breaks: ?*std.ArrayList(usize), + breaks: ?*Breaks, ) Error!?*obj.ObjFunction; +const Break = struct { + ip: usize, // The op code will tell us if this is a continue or a break statement + label_node: ?Ast.Node.Index = null, +}; + +const Breaks = std.ArrayList(Break); + current: ?*Frame = null, ast: Ast, gc: *GarbageCollector, @@ -193,7 +200,7 @@ pub fn emitOpCode(self: *Self, location: Ast.TokenIndex, code: Chunk.OpCode) !vo pub fn emitLoop(self: *Self, location: Ast.TokenIndex, loop_start: usize) !void { const offset: usize = self.currentCode() - loop_start + 1; - if (offset > 16777215) { + if (offset > std.math.maxInt(u24)) { self.reportError(.loop_body_too_large, "Loop body too large."); } @@ -214,7 +221,7 @@ pub fn patchJumpOrLoop(self: *Self, offset: usize, loop_start: ?usize) !void { if (code == .OP_LOOP) { // Patching a continue statement std.debug.assert(loop_start != null); const loop_offset: usize = offset - loop_start.? + 1; - if (loop_offset > 16777215) { + if (loop_offset > std.math.maxInt(u24)) { self.reportError(.loop_body_too_large, "Loop body too large."); } @@ -225,6 +232,18 @@ pub fn patchJumpOrLoop(self: *Self, offset: usize, loop_start: ?usize) !void { } } +fn patchBreaks(self: *Self, breaks: *Breaks, previous_breaks: ?*Breaks, loop_node: Ast.Node.Index, loop_start: usize) !void { + for (breaks.items) |brk| { + if (brk.label_node == null or brk.label_node.? == loop_node) { + try self.patchJumpOrLoop(brk.ip, loop_start); + } else if (previous_breaks) |brks| { // The break/continue if for an upper scope + try brks.append(brk); + } else { + unreachable; // Should not happen: we search for the scope during parsing + } + } +} + pub fn patchJump(self: *Self, offset: usize) void { std.debug.assert(offset < self.currentCode()); @@ -368,7 +387,7 @@ fn endScope(self: *Self, node: Ast.Node.Index) Error!void { } } -inline fn generateNode(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +inline fn generateNode(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { if (self.synchronize(node)) { return null; } @@ -392,7 +411,7 @@ fn nodeValue(self: *Self, node: Ast.Node.Index) Error!?Value { return value.*; } -fn generateAs(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateAs(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const locations = self.ast.nodes.items(.location); const node_location = locations[node]; const components = self.ast.nodes.items(.components)[node].As; @@ -429,7 +448,7 @@ fn generateAs(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) return null; } -fn generateAsyncCall(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateAsyncCall(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const locations = self.ast.nodes.items(.location); const type_defs = self.ast.nodes.items(.type_def); const node_location = locations[node]; @@ -451,7 +470,7 @@ fn generateAsyncCall(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList( return null; } -fn generateBinary(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateBinary(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const components = self.ast.nodes.items(.components)[node].Binary; const locations = self.ast.nodes.items(.location); @@ -798,7 +817,7 @@ fn generateBinary(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usi return null; } -fn generateBlock(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateBlock(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const tags = self.ast.nodes.items(.tag); var seen_return = false; @@ -828,7 +847,7 @@ 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 { +fn generateBlockExpression(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { for (self.ast.nodes.items(.components)[node].BlockExpression) |statement| { _ = try self.generateNode(statement, breaks); } @@ -839,7 +858,7 @@ fn generateBlockExpression(self: *Self, node: Ast.Node.Index, breaks: ?*std.Arra return null; } -fn generateOut(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateOut(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { _ = try self.generateNode(self.ast.nodes.items(.components)[node].Out, breaks); try self.patchOptJumps(node); @@ -848,7 +867,7 @@ fn generateOut(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize) return null; } -fn generateBoolean(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateBoolean(self: *Self, node: Ast.Node.Index, _: ?*Breaks) Error!?*obj.ObjFunction { try self.emitOpCode( self.ast.nodes.items(.location)[node], if (self.ast.nodes.items(.components)[node].Boolean) .OP_TRUE else .OP_FALSE, @@ -860,14 +879,35 @@ fn generateBoolean(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) return null; } -fn generateBreak(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateBreak(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { // Close scope(s), then jump try self.endScope(node); try breaks.?.append( - try self.emitJump( - self.ast.nodes.items(.location)[node], - .OP_JUMP, - ), + .{ + .ip = try self.emitJump( + self.ast.nodes.items(.location)[node], + .OP_JUMP, + ), + .label_node = self.ast.nodes.items(.components)[node].Break, + }, + ); + + try self.patchOptJumps(node); + + return null; +} + +fn generateContinue(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { + // Close scope(s), then jump + try self.endScope(node); + try breaks.?.append( + .{ + .ip = try self.emitJump( + self.ast.nodes.items(.location)[node], + .OP_LOOP, + ), + .label_node = self.ast.nodes.items(.components)[node].Continue, + }, ); try self.patchOptJumps(node); @@ -875,7 +915,7 @@ fn generateBreak(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usiz return null; } -fn generateCall(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateCall(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const type_defs = self.ast.nodes.items(.type_def); const locations = self.ast.nodes.items(.location); const node_components = self.ast.nodes.items(.components); @@ -1352,22 +1392,7 @@ fn generateCall(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize return null; } -fn generateContinue(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { - // Close scope(s), then jump - try self.endScope(node); - try breaks.?.append( - try self.emitJump( - self.ast.nodes.items(.location)[node], - .OP_LOOP, - ), - ); - - try self.patchOptJumps(node); - - return null; -} - -fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const node_components = self.ast.nodes.items(.components); const type_defs = self.ast.nodes.items(.type_def); const locations = self.ast.nodes.items(.location); @@ -1550,7 +1575,7 @@ fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize) return null; } -fn generateDoUntil(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateDoUntil(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const locations = self.ast.nodes.items(.location); const type_defs = self.ast.nodes.items(.type_def); const node_components = self.ast.nodes.items(.components); @@ -1558,10 +1583,10 @@ fn generateDoUntil(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) const loop_start = self.currentCode(); - var breaks = std.ArrayList(usize).init(self.gc.allocator); - defer breaks.deinit(); + var lbreaks = Breaks.init(self.gc.allocator); + defer lbreaks.deinit(); - _ = try self.generateNode(components.body, &breaks); + _ = try self.generateNode(components.body, &lbreaks); const condition_type_def = type_defs[components.condition].?; @@ -1577,7 +1602,7 @@ fn generateDoUntil(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) ); } - _ = try self.generateNode(components.condition, &breaks); + _ = try self.generateNode(components.condition, &lbreaks); try self.emitOpCode(locations[node], .OP_NOT); const exit_jump = try self.emitJump(locations[node], .OP_JUMP_IF_FALSE); @@ -1589,9 +1614,12 @@ fn generateDoUntil(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) try self.emitOpCode(locations[node], .OP_POP); // Pop condition // Patch breaks - for (breaks.items) |jump| { - try self.patchJumpOrLoop(jump, loop_start); - } + try self.patchBreaks( + &lbreaks, + breaks, + node, + loop_start, + ); try self.patchOptJumps(node); try self.endScope(node); @@ -1599,7 +1627,7 @@ fn generateDoUntil(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) return null; } -fn generateEnum(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateEnum(self: *Self, node: Ast.Node.Index, _: ?*Breaks) Error!?*obj.ObjFunction { const locations = self.ast.nodes.items(.location); const type_defs = self.ast.nodes.items(.type_def); const node_components = self.ast.nodes.items(.components); @@ -1661,7 +1689,7 @@ fn generateEnum(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Er return null; } -fn generateExport(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateExport(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const components = self.ast.nodes.items(.components)[node].Export; if (components.declaration) |decl| { @@ -1674,7 +1702,7 @@ fn generateExport(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usi return null; } -fn generateExpression(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateExpression(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const locations = self.ast.nodes.items(.location); const components = self.ast.nodes.items(.components); const expr = components[node].Expression; @@ -1713,7 +1741,7 @@ fn generateExpression(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList return null; } -fn generateFloat(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateFloat(self: *Self, node: Ast.Node.Index, _: ?*Breaks) Error!?*obj.ObjFunction { try self.emitConstant( self.ast.nodes.items(.location)[node], try self.ast.toValue(node, self.gc), @@ -1725,7 +1753,7 @@ fn generateFloat(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) E return null; } -fn generateFor(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateFor(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const locations = self.ast.nodes.items(.location); const type_defs = self.ast.nodes.items(.type_def); const node_components = self.ast.nodes.items(.components); @@ -1781,7 +1809,7 @@ fn generateFor(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize) self.patchJump(body_jump); - var lbreaks: std.ArrayList(usize) = std.ArrayList(usize).init(self.gc.allocator); + var lbreaks = Breaks.init(self.gc.allocator); defer lbreaks.deinit(); _ = try self.generateNode(components.body, &lbreaks); @@ -1793,9 +1821,12 @@ fn generateFor(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize) try self.emitOpCode(locations[node], .OP_POP); // Pop condition // Patch breaks - for (lbreaks.items) |jump| { - try self.patchJumpOrLoop(jump, loop_start); - } + try self.patchBreaks( + &lbreaks, + breaks, + node, + loop_start, + ); try self.patchOptJumps(node); try self.endScope(node); @@ -1805,7 +1836,7 @@ fn generateFor(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize) return null; } -fn generateForceUnwrap(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateForceUnwrap(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const locations = self.ast.nodes.items(.location); const components = self.ast.nodes.items(.components)[node].ForceUnwrap; @@ -1833,7 +1864,7 @@ fn generateForceUnwrap(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayLis return null; } -fn generateForEach(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateForEach(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const node_components = self.ast.nodes.items(.components); const locations = self.ast.nodes.items(.location); const type_defs = self.ast.nodes.items(.type_def); @@ -2047,7 +2078,7 @@ fn generateForEach(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(us const exit_jump: usize = try self.emitJump(locations[node], .OP_JUMP_IF_FALSE); try self.emitOpCode(locations[node], .OP_POP); // Pop condition result - var lbreaks: std.ArrayList(usize) = std.ArrayList(usize).init(self.gc.allocator); + var lbreaks = Breaks.init(self.gc.allocator); defer lbreaks.deinit(); _ = try self.generateNode(components.body, &lbreaks); @@ -2060,9 +2091,12 @@ fn generateForEach(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(us try self.emitOpCode(locations[node], .OP_POP); // Pop condition result // Patch breaks - for (lbreaks.items) |jump| { - try self.patchJumpOrLoop(jump, loop_start); - } + try self.patchBreaks( + &lbreaks, + breaks, + node, + loop_start, + ); try self.patchOptJumps(node); // Should have key, [value,] iterable to pop @@ -2076,7 +2110,7 @@ fn generateForEach(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(us return null; } -fn generateFunction(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateFunction(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const node_components = self.ast.nodes.items(.components); const type_defs = self.ast.nodes.items(.type_def); const locations = self.ast.nodes.items(.location); @@ -2201,11 +2235,12 @@ fn generateFunction(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(u // If we're being imported, put all globals on the stack if (components.import_root) { - if (components.entry.?.exported_count > 16777215) { - self.reporter.reportErrorAt( + if (components.entry.?.exported_count > std.math.maxInt(u24)) { + self.reporter.reportErrorFmt( .export_count, self.ast.tokens.get(locations[node]), - "Can't export more than 16777215 values.", + "Can't export more than {} values.", + .{std.math.maxInt(u24)}, ); } @@ -2286,7 +2321,7 @@ fn generateFunction(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(u return current_function; } -fn generateFunDeclaration(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateFunDeclaration(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const node_components = self.ast.nodes.items(.components); const components = node_components[node].FunDeclaration; @@ -2306,7 +2341,7 @@ fn generateFunDeclaration(self: *Self, node: Ast.Node.Index, breaks: ?*std.Array return null; } -fn generateGenericResolve(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateGenericResolve(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const type_def = self.ast.nodes.items(.type_def)[node].?; const expr = self.ast.nodes.items(.components)[node].GenericResolve; const node_location = self.ast.nodes.items(.location)[node]; @@ -2397,7 +2432,7 @@ fn generateGenericResolve(self: *Self, node: Ast.Node.Index, breaks: ?*std.Array return null; } -fn generateGrouping(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateGrouping(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const components = self.ast.nodes.items(.components); const expr = components[node].Grouping; @@ -2409,7 +2444,7 @@ fn generateGrouping(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(u return null; } -fn generateIf(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateIf(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const type_defs = self.ast.nodes.items(.type_def); const locations = self.ast.nodes.items(.location); const node_components = self.ast.nodes.items(.components); @@ -2527,7 +2562,7 @@ fn generateIf(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) return null; } -fn generateImport(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateImport(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const components = self.ast.nodes.items(.components)[node].Import; const location = self.ast.nodes.items(.location)[node]; @@ -2546,7 +2581,7 @@ fn generateImport(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usi return null; } -fn generateInteger(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateInteger(self: *Self, node: Ast.Node.Index, _: ?*Breaks) Error!?*obj.ObjFunction { try self.emitConstant( self.ast.nodes.items(.location)[node], try self.ast.toValue(node, self.gc), @@ -2558,7 +2593,7 @@ fn generateInteger(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) return null; } -fn generateIs(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateIs(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const components = self.ast.nodes.items(.components)[node].Is; const location = self.ast.nodes.items(.location)[node]; const constant = try self.ast.toValue(components.constant, self.gc); @@ -2586,7 +2621,7 @@ fn generateIs(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) return null; } -fn generateList(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateList(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const locations = self.ast.nodes.items(.location); const components = self.ast.nodes.items(.components)[node].List; const type_defs = self.ast.nodes.items(.type_def); @@ -2630,7 +2665,7 @@ fn generateList(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize return null; } -fn generateMap(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateMap(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const locations = self.ast.nodes.items(.location); const components = self.ast.nodes.items(.components)[node].Map; const type_defs = self.ast.nodes.items(.type_def); @@ -2700,7 +2735,7 @@ fn generateMap(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize) return null; } -fn generateNamedVariable(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateNamedVariable(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const components = self.ast.nodes.items(.components)[node].NamedVariable; const locations = self.ast.nodes.items(.location); const type_defs = self.ast.nodes.items(.type_def); @@ -2761,7 +2796,7 @@ fn generateNamedVariable(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayL return null; } -fn generateNull(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateNull(self: *Self, node: Ast.Node.Index, _: ?*Breaks) Error!?*obj.ObjFunction { try self.emitOpCode(self.ast.nodes.items(.location)[node], .OP_NULL); try self.patchOptJumps(node); @@ -2770,7 +2805,7 @@ fn generateNull(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Er return null; } -fn generateObjectDeclaration(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateObjectDeclaration(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const locations = self.ast.nodes.items(.location); const type_defs = self.ast.nodes.items(.type_def); const lexemes = self.ast.tokens.items(.lexeme); @@ -2955,7 +2990,7 @@ fn generateObjectDeclaration(self: *Self, node: Ast.Node.Index, breaks: ?*std.Ar return null; } -fn generateObjectInit(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateObjectInit(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const locations = self.ast.nodes.items(.location); const type_defs = self.ast.nodes.items(.type_def); const lexemes = self.ast.tokens.items(.lexeme); @@ -3100,7 +3135,7 @@ fn generateObjectInit(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList return null; } -fn generatePattern(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generatePattern(self: *Self, node: Ast.Node.Index, _: ?*Breaks) Error!?*obj.ObjFunction { try self.emitConstant( self.ast.nodes.items(.location)[node], try self.ast.toValue(node, self.gc), @@ -3112,7 +3147,7 @@ fn generatePattern(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) return null; } -fn generateProtocolDeclaration(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateProtocolDeclaration(self: *Self, node: Ast.Node.Index, _: ?*Breaks) Error!?*obj.ObjFunction { const location = self.ast.nodes.items(.location)[node]; const components = self.ast.nodes.items(.components)[node].ProtocolDeclaration; const type_def = self.ast.nodes.items(.type_def)[node].?; @@ -3130,7 +3165,7 @@ fn generateProtocolDeclaration(self: *Self, node: Ast.Node.Index, _: ?*std.Array return null; } -fn generateRange(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateRange(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const type_defs = self.ast.nodes.items(.type_def); const components = self.ast.nodes.items(.components)[node].Range; const locations = self.ast.nodes.items(.location); @@ -3177,7 +3212,7 @@ fn generateRange(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usiz return null; } -fn generateResolve(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateResolve(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const fiber = self.ast.nodes.items(.components)[node].Resolve; const fiber_type_def = self.ast.nodes.items(.type_def)[fiber].?; const locations = self.ast.nodes.items(.location); @@ -3206,7 +3241,7 @@ fn generateResolve(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(us return null; } -fn generateResume(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateResume(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const fiber = self.ast.nodes.items(.components)[node].Resume; const fiber_type_def = self.ast.nodes.items(.type_def)[fiber].?; const locations = self.ast.nodes.items(.location); @@ -3235,7 +3270,7 @@ fn generateResume(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usi return null; } -fn generateReturn(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateReturn(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const components = self.ast.nodes.items(.components)[node].Return; const type_defs = self.ast.nodes.items(.type_def); const locations = self.ast.nodes.items(.location); @@ -3278,7 +3313,7 @@ fn generateReturn(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usi return null; } -fn generateString(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateString(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const location = self.ast.nodes.items(.location)[node]; const type_defs = self.ast.nodes.items(.type_def); const elements = self.ast.nodes.items(.components)[node].String; @@ -3317,7 +3352,7 @@ fn generateString(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usi return null; } -fn generateStringLiteral(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateStringLiteral(self: *Self, node: Ast.Node.Index, _: ?*Breaks) Error!?*obj.ObjFunction { try self.emitConstant( self.ast.nodes.items(.location)[node], self.ast.nodes.items(.components)[node].StringLiteral.toValue(), @@ -3329,7 +3364,7 @@ fn generateStringLiteral(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(u return null; } -fn generateSubscript(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateSubscript(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const locations = self.ast.nodes.items(.location); const location = locations[node]; const type_defs = self.ast.nodes.items(.type_def); @@ -3442,7 +3477,7 @@ fn generateSubscript(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList( return null; } -fn generateTry(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateTry(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const components = self.ast.nodes.items(.components)[node].Try; const type_defs = self.ast.nodes.items(.type_def); const locations = self.ast.nodes.items(.location); @@ -3562,7 +3597,7 @@ fn generateTry(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize) return null; } -fn generateThrow(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateThrow(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const components = self.ast.nodes.items(.components)[node].Throw; const type_defs = self.ast.nodes.items(.type_def); const location = self.ast.nodes.items(.location)[node]; @@ -3616,7 +3651,7 @@ fn generateThrow(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usiz return null; } -fn generateTypeExpression(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateTypeExpression(self: *Self, node: Ast.Node.Index, _: ?*Breaks) Error!?*obj.ObjFunction { const node_components = self.ast.nodes.items(.components); const type_defs = self.ast.nodes.items(.type_def); @@ -3631,7 +3666,7 @@ fn generateTypeExpression(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList( return null; } -fn generateTypeOfExpression(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateTypeOfExpression(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { _ = try self.generateNode(self.ast.nodes.items(.components)[node].TypeOfExpression, breaks); try self.emitOpCode( @@ -3645,7 +3680,7 @@ fn generateTypeOfExpression(self: *Self, node: Ast.Node.Index, breaks: ?*std.Arr return null; } -fn generateUnary(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateUnary(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const components = self.ast.nodes.items(.components)[node].Unary; const location = self.ast.nodes.items(.location)[node]; const expression_location = self.ast.nodes.items(.location)[components.expression]; @@ -3705,7 +3740,7 @@ fn generateUnary(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usiz return null; } -fn generateUnwrap(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateUnwrap(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const locations = self.ast.nodes.items(.location); const location = locations[node]; const components = self.ast.nodes.items(.components)[node].Unwrap; @@ -3746,7 +3781,7 @@ fn generateUnwrap(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usi return null; } -fn generateVarDeclaration(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateVarDeclaration(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const components = self.ast.nodes.items(.components)[node].VarDeclaration; const type_defs = self.ast.nodes.items(.type_def); const type_def = type_defs[node].?; @@ -3788,7 +3823,7 @@ fn generateVarDeclaration(self: *Self, node: Ast.Node.Index, breaks: ?*std.Array return null; } -fn generateVoid(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateVoid(self: *Self, node: Ast.Node.Index, _: ?*Breaks) Error!?*obj.ObjFunction { try self.emitOpCode( self.ast.nodes.items(.location)[node], .OP_VOID, @@ -3800,7 +3835,7 @@ fn generateVoid(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Er return null; } -fn generateWhile(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateWhile(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const components = self.ast.nodes.items(.components)[node].While; const type_defs = self.ast.nodes.items(.type_def); const locations = self.ast.nodes.items(.location); @@ -3837,7 +3872,7 @@ fn generateWhile(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usiz const exit_jump = try self.emitJump(location, .OP_JUMP_IF_FALSE); try self.emitOpCode(location, .OP_POP); - var while_breaks = std.ArrayList(usize).init(self.gc.allocator); + var while_breaks = Breaks.init(self.gc.allocator); defer while_breaks.deinit(); _ = try self.generateNode(components.body, &while_breaks); @@ -3848,9 +3883,12 @@ fn generateWhile(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usiz try self.emitOpCode(location, .OP_POP); // Pop condition (is not necessary if broke out of the loop) // Patch breaks - for (while_breaks.items) |jump| { - try self.patchJumpOrLoop(jump, loop_start); - } + try self.patchBreaks( + &while_breaks, + breaks, + node, + loop_start, + ); try self.patchOptJumps(node); try self.endScope(node); @@ -3860,7 +3898,7 @@ fn generateWhile(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usiz return null; } -fn generateYield(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateYield(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const expression = self.ast.nodes.items(.components)[node].Yield; const type_defs = self.ast.nodes.items(.type_def); const type_def = type_defs[node]; @@ -3913,7 +3951,7 @@ fn generateYield(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usiz return null; } -fn generateZdef(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { +fn generateZdef(self: *Self, node: Ast.Node.Index, _: ?*Breaks) Error!?*obj.ObjFunction { if (is_wasm) { return null; } diff --git a/src/Jit.zig b/src/Jit.zig index bffadde7..42ea3d2b 100644 --- a/src/Jit.zig +++ b/src/Jit.zig @@ -27,6 +27,14 @@ const OptJump = struct { } }; +const Break = struct { + break_label: m.MIR_insn_t, + continue_label: m.MIR_insn_t, + node: Ast.Node.Index, +}; + +const Breaks = std.ArrayList(Break); + const GenState = struct { module: m.MIR_module_t, prototypes: std.AutoHashMap(ExternApi, m.MIR_item_t), @@ -54,17 +62,20 @@ const GenState = struct { // Avoid register name collisions registers: std.AutoHashMap([*:0]const u8, usize), - // Label to jump to when breaking a loop + // Label to jump to when breaking a loop without a label break_label: m.MIR_insn_t = null, - // Label to jump to when continuing a loop + // Label to jump to when continuing a loop whithout a label continue_label: m.MIR_insn_t = null, + breaks_label: Breaks, + pub fn deinit(self: *GenState) void { self.prototypes.deinit(); self.registers.deinit(); if (self.try_should_handle) |*try_should_handle| { try_should_handle.deinit(); } + self.breaks_label.deinit(); } }; @@ -333,6 +344,7 @@ fn buildFunction(self: *Self, ast: Ast, closure: ?*o.ObjClosure, ast_node: Ast.N .ast_node = ast_node, .registers = std.AutoHashMap([*:0]const u8, usize).init(self.vm.gc.allocator), .closure = closure orelse self.state.?.closure, + .breaks_label = Breaks.init(self.vm.gc.allocator), }; const tag = self.state.?.ast.nodes.items(.tag)[ast_node]; @@ -2805,6 +2817,16 @@ fn generateWhile(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { const previous_continue_label = self.state.?.continue_label; self.state.?.continue_label = cond_label; + if (components.label != null) { + try self.state.?.breaks_label.append( + .{ + .node = node, + .break_label = out_label, + .continue_label = cond_label, + }, + ); + } + self.append(cond_label); self.BEQ( @@ -2822,6 +2844,10 @@ fn generateWhile(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { self.state.?.break_label = previous_out_label; self.state.?.continue_label = previous_continue_label; + if (components.label != null) { + _ = self.state.?.breaks_label.pop(); + } + return null; } @@ -2836,6 +2862,16 @@ fn generateDoUntil(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { const previous_continue_label = self.state.?.continue_label; self.state.?.continue_label = loop_label; + if (components.label != null) { + try self.state.?.breaks_label.append( + .{ + .node = node, + .break_label = out_label, + .continue_label = loop_label, + }, + ); + } + self.append(loop_label); _ = try self.generateNode(components.body); @@ -2851,6 +2887,10 @@ fn generateDoUntil(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { self.state.?.break_label = previous_out_label; self.state.?.continue_label = previous_continue_label; + if (components.label != null) { + _ = self.state.?.breaks_label.pop(); + } + return null; } @@ -2869,6 +2909,16 @@ fn generateFor(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { const previous_continue_label = self.state.?.continue_label; self.state.?.continue_label = cond_label; + if (components.label != null) { + try self.state.?.breaks_label.append( + .{ + .node = node, + .break_label = out_label, + .continue_label = cond_label, + }, + ); + } + if (self.state.?.ast_node != node) { // Init expressions (if not hotspot) for (components.init_declarations) |expr| { @@ -2899,13 +2949,35 @@ fn generateFor(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { self.state.?.break_label = previous_out_label; self.state.?.continue_label = previous_continue_label; + if (components.label != null) { + _ = self.state.?.breaks_label.pop(); + } + return null; } +fn findBreakLabel(self: *Self, node: Ast.Node.Index) Break { + var i = self.state.?.breaks_label.items.len - 1; + while (i >= 0) : (i -= 1) { + const brk = self.state.?.breaks_label.items[i]; + + if (brk.node == node) { + return brk; + } + } + + // Should not happen: searched when parsing + unreachable; +} + fn generateBreak(self: *Self, break_node: Ast.Node.Index) Error!?m.MIR_op_t { try self.closeScope(break_node); - self.JMP(self.state.?.break_label.?); + if (self.state.?.ast.nodes.items(.components)[break_node].Break) |label_node| { + self.JMP(self.findBreakLabel(label_node).break_label); + } else { + self.JMP(self.state.?.break_label.?); + } return null; } @@ -2913,7 +2985,11 @@ fn generateBreak(self: *Self, break_node: Ast.Node.Index) Error!?m.MIR_op_t { fn generateContinue(self: *Self, continue_node: Ast.Node.Index) Error!?m.MIR_op_t { try self.closeScope(continue_node); - self.JMP(self.state.?.continue_label.?); + if (self.state.?.ast.nodes.items(.components)[continue_node].Continue) |label_node| { + self.JMP(self.findBreakLabel(label_node).continue_label); + } else { + self.JMP(self.state.?.continue_label.?); + } return null; } @@ -4040,6 +4116,16 @@ fn generateForEach(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { const previous_continue_label = self.state.?.continue_label; self.state.?.continue_label = cond_label; + if (components.label != null) { + try self.state.?.breaks_label.append( + .{ + .node = node, + .break_label = out_label, + .continue_label = cond_label, + }, + ); + } + self.append(cond_label); // Call appropriate `next` method @@ -4114,6 +4200,10 @@ fn generateForEach(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { self.state.?.break_label = previous_out_label; self.state.?.continue_label = previous_continue_label; + if (components.label != null) { + _ = self.state.?.breaks_label.pop(); + } + return null; } @@ -4642,6 +4732,7 @@ pub fn compileZdefContainer(self: *Self, ast: Ast, zdef_element: Ast.Zdef.ZdefEl .ast_node = undefined, .registers = std.AutoHashMap([*:0]const u8, usize).init(self.vm.gc.allocator), .closure = undefined, + .breaks_label = Breaks.init(self.vm.gc.allocator), }; defer self.reset(); @@ -4930,6 +5021,7 @@ pub fn compileZdef(self: *Self, buzz_ast: Ast, zdef: Ast.Zdef.ZdefElement) Error .ast_node = undefined, .registers = std.AutoHashMap([*:0]const u8, usize).init(self.vm.gc.allocator), .closure = undefined, + .breaks_label = Breaks.init(self.vm.gc.allocator), }; defer self.reset(); diff --git a/src/Parser.zig b/src/Parser.zig index 827c28cb..9bbc864d 100644 --- a/src/Parser.zig +++ b/src/Parser.zig @@ -227,13 +227,13 @@ pub const UpValue = struct { pub const Frame = struct { enclosing: ?*Frame = null, - // TODO: make this a multiarray? locals: [255]Local, local_count: u8 = 0, - // TODO: make this a multiarray? upvalues: [255]UpValue, upvalue_count: u8 = 0, scope_depth: u32 = 0, + // Keep track of the node that introduced the scope (useful for labeled break/continue statements) + scopes: std.ArrayList(?Ast.Node.Index), // If false, `return` was omitted or within a conditionned block (if, loop, etc.) // We only count `return` emitted within the scope_depth 0 of the current function or unconditionned else statement function_node: Ast.Node.Index, @@ -244,6 +244,12 @@ pub const Frame = struct { in_try: bool = false, in_block_expression: ?u32 = null, + pub fn deinit(self: *Frame) void { + self.scopes.deinit(); + self.constants.deinit(); + // self.generics ends up in AST node so we don't deinit it + } + pub fn resolveGeneric(self: Frame, name: *obj.ObjString) ?*obj.ObjTypeDef { if (self.generics) |generics| { if (generics.get(name)) |type_def| { @@ -954,6 +960,7 @@ fn beginFrame(self: *Self, function_type: obj.ObjFunction.FunctionType, function .enclosing = enclosing, .function_node = function_node, .constants = std.ArrayList(Value).init(self.gc.allocator), + .scopes = std.ArrayList(?Ast.Node.Index).init(self.gc.allocator), }; if (function_type == .Extern) { @@ -1044,18 +1051,22 @@ fn endFrame(self: *Self) Ast.Node.Index { } } + self.current.?.deinit(); + const current_node = self.current.?.function_node; self.current = self.current.?.enclosing; return current_node; } -fn beginScope(self: *Self) void { +fn beginScope(self: *Self, at: ?Ast.Node.Index) !void { + try self.current.?.scopes.append(at); self.current.?.scope_depth += 1; } fn endScope(self: *Self) ![]Chunk.OpCode { const current = self.current.?; + _ = current.scopes.pop(); var closing = std.ArrayList(Chunk.OpCode).init(self.gc.allocator); current.scope_depth -= 1; @@ -4653,7 +4664,7 @@ fn @"if"(self: *Self, is_statement: bool, loop_scope: ?LoopScope) Error!Ast.Node try self.consume(.LeftParen, "Expected `(` after `if`."); - self.beginScope(); + try self.beginScope(null); const condition = try self.expression(false); const condition_type_def = self.ast.nodes.items(.type_def)[condition]; @@ -4699,7 +4710,7 @@ fn @"if"(self: *Self, is_statement: bool, loop_scope: ?LoopScope) Error!Ast.Node } else if (is_statement) { try self.consume(.LeftBrace, "Expected `{` after `else`."); - self.beginScope(); + try self.beginScope(null); else_branch = try self.block(loop_scope); self.ast.nodes.items(.ends_scope)[else_branch.?] = try self.endScope(); } else { @@ -4953,7 +4964,7 @@ fn function( ); try self.beginFrame(function_type, function_node, this); - self.beginScope(); + try self.beginScope(null); // The functiont tyepdef is created in several steps, some need already parsed information like return type // We create the incomplete type now and enrich it. @@ -5669,7 +5680,7 @@ fn blockExpression(self: *Self, _: bool) Error!Ast.Node.Index { try self.consume(.LeftBrace, "Expected `{` at start of block expression"); - self.beginScope(); + try self.beginScope(null); self.current.?.in_block_expression = self.current.?.scope_depth; var statements = std.ArrayList(Ast.Node.Index).init(self.gc.allocator); @@ -6139,7 +6150,7 @@ fn objectDeclaration(self: *Self) Error!Ast.Node.Index { try self.consume(.Greater, "Expected `>` after generic types list"); } - self.beginScope(); + try self.beginScope(null); // Body try self.consume(.LeftBrace, "Expected `{` before object body."); @@ -6458,7 +6469,7 @@ fn protocolDeclaration(self: *Self) Error!Ast.Node.Index { .resolved_type = resolved_type, }; - self.beginScope(); + try self.beginScope(null); // Body try self.consume(.LeftBrace, "Expected `{` before protocol body."); @@ -7916,7 +7927,7 @@ fn forStatement(self: *Self) Error!Ast.Node.Index { try self.consume(.LeftParen, "Expected `(` after `for`."); - self.beginScope(); + try self.beginScope(null); // Should be either VarDeclaration or expression var init_declarations = std.ArrayList(Ast.Node.Index).init(self.gc.allocator); @@ -7957,33 +7968,46 @@ fn forStatement(self: *Self) Error!Ast.Node.Index { try self.consume(.RightParen, "Expected `)` after `for` expressions."); - try self.consume(.LeftBrace, "Expected `{` after `for` definition."); + const label = if (try self.match(.Colon)) lbl: { + try self.consume(.Identifier, "Expected label after `:`."); - self.beginScope(); - const body = try self.block( - .{ - .loop_type = .For, - .loop_body_scope = self.current.?.scope_depth, - }, - ); - self.ast.nodes.items(.ends_scope)[body] = try self.endScope(); + break :lbl self.current_token.? - 1; + } else null; - return try self.ast.appendNode( + try self.consume(.LeftBrace, "Expected `{`."); + + // We add it before parsing the body so that we can find it on a labeled break/continue statement + const for_node = try self.ast.appendNode( .{ .tag = .For, .location = start_location, - .end_location = self.current_token.? - 1, + .end_location = undefined, .components = .{ .For = .{ .init_declarations = init_declarations.items, .condition = condition, .post_loop = post_loop.items, - .body = body, + .body = undefined, + .label = label, }, }, - .ends_scope = try self.endScope(), }, ); + + try self.beginScope(for_node); + const body = try self.block( + .{ + .loop_type = .For, + .loop_body_scope = self.current.?.scope_depth, + }, + ); + self.ast.nodes.items(.ends_scope)[body] = try self.endScope(); + + self.ast.nodes.items(.end_location)[for_node] = self.current_token.? - 1; + self.ast.nodes.items(.components)[for_node].For.body = body; + self.ast.nodes.items(.ends_scope)[for_node] = try self.endScope(); + + return for_node; } fn forEachStatement(self: *Self) Error!Ast.Node.Index { @@ -7991,7 +8015,7 @@ fn forEachStatement(self: *Self) Error!Ast.Node.Index { try self.consume(.LeftParen, "Expected `(` after `foreach`."); - self.beginScope(); + try self.beginScope(null); var key = try self.varDeclaration( false, @@ -8054,34 +8078,47 @@ fn forEachStatement(self: *Self) Error!Ast.Node.Index { try self.consume(.RightParen, "Expected `)` after `foreach`."); - try self.consume(.LeftBrace, "Expected `{` after `foreach` definition."); + const label = if (try self.match(.Colon)) lbl: { + try self.consume(.Identifier, "Expected label after `:`."); - self.beginScope(); - const body = try self.block( - .{ - .loop_type = .ForEach, - .loop_body_scope = self.current.?.scope_depth, - }, - ); - self.ast.nodes.items(.ends_scope)[body] = try self.endScope(); + break :lbl self.current_token.? - 1; + } else null; - return try self.ast.appendNode( + try self.consume(.LeftBrace, "Expected `{`."); + + // We add it before parsing the body so that we can find it on a labeled break/continue statement + const foreach_node = try self.ast.appendNode( .{ .tag = .ForEach, .location = start_location, - .end_location = self.current_token.? - 1, + .end_location = undefined, .components = .{ .ForEach = .{ .key = key, .value = value.?, .iterable = iterable, - .body = body, + .body = undefined, .key_omitted = key_omitted, + .label = label, }, }, - .ends_scope = try self.endScope(), }, ); + + try self.beginScope(foreach_node); + const body = try self.block( + .{ + .loop_type = .ForEach, + .loop_body_scope = self.current.?.scope_depth, + }, + ); + self.ast.nodes.items(.ends_scope)[body] = try self.endScope(); + + self.ast.nodes.items(.end_location)[foreach_node] = self.current_token.? - 1; + self.ast.nodes.items(.components)[foreach_node].ForEach.body = body; + self.ast.nodes.items(.ends_scope)[foreach_node] = try self.endScope(); + + return foreach_node; } fn whileStatement(self: *Self) Error!Ast.Node.Index { @@ -8093,38 +8130,73 @@ fn whileStatement(self: *Self) Error!Ast.Node.Index { try self.consume(.RightParen, "Expected `)` after `while` condition."); - try self.consume(.LeftBrace, "Expected `{` after `if` condition."); + const label = if (try self.match(.Colon)) lbl: { + try self.consume(.Identifier, "Expected label after `:`."); - self.beginScope(); - const body = try self.block( - .{ - .loop_type = .While, - .loop_body_scope = self.current.?.scope_depth, - }, - ); - self.ast.nodes.items(.ends_scope)[body] = try self.endScope(); + break :lbl self.current_token.? - 1; + } else null; - return try self.ast.appendNode( + try self.consume(.LeftBrace, "Expected `{`."); + + // We add it before parsing the body so that we can find it on a labeled break/continue statement + const while_node = try self.ast.appendNode( .{ .tag = .While, .location = start_location, - .end_location = self.current_token.? - 1, + .end_location = undefined, .components = .{ .While = .{ .condition = condition, - .body = body, + .body = undefined, + .label = label, }, }, }, ); + + try self.beginScope(while_node); + const body = try self.block( + .{ + .loop_type = .While, + .loop_body_scope = self.current.?.scope_depth, + }, + ); + self.ast.nodes.items(.ends_scope)[body] = try self.endScope(); + + self.ast.nodes.items(.end_location)[while_node] = self.current_token.? - 1; + self.ast.nodes.items(.components)[while_node].While.body = body; + + return while_node; } fn doUntilStatement(self: *Self) Error!Ast.Node.Index { const start_location = self.current_token.? - 1; - try self.consume(.LeftBrace, "Expected `{` after `do`."); + const label = if (try self.match(.Colon)) lbl: { + try self.consume(.Identifier, "Expected label after `:`."); + + break :lbl self.current_token.? - 1; + } else null; - self.beginScope(); + try self.consume(.LeftBrace, "Expected `{`."); + + // We add it before parsing the body so that we can find it on a labeled break/continue statement + const dountil_node = try self.ast.appendNode( + .{ + .tag = .DoUntil, + .location = start_location, + .end_location = undefined, + .components = .{ + .DoUntil = .{ + .condition = undefined, + .body = undefined, + .label = label, + }, + }, + }, + ); + + try self.beginScope(null); const body = try self.block( .{ .loop_type = .Do, @@ -8141,19 +8213,11 @@ fn doUntilStatement(self: *Self) Error!Ast.Node.Index { try self.consume(.RightParen, "Expected `)` after `until` condition."); - return try self.ast.appendNode( - .{ - .tag = .DoUntil, - .location = start_location, - .end_location = self.current_token.? - 1, - .components = .{ - .DoUntil = .{ - .condition = condition, - .body = body, - }, - }, - }, - ); + self.ast.nodes.items(.end_location)[dountil_node] = self.current_token.? - 1; + self.ast.nodes.items(.components)[dountil_node].DoUntil.condition = condition; + self.ast.nodes.items(.components)[dountil_node].DoUntil.body = body; + + return dountil_node; } fn returnStatement(self: *Self) Error!Ast.Node.Index { @@ -8266,7 +8330,7 @@ fn tryStatement(self: *Self) Error!Ast.Node.Index { try self.consume(.LeftBrace, "Expected `{` after `try`"); - self.beginScope(); + try self.beginScope(null); const body = try self.block(null); self.ast.nodes.items(.ends_scope)[body] = try self.endScope(); @@ -8280,7 +8344,7 @@ fn tryStatement(self: *Self) Error!Ast.Node.Index { self.reportError(.syntax, "Catch clause not allowed after unconditional catch"); } - self.beginScope(); + try self.beginScope(null); const type_def = try self.parseTypeDef(null, true); @@ -8310,7 +8374,7 @@ fn tryStatement(self: *Self) Error!Ast.Node.Index { } else if (unconditional_clause == null) { try self.consume(.LeftBrace, "Expected `{` after `catch`"); - self.beginScope(); + try self.beginScope(null); unconditional_clause = try self.block(null); self.ast.nodes.items(.ends_scope)[unconditional_clause.?] = try self.endScope(); } else { @@ -8336,52 +8400,133 @@ fn tryStatement(self: *Self) Error!Ast.Node.Index { ); } -fn breakStatement(self: *Self, loop_scope: ?LoopScope) Error!Ast.Node.Index { - const start_location = self.current_token.? - 1; +// Go up scopes until it finds a loop node with a matching label +fn findLabel(self: *Self, label: Ast.TokenIndex) ?struct { node: Ast.Node.Index, depth: u32 } { + const tags = self.ast.nodes.items(.tag); + const components = self.ast.nodes.items(.components); + const lexemes = self.ast.tokens.items(.lexeme); - if (loop_scope == null) { - self.reportError(.syntax, "break is not allowed here."); - } + var depth = self.current.?.scope_depth - 1; + while (depth >= 0) : (depth -= 1) { + if (self.current.?.scopes.items[depth]) |scope_node| { + switch (tags[scope_node]) { + .For => { + if (components[scope_node].For.label) |scope_label| { + if (std.mem.eql(u8, lexemes[scope_label], lexemes[label])) { + return .{ + .node = scope_node, + .depth = depth, + }; + } + } + }, + .ForEach => { + if (components[scope_node].ForEach.label) |scope_label| { + if (std.mem.eql(u8, lexemes[scope_label], lexemes[label])) { + return .{ + .node = scope_node, + .depth = depth, + }; + } + } + }, + .While => { + if (components[scope_node].While.label) |scope_label| { + if (std.mem.eql(u8, lexemes[scope_label], lexemes[label])) { + return .{ + .node = scope_node, + .depth = depth, + }; + } + } + }, + .DoUntil => { + if (components[scope_node].DoUntil.label) |scope_label| { + if (std.mem.eql(u8, lexemes[scope_label], lexemes[label])) { + return .{ + .node = scope_node, + .depth = depth, + }; + } + } + }, + else => {}, + } + } - try self.consume(.Semicolon, "Expected `;` after statement."); + if (depth == 0) { + break; + } + } - return try self.ast.appendNode( - .{ - .tag = .Break, - .location = start_location, - .end_location = self.current_token.? - 1, - .ends_scope = if (loop_scope != null) - try self.closeScope(loop_scope.?.loop_body_scope) - else - null, - .components = .{ - .Break = {}, - }, - }, - ); + return null; } -fn continueStatement(self: *Self, loop_scope: ?LoopScope) Error!Ast.Node.Index { +fn breakContinueStatement(self: *Self, @"break": bool, loop_scope: ?LoopScope) Error!Ast.Node.Index { const start_location = self.current_token.? - 1; - if (loop_scope == null) { - self.reportError(.syntax, "continue is not allowed here."); + const label = if (try self.match(.Identifier)) + self.current_token.? - 1 + else + null; + + const label_scope = if (label) |lbl| + self.findLabel(lbl) + else + null; + + if (label != null and label_scope == null) { + self.reportErrorFmt( + .label_does_not_exists, + "Label `{s}` does not exists.", + .{ + self.ast.tokens.items(.lexeme)[label.?], + }, + ); + } + + if (label == null and loop_scope == null) { + self.reportError(.syntax, "break is not allowed here."); } try self.consume(.Semicolon, "Expected `;` after statement."); return try self.ast.appendNode( .{ - .tag = .Continue, + .tag = if (@"break") .Break else .Continue, .location = start_location, .end_location = self.current_token.? - 1, - .ends_scope = if (loop_scope != null) - try self.closeScope(loop_scope.?.loop_body_scope) + .ends_scope = if (loop_scope != null or label_scope != null) + try self.closeScope( + if (label_scope) |scope| + scope.depth + 1 + else + loop_scope.?.loop_body_scope, + ) else null, - .components = .{ - .Continue = {}, - }, + .components = if (@"break") + .{ + .Break = if (label_scope) |scope| + scope.node + else + null, + } + else + .{ + .Continue = if (label_scope) |scope| + scope.node + else + null, + }, }, ); } + +fn continueStatement(self: *Self, loop_scope: ?LoopScope) Error!Ast.Node.Index { + return self.breakContinueStatement(false, loop_scope); +} + +fn breakStatement(self: *Self, loop_scope: ?LoopScope) Error!Ast.Node.Index { + return self.breakContinueStatement(true, loop_scope); +} diff --git a/src/Reporter.zig b/src/Reporter.zig index a4a42aaf..512d75a5 100644 --- a/src/Reporter.zig +++ b/src/Reporter.zig @@ -108,6 +108,7 @@ pub const Error = enum(u8) { import_already_exists = 92, code_after_return = 93, unused_import = 94, + label_does_not_exists = 95, }; // Inspired by https://github.com/zesterer/ariadne diff --git a/tests/072-labels.buzz b/tests/072-labels.buzz new file mode 100644 index 00000000..79cfbcce --- /dev/null +++ b/tests/072-labels.buzz @@ -0,0 +1,66 @@ +import "std"; + +test "labeled break" { + var i = 0; + while (i < 100) :here { + i = i + 1; + + if (i == 10) { + break here; + } + } + + std.assert(i == 10); +} + +test "labeled break in nested loop" { + var i = 0; + foreach (int _ in 0..100) :here { + var _ = "hello"; + + while (i < 100) { + i = i + 1; + + if (i == 10) { + break here; + } + } + } + + std.assert(i == 10); +} + +test "labeled break in deeply nested loop" { + var i = 0; + foreach (int j in 0..100) :here { + var _ = "hello"; + + while (j < 100) { + var _ = "bye"; + + while (i < 100) { + i = i + 1; + + if (i == 10) { + break here; + } + } + } + } + + std.assert(i == 10); +} + +test "labeled continue" { + var i = 0; + foreach (int j in 0..10) :here { + if (j == 3) { + continue here; + } + + i = i + j; + } + + std.assert(i == 42); +} + diff --git a/tests/compile_errors/029-label-not-found.buzz b/tests/compile_errors/029-label-not-found.buzz new file mode 100644 index 00000000..05ca3b5a --- /dev/null +++ b/tests/compile_errors/029-label-not-found.buzz @@ -0,0 +1,6 @@ +| Label `somewhere` does not exists. +test "using a label that does not exists" { + while (true) { + break somewhere; + } +} From d0b71a92542bbe10aa16afa3f37e58cb0c1062b7 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Wed, 15 May 2024 16:47:10 +0200 Subject: [PATCH 2/5] fix: range -> rg All other type are abbreviation, no reason to use a full word for ranges --- CHANGELOG.md | 2 +- src/Token.zig | 2 +- src/obj.zig | 4 ++-- tests/053-range.buzz | 26 +++++++++++++------------- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 173f69c5..09bae0b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,7 +43,7 @@ var value = from { - By default, imported symbols from another script will be under `libprefix.XXXX` - When importing something, you can still redefine its namespace prefix with `import "..." as mynewnamespace` or remove it altogether with `import "..." _` - Ranges are now an actual buzz value (https://github.com/buzz-language/buzz/issues/170) - - new `range` type + - new `rg` type - `myrange.toList()` transforms a range into a list of integers - `myrange.low` and `myrange.high` to get a range bounds - works with `foreach` diff --git a/src/Token.zig b/src/Token.zig index 40b19c03..4a0fb587 100644 --- a/src/Token.zig +++ b/src/Token.zig @@ -217,7 +217,7 @@ pub const keywords = std.StaticStringMap(Type).initComptime( .{ "out", .Out }, .{ "pat", .Pat }, .{ "protocol", .Protocol }, - .{ "range", .Range }, + .{ "rg", .Range }, .{ "resolve", .Resolve }, .{ "resume", .Resume }, .{ "return", .Return }, diff --git a/src/obj.zig b/src/obj.zig index 609bfe94..054daa6f 100644 --- a/src/obj.zig +++ b/src/obj.zig @@ -2524,7 +2524,7 @@ pub const ObjRange = struct { .{ .{ "toList", "extern Function toList() > [int]" }, .{ "len", "extern Function len() > int" }, - .{ "invert", "extern Function invert() > range" }, + .{ "invert", "extern Function invert() > rg" }, }, ); @@ -4025,7 +4025,7 @@ pub const ObjTypeDef = struct { .String => try writer.writeAll("str"), .Pattern => try writer.writeAll("pat"), .Any => try writer.writeAll("any"), - .Range => try writer.writeAll("range"), + .Range => try writer.writeAll("rg"), .Fiber => { try writer.writeAll("fib<"); try self.resolved_type.?.Fiber.return_type.toStringRaw(writer, qualified); diff --git a/tests/053-range.buzz b/tests/053-range.buzz index 993aab8b..21d0ffef 100644 --- a/tests/053-range.buzz +++ b/tests/053-range.buzz @@ -2,13 +2,13 @@ import "std"; test "Range" { int limit = 10; - range rg = 0..limit; + rg range = 0..limit; - std.assert(rg == 0..10, message: "Could compare ranges"); - std.assert(rg.low == 0, message: "Could get low limit of range"); - std.assert(rg.high == 10, message: "Could get high limit of range"); + std.assert(range == 0..10, message: "Could compare ranges"); + std.assert(range.low == 0, message: "Could get low limit of range"); + std.assert(range.high == 10, message: "Could get high limit of range"); - [int] list = rg.toList(); + [int] list = range.toList(); std.assert(list.len() == 10, message: "Could create list from range"); int sum = 0; @@ -17,18 +17,18 @@ test "Range" { } std.assert(sum == 45, message: "Could iterate over range"); - std.assert(rg.len() == 10, message: "Could get range length"); + std.assert(range.len() == 10, message: "Could get range length"); } test "Inverted range" { int limit = 0; - range rg = 10..limit; + rg range = 10..limit; - std.assert((0..10).invert() == rg, message: "Could invert range"); - std.assert(rg.low == 10, message: "Could get low limit of range"); - std.assert(rg.high == 0, message: "Could get high limit of range"); + std.assert((0..10).invert() == range, message: "Could invert range"); + std.assert(range.low == 10, message: "Could get low limit of range"); + std.assert(range.high == 0, message: "Could get high limit of range"); - [int] list = rg.toList(); + [int] list = range.toList(); std.assert(list.len() == 10, message: "Could create list from inverted range"); int sum = 0; @@ -37,5 +37,5 @@ test "Inverted range" { } std.assert(sum == 55, message: "Could iterate over inverted range"); - std.assert(rg.len() == 10, message: "Could get range length"); -} \ No newline at end of file + std.assert(range.len() == 10, message: "Could get range length"); +} From bd2f0064a8597b6ece805b1bda08ce9bd65441c0 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Wed, 15 May 2024 16:49:40 +0200 Subject: [PATCH 3/5] fix: Removed tools which contained unmaintained unusable things --- tools/README.md | 5 - tools/gendoc.buzz | 208 --------- tools/lsp.buzz | 1140 --------------------------------------------- 3 files changed, 1353 deletions(-) delete mode 100644 tools/README.md delete mode 100644 tools/gendoc.buzz delete mode 100755 tools/lsp.buzz diff --git a/tools/README.md b/tools/README.md deleted file mode 100644 index e84cd98d..00000000 --- a/tools/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Tools - -None of those tool is in a usable state right now: -- `gendoc.buzz`: extracts docblock from buzz code and generate markdown documentation from it -- `lsp.buzz`: language server protocol for buzz \ No newline at end of file diff --git a/tools/gendoc.buzz b/tools/gendoc.buzz deleted file mode 100644 index 22891366..00000000 --- a/tools/gendoc.buzz +++ /dev/null @@ -1,208 +0,0 @@ -import "std"; -import "debug"; -import "serialize"; -import "io"; -import "fs"; -import "errors"; -import "buffer"; - -object Declaration { - str declarationType, - str? name = null, - str typeDef, - str? docblock, - - {str: Declaration} subDeclarations, - - static fun init( - str declarationType, - str typeDef, - str? docblock, - {str: Declaration}? subDeclarations - ) > Declaration -> Declaration{ - declarationType = declarationType, - typeDef = typeDef, - docblock = docblock, - subDeclarations = subDeclarations ?? {}, - } - - | Split docblock by @ instructions - static fun handleDocblock(str docblock) > {str: str} { - const pat atPattern = $"@([^@ ]+)(\s+[^\n@]+)?"; - {str: str} out = {}; - - foreach (str line in docblock.split("\\n")) { - if (atPattern.match(line) -> at) { - if (at.len() > 2) { - out[at[1].trim()] = at[2]; - } else { - out[at[1].trim()] = ""; - } - } else { - out["unspecified"] = (out["unspecified"] ?? "") + line; - } - } - - return out; - } - - fun toMarkdown(str? heading, Buffer out) > void !> WriteWhileReadingError { - const pat atParamPattern = $"([^\s]+)\s+(.+)"; - - if (this.docblock == null) { - return; - } - - {str: str} processed = Declaration.handleDocblock(this.docblock ?? ""); - - | Ignore things marked with @private - if (processed["private"] != null) { - return; - } - - out.write("\n{heading ?? "###"} `{this.typeDef} {this.name ?? ""}`\n"); - - out.write(processed["unspecified"] ?? ""); - - foreach (str instruction, str content in processed) { - if (instruction == "param") { - [str]? param = atParamPattern.match(content); - - out.write("\n- "); - if (param != null) { - out.write("**`{param![1]}`:** {param![2]}\n"); - } else { - out.write("{content}\n"); - } - } else if (instruction == "return") { - out.write("\n\n**Returns:** {content}"); - } - } - } -} - -fun extractDeclarations([Boxed] statements) > [Declaration] !> JsonParseError { - [Declaration] declarations = []; - - foreach (Boxed statement in statements) { - {str: Boxed} statementMap = statement.mapValue(); - str nodeType = statement.q(["node"]).stringValue(); - - if ( - nodeType == "Function" - or nodeType == "VarDeclaration" - or nodeType == "FunDeclaration" - or nodeType == "Enum" - or nodeType == "ObjectDeclaration" - ) { - Declaration declaration = Declaration.init( - declarationType: nodeType, - typeDef: statement.q(["type_def"]).stringValue(), - docblock: statement.q(["docblock"]).string(), - ); - - if (nodeType == "VarDeclaration") { - declaration.name = statement.q(["name"]).string(); - } - - if (nodeType == "ObjectDeclaration") { - {str: Boxed} props = statement.q(["members"]).mapValue(); - foreach (str name, Boxed member in props) { - str typeDef = member.q(["type_def"]).stringValue(); - str displayName = "{typeDef} {name}"; - if (typeDef.indexOf("fun") == 0) { - displayName = typeDef; - } - - declaration.subDeclarations[name] = - Declaration.init( - declarationType: "member", - typeDef: displayName, - docblock: member.mapValue()["docblock"]?.string(), - ); - } - } - - declarations.append(declaration); - } - } - - return declarations; -} - -fun getDeclarations(str path) > [Declaration] - !> FileSystemError, - UnexpectedError, - ReadWriteError, - CompileError, - JsonParseError, - WriteWhileReadingError { - File file = File.open(path, mode: FileMode.read); - - str source = ast( - file.readAll(), - scriptName: path.sub(0, len: path.indexOf(".buzz")!) - ); - file.close(); - - Boxed? root = null; - - try { - root = jsonDecode(source); - } catch (JsonParseError error) { - print("Could not decode ast of for {path}"); - - return []; - } - - | Root must be a function - assert(root?.q(["node"]).string() ?? "" == "Function", message: "Expected root node to be a function"); - - return extractDeclarations(root?.q(["body", "statements"]).listValue() ?? []); -} - -fun genMarkdownDoc([Declaration] declarations, Buffer out) > void !> WriteWhileReadingError { - foreach (Declaration decl in declarations) { - decl.toMarkdown("###", out: out); - } -} - -fun main([str] args) > void - !> FileSystemError, - ReadWriteError, - InvalidArgumentError, - UnexpectedError, - CompileError, - JsonParseError, - WriteWhileReadingError -{ - {str: str} markdownDoc = {}; - foreach (str file in list("src/lib")) { - if (file.endsWith(".buzz")) { - print("Generating doc for {file}..."); - [Declaration] declarations = getDeclarations("src/lib/{file}"); - - if (declarations.len() > 0) { - Buffer out = Buffer.init(); - - genMarkdownDoc(declarations, out: out); - markdownDoc[file] = out.toString(); - } - } - } - - File mdFile = File.open("{currentDirectory()}/doc/std.md", mode: FileMode.write); - - mdFile.write("# Buzz std lib\n## Table of contents\n"); - foreach (str lib, str libDoc in markdownDoc) { - const str libName = lib.sub(0, len: lib.indexOf(".buzz")!); - mdFile.write("\n- [{libName}](#{libName.replace(" ", with: "-")})"); - } - - foreach (str lib, str libDoc in markdownDoc) { - const str libName = lib.sub(0, len: lib.indexOf(".buzz")!); - mdFile.write("\n## {libName}\n{libDoc}"); - } - - mdFile.close(); -} \ No newline at end of file diff --git a/tools/lsp.buzz b/tools/lsp.buzz deleted file mode 100755 index 26a1b4af..00000000 --- a/tools/lsp.buzz +++ /dev/null @@ -1,1140 +0,0 @@ -#!/usr/bin/env buzz - -import "std"; -import "io"; -import "serialize"; -import "os" as os; -import "debug" as debug; -import "errors"; -import "buffer"; - -object LspState { - | TODO: Maybe bad to keep this in memory - {str: str} sources, - {str: Boxed} ast, -} - -enum(int) SymbolKind { - File = 1, - Module = 2, - Namespace = 3, - Package = 4, - Class = 5, - Method = 6, - Property = 7, - Field = 8, - Constructor = 9, - Enum = 10, - Interface = 11, - KFunction = 12, - Variable = 13, - Constant = 14, - String = 15, - Number = 16, - Boolean = 17, - Array = 18, - Object = 19, - Key = 20, - Null = 21, - EnumMember = 22, - Struct = 23, - Event = 24, - Operator = 25, - TypeParameter = 26, -} - -enum LSPErrorType { - ReadError, - WriteError, - ParseError, -} - -object LSPError { - LSPErrorType errorType, - str message, - - static fun init(LSPErrorType errorType) > LSPError { - str message = "Error occured"; - if (errorType == LSPErrorType.ReadError) { - message = "Error while reading request"; - } else if (errorType == LSPErrorType.WriteError) { - message = "Error while writing response"; - } else if (errorType == LSPErrorType.ParseError) { - message = "Error while parsing buzz script"; - } - - return LSPError{ - errorType = errorType, - message = message - }; - } -} - -fun readContentLength() > int? !> LSPError { - while (true) { - try { - str? header = stdin.readLine(); - - if (header == null) { - throw LSPError{ - errorType = LSPErrorType.ReadError, - message = "Could not parse request header", - }; - } - - | Consume \r\n - stdin.read(2); - | Strip \r - header = header!.sub(0, len: header!.len() - 1); - const int? colon = header!.indexOf(": "); - - if (colon == null) { - throw LSPError{ - errorType = LSPErrorType.ReadError, - message = "Could not parse request header", - }; - } - - const str name = header!.sub(0, len: colon!); - const str value = header!.sub(colon! + 2); - - if (name == "Content-Length") { - return parseInt(value); - } - } catch { - throw LSPError{ - errorType = LSPErrorType.ReadError, - message = "Could not parse request header", - }; - } - } - - return null; -} - -fun respond(str? strId, int? numId, Boxed result) > void !> LSPError - { - try { - const Boxed response = Boxed.init( - { - , - "jsonrpc": "2.0", - "id": if (strId != null) (strId as? any) else (numId as? any), - "result": result - } - ); - - const str stringResponse = jsonEncode(response); - - stderr.write("Content-Length: {stringResponse.len()}\r\n\r\n{stringResponse}"); - stdout.write("Content-Length: {stringResponse.len()}\r\n\r\n{stringResponse}"); - } catch { - throw LSPError.init(LSPErrorType.WriteError); - } -} - -fun withinNode(Boxed node, str uri, int line, int column) > bool { - const str script = node.q(["location", "script"]).stringValue(); - const int startLine = node.q(["location", "start_line"]).integer() ?? -1; - const int startColumn = node.q(["location", "start_column"]).integer() ?? -1; - const int endLine = node.q(["location", "end_line"]).integer() ?? -1; - const int endColumn = node.q(["location", "end_column"]).integer() ?? -1; - - const bool result = uri.endsWith(script) - and line >= startLine - and line <= endLine - and (line != startLine or (column - 1) >= startColumn) - and (line != endLine or (column - 1) >= endColumn) - and (endLine != startLine or ((column - 1) >= startColumn and (column - 1) <= endColumn)); - - stderr.write("is {uri}:{line + 1}:{column} within {node.q(["node"]).stringValue()} {script}:{startLine + 1}:{startColumn} - {endLine + 1}:{endColumn} -> {result}\n") catch void; - - return result; -} - -| Go down the ast tree to find the smallest node under `line`:`column` -fun findNodeUnder([Boxed] trail, Boxed root, str uri, int line, int column) > void { - if (!withinNode(root, uri: uri, line: line, column: column)) { - return; - } - - trail.append(root); - - const str node = root.q(["node"]).string() ?? "-"; - - if (node == "Function") { - const Boxed body = root.q(["body"]); - - if (withinNode(body, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: body, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Enum") { - const [Boxed] cases = root.q(["cases"]).listValue(); - - foreach (int i, Boxed case in cases) { - if (withinNode(case, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: case, uri: uri, line: line, column: column); - - return; - } - } - - return; - } - - if (node == "VarDeclaration") { - const Boxed value = root.q(["value"]); - - if (withinNode(value, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: value, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "FunDeclaration") { - const Boxed function = root.q(["function"]); - - if (withinNode(function, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: function, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "ObjectDeclaration") { - const {str: Boxed} methods = root.q(["methods"]).mapValue(); - const {str: Boxed} members = root.q(["members"]).mapValue(); - - foreach (str name, Boxed method in methods) { - if (withinNode(method, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: method, uri: uri, line: line, column: column); - - return; - } - } - - foreach (str name, Boxed member in members) { - if (withinNode(member, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: member, uri: uri, line: line, column: column); - - return; - } - } - - return; - } - - if (node == "Binary") { - const Boxed left = root.q(["left"]); - const Boxed right = root.q(["right"]); - - if (withinNode(left, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: left, uri: uri, line: line, column: column); - - return; - } - - if (withinNode(right, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: right, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Unary") { - const Boxed left = root.q(["left"]); - - if (withinNode(left, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: left, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Subscript") { - const Boxed subscripted = root.q(["subscripted"]); - const Boxed index = root.q(["index"]); - const Boxed value = root.q(["value"]); - - if (withinNode(subscripted, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: subscripted, uri: uri, line: line, column: column); - - return; - } - - if (withinNode(index, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: index, uri: uri, line: line, column: column); - - return; - } - - if (withinNode(value, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: value, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Unwrap") { - const Boxed unwrapped = root.q(["unwrapped"]); - - if (withinNode(unwrapped, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: unwrapped, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "ForceUnwrap") { - const Boxed unwrapped = root.q(["unwrapped"]); - - if (withinNode(unwrapped, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: unwrapped, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Is") { - const Boxed left = root.q(["left"]); - - if (withinNode(left, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: left, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Expression") { - const Boxed expression = root.q(["expression"]); - - if (withinNode(expression, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: expression, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "NamedVariable") { - const Boxed value = root.q(["value"]); - - if (value.map() != null and withinNode(value, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: value, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Number") { - return; - } - - if (node == "String") { - const [Boxed] elements = root.q(["elements"]).listValue(); - - foreach (int i, Boxed element in elements) { - if (withinNode(element, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: element, uri: uri, line: line, column: column); - - return; - } - } - - return; - } - - if (node == "StringLiteral") { - return; - } - - if (node == "Pattern") { - return; - } - - if (node == "Boolean") { - return; - } - - if (node == "Null") { - return; - } - - if (node == "List") { - const [Boxed] items = root.q(["items"]).listValue(); - - foreach (int i, Boxed item in items) { - if (withinNode(item, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: item, uri: uri, line: line, column: column); - - return; - } - } - - return; - } - - if (node == "Map") { - const {str: Boxed} map = root.q(["items"]).mapValue(); - - foreach (str key, Boxed value in map) { - if (withinNode(value, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: value, uri: uri, line: line, column: column); - - return; - } - } - - return; - } - - if (node == "Dot") { - const Boxed callee = root.q(["callee"]); - const Boxed? value = root.mapValue()["value"]; - const Boxed? call = root.mapValue()["call"]; - - if (withinNode(callee, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: callee, uri: uri, line: line, column: column); - - return; - } - - if (value != null and withinNode(value!, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: value!, uri: uri, line: line, column: column); - - return; - } - - if (call != null and withinNode(call!, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: call!, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "ObjectInit") { - const Boxed objectRef = root.q(["object"]); - const [Boxed] properties = root.q(["properties"]).listValue(); - - if (withinNode(objectRef, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: objectRef, root: objectRef, uri: uri, line: line, column: column); - - return; - } - - foreach (int i, Boxed property in properties) { - if (withinNode(property, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: property, uri: uri, line: line, column: column); - - return; - } - } - - return; - } - - if (node == "Throw") { - const Boxed errorValue = root.q(["error_value"]); - - if (withinNode(errorValue, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: errorValue, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Break") { - return; - } - - if (node == "Continue") { - return; - } - - if (node == "Call") { - const Boxed? callee = root.mapValue()["callee"]; - const [Boxed] arguments = root.q(["arguments"]).listValue(); - - if (callee != null and withinNode(callee!, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: callee!, uri: uri, line: line, column: column); - - return; - } - - foreach (int i, Boxed argument in arguments) { - const Boxed argNode = argument.q(["value"]); - if (withinNode(argNode, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: argNode, uri: uri, line: line, column: column); - - return; - } - } - - return; - } - - if (node == "AsyncCall") { - const Boxed call = root.q(["call"]); - - if (withinNode(call, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: call, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Resume") { - const Boxed fiber = root.q(["fiber"]); - - if (withinNode(fiber, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: fiber, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Resolve") { - const Boxed fiber = root.q(["fiber"]); - - if (withinNode(fiber, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: fiber, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Yield") { - const Boxed expression = root.q(["expression"]); - - if (withinNode(expression, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: expression, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "If") { - const Boxed condition = root.q(["condition"]); - const Boxed body = root.q(["body"]); - const Boxed? elseBranch = root.mapValue()["else"]; - - if (withinNode(condition, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: condition, uri: uri, line: line, column: column); - - return; - } - - if (withinNode(body, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: body, uri: uri, line: line, column: column); - - return; - } - - if (elseBranch != null and withinNode(elseBranch!, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: elseBranch!, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Block") { - const [Boxed] statements = root.q(["statements"]).listValue(); - - foreach (int i, Boxed statement in statements) { - if (withinNode(statement, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: statement, uri: uri, line: line, column: column); - - return; - } - } - - return; - } - - if (node == "Return") { - const Boxed value = root.q(["value"]); - - if (withinNode(value, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: value, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "For") { - const [Boxed] initDeclarations = root.q(["init_declarations"]).listValue(); - const Boxed condition = root.q(["condition"]); - const [Boxed] postLoops = root.q(["postLoop"]).listValue(); - const Boxed body = root.q(["body"]); - - if (withinNode(condition, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: condition, uri: uri, line: line, column: column); - - return; - } - - foreach (int i, Boxed initDeclaration in initDeclarations) { - if (withinNode(initDeclaration, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: initDeclaration, uri: uri, line: line, column: column); - - return; - } - } - - foreach (int i, Boxed postLoop in postLoops) { - if (withinNode(postLoop, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: postLoop, uri: uri, line: line, column: column); - - return; - } - } - - if (withinNode(body, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: body, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "ForEach") { - const Boxed? key = root.mapValue()["key"]; - const Boxed value = root.q(["value"]); - const Boxed iterable = root.q(["iterable"]); - const Boxed block = root.q(["block"]); - - if (key != null and withinNode(key!, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: key!, uri: uri, line: line, column: column); - - return; - } - - if (withinNode(value, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: value, uri: uri, line: line, column: column); - - return; - } - - if (withinNode(iterable, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: iterable, uri: uri, line: line, column: column); - - return; - } - - if (withinNode(block, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: block, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "DoUntil") { - const Boxed condition = root.q(["condition"]); - const Boxed block = root.q(["block"]); - - if (withinNode(condition, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: condition, uri: uri, line: line, column: column); - - return; - } - - if (withinNode(block, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: block, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "While") { - const Boxed condition = root.q(["condition"]); - const Boxed block = root.q(["block"]); - - if (withinNode(condition, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: condition, uri: uri, line: line, column: column); - - return; - } - - if (withinNode(block, uri: uri, line: line, column: column)) { - findNodeUnder(trail, root: block, uri: uri, line: line, column: column); - - return; - } - - return; - } - - if (node == "Export") { - return; - } - - if (node == "Import") { - return; - } -} - -| Search symbol with a list of statements without going into a deeper scope -fun findSymbolInScope([Boxed] statements, str symbol) > Boxed? { - foreach (int i, Boxed statement in statements) { - const str statementType = statement.q(["node"]).stringValue(); - - if (statementType == "VarDeclaration") { - if (symbol == statement.q(["name"]).stringValue()) { - return statement; - } - } else if (statementType == "FunDeclaration") { - if (symbol == statement.q(["function", "name"]).stringValue()) { - return statement; - } - } else if (statementType == "ObjectDeclaration") { - if (statement.q(["type_def"]).stringValue().endsWith(symbol)) { - return statement; - } - } - } - - return null; -} - -| Climp up ast tree from the leaf to find its declaration -fun findSymbol([Boxed] trail, str symbol) > Boxed? { - for (int i = trail.len() - 1; i >= 0; i = i - 1) { - Boxed node = trail[i]; - const str nodeType = node.q(["node"]).stringValue(); - - if (nodeType == "Block") { - Boxed? declaration = findSymbolInScope(node.q(["statements"]).listValue(), symbol: symbol); - - if (declaration != null) { - return declaration; - } - } else if (nodeType == "Function") { - Boxed? declaration = findSymbolInScope(node.q(["body", "statements"]).listValue(), symbol: symbol); - - if (declaration != null) { - return declaration; - } - } - } - - return null; -} - -| Climp up ast tree from the leaf to find its declaration -fun findDeclaration([Boxed] trail) > Boxed? !> LSPError { - assert(trail.len() >= 2, message: "Trail should not be empty"); - - try { - stderr.write("Searching in trail: "); - foreach (int i, Boxed e in trail) { - stderr.write("{e.q(["node"]).stringValue()} > "); - } - stderr.write("\n"); - - Boxed leaf = trail[trail.len() - 1]; - str leafType = leaf.q(["node"]).stringValue(); - - if (leafType == "NamedVariable") { - const str symbol = leaf.q(["identifier"]).stringValue(); - const bool global = leaf.q(["slot_type"]).stringValue().endsWith("Global"); - - if (global) { - | Its a global we can search from the root directly - return findSymbol([trail[0]], symbol: symbol); - } else { - return findSymbol(trail.sub(0, len: trail.len() - 1), symbol: symbol); - } - } else if (leafType == "Function") { - - } else if (leafType == "Dot") { - stderr.write("TODO Dot\n"); - } else { - stderr.write("Can't investigate a {leafType}\n"); - } - } catch { - throw LSPError.init(LSPErrorType.WriteError); - } - - return null; -} - -fun getAst(LspState state, str filename) > Boxed !> LSPError { - if (!filename.startsWith("file://")) { - throw LSPError.init(LSPErrorType.ParseError); - } - - | Dismiss "file://" prefix - filename = filename.sub(7); - - str? source = state.sources[filename]; - if (source == null) { - try { - File file = File.open(filename, mode: FileMode.read); - - source = file.readAll(); - state.sources[filename] = source!; - - file.close(); - } catch { - throw LSPError.init(LSPErrorType.ParseError); - } - } - - if (state.ast[filename] == null) { - try { - stderr.write("Getting ast for {filename}\n"); - - str astString = debug.ast(source!, scriptName: filename); - - state.ast[filename] = jsonDecode(astString); - } catch { - throw LSPError.init(LSPErrorType.ParseError); - } - } - - return state.ast[filename]!; -} - -fun getSymbolsAt(Boxed node, [Boxed] declarations) > void !> CircularReference, NotSerializable { - const str ndType = node.q(["node"]).stringValue(); - - if (ndType == "Function") { - getSymbolsAt(node.q(["body"]), declarations: declarations); - - return; - } - - if (ndType == "Enum") { - declarations.append(Boxed.init( - { - , - "name": node.q(["type_def"]).stringValue().sub(5), - "kind": SymbolKind.Enum.value, - "range": locationToRange(node.q(["location"])).data, - "selectionRange": locationToRange(node.q(["location"])).data, - } - )); - - return; - } - - if (ndType == "VarDeclaration") { - declarations.append(Boxed.init( - { - , - "name": node.q(["name"]).stringValue(), - "kind": SymbolKind.Variable.value, - "range": locationToRange(node.q(["location"])).data, - "selectionRange": locationToRange(node.q(["location"])).data, - } - )); - - return; - } - - if (ndType == "FunDeclaration") { - pat namePattern = $"fun (.+)\("; - str name = namePattern.match(node.q(["type_def"]).stringValue())![1]; - - declarations.append(Boxed.init( - { - , - "name": name, - "kind": SymbolKind.KFunction.value, - "range": locationToRange(node.q(["location"])).data, - "selectionRange": locationToRange(node.q(["location"])).data, - } - )); - - return; - } - - if (ndType == "ObjectDeclaration") { - pat namePattern = $"(object) (.+)"; - [str] match = namePattern.match(node.q(["type_def"]).stringValue())!; - SymbolKind kind = SymbolKind.Object; - str name = match[2]; - - declarations.append(Boxed.init( - { - , - "name": name, - "kind": kind.value, - "range": locationToRange(node.q(["location"])).data, - "selectionRange": locationToRange(node.q(["location"])).data, - } - )); - - foreach (str methodName, Boxed method in node.q(["methods"]).mapValue()) { - declarations.append(Boxed.init( - { - , - "name": methodName, - "kind": SymbolKind.Method.value, - "range": locationToRange(method.q(["location"])).data, - "selectionRange": locationToRange(method.q(["location"])).data, - } - )); - } - - foreach (str propertyName, Boxed def in node.q(["members"]).mapValue()) { - declarations.append(Boxed.init( - { - , - "name": propertyName, - "kind": SymbolKind.Property.value, - | TODO: add property location - "range": locationToRange(node.q(["location"])).data, - "selectionRange": locationToRange(node.q(["location"])).data, - } - )); - } - - return; - } - - if (ndType == "Expression") { - getSymbolsAt(node.q(["expression"]), declarations: declarations); - - return; - } - - if (ndType == "If") { - getSymbolsAt(node.q(["condition"]), declarations: declarations); - getSymbolsAt(node.q(["body"]), declarations: declarations); - getSymbolsAt(node.q(["else"]), declarations: declarations); - - return; - } - - if (ndType == "Block") { - foreach (int i, Boxed statement in node.q(["statements"]).listValue()) { - getSymbolsAt(statement, declarations: declarations); - } - } - - if (ndType == "For") { - getSymbolsAt(node.q(["body"]), declarations: declarations); - - return; - } - - if (ndType == "ForEach") { - getSymbolsAt(node.q(["block"]), declarations: declarations); - - return; - } - - if (ndType == "DoUntil") { - getSymbolsAt(node.q(["block"]), declarations: declarations); - - return; - } - - if (ndType == "While") { - getSymbolsAt(node.q(["block"]), declarations: declarations); - - return; - } -} - -fun getSymbols(LspState state, {str: Boxed} request) > Boxed !> CircularReference, NotSerializable { - const Boxed ast = getAst(state, filename: request["params"]?.q(["textDocument", "uri"]).stringValue() ?? "") catch Boxed{}; - - [Boxed] declarations = []; - - getSymbolsAt(ast, declarations: declarations); - - return Boxed.init(declarations); -} - -fun locationToRange(Boxed location) > Boxed !> CircularReference, NotSerializable { - const int startLine = location.q(["start_line"]).integer() ?? -1; - const int startColumn = location.q(["start_column"]).integer() ?? -1; - const int endLine = location.q(["end_line"]).integer() ?? -1; - const int endColumn = location.q(["end_column"]).integer() ?? -1; - - return Boxed.init( - { - , - "start": { - , - "line": startLine, - "character": startColumn, - }, - "end": { - , - "line": endLine, - "character": endColumn, - } - } - ); -} - -fun gotoDefinition(LspState state, {str: Boxed} request) > Boxed !> LSPError, CircularReference, NotSerializable { - const {str: Boxed} textDocument = request["params"]?.q(["textDocument"]).mapValue() ?? {}; - str uri = textDocument["uri"]?.stringValue() ?? ""; - const {str: Boxed} position = request["params"]?.q(["position"]).mapValue() ?? {}; - const int line = position["line"]?.integerValue() ?? 0; - const int column = position["character"]?.integerValue() ?? 0; - - Boxed ast = getAst(state, filename: uri); - - | Skip entry point - [Boxed] roots = ast.q(["body", "statements"]).list() ?? []; - foreach (int i, Boxed root in roots) { - [Boxed] trail = [ast]; - - findNodeUnder(trail, root: root, uri: uri, line: line, column: column); - - if (trail.len() > 1) { - Boxed? declaration = findDeclaration(trail); - - if (declaration != null) { - return Boxed.init( - { - , - "uri": uri, - "range": locationToRange(declaration!.q(["location"])).data, - } - ); - } - - break; - } - } - - return Boxed{}; -} - -fun stop(LspState state, {str: Boxed} request) > Boxed { - os.exit(0); - - return Boxed{}; -} - -| Possible commands: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#languageFeatures -const {str: Function(LspState state, {str: Boxed} request) > Boxed} handlers = { - "exit": stop, - "shutdown": stop, - "initialize": fun (LspState state, {str: Boxed} request) - -> Boxed.init( - { - , - "capabilities": { - , - "declarationProvider": true, - "definitionProvider": true, - "documentSymbolProvider": true, - }, - "serverInfo": { - , - "name": "buzz-lsp", - "version": "0.0.1", - }, - }, - ) catch Boxed{}, - | There's not really a difference between declaration and definition in buzz - "textDocument/declaration": gotoDefinition, - "textDocument/definition": gotoDefinition, - "textDocument/documentSymbol": getSymbols, -}; - -fun main([str] args) > void !> any { - LspState state = LspState{ - sources = {}, - ast = {}, - }; - - while (true) { - int? contentLength = readContentLength(); - - if (contentLength == null or contentLength! <= 0) { - throw "Request is empty"; - } - - str? requestRaw = stdin.read(contentLength ?? 0) catch null; - - stderr.write("Request is: `{requestRaw!}`\n") catch void; - - if (requestRaw == null) { - throw "Could not read request"; - } - - {str: Boxed} request = (jsonDecode(requestRaw!) catch Boxed{}).mapValue(); - const str? method = request["method"]?.string(); - - stderr.write("Method is: `{method ?? "none"}`\n") catch void; - - Boxed result = Boxed{}; - if (method != null and handlers[method!] != null) { - result = handlers[method!]!(state, request: request); - } - - respond( - strId: request["id"]?.string(), - numId: request["id"]?.integer(), - result: result, - ); - } -} - -test "documentSymbol" { - LspState state = LspState{ - sources = {}, - ast = {}, - }; - - {str: Boxed} request = jsonDecode( - `\{"jsonrpc":"2.0","id":163,"method":"textDocument/definition","params":\{"textDocument":\{"uri":"file:///Users/giann/git/buzz/src/lib/serialize.buzz"},"position":\{"line":300,"character":22}}}` - ).mapValue(); - - print(jsonEncode(handlers["textDocument/definition"]!(state, request: request))); -} \ No newline at end of file From 2c401b4cc64d58d956124fb5acedf26a3b70cdd9 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Wed, 15 May 2024 17:00:50 +0200 Subject: [PATCH 4/5] chore: Updated CHANGELOG --- CHANGELOG.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09bae0b7..5e6c37dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased ## Added +- Tail call optimization (https://github.com/buzz-language/buzz/issues/9) - REPL (https://github.com/buzz-language/buzz/issues/17) available by running buzz without any argument - WASM build (https://github.com/buzz-language/buzz/issues/142) and [web REPL](https://buzz-lang.dev/repl.html) - Function argument names and object property names can be omitted if the provided value is a named variable with the same name (https://github.com/buzz-language/buzz/issues/204) @@ -36,7 +37,7 @@ var value = from { out result; } ``` -- `recursive_call_limit` build option limit recursive calls (default to 200) +- `recursive_call_limit` build option limit recursive calls - Compiler will warn about code after a `return` statement - Compiler will warn about unreferenced imports (https://github.com/buzz-language/buzz/issues/272) - `namespace` (https://github.com/buzz-language/buzz/issues/271): if a script exports at least one symbol, it has to define a namespace for the script with `namespace mynamespace` @@ -55,11 +56,16 @@ var value = from { ## Changed - Map type notation has changed from `{K, V}` to `{K: V}`. Similarly map expression with specified typed went from `{, ...}` to `{, ...}` (https://github.com/buzz-language/buzz/issues/253) - `File.readLine`, `File.readAll`, `Socket.readLine`, `Socket.readAll` have now an optional `maxSize` argument -- Tail call optimization (https://github.com/buzz-language/buzz/issues/9). The effect should be limited for recursive calls since the JIT compiler should kick in pretty quickly in those use cases. - Empty list and map without a specified type resolve to `[any]`/`{any: any}` unless the variable declaration context provides the type (https://github.com/buzz-language/buzz/issues/86) - Function yield type is now prefixed with `*>`: `fun willYield() > T > Y?` becomes `fun willYield() > T *> Y?` (https://github.com/buzz-language/buzz/issues/257) +- Temporarily disabled `--tree` and `--fmt`. The AST has been completely reworked and those feature will take some work to come back. +- `math.random` removed in favor of `std.random` ## Fixed +- A bunch of crash after reported error. buzz tries to hit a maximum of syntax/compile errors by continuing after an error has been reported. This can lead to unexpected state and crash. +- Trying to resolve a global when only its prefix was provided would result in infinite recursion +- Forbid use of `yield`/`resume`/`resolve` in the global scope +- Would break on unfinished char literal # 0.3.0 (10-14-2023) ## Added From 0b996a7c352e993f6d9d756970cb528ca8ea33c6 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Thu, 16 May 2024 08:14:16 +0200 Subject: [PATCH 5/5] chore: zig 0.13.0-dev.211+6a65561e3 --- README.md | 2 +- build.zig | 135 ++++++++++++++----------------------------------- src/Parser.zig | 31 +++++------- src/Token.zig | 1 + 4 files changed, 55 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index 17a67be2..ba620ac3 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ A small/lightweight statically typed scripting language written in Zig ## How to build and install -_Latest zig version supported: 0.13.0-dev.73+db890dbae_ +_Latest zig version supported: 0.13.0-dev.211+6a65561e3_ ### Requirements - Since this is built with Zig, you should be able to build buzz on a wide variety of architectures even though this has only been tested on x86/M1. diff --git a/build.zig b/build.zig index 3d737073..972204bd 100644 --- a/build.zig +++ b/build.zig @@ -98,7 +98,7 @@ fn getBuzzPrefix(b: *Build) ![]const u8 { pub fn build(b: *Build) !void { // Check minimum zig version const current_zig = builtin.zig_version; - const min_zig = std.SemanticVersion.parse("0.13.0-dev.73+db890dbae") catch return; + const min_zig = std.SemanticVersion.parse("0.13.0-dev.211+6a65561e3") catch return; if (current_zig.order(min_zig).compare(.lt)) { @panic(b.fmt("Your Zig version v{} does not meet the minimum build requirement of v{}", .{ current_zig, min_zig })); } @@ -273,15 +273,11 @@ pub fn build(b: *Build) !void { } includes.appendSlice(&[_][]const u8{ - "/usr/local/include", - "/usr/include", "./vendors/mir", "./vendors/mimalloc/include", }) catch unreachable; llibs.appendSlice(&[_][]const u8{ - "/usr/local/lib", - "/usr/lib", "./vendors/mir", }) catch unreachable; @@ -298,33 +294,9 @@ pub fn build(b: *Build) !void { else null; - // If macOS, add homebrew paths - if (builtin.os.tag == .macos) { - const result = std.ChildProcess.run( - .{ - .allocator = b.allocator, - .argv = &[_][]const u8{ "brew", "--prefix" }, - }, - ) catch null; - - const prefix = if (result) |r| - std.mem.trim(u8, r.stdout, "\n") - else - std.posix.getenv("HOMEBREW_PREFIX") orelse "/opt/homebrew"; - - var include = std.ArrayList(u8).init(b.allocator); - include.writer().print("{s}{s}include", .{ prefix, std.fs.path.sep_str }) catch unreachable; - - var lib = std.ArrayList(u8).init(b.allocator); - lib.writer().print("{s}{s}lib", .{ prefix, std.fs.path.sep_str }) catch unreachable; - - includes.append(include.items) catch unreachable; - llibs.append(lib.items) catch unreachable; - } - var exe = b.addExecutable(.{ .name = "buzz", - .root_source_file = .{ .path = "src/main.zig" }, + .root_source_file = b.path("src/main.zig"), .target = target, .optimize = build_mode, }); @@ -349,10 +321,10 @@ pub fn build(b: *Build) !void { b.step("run", "run buzz").dependOn(&run_exe.step); for (includes.items) |include| { - exe.addIncludePath(.{ .path = include }); + exe.addIncludePath(b.path(include)); } for (llibs.items) |lib| { - exe.addLibraryPath(.{ .path = lib }); + exe.addLibraryPath(b.path(lib)); } for (sys_libs.items) |slib| { // FIXME: if mir is linked as static library (libmir.a), here also need to link libc @@ -367,20 +339,22 @@ pub fn build(b: *Build) !void { if (!is_wasm) { // Building buzz api library - var lib = b.addSharedLibrary(.{ - .name = "buzz", - .root_source_file = .{ .path = "src/buzz_api.zig" }, - .target = target, - .optimize = build_mode, - }); + var lib = b.addSharedLibrary( + .{ + .name = "buzz", + .root_source_file = b.path("src/buzz_api.zig"), + .target = target, + .optimize = build_mode, + }, + ); b.installArtifact(lib); for (includes.items) |include| { - lib.addIncludePath(.{ .path = include }); + lib.addIncludePath(b.path(include)); } for (llibs.items) |llib| { - lib.addLibraryPath(.{ .path = llib }); + lib.addLibraryPath(b.path(llib)); } for (sys_libs.items) |slib| { lib.linkSystemLibrary(slib); @@ -403,6 +377,7 @@ pub fn build(b: *Build) !void { lib.linkSystemLibrary("bcrypt"); } } + // So that JIT compiled function can reference buzz_api exe.linkLibrary(lib); if (lib_linenoise) |ln| { @@ -440,8 +415,16 @@ pub fn build(b: *Build) !void { for (libraries) |library| { // Copy buzz definitions const step = b.addInstallLibFile( - .{ .path = b.fmt("src/lib/{s}.buzz", .{library.name}) }, - b.fmt("buzz/{s}.buzz", .{library.name}), + b.path( + b.fmt( + "src/lib/{s}.buzz", + .{library.name}, + ), + ), + b.fmt( + "buzz/{s}.buzz", + .{library.name}, + ), ); install_step.dependOn(&step.step); @@ -451,7 +434,7 @@ pub fn build(b: *Build) !void { var std_lib = b.addSharedLibrary(.{ .name = library.name, - .root_source_file = .{ .path = library.path.? }, + .root_source_file = b.path(library.path.?), .target = target, .optimize = build_mode, }); @@ -462,10 +445,10 @@ pub fn build(b: *Build) !void { // No need to link anything when building for wasm since everything is static for (includes.items) |include| { - std_lib.addIncludePath(.{ .path = include }); + std_lib.addIncludePath(b.path(include)); } for (llibs.items) |llib| { - std_lib.addLibraryPath(.{ .path = llib }); + std_lib.addLibraryPath(b.path(llib)); } for (sys_libs.items) |slib| { std_lib.linkSystemLibrary(slib); @@ -487,17 +470,6 @@ pub fn build(b: *Build) !void { std_lib.linkLibrary(lib); std_lib.root_module.addImport("build_options", build_option_module); - // Adds `$BUZZ_PATH/lib` and `/usr/local/lib/buzz` as search path for other shared lib referenced by this one (libbuzz.dylib most of the time) - std_lib.addRPath( - .{ - .path = b.fmt( - "{s}" ++ std.fs.path.sep_str ++ "lib/buzz", - .{try getBuzzPrefix(b)}, - ), - }, - ); - std_lib.addRPath(.{ .path = "/usr/local/lib/buzz" }); - b.default_step.dependOn(&std_lib.step); library_steps.append(std_lib) catch unreachable; @@ -505,15 +477,15 @@ pub fn build(b: *Build) !void { } const tests = b.addTest(.{ - .root_source_file = .{ .path = "src/main.zig" }, + .root_source_file = b.path("src/main.zig"), .target = target, .optimize = build_mode, }); for (includes.items) |include| { - tests.addIncludePath(.{ .path = include }); + tests.addIncludePath(b.path(include)); } for (llibs.items) |llib| { - tests.addLibraryPath(.{ .path = llib }); + tests.addLibraryPath(b.path(llib)); } for (sys_libs.items) |slib| { tests.linkSystemLibrary(slib); @@ -534,7 +506,7 @@ pub fn build(b: *Build) !void { const test_step = b.step("test", "Run all the tests"); const run_tests = b.addRunArtifact(tests); - run_tests.cwd = Build.LazyPath{ .path = "." }; + run_tests.cwd = b.path("."); run_tests.setEnvironmentVariable("BUZZ_PATH", try getBuzzPrefix(b)); run_tests.step.dependOn(install_step); // wait for libraries to be installed test_step.dependOn(&run_tests.step); @@ -547,15 +519,15 @@ pub fn build(b: *Build) !void { pub fn buildPcre2(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) !*Build.Step.Compile { const copyFiles = b.addWriteFiles(); copyFiles.addCopyFileToSource( - .{ .path = "vendors/pcre2/src/config.h.generic" }, + b.path("vendors/pcre2/src/config.h.generic"), "vendors/pcre2/src/config.h", ); copyFiles.addCopyFileToSource( - .{ .path = "vendors/pcre2/src/pcre2.h.generic" }, + b.path("vendors/pcre2/src/pcre2.h.generic"), "vendors/pcre2/src/pcre2.h", ); copyFiles.addCopyFileToSource( - .{ .path = "vendors/pcre2/src/pcre2_chartables.c.dist" }, + b.path("vendors/pcre2/src/pcre2_chartables.c.dist"), "vendors/pcre2/src/pcre2_chartables.c", ); @@ -564,7 +536,7 @@ pub fn buildPcre2(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin .target = target, .optimize = optimize, }); - lib.addIncludePath(.{ .path = "src" }); + lib.addIncludePath(b.path("src")); lib.addCSourceFiles( .{ .files = &.{ @@ -619,38 +591,9 @@ pub fn buildMimalloc(b: *Build, target: Build.ResolvedTarget, optimize: std.buil }, ); - lib.addIncludePath(.{ .path = "./vendors/mimalloc/include" }); + lib.addIncludePath(b.path("./vendors/mimalloc/include")); lib.linkLibC(); - if (lib.root_module.resolved_target.?.result.os.tag == .macos) { - var macOS_sdk_path = std.ArrayList(u8).init(b.allocator); - try macOS_sdk_path.writer().print( - "{s}/usr/include", - .{ - (std.ChildProcess.run(.{ - .allocator = b.allocator, - .argv = &.{ - "xcrun", - "--show-sdk-path", - }, - .cwd = b.pathFromRoot("."), - .expand_arg0 = .expand, - }) catch { - std.debug.print("Warning: failed to get MacOSX sdk path", .{}); - unreachable; - }).stdout, - }, - ); - - lib.addSystemIncludePath(.{ .path = macOS_sdk_path.items }); - // Github macos-12 runner (https://github.com/actions/runner-images/blob/main/images/macos/macos-12-Readme.md). - lib.addSystemIncludePath(.{ .path = "/Applications/Xcode_14.0.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include" }); - lib.addSystemIncludePath(.{ .path = "/Library/Developer/CommandLineTools/SDKs/MacOSX14.0.sdk/usr/include" }); - lib.addSystemIncludePath(.{ .path = "/Library/Developer/CommandLineTools/SDKs/MacOSX13.3.sdk/usr/include" }); - lib.addSystemIncludePath(.{ .path = "/Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk/usr/include" }); - lib.addSystemIncludePath(.{ .path = "/Library/Developer/CommandLineTools/SDKs/MacOSX12.1.sdk/usr/include" }); - } - lib.addCSourceFiles( .{ .files = &.{ @@ -690,7 +633,7 @@ pub fn buildLinenoise(b: *Build, target: Build.ResolvedTarget, optimize: std.bui .optimize = optimize, }); - lib.addIncludePath(.{ .path = "vendors/linenoise" }); + lib.addIncludePath(b.path("vendors/linenoise")); lib.addCSourceFiles( .{ .files = &.{ @@ -738,7 +681,7 @@ pub fn buildWasmReplDemo(b: *Build, exe: *Build.Step.Compile) void { b.getInstallStep().dependOn(&esbuild.step); const copyRepl = b.addInstallBinFile( - .{ .path = "src/repl.html" }, + b.path("src/repl.html"), "repl.html", ); b.getInstallStep().dependOn(©Repl.step); diff --git a/src/Parser.zig b/src/Parser.zig index 9bbc864d..8fef7e92 100644 --- a/src/Parser.zig +++ b/src/Parser.zig @@ -5017,18 +5017,17 @@ fn function( const generic_identifier_token = self.current_token.? - 1; const generic_identifier = try self.gc.copyString(self.ast.tokens.items(.lexeme)[generic_identifier_token]); if ((self.current.?.generics == null or self.current.?.generics.?.get(generic_identifier) == null) and (self.current_object == null or self.current_object.?.generics == null or self.current_object.?.generics.?.get(generic_identifier) == null)) { - const generic = obj.ObjTypeDef.GenericDef{ - .origin = self.ast.nodes.items(.type_def)[function_node].?.resolved_type.?.Function.id, - .index = i, - }; - const resolved_type = obj.ObjTypeDef.TypeUnion{ .Generic = generic }; - try function_typedef.resolved_type.?.Function.generic_types.put( generic_identifier, try self.gc.type_registry.getTypeDef( obj.ObjTypeDef{ .def_type = .Generic, - .resolved_type = resolved_type, + .resolved_type = .{ + .Generic = .{ + .origin = self.ast.nodes.items(.type_def)[function_node].?.resolved_type.?.Function.id, + .index = i, + }, + }, }, ), ); @@ -6454,19 +6453,17 @@ fn protocolDeclaration(self: *Self) Error!Ast.Node.Index { const placeholder_index = try self.declarePlaceholder(protocol_name, null); const protocol_placeholder = self.globals.items[placeholder_index].type_def; - const protocol_def = obj.ObjObject.ProtocolDef.init( - self.gc.allocator, - self.ast.tokens.get(protocol_name), - try self.gc.copyString(self.ast.tokens.items(.lexeme)[protocol_name]), - try self.gc.copyString(qualified_protocol_name.items), - ); - - const resolved_type = obj.ObjTypeDef.TypeUnion{ .Protocol = protocol_def }; - // Create type var protocol_type: obj.ObjTypeDef = .{ .def_type = .Protocol, - .resolved_type = resolved_type, + .resolved_type = .{ + .Protocol = obj.ObjObject.ProtocolDef.init( + self.gc.allocator, + self.ast.tokens.get(protocol_name), + try self.gc.copyString(self.ast.tokens.items(.lexeme)[protocol_name]), + try self.gc.copyString(qualified_protocol_name.items), + ), + }, }; try self.beginScope(null); diff --git a/src/Token.zig b/src/Token.zig index 4a0fb587..8014e669 100644 --- a/src/Token.zig +++ b/src/Token.zig @@ -180,6 +180,7 @@ pub const Type = enum { Range, // range }; +// FIXME if case had the same name as the actual token we could simply use @tagName pub const keywords = std.StaticStringMap(Type).initComptime( .{ .{ "and", .And },