diff --git a/src/frontend/compiler.nim b/src/frontend/compiler.nim index 013d1be..53ed8e9 100644 --- a/src/frontend/compiler.nim +++ b/src/frontend/compiler.nim @@ -167,6 +167,15 @@ type # patch. Used for break statements breakJumps: seq[int] + NamedBlock = ref object + ## A "named block object", similar + ## to a loop object. Used to emit + ## appropriate jump offsets + start: int + depth: int + breakJumps: seq[int] + name: string + Compiler* = ref object ## A wrapper around the Peon compiler's state @@ -191,6 +200,8 @@ type # The current loop being compiled (used to # keep track of where to jump) currentLoop: Loop + # Stack of named blocks + namedBlocks: seq[NamedBlock] # Are we in REPL mode? If so, Pop instructions # for expression statements at the top level are # swapped for a special instruction that prints @@ -1616,6 +1627,9 @@ proc patchBreaks(self: Compiler) = ## the loop is fully compiled for brk in self.currentLoop.breakJumps: self.patchJump(brk) + for blk in self.namedBlocks: + for brk in blk.breakJumps: + self.patchJump(brk) proc handleMagicPragma(self: Compiler, pragma: Pragma, name: Name) = @@ -2412,19 +2426,45 @@ proc returnStmt(self: Compiler, node: ReturnStmt) = proc continueStmt(self: Compiler, node: ContinueStmt) = ## Compiles continue statements. A continue statement ## jumps to the next iteration in a loop - if self.currentLoop.start > 16777215: - self.error("too much code to jump over in continue statement") - self.emitByte(Jump, node.token.line) - self.emitBytes(self.currentLoop.start.toTriple(), node.token.line) + if node.label.isNil(): + if self.currentLoop.start > 16777215: + self.error("too much code to jump over in continue statement") + self.emitByte(Jump, node.token.line) + self.emitBytes(self.currentLoop.start.toTriple(), node.token.line) + else: + var blocks: seq[NamedBlock] = @[] + var found: bool = false + for blk in reversed(self.namedBlocks): + blocks.add(blk) + if blk.name == node.label.token.lexeme: + found = true + break + if not found: + self.error(&"unknown block name '{node.label.token.lexeme}'", node.label) + self.emitByte(Jump, node.token.line) + self.emitBytes(blocks[^1].start.toTriple(), node.token.line) proc breakStmt(self: Compiler, node: BreakStmt) = ## Compiles break statements. A break statement ## jumps to the end of the loop - self.currentLoop.breakJumps.add(self.emitJump(OpCode.JumpForwards, node.token.line)) - if self.currentLoop.depth > self.depth: - # Breaking out of a loop closes its scope - self.endScope() + if node.label.isNil(): + self.currentLoop.breakJumps.add(self.emitJump(OpCode.JumpForwards, node.token.line)) + if self.currentLoop.depth > self.depth: + # Breaking out of a loop closes its scope + self.endScope() + else: + var blocks: seq[NamedBlock] = @[] + var found: bool = false + for blk in reversed(self.namedBlocks): + blocks.add(blk) + if blk.name == node.label.token.lexeme: + for blk in blocks: + blk.breakJumps.add(self.emitJump(OpCode.JumpForwards, node.token.line)) + found = true + break + if not found: + self.error(&"unknown block name '{node.label.token.lexeme}'", node.label) proc importStmt(self: Compiler, node: ImportStmt) = @@ -2465,6 +2505,23 @@ proc exportStmt(self: Compiler, node: ExportStmt) = discard +proc namedBlock(self: Compiler, node: NamedBlockStmt) = + ## Compiles named blocks + self.beginScope() + var last: Declaration + for decl in node.code: + if not last.isNil(): + case last.kind: + of NodeKind.breakStmt, NodeKind.continueStmt: + self.warning(UnreachableCode, &"code after '{last.token.lexeme}' statement is unreachable", nil, last) + else: + discard + self.declaration(decl) + last = decl + self.patchBreaks() + self.endScope() + + proc statement(self: Compiler, node: Statement) = ## Compiles all statements case node.kind: @@ -2480,6 +2537,14 @@ proc statement(self: Compiler, node: Statement) = self.printRepl(kind, expression) else: self.emitByte(Pop, node.token.line) + of NodeKind.namedBlockStmt: + self.namedBlocks.add(NamedBlock(start: self.chunk.code.len(), + depth: self.depth, + breakJumps: @[], + name: NamedBlockStmt(node).name.token.lexeme)) + self.namedBlock(NamedBlockStmt(node)) + #self.patchBreaks() + discard self.namedBlocks.pop() of NodeKind.ifStmt: self.ifStmt(IfStmt(node)) of NodeKind.assertStmt: diff --git a/src/frontend/meta/ast.nim b/src/frontend/meta/ast.nim index 3855ee6..461f8da 100644 --- a/src/frontend/meta/ast.nim +++ b/src/frontend/meta/ast.nim @@ -41,6 +41,7 @@ type whileStmt, forEachStmt, blockStmt, + namedBlockStmt, raiseStmt, assertStmt, tryStmt, @@ -205,6 +206,9 @@ type BlockStmt* = ref object of Statement code*: seq[Declaration] + NamedBlockStmt* = ref object of BlockStmt + name*: IdentExpr + ForStmt* = ref object of Statement discard # Unused @@ -230,8 +234,10 @@ type expression*: Expression BreakStmt* = ref object of Statement + label*: IdentExpr ContinueStmt* = ref object of Statement + label*: IdentExpr ReturnStmt* = ref object of Statement value*: Expression @@ -543,6 +549,13 @@ proc newBlockStmt*(code: seq[Declaration], token: Token): BlockStmt = result.token = token +proc newNamedBlockStmt*(code: seq[Declaration], name: IdentExpr, token: Token): NamedBlockStmt = + result = NamedBlockStmt(kind: namedBlockStmt) + result.code = code + result.token = token + result.name = name + + proc newWhileStmt*(condition: Expression, body: Statement, token: Token): WhileStmt = result = WhileStmt(kind: whileStmt) @@ -560,14 +573,16 @@ proc newForEachStmt*(identifier: IdentExpr, expression: Expression, result.token = token -proc newBreakStmt*(token: Token): BreakStmt = +proc newBreakStmt*(token: Token, label: IdentExpr = nil): BreakStmt = result = BreakStmt(kind: breakStmt) result.token = token + result.label = label -proc newContinueStmt*(token: Token): ContinueStmt = +proc newContinueStmt*(token: Token, label: IdentExpr = nil): ContinueStmt = result = ContinueStmt(kind: continueStmt) result.token = token + result.label = label proc newReturnStmt*(value: Expression, token: Token): ReturnStmt = diff --git a/src/frontend/parser.nim b/src/frontend/parser.nim index 253d9f8..f08f38e 100644 --- a/src/frontend/parser.nim +++ b/src/frontend/parser.nim @@ -620,13 +620,39 @@ proc blockStmt(self: Parser): Statement = self.endScope() +proc namedBlockStmt(self: Parser): Statement = + ## Parses named block statement + self.beginScope() + let tok = self.peek(-1) + var code: seq[Declaration] = @[] + self.expect(Identifier, "expecting block name after 'block'") + var name = newIdentExpr(self.peek(-1), self.scopeDepth) + name.file = self.file + let enclosingLoop = self.currentLoop + self.currentLoop = Loop + self.expect(LeftBrace, "expecting '{' after 'block'") + while not self.check(RightBrace) and not self.done(): + code.add(self.declaration()) + if code[^1].isNil(): + code.delete(code.high()) + self.expect(RightBrace, "expecting '}'") + result = newNamedBlockStmt(code, name, tok) + result.file = self.file + self.endScope() + self.currentLoop = enclosingLoop + + proc breakStmt(self: Parser): Statement = ## Parses break statements let tok = self.peek(-1) + var label: IdentExpr if self.currentLoop != Loop: self.error("'break' cannot be used outside loops") + if self.match(Identifier): + label = newIdentExpr(self.peek(-1), self.scopeDepth) + label.file = self.file endOfLine("missing semicolon after 'break'") - result = newBreakStmt(tok) + result = newBreakStmt(tok, label) result.file = self.file @@ -643,10 +669,14 @@ proc deferStmt(self: Parser): Statement = proc continueStmt(self: Parser): Statement = ## Parses continue statements let tok = self.peek(-1) + var label: IdentExpr if self.currentLoop != Loop: self.error("'continue' cannot be used outside loops") + if self.match(Identifier): + label = newIdentExpr(self.peek(-1), self.scopeDepth) + label.file = self.file endOfLine("missing semicolon after 'continue'") - result = newContinueStmt(tok) + result = newContinueStmt(tok, label) result.file = self.file @@ -1225,6 +1255,9 @@ proc statement(self: Parser): Statement = of Try: discard self.step() result = self.tryStmt() + of Block: + discard self.step() + result = self.namedBlockStmt() else: result = self.expressionStatement() result.file = self.file diff --git a/src/util/symbols.nim b/src/util/symbols.nim index 8a50a52..69b6d3b 100644 --- a/src/util/symbols.nim +++ b/src/util/symbols.nim @@ -55,6 +55,7 @@ proc fillSymbolTable*(tokenizer: Lexer) = tokenizer.symbols.addKeyword("false", False) tokenizer.symbols.addKeyword("ref", TokenType.Ref) tokenizer.symbols.addKeyword("ptr", TokenType.Ptr) + tokenizer.symbols.addKeyword("block", TokenType.Block) for sym in [">", "<", "=", "~", "/", "+", "-", "_", "*", "?", "@", ":", "==", "!=", ">=", "<=", "+=", "-=", "/=", "*=", "**=", "!", "%", "&", "|", "^", ">>", "<<"]: diff --git a/tests/blocks.pn b/tests/blocks.pn new file mode 100644 index 0000000..e621348 --- /dev/null +++ b/tests/blocks.pn @@ -0,0 +1,25 @@ +import std; + + +block outer { + var x = 1; + print(x == 1); + block inner { + var x = 2; + print(x == 2); + break outer; + print("nope"); + } +} +var x = 3; +print(x == 3); + + +var count = 0; +block loop { + if count < 5 { + count = count + 1; + continue loop; + } +} +print(count == 5); \ No newline at end of file