Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Make Case studies work in JIT #295

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions examples/casestudies/anf.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
```

Expand All @@ -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").

Expand Down Expand Up @@ -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)))
Expand Down Expand Up @@ -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
Expand All @@ -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"))
Expand Down
16 changes: 16 additions & 0 deletions examples/casestudies/benchmark_anf.effekt
Original file line number Diff line number Diff line change
@@ -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)))
}
83 changes: 58 additions & 25 deletions examples/casestudies/lexer.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 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 {
case (Number(),Number()) => true
case (Ident(),Ident()) => true
case (Punct(),Punct()) => true
case (Space(),Space()) => true
case _ => false
}
def showTK(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 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("(" ++ showT(x) ++ "," ++ showT(y) ++ "," ++ showT(z) ++ ")")
}
```
Tokens simply are tagged with a token type (distinguishing numbers, identifiers, and punctuation),
the original text of the token and its position.
Expand Down Expand Up @@ -57,8 +74,9 @@ 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
val dummyPosition = Position(0, 0, 0)
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 = {
var in = l;
Expand All @@ -68,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)
}
}
Expand All @@ -77,23 +95,23 @@ 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)
}
```
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())
}
}
Expand All @@ -106,13 +124,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"))
]
```

Expand All @@ -130,10 +163,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 lines: List[String] = text.split("\n")
val len = last(lines).getOrElse{ panic("empty") }.length
// compute new positions
index = index + text.length
line = line + lines.size - 1
Expand All @@ -146,7 +179,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()
Expand All @@ -157,13 +190,13 @@ 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())
do LexerError("Unexpected EOS", position()).absurd
else {
val tok = tryMatchAll(tokenDesriptors).getOrElse {
do LexerError("Cannot tokenize input", position())
val tok = tryMatchAll(tokenDesriptors()).getOrElse {
do LexerError("Cannot tokenize input", position()).absurd
}
consume(tok.text)
resume(tok)
Expand Down Expand Up @@ -191,7 +224,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 =
Expand Down
Loading
Loading