From e177d56aa21dd46cc8b21e099e9b061589d4005f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Thu, 19 Oct 2023 17:21:00 +0200 Subject: [PATCH 01/16] Make the case studies lexer/parser/anf/prettyprinter work with JIT --- examples/casestudies/anf.md | 21 ++++++-- examples/casestudies/lexer.md | 70 +++++++++++++++++++-------- examples/casestudies/parser.md | 12 +++++ examples/casestudies/prettyprinter.md | 1 + libraries/jit/immutable/list.effekt | 12 ++++- libraries/jit/text/string.effekt | 11 +++++ 6 files changed, 102 insertions(+), 25 deletions(-) diff --git a/examples/casestudies/anf.md b/examples/casestudies/anf.md index cc5a5e5c7..76202db29 100644 --- a/examples/casestudies/anf.md +++ b/examples/casestudies/anf.md @@ -18,12 +18,13 @@ module examples/casestudies/anf import examples/casestudies/parser // for the Tree datatype import examples/casestudies/prettyprinter // for the combined example +import text/string // for show ``` To recall the Tree datatype, here is an example tree: ``` // let x = f(g(42)) in x -val exampleTree: Tree = +def exampleTree(): Tree = Let("x", App("f", App("g", Lit(42))), Var("x")) ``` @@ -37,11 +38,21 @@ type Expr { CLit(value: Int); CVar(name: String) } +def show(e: Expr): String = e match { + case CLit(v) => "CLit(" ++ show(v) ++ ")" + case CVar(n) => "CVar(" ++ show(n) ++ ")" +} type Stmt { CLet(name: String, binding: Stmt, body: Stmt); CApp(name: String, arg: Expr); CRet(expr: Expr) } +def show(s: Stmt): String = s match { + case CLet(n,bi,bo) => "CLet(" ++ show(n) ++ ", " ++ show(bi) ++ ", " ++ show(bo) ++ ")" + case CApp(n,a) => "CApp(" ++ show(n) ++ ", " ++ show(a) ++ ")" + case CRet(e) => "CRet(" ++ show(e) ++ ")" +} +def println(s: Stmt) = println(show(s)) ``` We prefix all constructors with `C...` to distinguish them from the source language ("C" for "Core"). @@ -99,7 +110,7 @@ def translate(e: Tree): Stmt = For our example, calling `translate` results in: ``` -val exampleResult = translate(exampleTree) +def exampleResult() = translate(exampleTree()) //> CLet(x, CLet(x1, CRet(CLit(42)), // CLet(x2, CApp(g, CVar(x1)), CApp(f, CVar(x2)))), // CRet(CVar(x))) @@ -148,7 +159,7 @@ def pretty(s: Stmt) = pretty(40) { toDocStmt(s) } Using the pretty printer, we can print our example result from above: ``` -val examplePretty = pretty(exampleResult) +def examplePretty() = pretty(exampleResult()) // let x = let x1 = return 42 in let x2 = // g(x1) // in f(x2) in return x @@ -167,8 +178,8 @@ def pipeline(input: String): String = Here we use `pipeline` to translate some examples: ``` def main() = { - println(exampleResult) - println(examplePretty) + println(exampleResult()) + println(examplePretty()) println("----") println(pipeline("42")) diff --git a/examples/casestudies/lexer.md b/examples/casestudies/lexer.md index 2d1a6ba56..5d2015ddd 100644 --- a/examples/casestudies/lexer.md +++ b/examples/casestudies/lexer.md @@ -15,20 +15,37 @@ Before we get started, we require a few imports to deal with strings and regular module examples/casestudies/lexer import text/string -import text/regex import immutable/option import immutable/list -import mutable/array ``` ## Tokens and Positions First we define the datatypes to represent lexemes (tokens) and positions in the input stream: ``` record Position(line: Int, col: Int, index: Int) +def infixEq(l: Position, r: Position): Boolean = (l.line == r.line) && (l.col == r.col) && (l.index == r.index) +def show(p: Position): String = "Position(" ++ show(p.line) ++ ", " ++ show(p.col) ++ ", " ++ show(p.index) ++ ")" type TokenKind { Number(); Ident(); Punct(); Space() } +def infixEq(l: TokenKind, r: TokenKind): Boolean = (l,r) match { + case (Number(),Number()) => true + case (Ident(),Ident()) => true + case (Punct(),Punct()) => true + case (Space(),Space()) => true + case _ => false +} +def show(t: TokenKind): String = t match { + case Number() => "Number()" + case Ident() => "Ident()" + case Punct() => "Punct()" + case Space() => "Space()" +} record Token(kind: TokenKind, text: String, position: Position) +def show(t: Token): String = "Token(" ++ show(t.kind) ++ ", " ++ show(t.text) ++ ", " ++ show(t.position) ++ ")" +def println(arg: Tuple3[Token, Token, Token]): Unit = arg match { + case (x,y,z) => println("(" ++ show(x) ++ "," ++ show(y) ++ "," ++ show(z) ++ ")") +} ``` Tokens simply are tagged with a token type (distinguishing numbers, identifiers, and punctuation), the original text of the token and its position. @@ -58,7 +75,7 @@ def example1() = { 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 -val dummyPosition = Position(0, 0, 0) +def dummyPosition() = Position(0, 0, 0) def lexerFromList[R](l: List[Token]) { program: => R / Lexer }: R / LexerError = { var in = l; @@ -68,7 +85,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()) case Cons(tok, _) => resume(tok) } } @@ -83,17 +100,17 @@ def report { prog: => Unit / LexerError }: Unit = ``` Given a list of example tokens ``` -val exampleTokens = [ - Token(Ident(), "foo", dummyPosition), - Token(Punct(), "(", dummyPosition), - Token(Punct(), ")", dummyPosition) +def exampleTokens() = [ + Token(Ident(), "foo", dummyPosition()), + Token(Punct(), "(", dummyPosition()), + Token(Punct(), ")", dummyPosition()) ] ``` we can compose the two handlers to run our example consumer: ``` def runExample1() = report { - exampleTokens.lexerFromList { + exampleTokens().lexerFromList { println(example1()) } } @@ -106,13 +123,28 @@ processes an input and computes the tokens contained therein. This time, we use a number of different regular expressions to recognize lexemes. First, we define the different token types as a list of pairs of regular expressions and token types. ``` +type Regex { + SeqOf(options: String) + OneOf(options: String) +} +def exec(r: Regex, input: String): Option[String] = r match { + case SeqOf(chars) => + val r = takeWhile(input){ c => oneOf(c, chars) } + if (r == "") { None() } else { Some(r) } + case OneOf(chars) => + input.charAt(0) match { + case Some(c) => if (oneOf(c, chars)) { Some(c) } else { None() } + case None() => None() + } +} record TokenRx(kind: TokenKind, rx: Regex) -val tokenDesriptors = [ - TokenRx(Number(), "^[0-9]+".regex), - TokenRx(Ident(), "^[a-zA-Z]+".regex), - TokenRx(Punct(), "^[=,.()\\[\\]{}:]".regex), - TokenRx(Space(), "^[ \t\n]+".regex) +def oneOf(char: String, chars: String): Boolean = any(chars.map{ c => c == char }) +def tokenDesriptors() = [ + TokenRx(Number(), SeqOf("0123456789")), + TokenRx(Ident(), SeqOf("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")), + TokenRx(Punct(), OneOf("=,.()\\[\\]{}:")), + TokenRx(Space(), SeqOf(" \t\n")) ] ``` @@ -130,10 +162,10 @@ A few local helper functions ease the handling of the input stream. At the same time, we need to keep track of the line information. ``` def position() = Position(line, col, index) - def input() = in.substring(index) + def input() = in.substring(index, in.length) def consume(text: String): Unit = { val lines = text.split("\n") - val len = lines.unsafeGet(lines.size - 1).length + val len = last(lines).getOrElse{ panic("empty") }.length // compute new positions index = index + text.length line = line + lines.size - 1 @@ -146,7 +178,7 @@ the input stream, without advancing it. Its companion `tryMatchAll` returns the matched by any of the matches in the given description list. ``` def tryMatch(desc: TokenRx): Option[Token] = - desc.rx.exec(input()).map { m => Token(desc.kind, m.matched, position()) } + desc.rx.exec(input()).map { m => Token(desc.kind, m, position()) } def tryMatchAll(descs: List[TokenRx]): Option[Token] = descs match { case Nil() => None() @@ -157,12 +189,12 @@ Now defining the lexer is trivial. We just need to use `tryMatchAll` and either the input, or not. ``` try { prog() } with Lexer { - def peek() = resume(tryMatchAll(tokenDesriptors)) + def peek() = resume(tryMatchAll(tokenDesriptors())) def next() = if (eos()) do LexerError("Unexpected EOS", position()) else { - val tok = tryMatchAll(tokenDesriptors).getOrElse { + val tok = tryMatchAll(tokenDesriptors()).getOrElse { do LexerError("Cannot tokenize input", position()) } consume(tok.text) diff --git a/examples/casestudies/parser.md b/examples/casestudies/parser.md index 44783ceb2..0258b0b32 100644 --- a/examples/casestudies/parser.md +++ b/examples/casestudies/parser.md @@ -86,6 +86,12 @@ type Tree { Let(name: String, binding: Tree, body: Tree) App(name: String, arg: Tree) } +def show(t: Tree): String = t match { + case Lit(v) => "Lit(" ++ show(v) ++ ")" + case Var(name) => "Var(" ++ show(name) ++ ")" + case Let(n,b,body) => "Let(" ++ show(n) ++ ", " ++ show(b) ++ ", " ++ show(body) ++ ")" + case App(n,a) => "App(" ++ show(n) ++ ", " ++ show(a) ++ ")" +} ``` Let us start by defining the parser for numeric literals. @@ -196,6 +202,10 @@ type ParseResult[R] { Success(t: R); Failure(msg: String) } +def showPR[A](p: ParseResult[A]){f: A => String}: String = p match { + case Success(t) => "Success(" ++ f(t) ++ ")" + case Failure(msg) => "Failure(" ++ msg ++ ")" +} ``` The parsing algorithm is simply implemented as a handler for `Parser`. @@ -225,6 +235,8 @@ the lexer positions will be restored when calling the continuation a second time Having implemented a handler for the `Parser` effect, we can run our example "grammars" on some inputs. ``` +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() }) diff --git a/examples/casestudies/prettyprinter.md b/examples/casestudies/prettyprinter.md index 81015aee0..58f794f8b 100644 --- a/examples/casestudies/prettyprinter.md +++ b/examples/casestudies/prettyprinter.md @@ -279,6 +279,7 @@ for a particular choice-point, we resume a second time with `Vertical`. Handling the output emitter is straightforward. Here, we simply store all emitted elements in a string: ``` +effect Foo(): Unit def writer { p: => Unit / Emit } = { var out = ""; try { p(); out } with Emit { diff --git a/libraries/jit/immutable/list.effekt b/libraries/jit/immutable/list.effekt index 6dc7e4057..59f947e63 100644 --- a/libraries/jit/immutable/list.effekt +++ b/libraries/jit/immutable/list.effekt @@ -75,15 +75,25 @@ def head[A](l: List[A]): A / Exception[EmptyList] = l match { } def tail[A](l: List[A]): List[A] / Exception[EmptyList] = l match { - case Nil() => do raise(EmptyList(), "Trying to get the head of an empty list") + case Nil() => do raise(EmptyList(), "Trying to get the tail of an empty list") case Cons(a, rest) => rest } +def last[A](l: List[A]): Option[A] = { + var res = None() + l.foreach { e => res = Some(e) } + res +} def headOption[A](l: List[A]): Option[A] = l match { case Nil() => None() case Cons(a, rest) => Some(a) } +def any(l: List[Boolean]): Boolean = l match { + case Nil() => false + case Cons(v,rest) => if (v) { true } else { any(rest) } +} + // elements that satisfy the predicate go into the left list def partition[A](l: List[A]) { pred: A => Boolean }: (List[A], List[A]) = { var lefts: List[A] = Nil() diff --git a/libraries/jit/text/string.effekt b/libraries/jit/text/string.effekt index 515b440a1..70aa619f3 100644 --- a/libraries/jit/text/string.effekt +++ b/libraries/jit/text/string.effekt @@ -51,6 +51,17 @@ def map[A](str: String){ f: String => A }: List[A] = { } rec(0) } +def takeWhile(str: String){ f: String => Boolean }: String = { + def rec(i: Int, acc: String): String = { + if (i < length(str)) { + val c = unsafeCharAt(str, i) + if (f(c)) { + rec(i+1, acc ++ c) + } else acc + } else acc + } + rec(0, "") +} def indexOf(str: String, start: Int, substr: String): Option[Int] = { if(length(str) - start < length(substr)) { None() From cc062ee9dd0d94ee9c6ed5761b1dc235dd4c67d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Thu, 19 Oct 2023 19:23:12 +0200 Subject: [PATCH 02/16] Start making the case studies work on mlton --- examples/casestudies/lexer.md | 4 +-- examples/casestudies/parser.md | 2 +- libraries/ml/immutable/list.effekt | 11 +++++++ libraries/ml/text/string.effekt | 47 ++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/examples/casestudies/lexer.md b/examples/casestudies/lexer.md index 5d2015ddd..f6ac31528 100644 --- a/examples/casestudies/lexer.md +++ b/examples/casestudies/lexer.md @@ -27,7 +27,7 @@ def infixEq(l: Position, r: Position): Boolean = (l.line == r.line) && (l.col == def show(p: Position): String = "Position(" ++ show(p.line) ++ ", " ++ show(p.col) ++ ", " ++ show(p.index) ++ ")" type TokenKind { Number(); Ident(); Punct(); Space() } -def infixEq(l: TokenKind, r: TokenKind): Boolean = (l,r) match { +def eq(l: TokenKind, r: TokenKind): Boolean = (l,r) match { case (Number(),Number()) => true case (Ident(),Ident()) => true case (Punct(),Punct()) => true @@ -223,7 +223,7 @@ Interestingly, a whitespace skipping lexer can be implemented as a _effect trans ``` def skipSpaces(): Unit / Lexer = do peek() match { case None() => () - case Some(t) => if (t.kind == Space()) { do next(); skipSpaces() } else () + case Some(t) => if (eq(t.kind, Space())) { do next(); skipSpaces() } else () } def skipWhitespace[R] { prog: => R / Lexer }: R / Lexer = diff --git a/examples/casestudies/parser.md b/examples/casestudies/parser.md index 0258b0b32..05cfff194 100644 --- a/examples/casestudies/parser.md +++ b/examples/casestudies/parser.md @@ -46,7 +46,7 @@ def accept { p: Token => Boolean } : Token / Parser = { Using `accept`, we can define parsers for the different token types. ``` def any() = accept { t => true } -def accept(exp: TokenKind) = accept { t => t.kind == exp } +def accept(exp: TokenKind) = accept { t => eq(t.kind, exp) } def ident() = accept(Ident()).text def number() = accept(Number()).text def punct(p: String) = { diff --git a/libraries/ml/immutable/list.effekt b/libraries/ml/immutable/list.effekt index 2c9a6e963..db0ca74cf 100644 --- a/libraries/ml/immutable/list.effekt +++ b/libraries/ml/immutable/list.effekt @@ -80,6 +80,11 @@ def drop[A](l: List[A], n: Int): List[A] = case Cons(a, rest) => rest.drop(n - 1) } +def any(l: List[Boolean]): Boolean = l match { + case Nil() => false + case Cons(v,rest) => if (v) { true } else { any(rest) } +} + def nonEmpty[A](l: List[A]): Boolean = l match { case Nil() => false case Cons(a, rest) => true @@ -95,6 +100,12 @@ def tail[A](l: List[A]): List[A] / Exception[EmptyList] = l match { case Cons(a, rest) => rest } +def last[A](l: List[A]): Option[A] = { + var res = None() + l.foreach { e => res = Some(e) } + res +} + def headOption[A](l: List[A]): Option[A] = l match { case Nil() => None() case Cons(a, rest) => Some(a) diff --git a/libraries/ml/text/string.effekt b/libraries/ml/text/string.effekt index 3f99bf2a3..18a8fb57a 100644 --- a/libraries/ml/text/string.effekt +++ b/libraries/ml/text/string.effekt @@ -2,6 +2,7 @@ module text/string import effekt import immutable/option +import immutable/list import internal/option // For more information on the sml string type: @@ -12,6 +13,18 @@ def charAt(str: String, index: Int): Option[String] = Some(unsafeCharAt(str, index)) else None() +def takeWhile(str: String){ f: String => Boolean }: String = { + def rec(i: Int, acc: String): String = { + if (i < length(str)) { + val c = unsafeCharAt(str, i) + if (f(c)) { + rec(i+1, acc ++ c) + } else acc + } else acc + } + rec(0, "") +} + extern pure def length(str: String): Int = "String.size str" @@ -28,10 +41,17 @@ def substring(str: String, from: Int): String = if (from < 0 || length(str) <= from) str else unsafeSubstring(str, from) +def substring(str: String, from: Int, endp: Int): String = + if (from < 0 || length(str) <= from || length(str) <= endp) + str + else unsafeSubstring(str, from, endp) extern pure def unsafeSubstring(str: String, from: Int): String = "String.extract (str, from, NONE)" +extern pure def unsafeSubstring(str: String, from: Int, endp: Int): String = + "String.extract (str, from, endp)" + // extern pure def split(str: String, sep: String): Array[String] = // "Array.fromList (map (String.str) (String.explode str))" @@ -47,6 +67,33 @@ def toInt(str: String): Option[Int] = toOption(internalStringToInt(str)) extern pure def unsafeCharAt(str: String, n: Int): String = "String.sub (str, n)" +def map[A](str: String){ f: String => A }: List[A] = { + def rec(i: Int): List[A] = { + if(i < length(str)) { + Cons(f(str.unsafeCharAt(i)), rec(i+1)) + } else { Nil() } + } + rec(0) +} +def split(str: String, sep: String): List[String] = { + def rec(startcheck: Int, startcopy: Int): List[String] = { + if (length(str) < startcheck + length(sep)) { + Cons(substring(str, startcopy), Nil()) + } else { + if(substring(str, startcheck, startcheck + length(sep)) == sep) { + Cons(substring(str, startcopy, startcheck), + rec(startcheck + length(sep), startcheck + length(sep))) + } else { + rec(startcheck + 1, startcopy) + } + } + } + if (sep == "") { + map(str){ c => c } + } else { + rec(0,0) + } +} val ANSI_GREEN = "\u001b[32m" val ANSI_RED = "\u001b[31m" From 5c35f0e55a47b60a7e0c85498fbf0f64c15fe113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Thu, 19 Oct 2023 21:38:31 +0200 Subject: [PATCH 03/16] WIP: Fix parser for mlton --- examples/casestudies/parser.md | 20 ++++++++++---------- libraries/jit/immutable/list.effekt | 5 +++++ libraries/ml/immutable/list.effekt | 5 +++++ 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/examples/casestudies/parser.md b/examples/casestudies/parser.md index 05cfff194..c04376849 100644 --- a/examples/casestudies/parser.md +++ b/examples/casestudies/parser.md @@ -68,11 +68,11 @@ def or[R] { p: => R } { q: => R } = def opt[R] { p: => R }: Option[R] / Parser = or { Some(p()) } { None() } -def many { p: => Unit }: Unit / Parser = - or { some { p() } } { () } +def many[R] { p: => R }: List[R] / Parser = + or { some[R] { p() } } { Nil() } -def some { p: => Unit }: Unit / Parser = - { p(); many { p() } } +def some[R] { p: => R }: List[R] / Parser = + { Cons(p(), many { p() }) } ``` ## Example: A Simple Expression Language @@ -171,19 +171,19 @@ The following example implements an example ``` It uses local (mutable) variables to count the number of leafs as semantic action. ``` -def parseCalls(): Int / Parser = +def parseCalls(): Int / Parser = { or { number(); 1 } { - var count = 1; ident(); punct("("); - count = count + parseCalls(); - many { + val count = parseCalls() + + sum(many { punct(","); - count = count + parseCalls() - }; + parseCalls() + }); punct(")"); count } +} ``` Notice how the user defined combinator `many` feels like a built-in control operator `while`. diff --git a/libraries/jit/immutable/list.effekt b/libraries/jit/immutable/list.effekt index 59f947e63..8dfc831a8 100644 --- a/libraries/jit/immutable/list.effekt +++ b/libraries/jit/immutable/list.effekt @@ -93,6 +93,11 @@ 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 +} // elements that satisfy the predicate go into the left list def partition[A](l: List[A]) { pred: A => Boolean }: (List[A], List[A]) = { diff --git a/libraries/ml/immutable/list.effekt b/libraries/ml/immutable/list.effekt index db0ca74cf..9ccb0a8d9 100644 --- a/libraries/ml/immutable/list.effekt +++ b/libraries/ml/immutable/list.effekt @@ -84,6 +84,11 @@ 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 nonEmpty[A](l: List[A]): Boolean = l match { case Nil() => false From 5eafd2352ff4070b9487db47c2877616fda7c1bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Thu, 19 Oct 2023 21:58:19 +0200 Subject: [PATCH 04/16] WIP: Fix parser for mlton --- examples/casestudies/parser.md | 15 ++++++++------- libraries/ml/text/string.effekt | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/examples/casestudies/parser.md b/examples/casestudies/parser.md index c04376849..5c065c7de 100644 --- a/examples/casestudies/parser.md +++ b/examples/casestudies/parser.md @@ -16,6 +16,7 @@ module examples/casestudies/parser import examples/casestudies/lexer import immutable/option +import immutable/list import text/string ``` @@ -245,12 +246,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/libraries/ml/text/string.effekt b/libraries/ml/text/string.effekt index 18a8fb57a..e2d704db1 100644 --- a/libraries/ml/text/string.effekt +++ b/libraries/ml/text/string.effekt @@ -50,7 +50,7 @@ extern pure def unsafeSubstring(str: String, from: Int): String = "String.extract (str, from, NONE)" extern pure def unsafeSubstring(str: String, from: Int, endp: Int): String = - "String.extract (str, from, endp)" + "String.extract (str, from, SOME(endp))" // extern pure def split(str: String, sep: String): Array[String] = // "Array.fromList (map (String.str) (String.explode str))" @@ -65,7 +65,7 @@ extern pure def internalStringToInt(str: String): MLOption[Int] = def toInt(str: String): Option[Int] = toOption(internalStringToInt(str)) extern pure def unsafeCharAt(str: String, n: Int): String = - "String.sub (str, n)" + "String.str (String.sub (str, n))" def map[A](str: String){ f: String => A }: List[A] = { def rec(i: Int): List[A] = { From c8d260e980095409cf25d264b630075aa5ddafa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Fri, 20 Oct 2023 09:38:11 +0200 Subject: [PATCH 05/16] Lexer: Remove polymorphic effect --- examples/casestudies/lexer.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/casestudies/lexer.md b/examples/casestudies/lexer.md index f6ac31528..dd1bdf48e 100644 --- a/examples/casestudies/lexer.md +++ b/examples/casestudies/lexer.md @@ -74,7 +74,7 @@ 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): Nothing def dummyPosition() = Position(0, 0, 0) def lexerFromList[R](l: List[Token]) { program: => R / Lexer }: R / LexerError = { @@ -94,7 +94,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) } ``` From 9cf024839c0a2fca36e48eff97600f961f0093e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Fri, 20 Oct 2023 11:02:47 +0200 Subject: [PATCH 06/16] Library changes for ML --- libraries/ml/immutable/list.effekt | 7 +++---- libraries/ml/text/string.effekt | 5 +++++ 2 files changed, 8 insertions(+), 4 deletions(-) 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..0b2010965 100644 --- a/libraries/ml/text/string.effekt +++ b/libraries/ml/text/string.effekt @@ -36,6 +36,11 @@ extern pure def endsWith(str: String, prefix: String): Boolean = // 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) From de94834714ceaf3fde10bc9817079558e8eae8ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Fri, 20 Oct 2023 11:31:21 +0200 Subject: [PATCH 07/16] Make lexer work for mlton and jit --- examples/casestudies/lexer.md | 9 ++-- examples/casestudies/parser.md | 77 ++++++++++++++------------- examples/casestudies/prettyprinter.md | 1 - libraries/ml/effekt.effekt | 4 +- libraries/ml/text/string.effekt | 10 ++-- 5 files changed, 53 insertions(+), 48 deletions(-) diff --git a/examples/casestudies/lexer.md b/examples/casestudies/lexer.md index dd1bdf48e..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(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 5c065c7de..1afe285f2 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 58f794f8b..31db10eb1 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 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/text/string.effekt b/libraries/ml/text/string.effekt index 0b2010965..fc8d22a33 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)" From 34f26465885f3814cd2faaadb7853395660a99a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Fri, 20 Oct 2023 11:34:30 +0200 Subject: [PATCH 08/16] Parser: Only parseExpr for now --- examples/casestudies/parser.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/casestudies/parser.md b/examples/casestudies/parser.md index 1afe285f2..0cc51e84e 100644 --- a/examples/casestudies/parser.md +++ b/examples/casestudies/parser.md @@ -242,19 +242,19 @@ Having implemented a handler for the `Parser` effect, we can run our example "gr 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("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("}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 From 82f64ed841f1af1083b3fd193d107f1dce5a354a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Fri, 20 Oct 2023 11:38:28 +0200 Subject: [PATCH 09/16] Parser: Don't skipWhitespace. Then ML works --- examples/casestudies/parser.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/casestudies/parser.md b/examples/casestudies/parser.md index 0cc51e84e..5a7a9219c 100644 --- a/examples/casestudies/parser.md +++ b/examples/casestudies/parser.md @@ -216,7 +216,7 @@ 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) @@ -242,12 +242,11 @@ Having implemented a handler for the `Parser` effect, we can run our example "gr 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("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("}42") { parseExpr() }) println(parse("42") { parseExpr() }) From 1f49587de3767e5c26145b9569c2a05eb9575336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Fri, 20 Oct 2023 16:55:55 +0200 Subject: [PATCH 10/16] Revert "Parser: Don't skipWhitespace. Then ML works" This reverts commit 46e89cf193418b083ee97604da0021645951059e. --- examples/casestudies/parser.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/casestudies/parser.md b/examples/casestudies/parser.md index 5a7a9219c..0cc51e84e 100644 --- a/examples/casestudies/parser.md +++ b/examples/casestudies/parser.md @@ -216,7 +216,7 @@ The parsing algorithm is simply implemented as a handler for `Parser`. ``` def parse[R](input: String) { p: => R / Parser }: ParseResult[R] = try { - lexer(input) { Success(p()) } + lexer(input) { skipWhitespace { Success(p()) } } } with Nondet { def alt() = resume(true) match { case Failure(msg) => resume(false) @@ -242,11 +242,12 @@ Having implemented a handler for the `Parser` effect, we can run our example "gr 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("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("}42") { parseExpr() }) println(parse("42") { parseExpr() }) From 2a9e548fbcb80986f5ccf7334961c281c98aa75c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Fri, 20 Oct 2023 16:57:05 +0200 Subject: [PATCH 11/16] Revert "Parser: Only parseExpr for now" This reverts commit d0d5c015bd03d24cf130ccf7a29be89ff7b370a2. --- examples/casestudies/parser.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/casestudies/parser.md b/examples/casestudies/parser.md index 0cc51e84e..1afe285f2 100644 --- a/examples/casestudies/parser.md +++ b/examples/casestudies/parser.md @@ -242,19 +242,19 @@ Having implemented a handler for the `Parser` effect, we can run our example "gr 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("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("}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 From 9ef14f7a97f9c59b1663c9f98f820f70f4daa38b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Fri, 20 Oct 2023 16:58:24 +0200 Subject: [PATCH 12/16] Re-uncomment more examples --- examples/casestudies/parser.md | 26 +++++++++++++------------- examples/casestudies/prettyprinter.md | 1 + 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/examples/casestudies/parser.md b/examples/casestudies/parser.md index 1afe285f2..b39689919 100644 --- a/examples/casestudies/parser.md +++ b/examples/casestudies/parser.md @@ -243,18 +243,18 @@ 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("}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("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() }) + 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 31db10eb1..053fabf6b 100644 --- a/examples/casestudies/prettyprinter.md +++ b/examples/casestudies/prettyprinter.md @@ -19,6 +19,7 @@ Furthermore, the library presented here is neither linear ``` module examples/casestudies/prettyprinter +import examples/casestudies/parser import immutable/option import immutable/list import text/string From b5a74da1dd249d71d7b8aeb029a86903ed499ba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Fri, 20 Oct 2023 17:27:36 +0200 Subject: [PATCH 13/16] Make it run in js --- examples/casestudies/lexer.md | 10 +++++----- libraries/js/immutable/list.effekt | 16 ++++++++++++++++ libraries/js/mutable/array.effekt | 3 ++- libraries/js/text/string.effekt | 26 +++++++++++++++++++++++++- 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/examples/casestudies/lexer.md b/examples/casestudies/lexer.md index 57e192822..87b0f023e 100644 --- a/examples/casestudies/lexer.md +++ b/examples/casestudies/lexer.md @@ -24,7 +24,7 @@ First we define the datatypes to represent lexemes (tokens) and positions in the ``` record Position(line: Int, col: Int, index: Int) def infixEq(l: Position, r: Position): Boolean = (l.line == r.line) && (l.col == r.col) && (l.index == r.index) -def show(p: Position): String = "Position(" ++ show(p.line) ++ ", " ++ show(p.col) ++ ", " ++ show(p.index) ++ ")" +def showPos(p: Position): String = "Position(" ++ show(p.line) ++ ", " ++ show(p.col) ++ ", " ++ show(p.index) ++ ")" type TokenKind { Number(); Ident(); Punct(); Space() } def eq(l: TokenKind, r: TokenKind): Boolean = (l,r) match { @@ -34,7 +34,7 @@ def eq(l: TokenKind, r: TokenKind): Boolean = (l,r) match { case (Space(),Space()) => true case _ => false } -def show(t: TokenKind): String = t match { +def showTK(t: TokenKind): String = t match { case Number() => "Number()" case Ident() => "Ident()" case Punct() => "Punct()" @@ -42,9 +42,9 @@ def show(t: TokenKind): String = t match { } record Token(kind: TokenKind, text: String, position: Position) -def show(t: Token): String = "Token(" ++ show(t.kind) ++ ", " ++ show(t.text) ++ ", " ++ show(t.position) ++ ")" +def showT(t: Token): String = "Token(" ++ showTK(t.kind) ++ ", " ++ show(t.text) ++ ", " ++ showPos(t.position) ++ ")" def println(arg: Tuple3[Token, Token, Token]): Unit = arg match { - case (x,y,z) => println("(" ++ show(x) ++ "," ++ show(y) ++ "," ++ show(z) ++ ")") + case (x,y,z) => println("(" ++ showT(x) ++ "," ++ showT(y) ++ "," ++ showT(z) ++ ")") } ``` Tokens simply are tagged with a token type (distinguishing numbers, identifiers, and punctuation), @@ -165,7 +165,7 @@ At the same time, we need to keep track of the line information. def position() = Position(line, col, index) def input() = in.substring(index, in.length) def consume(text: String): Unit = { - val lines = text.split("\n") + val lines: List[String] = text.split("\n") val len = last(lines).getOrElse{ panic("empty") }.length // compute new positions index = index + text.length diff --git a/libraries/js/immutable/list.effekt b/libraries/js/immutable/list.effekt index 5caa4ae60..b5f6e8bd7 100644 --- a/libraries/js/immutable/list.effekt +++ b/libraries/js/immutable/list.effekt @@ -87,12 +87,28 @@ def tail[A](l: List[A]): List[A] / Exception[EmptyList] = l match { case Nil() => do raise(EmptyList(), "Trying to get the head of an empty list") case Cons(a, rest) => rest } +def last[A](l: List[A]): Option[A] = { + var res = None() + l.foreach { e => res = Some(e) } + res +} def headOption[A](l: List[A]): Option[A] = l match { case Nil() => None() case Cons(a, rest) => Some(a) } + +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 +} + // elements that satisfy the predicate go into the left list def partition[A](l: List[A]) { pred: A => Boolean }: (List[A], List[A]) = { var lefts: List[A] = Nil() diff --git a/libraries/js/mutable/array.effekt b/libraries/js/mutable/array.effekt index 5b66f55cb..7d4ba9d85 100644 --- a/libraries/js/mutable/array.effekt +++ b/libraries/js/mutable/array.effekt @@ -29,9 +29,10 @@ extern pure def copy[T](arr: Array[T]): Array[T] = def toList[T](arr: Array[T]): List[T] = { var i = arr.size - 1; var l = Nil[T]() - while (i > 0) { + while (i >= 0) { l = Cons(unsafeGet(arr, i), l) i = i - 1 } l } +def last[A](arr: Array[A]): Option[A] = get(arr, arr.size-1) diff --git a/libraries/js/text/string.effekt b/libraries/js/text/string.effekt index 1ef0a4d22..8c6a18191 100644 --- a/libraries/js/text/string.effekt +++ b/libraries/js/text/string.effekt @@ -15,8 +15,12 @@ extern pure def repeat(str: String, n: Int): String = extern pure def substring(str: String, from: Int): String = "str.substring(from)" +extern pure def substring(str: String, from: Int, to: Int): String = + "str.substring(from, to)" + extern pure def split(str: String, sep: String): Array[String] = "str.split(sep)" +def split(str: String, sep: String): List[String] = { split(str, sep).toList } extern pure def trim(str: String): String = "str.trim()" @@ -28,4 +32,24 @@ extern pure def unsafeCharAt(str: String, n: Int): String = "str[n]" extern pure def unsafeToInt(str: String): Int = - "Number.isNaN(parseInt(str)) ? undefined : parseInt(str)" \ No newline at end of file + "Number.isNaN(parseInt(str)) ? undefined : parseInt(str)" + +def takeWhile(str: String){ f: String => Boolean }: String = { + def rec(i: Int, acc: String): String = { + if (i < length(str)) { + val c = unsafeCharAt(str, i) + if (f(c)) { + rec(i+1, acc ++ c) + } else acc + } else acc + } + rec(0, "") +} +def map[A](str: String){ f: String => A }: List[A] = { + def rec(i: Int): List[A] = { + if (i < length(str)) { + Cons(f(unsafeCharAt(str, i)), rec(i+1)) + } else Nil() + } + rec(0) +} \ No newline at end of file From dae9bca63c3e4a02b2f38baa01e7b6040142b1d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Fri, 20 Oct 2023 17:46:22 +0200 Subject: [PATCH 14/16] Make it run in chez-lift --- libraries/chez/common/immutable/list.effekt | 16 +++++++ libraries/chez/common/text/string.effekt | 50 +++++++++++++++++++-- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/libraries/chez/common/immutable/list.effekt b/libraries/chez/common/immutable/list.effekt index 047141d4c..a8bffdf2f 100644 --- a/libraries/chez/common/immutable/list.effekt +++ b/libraries/chez/common/immutable/list.effekt @@ -81,7 +81,23 @@ def tail[A](l: List[A]): List[A] = l match { case Cons(a, rest) => rest } +def last[A](l: List[A]): Option[A] = { + var res = None() + l.foreach { e => res = Some(e) } + res +} + def headOption[A](l: List[A]): Option[A] = l match { case Nil() => None() case Cons(a, rest) => Some(a) } + +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 +} \ No newline at end of file diff --git a/libraries/chez/common/text/string.effekt b/libraries/chez/common/text/string.effekt index ab41d1f1d..4f4cf7c31 100644 --- a/libraries/chez/common/text/string.effekt +++ b/libraries/chez/common/text/string.effekt @@ -2,8 +2,8 @@ module text/string import immutable/option -// def charAt(str: String, index: Int): Option[String] = -// str.unsafeCharAt(index).undefinedToOption +def charAt(str: String, index: Int): Option[String] = + str.unsafeCharAt(index).undefinedToOption extern pure def length(str: String): Int = "(string-length str)" @@ -14,15 +14,57 @@ extern pure def repeat(str: String, n: Int): String = extern pure def substring(str: String, from: Int): String = "(substring str from (string-length str))" +extern pure def substring(str: String, from: Int, to: Int): String = + "(substring str from to)" + // extern pure def trim(str: String): String = // "str.trim()" def toInt(str: String): Option[Int] = str.unsafeToInt.undefinedToOption -// extern pure def unsafeCharAt(str: String, n: Int): String = -// "str[n]" +extern pure def unsafeCharAt(str: String, n: Int): String = + "(string (string-ref str n))" // returns #f if not a number extern pure def unsafeToInt(str: String): Int = "(string->number str)" + +def takeWhile(str: String){ f: String => Boolean }: String = { + def rec(i: Int, acc: String): String = { + if (i < length(str)) { + val c = unsafeCharAt(str, i) + if (f(c)) { + rec(i+1, acc ++ c) + } else acc + } else acc + } + rec(0, "") +} +def map[A](str: String){ f: String => A }: List[A] = { + def rec(i: Int): List[A] = { + if (i < length(str)) { + Cons(f(unsafeCharAt(str, i)), rec(i+1)) + } else Nil() + } + rec(0) +} +def split(str: String, sep: String): List[String] = { + def rec(startcheck: Int, startcopy: Int): List[String] = { + if (length(str) < startcheck + length(sep)) { + Cons(substring(str, startcopy, length(str)), Nil()) + } else { + if(substring(str, startcheck, startcheck + length(sep)) == sep) { + Cons(substring(str, startcopy, startcheck), + rec(startcheck + length(sep), startcheck + length(sep))) + } else { + rec(startcheck + 1, startcopy) + } + } + } + if (sep == "") { + map(str){ c => c } + } else { + rec(0,0) + } +} \ No newline at end of file From 38612cff98d7265817f7238e736782103f1b088d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Fri, 20 Oct 2023 20:07:40 +0200 Subject: [PATCH 15/16] Minor --- examples/casestudies/parser.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/casestudies/parser.md b/examples/casestudies/parser.md index b39689919..7c1087f46 100644 --- a/examples/casestudies/parser.md +++ b/examples/casestudies/parser.md @@ -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)).absurd + else do fail("Unexpected token " ++ showT(got)).absurd } ``` From 46e6db46e2cafaea43e5087befafcafda2305fda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Sat, 21 Oct 2023 01:05:20 +0200 Subject: [PATCH 16/16] Minor --- examples/casestudies/benchmark_anf.effekt | 16 ++++++++++++++++ examples/casestudies/prettyprinter.md | 1 - 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 examples/casestudies/benchmark_anf.effekt diff --git a/examples/casestudies/benchmark_anf.effekt b/examples/casestudies/benchmark_anf.effekt new file mode 100644 index 000000000..208aa0d62 --- /dev/null +++ b/examples/casestudies/benchmark_anf.effekt @@ -0,0 +1,16 @@ +import examples/casestudies/anf +import examples/casestudies/parser +import examples/casestudies/prettyprinter +import text/string + +def prettyish(s: Stmt) = pretty(250) { toDocStmt(s) } +def pipelineB(input: String) = + parse(input) { parseExpr() } match { + case Success(tree) => prettyish(translate(tree)) + case Failure(msg) => msg + } + +def main() = { + val input = "foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(42))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))"; + println(length(pipelineB(input))) +} diff --git a/examples/casestudies/prettyprinter.md b/examples/casestudies/prettyprinter.md index 053fabf6b..c92ca10e7 100644 --- a/examples/casestudies/prettyprinter.md +++ b/examples/casestudies/prettyprinter.md @@ -279,7 +279,6 @@ for a particular choice-point, we resume a second time with `Vertical`. Handling the output emitter is straightforward. Here, we simply store all emitted elements in a string: ``` -effect Foo(): Unit def writer { p: => Unit / Emit } = { var out = ""; try { p(); out } with Emit {