Skip to content

Commit 85e67ef

Browse files
authored
Continuation statements to omit braces (#1089)
This allows us to use multiple statements in a single statement position without braces. Current rules: - no definitions allowed, when no braces are used(`def`) -- could potentially be relaxed - no sequencing of expressions (`expr; expr`) outside of braces It should be fully backwards compatible for code that parsed before. It accepts more programs and also changes the error message for one failing test case. Here is how it looks like (also see the added test) ``` def main() = with filesystem; val port = readInt(); with network(port); println("Running!") ```
1 parent 897e4b5 commit 85e67ef

File tree

8 files changed

+114
-80
lines changed

8 files changed

+114
-80
lines changed

effekt/jvm/src/test/scala/effekt/ParserTests.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ class ParserTests extends munit.FunSuite {
9494
parse(input, _.stmt())
9595

9696
def parseStmts(input: String, positions: Positions = new Positions())(using munit.Location): Stmt =
97-
parse(input, _.stmts())
97+
parse(input, _.stmts(inBraces = true))
9898

9999
def parseMatchPattern(input: String, positions: Positions = new Positions())(using munit.Location): MatchPattern =
100100
parse(input, _.matchPattern())

effekt/shared/src/main/scala/effekt/Parser.scala

Lines changed: 40 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -233,46 +233,55 @@ class Parser(positions: Positions, tokens: Seq[Token], source: Source) {
233233
/**
234234
* Statements
235235
*/
236-
def stmts(): Stmt =
236+
def stmts(inBraces: Boolean = false): Stmt =
237237
nonterminal:
238238
(peek.kind match {
239-
case `val` => valStmt()
240-
case _ if isDefinition => DefStmt(definition(), semi() ~> stmts(), span())
241-
case `with` => withStmt()
242-
case `var` => DefStmt(varDef(), semi() ~> stmts(), span())
239+
case `{` => BlockStmt(braces { stmts(inBraces = true) }, span())
240+
case `val` => valStmt(inBraces)
241+
case _ if isDefinition && inBraces => DefStmt(definition(), semi() ~> stmts(inBraces), span())
242+
case _ if isDefinition => fail("Definitions are only allowed, when enclosed in braces.")
243+
case `with` => withStmt(inBraces)
244+
case `var` => DefStmt(varDef(), semi() ~> stmts(inBraces), span())
243245
case `return` =>
244-
val result = `return` ~> Return(expr() <~ maybeSemi(), span())
245-
result
246-
case `}` | `}$` => // Unexpected end of <STMTS> =>
246+
// trailing semicolon only allowed when in braces
247+
`return` ~> Return(expr() <~ (if inBraces then maybeSemi()), span())
248+
case `}` | `}$` if inBraces => // Unexpected end of <STMTS> =>
247249
// insert a synthetic `return ()` into the block
248250
Return(UnitLit(span().emptyAfter.synthesized), span().emptyAfter.synthesized)
251+
// for now we do not allow multiple expressions in single-statement mode.
252+
// That is, we rule out
253+
// def foo() =
254+
// expr;
255+
// expr
256+
case _ if !inBraces =>
257+
Return(expr(), span())
249258
case _ =>
250259
val e = expr()
251260
semi()
252261
if returnPosition then Return(e, span())
253-
else ExprStmt(e, stmts(), span())
262+
else ExprStmt(e, stmts(inBraces), span())
254263
}) labelled "statements"
255264

256265
// ATTENTION: here the grammar changed (we added `with val` to disambiguate)
257266
// with val <ID> (: <TYPE>)? = <EXPR>; <STMTS>
258267
// with val (<ID> (: <TYPE>)?...) = <EXPR>
259268
// with <EXPR>; <STMTS>
260-
def withStmt(): Stmt = `with` ~> peek.kind match {
269+
def withStmt(inBraces: Boolean): Stmt = `with` ~> peek.kind match {
261270
case `val` =>
262271
val params = (`val` ~> peek.kind match {
263272
case `(` => valueParamsOpt()
264273
case _ => List(valueParamOpt()) // TODO copy position
265274
})
266-
desugarWith(params, Nil, `=` ~> expr(), semi() ~> stmts(), span())
275+
desugarWith(params, Nil, `=` ~> expr(), semi() ~> stmts(inBraces), span())
267276

268277
case `def` =>
269278
val params = (`def` ~> peek.kind match {
270279
case `{` => blockParamsOpt()
271280
case _ => List(blockParamOpt()) // TODO copy position
272281
})
273-
desugarWith(Nil, params, `=` ~> expr(), semi() ~> stmts(), span())
282+
desugarWith(Nil, params, `=` ~> expr(), semi() ~> stmts(inBraces), span())
274283

275-
case _ => desugarWith(Nil, Nil, expr(), semi() ~> stmts(), span())
284+
case _ => desugarWith(Nil, Nil, expr(), semi() ~> stmts(inBraces), span())
276285
}
277286

278287
def desugarWith(vparams: List[ValueParam], bparams: List[BlockParam], call: Term, body: Stmt, withSpan: Span): Stmt = call match {
@@ -320,11 +329,10 @@ class Parser(positions: Positions, tokens: Seq[Token], source: Source) {
320329
def stmt(): Stmt =
321330
nonterminal:
322331
{
323-
if peek(`{`) then BlockStmt(braces { stmts() }, span())
332+
if peek(`{`) then BlockStmt(braces { stmts(inBraces = true) }, span())
324333
else when(`return`) { Return(expr(), span()) } { Return(expr(), span()) }
325334
} labelled "statement"
326335

327-
328336
/**
329337
* Main entry point for the repl.
330338
*/
@@ -467,7 +475,7 @@ class Parser(positions: Positions, tokens: Seq[Token], source: Source) {
467475
nonterminal:
468476
manyWhile(definition(), isDefinition)
469477

470-
def functionBody: Stmt = stmt() // TODO error context: "the body of a function definition"
478+
def functionBody: Stmt = stmts() // TODO error context: "the body of a function definition"
471479

472480
def valDef(): Def =
473481
nonterminal:
@@ -479,7 +487,7 @@ class Parser(positions: Positions, tokens: Seq[Token], source: Source) {
479487
* In statement position, val-definitions can also be destructing:
480488
* i.e. val (l, r) = point(); ...
481489
*/
482-
def valStmt(): Stmt =
490+
def valStmt(inBraces: Boolean): Stmt =
483491
nonterminal:
484492
val doc = maybeDocumentation()
485493
val startPos = pos()
@@ -493,21 +501,21 @@ class Parser(positions: Positions, tokens: Seq[Token], source: Source) {
493501
val binding = stmt()
494502
val endPos = pos()
495503
val valDef = ValDef(id, tpe, binding, doc, Span(source, startPos, endPos)).withRangeOf(startMarker, binding)
496-
DefStmt(valDef, { semi(); stmts() }, span())
504+
DefStmt(valDef, { semi(); stmts(inBraces) }, span())
497505
}
498506
def matchLhs() =
499507
maybeDocumentation() ~ (`val` ~> matchPattern()) ~ manyWhile(`and` ~> matchGuard(), `and`) <~ `=` match {
500508
case doc ~ AnyPattern(id, _) ~ Nil =>
501509
val binding = stmt()
502510
val endPos = pos()
503511
val valDef = ValDef(id, None, binding, doc, Span(source, startPos, endPos)).withRangeOf(startMarker, binding)
504-
DefStmt(valDef, { semi(); stmts() }, span())
512+
DefStmt(valDef, { semi(); stmts(inBraces) }, span())
505513
case doc ~ p ~ guards =>
506514
// matches do not support doc comments, so we ignore `doc`
507515
val sc = expr()
508516
val endPos = pos()
509517
val default = when(`else`) { Some(stmt()) } { None }
510-
val body = semi() ~> stmts()
518+
val body = semi() ~> stmts(inBraces)
511519
val clause = MatchClause(p, guards, body, Span(source, p.span.from, sc.span.to)).withRangeOf(p, sc)
512520
val matching = Match(List(sc), List(clause), default, Span(source, startPos, endPos, Synthesized)).withRangeOf(startMarker, sc)
513521
Return(matching, span().synthesized)
@@ -536,7 +544,7 @@ class Parser(positions: Positions, tokens: Seq[Token], source: Source) {
536544
else
537545
// [...](<PARAM>...) {...} `=` <STMT>>
538546
val (tps, vps, bps) = params()
539-
FunDef(id, tps, vps, bps, maybeReturnAnnotation(), `=` ~> stmt(), doc, span())
547+
FunDef(id, tps, vps, bps, maybeReturnAnnotation(), `=` ~> stmts(), doc, span())
540548

541549

542550
// right now: data type definitions (should be renamed to `data`) and type aliases
@@ -691,7 +699,7 @@ class Parser(positions: Positions, tokens: Seq[Token], source: Source) {
691699
nonterminal:
692700
peek.kind match {
693701
case _: Ident => (peek(1).kind match {
694-
case `{` => ExternBody.EffektExternBody(featureFlag(), `{` ~> stmts() <~ `}`, span())
702+
case `{` => ExternBody.EffektExternBody(featureFlag(), `{` ~> stmts(inBraces = true) <~ `}`, span())
695703
case _ => ExternBody.StringExternBody(maybeFeatureFlag(), template(), span())
696704
}) labelled "extern body (string or block)"
697705
case _ => ExternBody.StringExternBody(maybeFeatureFlag(), template(), span())
@@ -793,14 +801,14 @@ class Parser(positions: Positions, tokens: Seq[Token], source: Source) {
793801
def ifExpr(): Term =
794802
nonterminal:
795803
If(`if` ~> parens { matchGuards().unspan },
796-
stmt(),
797-
when(`else`) { stmt() } { Return(UnitLit(span().emptyAfter), span().emptyAfter) }, span())
804+
stmts(),
805+
when(`else`) { stmts() } { Return(UnitLit(span().emptyAfter), span().emptyAfter) }, span())
798806

799807
def whileExpr(): Term =
800808
nonterminal:
801809
While(`while` ~> parens { matchGuards().unspan },
802-
stmt(),
803-
when(`else`) { Some(stmt()) } { None },
810+
stmts(),
811+
when(`else`) { Some(stmts()) } { None },
804812
span())
805813

806814
def doExpr(): Term =
@@ -838,7 +846,7 @@ class Parser(positions: Positions, tokens: Seq[Token], source: Source) {
838846
// TODO deprecate
839847
def funExpr(): Term =
840848
nonterminal:
841-
val blockLiteral = `fun` ~> BlockLiteral(Nil, valueParams().unspan, Nil, braces { stmts() }, span())
849+
val blockLiteral = `fun` ~> BlockLiteral(Nil, valueParams().unspan, Nil, braces { stmts(inBraces = true) }, span())
842850
Box(Maybe.None(Span(source, pos(), pos(), Synthesized)), blockLiteral, blockLiteral.span.synthesized)
843851

844852
def unboxExpr(): Term =
@@ -920,9 +928,7 @@ class Parser(positions: Positions, tokens: Seq[Token], source: Source) {
920928
MatchClause(
921929
pattern,
922930
manyWhile(`and` ~> matchGuard(), `and`),
923-
// allow a statement enclosed in braces or without braces
924-
// both is allowed since match clauses are already delimited by `case`
925-
`=>` ~> (if (peek(`{`)) { stmt() } else { stmts() }),
931+
`=>` ~> stmts(inBraces = true),
926932
span()
927933
)
928934

@@ -1145,10 +1151,10 @@ class Parser(positions: Positions, tokens: Seq[Token], source: Source) {
11451151
case _ =>
11461152
// { (x: Int) => ... }
11471153
backtrack { lambdaParams() <~ `=>` } map {
1148-
case (tps, vps, bps) => BlockLiteral(tps, vps, bps, stmts(), Span.missing(source)) : BlockLiteral
1154+
case (tps, vps, bps) => BlockLiteral(tps, vps, bps, stmts(inBraces = true), Span.missing(source)) : BlockLiteral
11491155
} getOrElse {
11501156
// { <STMTS> }
1151-
BlockLiteral(Nil, Nil, Nil, stmts(), Span.missing(source)) : BlockLiteral
1157+
BlockLiteral(Nil, Nil, Nil, stmts(inBraces = true), Span.missing(source)) : BlockLiteral
11521158
}
11531159
}
11541160
}
@@ -1221,7 +1227,7 @@ class Parser(positions: Positions, tokens: Seq[Token], source: Source) {
12211227
peek.kind match {
12221228
case `<>` => `<>` ~> Hole(IdDef("hole", span().synthesized), Template(Nil, Nil), span())
12231229
case `<{` => {
1224-
val s = `<{` ~> stmts() <~ `}>`
1230+
val s = `<{` ~> stmts(inBraces = true) <~ `}>`
12251231
Hole(IdDef("hole", span().synthesized), Template(Nil, List(s)), span())
12261232
}
12271233
case _: HoleStr => {
@@ -1235,7 +1241,7 @@ class Parser(positions: Positions, tokens: Seq[Token], source: Source) {
12351241
def holeTemplate(): Template[Stmt] =
12361242
nonterminal:
12371243
val first = holeString()
1238-
val (s, strs) = manyWhile((`${` ~> stmts() <~ `}$`, holeString()), `${`).unzip
1244+
val (s, strs) = manyWhile((`${` ~> stmts(inBraces = true) <~ `}$`, holeString()), `${`).unzip
12391245
Template(first :: strs, s)
12401246

12411247
def holeString(): String =
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def foo() =
2+
def bar() = 42 // ERROR enclosed in braces
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
doing something
2+
doing something
3+
doing something
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
def compute(): Unit = println("doing something")
2+
3+
/// Prints "doing something" three times, since
4+
/// the second `compute()` is NOT part of `bar`.
5+
/// The language is NOT indentation senstive!
6+
def main() = {
7+
def bar() =
8+
compute(); // <--- this is part of bar!
9+
compute() // <--- this is part of main, not bar!
10+
11+
bar();
12+
bar()
13+
}

examples/pos/multistatement.check

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Open files
2+
Open port 8080
3+
Running!
4+
Close port
5+
Close files

examples/pos/multistatement.effekt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
def filesystem { p: => Unit }: Unit = {
2+
println("Open files")
3+
p()
4+
println("Close files")
5+
}
6+
def network(port: Int) { p: => Unit }: Unit = {
7+
println("Open port " ++ port.show)
8+
p()
9+
println("Close port")
10+
}
11+
def readInt(): Int = 8080
12+
13+
def main() =
14+
with filesystem;
15+
val port = readInt();
16+
with network(port);
17+
println("Running!")

0 commit comments

Comments
 (0)