diff --git a/examples/casestudies/lexer.md b/examples/casestudies/lexer.md index dd1bdf48e7..57e192822d 100644 --- a/examples/casestudies/lexer.md +++ b/examples/casestudies/lexer.md @@ -74,7 +74,8 @@ def example1() = { ## Handling the Lexer Effect with a given List A dummy lexer reading lexemes from a given list can be implemented as a handler for the `Lexer` effect. The definition uses the effect `LexerError` to signal the end of the input stream: ``` -effect LexerError(msg: String, pos: Position): Nothing +effect LexerError(msg: String, pos: Position): Unit +def absurd[A](unit: Unit): A = panic("should not happen") def dummyPosition() = Position(0, 0, 0) def lexerFromList[R](l: List[Token]) { program: => R / Lexer }: R / LexerError = { @@ -85,7 +86,7 @@ def lexerFromList[R](l: List[Token]) { program: => R / Lexer }: R / LexerError = case Cons(tok, _) => resume(Some(tok)) } def next() = in match { - case Nil() => do LexerError("Unexpected end of input", dummyPosition()) + case Nil() => do LexerError("Unexpected end of input", dummyPosition()).absurd case Cons(tok, _) => resume(tok) } } @@ -192,10 +193,10 @@ the input, or not. def peek() = resume(tryMatchAll(tokenDesriptors())) def next() = if (eos()) - do LexerError("Unexpected EOS", position()) + do LexerError("Unexpected EOS", position()).absurd else { val tok = tryMatchAll(tokenDesriptors()).getOrElse { - do LexerError("Cannot tokenize input", position()) + do LexerError("Cannot tokenize input", position()).absurd } consume(tok.text) resume(tok) diff --git a/examples/casestudies/parser.md b/examples/casestudies/parser.md index 5c065c7de4..1afe285f2a 100644 --- a/examples/casestudies/parser.md +++ b/examples/casestudies/parser.md @@ -25,7 +25,7 @@ Parsers can be expressed by using the lexer effect and process the token stream. ``` effect Nondet { def alt(): Boolean - def fail[A](msg: String): A + def fail(msg: String): Unit } effect Parser = { Nondet, Lexer } @@ -40,7 +40,7 @@ input stream and fails, if it does not match. def accept { p: Token => Boolean } : Token / Parser = { val got = do next(); if (p(got)) got - else do fail("Unexpected token " ++ show(got)) + else do fail("Unexpected token " ++ show(got)).absurd } ``` @@ -53,12 +53,12 @@ def number() = accept(Number()).text def punct(p: String) = { val tok = accept(Punct()) if (tok.text == p) () - else do fail("Expected " ++ p ++ " but got " ++ tok.text) + else do fail("Expected " ++ p ++ " but got " ++ tok.text).absurd } def kw(exp: String): Unit / Parser = { val got = ident(); if (got == exp) () - else do fail("Expected keyword " ++ exp ++ " but got " ++ got) + else do fail("Expected keyword " ++ exp ++ " but got " ++ got).absurd } ``` Using the effect for non-deterministic choice `alt`, we can model alternatives, optional matches and various repetitions: @@ -100,7 +100,7 @@ Let us start by defining the parser for numeric literals. def parseNum(): Tree / Parser = { val numText = number() val num = toInt(numText).getOrElse { - do fail("Expected number, but cannot convert input to integer: " ++ numText) + do fail("Expected number, but cannot convert input to integer: " ++ numText).absurd } Lit(num) } @@ -122,17 +122,8 @@ semantics of effects. Similarly, we can write parsers for let bindings, by sequentially composing our existing parsers: -``` -def parseLet(): Tree / Parser = { - kw("let"); - val name = ident(); - punct("="); - val binding = parseExpr(); - kw("in"); - val body = parseExpr(); - Let(name, binding, body) -} -``` + + Again, note how naturally the result can be composed from the individual results, much like manually writing a recursive descent parser. Compared to handcrafted parsers, the imperative parser combinators presented here offer a similar flexibility. At the same time, the semantics @@ -140,23 +131,35 @@ of `alt` and `fail` is still left open, offering flexibility in the implementati We proceed to implement the remaining parsers for our expression language: ``` -def parseGroup() = or { parseAtom() } { - punct("("); - val res = parseExpr(); - punct(")"); - res -} +def parseExpr(): Tree / Parser = { + + def parseLet(): Tree / {} = { + kw("let"); + val name = ident(); + punct("="); + val binding = parseExpr(); + kw("in"); + val body = parseExpr(); + Let(name, binding, body) + } -def parseApp(): Tree / Parser = { - val funName = ident(); - punct("("); - val arg = parseExpr(); - punct(")"); - App(funName, arg) -} + def parseGroup(): Tree / {} = or { parseAtom() } { + punct("("); + val res = parseExpr(); + punct(")"); + res + } + + def parseApp(): Tree / {} = { + val funName = ident(); + punct("("); + val arg = parseExpr(); + punct(")"); + App(funName, arg) + } -def parseExpr(): Tree / Parser = or { parseLet() } { or { parseApp() } { parseGroup() } } +} ``` ## Example: Combining Parsers and Local Mutable State @@ -219,8 +222,8 @@ def parse[R](input: String) { p: => R / Parser }: ParseResult[R] = try { case Failure(msg) => resume(false) case Success(res) => Success(res) } - def fail[A](msg) = Failure(msg) -} with LexerError[A] { (msg, pos) => + def fail(msg) = Failure(msg) +} with LexerError { (msg, pos) => Failure(msg) } ``` @@ -240,11 +243,11 @@ def println(p: ParseResult[Int]): Unit = println(showPR(p){ x => show(x) }) def println(p: ParseResult[Tree]): Unit = println(showPR(p){ x => show(x) }) def main() = { println(parse("42") { parseCalls() }) - println(parse("foo(1)") { parseCalls() }) - println(parse("foo(1, 2)") { parseCalls() }) - println(parse("foo(1, 2, 3, 4)") { parseCalls() }) - println(parse("foo(1, 2, bar(4, 5))") { parseCalls() }) - println(parse("foo(1, 2,\nbar(4, 5))") { parseCalls() }) +// println(parse("foo(1)") { parseCalls() }) +// println(parse("foo(1, 2)") { parseCalls() }) +// println(parse("foo(1, 2, 3, 4)") { parseCalls() }) +// println(parse("foo(1, 2, bar(4, 5))") { parseCalls() }) +// println(parse("foo(1, 2,\nbar(4, 5))") { parseCalls() }) // println(parse("}42") { parseExpr() }) // println(parse("42") { parseExpr() }) diff --git a/examples/casestudies/prettyprinter.md b/examples/casestudies/prettyprinter.md index 58f794f8bc..31db10eb14 100644 --- a/examples/casestudies/prettyprinter.md +++ b/examples/casestudies/prettyprinter.md @@ -19,7 +19,6 @@ Furthermore, the library presented here is neither linear ``` module examples/casestudies/prettyprinter -import examples/casestudies/parser // just needed for the example (Tree) import immutable/option import immutable/list import text/string diff --git a/libraries/ml/effekt.effekt b/libraries/ml/effekt.effekt index 087fb6f522..3e03956d42 100644 --- a/libraries/ml/effekt.effekt +++ b/libraries/ml/effekt.effekt @@ -189,7 +189,7 @@ record Tuple6[A, B, C, D, E, F](first: A, second: B, third: C, fourth: D, fifth: extern io def panic[R](msg: String): R = "raise Fail msg" -effect Exception[E] { +interface Exception[E] { def raise(exception: E, msg: String): Nothing } record RuntimeError() @@ -211,7 +211,7 @@ def ignoring[E] { prog: => Unit / Exception[E] }: Unit = // Control Flow // ============ -effect Control { +interface Control { def break(): Unit def continue(): Unit } diff --git a/libraries/ml/text/string.effekt b/libraries/ml/text/string.effekt index 0b20109656..fc8d22a339 100644 --- a/libraries/ml/text/string.effekt +++ b/libraries/ml/text/string.effekt @@ -10,8 +10,8 @@ import internal/option def charAt(str: String, index: Int): Option[String] = if (index < 0 || length(str) <= index) - Some(unsafeCharAt(str, index)) - else None() + None() + else Some(unsafeCharAt(str, index)) def takeWhile(str: String){ f: String => Boolean }: String = { def rec(i: Int, acc: String): String = { @@ -47,9 +47,11 @@ def substring(str: String, from: Int): String = str else unsafeSubstring(str, from) def substring(str: String, from: Int, endp: Int): String = - if (from < 0 || length(str) <= from || length(str) <= endp) + if (from < 0 || length(str) <= from) str - else unsafeSubstring(str, from, endp) + else if (length(str) <= endp) + unsafeSubstring(str, from) + else unsafeSubstring(str, from, endp-from) extern pure def unsafeSubstring(str: String, from: Int): String = "String.extract (str, from, NONE)"