Skip to content

Commit

Permalink
Make lexer work for mlton and jit
Browse files Browse the repository at this point in the history
  • Loading branch information
marzipankaiser committed Oct 20, 2023
1 parent 1cf81e7 commit 46eeab6
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 48 deletions.
9 changes: 5 additions & 4 deletions examples/casestudies/lexer.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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)
}
}
Expand Down Expand Up @@ -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)
Expand Down
77 changes: 40 additions & 37 deletions examples/casestudies/parser.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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
}
```

Expand All @@ -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:
Expand Down Expand Up @@ -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)
}
Expand All @@ -122,41 +122,44 @@ 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
of `alt` and `fail` is still left open, offering flexibility in the implementation of the actual underlying parsing algorithm.

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
Expand Down Expand Up @@ -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)
}
```
Expand All @@ -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() })
Expand Down
1 change: 0 additions & 1 deletion examples/casestudies/prettyprinter.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions libraries/ml/effekt.effekt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -211,7 +211,7 @@ def ignoring[E] { prog: => Unit / Exception[E] }: Unit =

// Control Flow
// ============
effect Control {
interface Control {
def break(): Unit
def continue(): Unit
}
Expand Down
10 changes: 6 additions & 4 deletions libraries/ml/text/string.effekt
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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)"
Expand Down

0 comments on commit 46eeab6

Please sign in to comment.