feat(parser): add recursive-descent parser for expressions and patterns
This commit is contained in:
1 parent
e13d734741
commit
896f222c0a
1 file changed
+378
@@ -0,0 +1,378 @@
|
|||||||
|
module Fml.Core.Parser
|
||||||
|
|
||||||
|
open System
|
||||||
|
open Fml.Core.Ast
|
||||||
|
open Fml.Core.Lexer
|
||||||
|
|
||||||
|
exception ParseError of string
|
||||||
|
|
||||||
|
let private isUpper (s: string) = s.Length > 0 && Char.IsUpper s.[0]
|
||||||
|
|
||||||
|
let parse (tokenList: Token list) : Phrase list =
|
||||||
|
let toks = Array.ofList tokenList
|
||||||
|
let mutable pos = 0
|
||||||
|
let peek () = toks.[pos]
|
||||||
|
let advance () =
|
||||||
|
let t = toks.[pos]
|
||||||
|
pos <- pos + 1
|
||||||
|
t
|
||||||
|
let expect tok =
|
||||||
|
if toks.[pos] = tok then pos <- pos + 1
|
||||||
|
else raise (ParseError(sprintf "expected %A but found %A" tok toks.[pos]))
|
||||||
|
let expectIdent () =
|
||||||
|
match advance () with
|
||||||
|
| TkIdent s -> s
|
||||||
|
| t -> raise (ParseError(sprintf "expected an identifier but found %A" t))
|
||||||
|
let startsAtom () =
|
||||||
|
match peek () with
|
||||||
|
| TkInt _ | TkBool _ | TkStr _ | TkIdent _ | TkUnderscore | TkLParen | TkLBracket | TkLBrace -> true
|
||||||
|
| _ -> false
|
||||||
|
|
||||||
|
let desugarParams ps body =
|
||||||
|
List.foldBack (fun p acc -> Lambda(p, acc)) ps body
|
||||||
|
|
||||||
|
let flattenArg (e: Expr) : Expr list =
|
||||||
|
match e with
|
||||||
|
| Tuple items -> items
|
||||||
|
| _ -> [ e ]
|
||||||
|
|
||||||
|
let makeLet isRec (bindings: (string * Expr) list) body =
|
||||||
|
match bindings with
|
||||||
|
| [ (name, rhs) ] -> Let(isRec, name, rhs, body)
|
||||||
|
| _ -> LetGroup(isRec, bindings, body)
|
||||||
|
|
||||||
|
let rec parseExpr () =
|
||||||
|
match peek () with
|
||||||
|
| TkLet -> parseLetExpr ()
|
||||||
|
| TkFun -> parseFun ()
|
||||||
|
| TkIf -> parseIf ()
|
||||||
|
| TkMatch -> parseMatch ()
|
||||||
|
| _ -> parseOr ()
|
||||||
|
|
||||||
|
and parseParams () =
|
||||||
|
let ps = ResizeArray<string>()
|
||||||
|
let mutable go = true
|
||||||
|
while go do
|
||||||
|
match peek () with
|
||||||
|
| TkIdent s -> advance () |> ignore; ps.Add s
|
||||||
|
| TkUnderscore -> advance () |> ignore; ps.Add "_"
|
||||||
|
| _ -> go <- false
|
||||||
|
List.ofSeq ps
|
||||||
|
|
||||||
|
and parseBinding () =
|
||||||
|
let name = expectIdent ()
|
||||||
|
let ps = parseParams ()
|
||||||
|
expect TkEquals
|
||||||
|
let rhs = parseExpr ()
|
||||||
|
name, desugarParams ps rhs
|
||||||
|
|
||||||
|
and parseBindings () =
|
||||||
|
let bindings = ResizeArray<string * Expr>()
|
||||||
|
bindings.Add(parseBinding ())
|
||||||
|
while peek () = TkKwAnd do
|
||||||
|
advance () |> ignore
|
||||||
|
bindings.Add(parseBinding ())
|
||||||
|
List.ofSeq bindings
|
||||||
|
|
||||||
|
and parseLetExpr () =
|
||||||
|
expect TkLet
|
||||||
|
let isRec = if peek () = TkRec then (advance () |> ignore; true) else false
|
||||||
|
let bindings = parseBindings ()
|
||||||
|
expect TkIn
|
||||||
|
let body = parseExpr ()
|
||||||
|
makeLet isRec bindings body
|
||||||
|
|
||||||
|
and parseFun () =
|
||||||
|
expect TkFun
|
||||||
|
let ps = parseParams ()
|
||||||
|
if List.isEmpty ps then raise (ParseError "fun requires at least one parameter")
|
||||||
|
expect TkArrow
|
||||||
|
let body = parseExpr ()
|
||||||
|
desugarParams ps body
|
||||||
|
|
||||||
|
and parseIf () =
|
||||||
|
expect TkIf
|
||||||
|
let c = parseExpr ()
|
||||||
|
expect TkThen
|
||||||
|
let t = parseExpr ()
|
||||||
|
expect TkElse
|
||||||
|
let e = parseExpr ()
|
||||||
|
If(c, t, e)
|
||||||
|
|
||||||
|
and parseMatch () =
|
||||||
|
expect TkMatch
|
||||||
|
let scrut = parseExpr ()
|
||||||
|
expect TkWith
|
||||||
|
if peek () = TkBar then advance () |> ignore
|
||||||
|
let cases = ResizeArray<Pattern * Expr>()
|
||||||
|
let firstPat = parsePattern ()
|
||||||
|
expect TkArrow
|
||||||
|
let firstBody = parseExpr ()
|
||||||
|
cases.Add(firstPat, firstBody)
|
||||||
|
while peek () = TkBar do
|
||||||
|
advance () |> ignore
|
||||||
|
let p = parsePattern ()
|
||||||
|
expect TkArrow
|
||||||
|
let b = parseExpr ()
|
||||||
|
cases.Add(p, b)
|
||||||
|
Match(scrut, List.ofSeq cases)
|
||||||
|
|
||||||
|
and parseOr () =
|
||||||
|
let mutable left = parseAnd ()
|
||||||
|
while peek () = TkOr do
|
||||||
|
advance () |> ignore
|
||||||
|
left <- BinOp("||", left, parseAnd ())
|
||||||
|
left
|
||||||
|
|
||||||
|
and parseAnd () =
|
||||||
|
let mutable left = parseCompare ()
|
||||||
|
while peek () = TkAnd do
|
||||||
|
advance () |> ignore
|
||||||
|
left <- BinOp("&&", left, parseCompare ())
|
||||||
|
left
|
||||||
|
|
||||||
|
and parseCompare () =
|
||||||
|
let mutable left = parseCons ()
|
||||||
|
let isCmp () =
|
||||||
|
match peek () with
|
||||||
|
| TkEquals | TkNotEq | TkLt | TkLe | TkGt | TkGe -> true
|
||||||
|
| _ -> false
|
||||||
|
while isCmp () do
|
||||||
|
let op =
|
||||||
|
match advance () with
|
||||||
|
| TkEquals -> "="
|
||||||
|
| TkNotEq -> "<>"
|
||||||
|
| TkLt -> "<"
|
||||||
|
| TkLe -> "<="
|
||||||
|
| TkGt -> ">"
|
||||||
|
| TkGe -> ">="
|
||||||
|
| _ -> ""
|
||||||
|
left <- BinOp(op, left, parseCons ())
|
||||||
|
left
|
||||||
|
|
||||||
|
and parseCons () =
|
||||||
|
let left = parseAdd ()
|
||||||
|
if peek () = TkCons then
|
||||||
|
advance () |> ignore
|
||||||
|
Cons(left, parseCons ())
|
||||||
|
else left
|
||||||
|
|
||||||
|
and parseAdd () =
|
||||||
|
let mutable left = parseMul ()
|
||||||
|
let isOp () =
|
||||||
|
match peek () with
|
||||||
|
| TkPlus | TkMinus | TkCaret -> true
|
||||||
|
| _ -> false
|
||||||
|
while isOp () do
|
||||||
|
let op =
|
||||||
|
match advance () with
|
||||||
|
| TkPlus -> "+"
|
||||||
|
| TkMinus -> "-"
|
||||||
|
| TkCaret -> "^"
|
||||||
|
| _ -> ""
|
||||||
|
left <- BinOp(op, left, parseMul ())
|
||||||
|
left
|
||||||
|
|
||||||
|
and parseMul () =
|
||||||
|
let mutable left = parseUnary ()
|
||||||
|
let isOp () =
|
||||||
|
match peek () with
|
||||||
|
| TkStar | TkSlash | TkMod -> true
|
||||||
|
| _ -> false
|
||||||
|
while isOp () do
|
||||||
|
let op =
|
||||||
|
match advance () with
|
||||||
|
| TkStar -> "*"
|
||||||
|
| TkSlash -> "/"
|
||||||
|
| TkMod -> "mod"
|
||||||
|
| _ -> ""
|
||||||
|
left <- BinOp(op, left, parseUnary ())
|
||||||
|
left
|
||||||
|
|
||||||
|
and parseUnary () =
|
||||||
|
if peek () = TkMinus then
|
||||||
|
advance () |> ignore
|
||||||
|
Neg(parseUnary ())
|
||||||
|
else
|
||||||
|
parseApp ()
|
||||||
|
|
||||||
|
and parseApp () =
|
||||||
|
let head = parsePostfix ()
|
||||||
|
match head with
|
||||||
|
| Construct(name, []) when isUpper name ->
|
||||||
|
if startsAtom () then Construct(name, flattenArg (parsePostfix ()))
|
||||||
|
else head
|
||||||
|
| _ ->
|
||||||
|
let mutable f = head
|
||||||
|
while startsAtom () do
|
||||||
|
f <- App(f, parsePostfix ())
|
||||||
|
f
|
||||||
|
|
||||||
|
and parsePostfix () =
|
||||||
|
let mutable base' = parseAtom ()
|
||||||
|
while peek () = TkDot do
|
||||||
|
advance () |> ignore
|
||||||
|
let name = expectIdent ()
|
||||||
|
base' <-
|
||||||
|
match base' with
|
||||||
|
| Construct(modName, []) when isUpper modName -> Var(modName + "." + name)
|
||||||
|
| _ -> Field(base', name)
|
||||||
|
base'
|
||||||
|
|
||||||
|
and parseAtom () =
|
||||||
|
match advance () with
|
||||||
|
| TkInt n -> IntLit n
|
||||||
|
| TkBool b -> BoolLit b
|
||||||
|
| TkStr s -> StringLit s
|
||||||
|
| TkIdent s -> if isUpper s then Construct(s, []) else Var s
|
||||||
|
| TkUnderscore -> Var "_"
|
||||||
|
| TkLBrace -> parseRecordLiteral ()
|
||||||
|
| TkLParen ->
|
||||||
|
if peek () = TkRParen then
|
||||||
|
advance () |> ignore
|
||||||
|
UnitLit
|
||||||
|
else
|
||||||
|
let first = parseExpr ()
|
||||||
|
if peek () = TkComma then
|
||||||
|
let items = ResizeArray<Expr>()
|
||||||
|
items.Add first
|
||||||
|
while peek () = TkComma do
|
||||||
|
advance () |> ignore
|
||||||
|
items.Add(parseExpr ())
|
||||||
|
expect TkRParen
|
||||||
|
Tuple(List.ofSeq items)
|
||||||
|
else
|
||||||
|
expect TkRParen
|
||||||
|
first
|
||||||
|
| TkLBracket ->
|
||||||
|
if peek () = TkRBracket then
|
||||||
|
advance () |> ignore
|
||||||
|
Nil
|
||||||
|
else
|
||||||
|
let items = ResizeArray<Expr>()
|
||||||
|
items.Add(parseExpr ())
|
||||||
|
while peek () = TkSemi do
|
||||||
|
advance () |> ignore
|
||||||
|
items.Add(parseExpr ())
|
||||||
|
expect TkRBracket
|
||||||
|
List.foldBack (fun e acc -> Cons(e, acc)) (List.ofSeq items) Nil
|
||||||
|
| t -> raise (ParseError(sprintf "unexpected token %A" t))
|
||||||
|
|
||||||
|
and parseRecordLiteral () =
|
||||||
|
let fields = ResizeArray<string * Expr>()
|
||||||
|
let parseField () =
|
||||||
|
let name = expectIdent ()
|
||||||
|
expect TkEquals
|
||||||
|
let value = parseExpr ()
|
||||||
|
fields.Add(name, value)
|
||||||
|
parseField ()
|
||||||
|
while peek () = TkSemi do
|
||||||
|
advance () |> ignore
|
||||||
|
if peek () <> TkRBrace then parseField ()
|
||||||
|
expect TkRBrace
|
||||||
|
Record(List.ofSeq fields)
|
||||||
|
|
||||||
|
and parsePattern () = parsePatCons ()
|
||||||
|
|
||||||
|
and parsePatCons () =
|
||||||
|
let left = parsePatApp ()
|
||||||
|
if peek () = TkCons then
|
||||||
|
advance () |> ignore
|
||||||
|
PCons(left, parsePatCons ())
|
||||||
|
else left
|
||||||
|
|
||||||
|
and startsPatAtom () =
|
||||||
|
match peek () with
|
||||||
|
| TkInt _ | TkBool _ | TkStr _ | TkIdent _ | TkUnderscore | TkLParen | TkLBracket | TkLBrace -> true
|
||||||
|
| _ -> false
|
||||||
|
|
||||||
|
and parsePatApp () =
|
||||||
|
match peek () with
|
||||||
|
| TkIdent s when isUpper s ->
|
||||||
|
advance () |> ignore
|
||||||
|
if startsPatAtom () then
|
||||||
|
let arg = parsePatAtom ()
|
||||||
|
let args =
|
||||||
|
match arg with
|
||||||
|
| PTuple ps -> ps
|
||||||
|
| _ -> [ arg ]
|
||||||
|
PConstruct(s, args)
|
||||||
|
else PConstruct(s, [])
|
||||||
|
| _ -> parsePatAtom ()
|
||||||
|
|
||||||
|
and parsePatAtom () =
|
||||||
|
match advance () with
|
||||||
|
| TkInt n -> PInt n
|
||||||
|
| TkBool b -> PBool b
|
||||||
|
| TkStr s -> PString s
|
||||||
|
| TkUnderscore -> PWildcard
|
||||||
|
| TkIdent s -> if isUpper s then PConstruct(s, []) else PVar s
|
||||||
|
| TkLBrace -> parseRecordPattern ()
|
||||||
|
| TkLParen ->
|
||||||
|
if peek () = TkRParen then
|
||||||
|
advance () |> ignore
|
||||||
|
PUnit
|
||||||
|
else
|
||||||
|
let first = parsePattern ()
|
||||||
|
if peek () = TkComma then
|
||||||
|
let items = ResizeArray<Pattern>()
|
||||||
|
items.Add first
|
||||||
|
while peek () = TkComma do
|
||||||
|
advance () |> ignore
|
||||||
|
items.Add(parsePattern ())
|
||||||
|
expect TkRParen
|
||||||
|
PTuple(List.ofSeq items)
|
||||||
|
else
|
||||||
|
expect TkRParen
|
||||||
|
first
|
||||||
|
| TkLBracket ->
|
||||||
|
if peek () = TkRBracket then
|
||||||
|
advance () |> ignore
|
||||||
|
PNil
|
||||||
|
else
|
||||||
|
let items = ResizeArray<Pattern>()
|
||||||
|
items.Add(parsePattern ())
|
||||||
|
while peek () = TkSemi do
|
||||||
|
advance () |> ignore
|
||||||
|
items.Add(parsePattern ())
|
||||||
|
expect TkRBracket
|
||||||
|
List.foldBack (fun p acc -> PCons(p, acc)) (List.ofSeq items) PNil
|
||||||
|
| t -> raise (ParseError(sprintf "unexpected token in pattern %A" t))
|
||||||
|
|
||||||
|
and parseRecordPattern () =
|
||||||
|
let fields = ResizeArray<string * Pattern>()
|
||||||
|
let parseField () =
|
||||||
|
let name = expectIdent ()
|
||||||
|
expect TkEquals
|
||||||
|
let p = parsePattern ()
|
||||||
|
fields.Add(name, p)
|
||||||
|
parseField ()
|
||||||
|
while peek () = TkSemi do
|
||||||
|
advance () |> ignore
|
||||||
|
if peek () <> TkRBrace then parseField ()
|
||||||
|
expect TkRBrace
|
||||||
|
PRecord(List.ofSeq fields)
|
||||||
|
|
||||||
|
let parsePhrase () =
|
||||||
|
match peek () with
|
||||||
|
| TkLet ->
|
||||||
|
expect TkLet
|
||||||
|
let isRec = if peek () = TkRec then (advance () |> ignore; true) else false
|
||||||
|
let bindings = parseBindings ()
|
||||||
|
if peek () = TkIn then
|
||||||
|
advance () |> ignore
|
||||||
|
let body = parseExpr ()
|
||||||
|
PhraseExpr(makeLet isRec bindings body)
|
||||||
|
else
|
||||||
|
PhraseLet(isRec, bindings)
|
||||||
|
| _ -> PhraseExpr(parseExpr ())
|
||||||
|
|
||||||
|
let phrases = ResizeArray<Phrase>()
|
||||||
|
if peek () <> TkEOF then
|
||||||
|
phrases.Add(parsePhrase ())
|
||||||
|
while peek () = TkDoubleSemi do
|
||||||
|
advance () |> ignore
|
||||||
|
if peek () <> TkEOF then phrases.Add(parsePhrase ())
|
||||||
|
if peek () <> TkEOF then
|
||||||
|
raise (ParseError(sprintf "unexpected token %A after phrase" (peek ())))
|
||||||
|
List.ofSeq phrases
|
||||||
Reference in new issue
Block a user