Added named blocks

This commit is contained in:
Mattia Giambirtone 2022-12-05 08:07:37 +01:00
parent 15ec8dce54
commit 4b6d86ad8e
5 changed files with 151 additions and 12 deletions

View File

@ -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:

View File

@ -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 =

View File

@ -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

View File

@ -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 [">", "<", "=", "~", "/", "+", "-", "_", "*", "?", "@", ":", "==", "!=",
">=", "<=", "+=", "-=", "/=", "*=", "**=", "!", "%", "&", "|", "^",
">>", "<<"]:

25
tests/blocks.pn Normal file
View File

@ -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);