diff --git a/effekt/shared/src/main/scala/effekt/lifted/Monomorphize.scala b/effekt/shared/src/main/scala/effekt/lifted/Monomorphize.scala index 5a48b7e53..06a90bb72 100644 --- a/effekt/shared/src/main/scala/effekt/lifted/Monomorphize.scala +++ b/effekt/shared/src/main/scala/effekt/lifted/Monomorphize.scala @@ -35,17 +35,17 @@ object Monomorphize extends Phase[CoreLifted, CoreLifted] { val binders = analysis.binders.collect { case (id: Id, ftpe: FlowType.Function) => s"${id.name}: ${ftpe.evidences.show}" }.toList.sorted.mkString("\n") - // println(s"""| - // |Constraints: - // |----------- - // |${constrs}) - // |""".stripMargin) - // - // println(s"""| - // | - // |Binders: - // |${binders} - // |""".stripMargin) + println(s"""| + |Constraints: + |----------- + |${constrs}) + |""".stripMargin) + + println(s"""| + | + |Binders: + |${binders} + |""".stripMargin) val (solved, cls) = solve(analysis.constraints) diff --git a/examples/casestudies/lexer.md b/examples/casestudies/lexer.md index f6ac31528..57e192822 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[A](msg: String, pos: Position): A +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) } } @@ -94,7 +95,7 @@ def lexerFromList[R](l: List[Token]) { program: => R / Lexer }: R / LexerError = We define a separate handler to report lexer errors to the console: ``` def report { prog: => Unit / LexerError }: Unit = - try { prog() } with LexerError[A] { (msg, pos) => + try { prog() } with LexerError { (msg, pos) => println(pos.line.show ++ ":" ++ pos.col.show ++ " " ++ msg) } ``` @@ -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 5c065c7de..1811acdc7 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 @@ -176,11 +179,7 @@ def parseCalls(): Int / Parser = { or { number(); 1 } { ident(); punct("("); - val count = parseCalls() + - sum(many { - punct(","); - parseCalls() - }); + val count = parseCalls() + sum(Nil()); punct(")"); count } @@ -213,14 +212,14 @@ The parsing algorithm is simply implemented as a handler for `Parser`. ``` def parse[R](input: String) { p: => R / Parser }: ParseResult[R] = try { - lexer(input) { skipWhitespace { Success(p()) } } + lexer(input) { Success(p()) } } with Nondet { def alt() = resume(true) match { 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) } ``` @@ -246,12 +245,12 @@ def main() = { 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() }) -// println(parse("let x = 4 in 42") { parseExpr() }) -// println(parse("let x = let y = 2 in 1 in 42") { parseExpr() }) -// println(parse("let x = (let y = 2 in 1) in 42") { parseExpr() }) -// println(parse("let x = (let y = f(42) in 1) in 42") { parseExpr() }) -// println(parse("let x = (let y = f(let z = 1 in z) in 1) in 42") { parseExpr() }) + println(parse("}42") { parseExpr() }) + println(parse("42") { parseExpr() }) + println(parse("let x = 4 in 42") { parseExpr() }) + println(parse("let x = let y = 2 in 1 in 42") { parseExpr() }) + println(parse("let x = (let y = 2 in 1) in 42") { parseExpr() }) + println(parse("let x = (let y = f(42) in 1) in 42") { parseExpr() }) + println(parse("let x = (let y = f(let z = 1 in z) in 1) in 42") { parseExpr() }) } ``` \ No newline at end of file diff --git a/examples/casestudies/prettyprinter.md b/examples/casestudies/prettyprinter.md index 58f794f8b..a8c67e959 100644 --- a/examples/casestudies/prettyprinter.md +++ b/examples/casestudies/prettyprinter.md @@ -68,13 +68,21 @@ type Direction { Horizontal(); Vertical() } effect Indent(): Int effect DefaultIndent(): Int effect Flow(): Direction +interface Indentation { + def setIndentation(n: Int): Unit + def getIndentation(): Int +} +interface GlobalDirection { + def setDirection(d: Direction): Unit + def getDirection(): Direction +} ``` The effect `Flow` represents the current layouting direction. Also the indentation of the document depends on the context and is therefore modeled as an effect. Computing the layout of a document to be pretty printed uses the above three effects: ``` -effect Layout = { Indent, DefaultIndent, Flow } +effect Layout = { Indent, DefaultIndent, Flow, Indentation, GlobalDirection } ``` ## Output: A Stream of Layout Elements @@ -129,9 +137,13 @@ Indentation can be configured by locally handling `Layout` and thereby changing ``` // Uses `n` as the indentation in the given document -def in[R](n: Int) { doc: => R / Layout }: R / Layout = - try { doc() } - with Indent { () => resume(n) } +def in[R](n: Int) { doc: => R / Layout }: R / Layout = { + val before = do getIndentation(); + do setIndentation(n) + val result = doc() + do setIndentation(before) + result +} // Adds `j` to the indentation in the current document def nest[R](j: Int) { doc: => R / Layout }: R / Layout = @@ -148,9 +160,13 @@ are _all_ layouted horizontally or vertically. Similarly, we can implement handl fix the direction: ``` -def in[R](dir: Direction) { doc: => R / Layout }: R / Layout = - try { doc() } - with Flow { () => resume(dir) } +def in[R](dir: Direction) { doc: => R / Layout }: R / Layout = { + val before = do getDirection(); + do setDirection(dir) + val result = doc() + do setDirection(before) + result +} def horizontal { p: => Unit / Layout }: Unit / Layout = Horizontal().in { p() } @@ -179,8 +195,8 @@ effect Pretty = { Emit, Layout, LayoutChoice } Using layout choices, we can express the maybe most important pretty printing combinator: ``` -def group { p: => Unit / Layout } = - do choice().in { p() } +def group { p: => Unit / Layout }: Unit / Layout = + p() ``` The `group` combinator expresses that depending on the result of `choice` we either layout all children horizontally or vertically. @@ -294,14 +310,27 @@ column position. def printer(width: Int, defaultIndent: Int) { prog: => Unit / { Emit, Layout } } : Unit / { Emit, LayoutChoice } = { // the position in the current line var pos: Int = 0; + var indentation: Int = 0; + + try { + var direction: Direction = do Flow() + + try { prog() } + // indentation starts at 0 + with Indent { () => resume(0) } + // simply handle the default indentation with a constant + with DefaultIndent { () => resume(defaultIndent) } + with Indentation { + def setIndentation(n) = { indentation = n; resume(()) } + def getIndentation() = resume(indentation) + } + with GlobalDirection { + def setDirection(n) = { direction = n; resume(()) } + def getDirection() = resume(direction) + } - try { prog() } - // we allow flow to be flexible on the top-level - with Flow { () => resume(do choice()) } - // indentation starts at 0 - with Indent { () => resume(0) } - // simply handle the default indentation with a constant - with DefaultIndent { () => resume(defaultIndent) } + // we allow flow to be flexible on the top-level + } with Flow { () => resume(do choice()) } ``` Maybe most interestingly, here we update the current position and invoke the effect operation `fail`, if the document exceeds the width. This will potentially cause backtracking and revision of a preceeding layout decision. @@ -352,19 +381,9 @@ def toDoc(t: Tree): Unit / Pretty = t match { case Var(name) => text(name) case Let(name, binding, body) => text("let"); space(); text(name); space(); text("="); - group { - nested { line(); toDoc(binding) }; - line(); - text("in") - }; - group { nested { line(); toDoc(body) } } - case App(name, arg) => text(name); parens { - group { nested { - linebreak(); - toDoc(arg) - }; linebreak() } + group { nested { toDoc(arg) } } } } ``` @@ -373,11 +392,7 @@ We can first use the parser from the [parser case study](parser) to obtain a parse tree, which we then pretty print: ``` -def parseAndPrint(text: String, width: Int): String = - parse(text) { parseExpr() } match { - case Success(tree) => pretty(width) { toDoc(tree) } - case Failure(text) => text - } +def parseAndPrint(text: String, width: Int): String = pretty(width) { toDoc(Lit(1)) } ``` For example, we obtain @@ -396,94 +411,92 @@ def example4() = parseAndPrint("let x = (let y = 2 in 1) in 42", 10) ``` def main() = { - println("-----"); + // println("-----"); println(pretty(5) { example1([1,2,3,4]) }); - println("----------"); + // println("----------"); println(pretty(10) { example1([1,2,3,4,5,6,7,8,9,1,2,3,4]) }); - println("----------") + // println("----------") println(example4()) - def example4b() = { - text("def"); space(); text("foo"); parens { - group { - nest(2) { - linebreak(); - group { text("x"); text(":"); space(); text("Int"); text(",") }; - line(); - group { text("y"); text(":"); space(); text("String") } - }; - linebreak() - } - } - } - def example3b() = { - example4b(); - space(); - braces { - group { - nest(2) { - line(); - text("var"); space(); text("z"); space(); text("="); space(); text("42"); text(";") - }; + def example4b() = + parens { + nest(2) { + linebreak(); + group { space(); }; line() - } - } - } - - def example6() = { - group { - text("this"); - nest(9) { - line(); - group { text("takes"); line(); text("many"); line(); text("f") } - }; - line(); - text("l") - } - } - - def example7() = { - group { - text("this"); - line(); - text("will"); - nest(9) { - line(); - group { text("take"); line(); text("many") } }; - line(); - text("lines") + linebreak() } - } - - def helloWorld() = { - text("hello") - line() - text("world") - } + // def example3b() = { + // example4b(); + // space(); + // braces { + // group { + // nest(2) { + // line(); + // text("var"); space(); text("z"); space(); text("="); space(); text("42"); text(";") + // }; + // line() + // } + // } + // } + + // def example6() = { + // group { + // text("this"); + // nest(9) { + // line(); + // group { text("takes"); line(); text("many"); line(); text("f") } + // }; + // line(); + // text("l") + // } + // } + + // def example7() = { + // group { + // text("this"); + // line(); + // text("will"); + // nest(9) { + // line(); + // group { text("take"); line(); text("many") } + // }; + // line(); + // text("lines") + // } + // } + + // def helloWorld() = { + // text("hello") + // line() + // text("world") + // } println("------------------------------"); println(pretty(30) { example4b() }); println("--------------------"); println(pretty(20) { example4b() }); - println("----------"); - println(pretty(50) { example3b() }); - println(pretty(15) { example3b() }); + // println("----------"); + // println(pretty(50) { example3b() }); + // println(pretty(15) { example3b() }); + + // println("------"); + // println(pretty(6) { example2() }); - println("------"); - println(pretty(6) { example2() }); + // println("---------------"); + // println(pretty(15) { example3() }); - println("---------------"); - println(pretty(15) { example3() }); + // println("--------------"); + // println(pretty(14) { example6() }); - println("--------------"); - println(pretty(14) { example6() }); + // println("--------------"); + // println(pretty(14) { example7() }) - println("--------------"); - println(pretty(14) { example7() }) + () } ``` \ No newline at end of file diff --git a/libraries/ml/effekt.effekt b/libraries/ml/effekt.effekt index 087fb6f52..3e03956d4 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/immutable/list.effekt b/libraries/ml/immutable/list.effekt index 9ccb0a8d9..46be20e5a 100644 --- a/libraries/ml/immutable/list.effekt +++ b/libraries/ml/immutable/list.effekt @@ -84,10 +84,9 @@ def any(l: List[Boolean]): Boolean = l match { case Nil() => false case Cons(v,rest) => if (v) { true } else { any(rest) } } -def sum(l: List[Int]): Int = { - var res = 0 - l.foreach { i => res = res + i } - res +def sum(l: List[Int]): Int = l match { + case Nil() => 0 + case Cons(n, xs) => n + sum(xs) } def nonEmpty[A](l: List[A]): Boolean = l match { diff --git a/libraries/ml/text/string.effekt b/libraries/ml/text/string.effekt index e2d704db1..d470bf5ea 100644 --- a/libraries/ml/text/string.effekt +++ b/libraries/ml/text/string.effekt @@ -34,8 +34,11 @@ extern pure def startsWith(str: String, prefix: String): Boolean = extern pure def endsWith(str: String, prefix: String): Boolean = "String.isSuffix prefix str" -// extern pure def repeat(str: String, n: Int): String = -// "???" +def repeat(str: String, n: Int): String = { + def go(n: Int, acc: String): String = + if (n == 0) acc else go(n - 1, acc ++ str) + go(n, "") +} def substring(str: String, from: Int): String = if (from < 0 || length(str) <= from) @@ -81,7 +84,7 @@ def split(str: String, sep: String): List[String] = { Cons(substring(str, startcopy), Nil()) } else { if(substring(str, startcheck, startcheck + length(sep)) == sep) { - Cons(substring(str, startcopy, startcheck), + Cons(substring(str, startcopy, startcheck), rec(startcheck + length(sep), startcheck + length(sep))) } else { rec(startcheck + 1, startcopy) @@ -90,8 +93,8 @@ def split(str: String, sep: String): List[String] = { } if (sep == "") { map(str){ c => c } - } else { - rec(0,0) + } else { + rec(0,0) } }