diff options
author | Chloe Brown <chloe.brown.00@outlook.com> | 2023-03-25 03:56:05 +0000 |
---|---|---|
committer | Chloe Brown <chloe.brown.00@outlook.com> | 2023-03-25 03:56:05 +0000 |
commit | b97d796c45a4ffdb475c03330c62ff8be2911d85 (patch) | |
tree | 73f98786420d77d6d259bd6f97843d6388b26c18 | |
parent | 91e2eaf87d10afbfcaeced4f125683f9bb6381df (diff) |
Add a normaliser for untyped lambda terms.
-rw-r--r-- | obs.ipkg | 5 | ||||
-rw-r--r-- | src/Main.idr | 435 |
2 files changed, 440 insertions, 0 deletions
@@ -6,3 +6,8 @@ depends = contrib options = "--total" +executable = "cc-eval" +main = Main + +modules + = Main diff --git a/src/Main.idr b/src/Main.idr new file mode 100644 index 0000000..04f9839 --- /dev/null +++ b/src/Main.idr @@ -0,0 +1,435 @@ +module Main + +import Data.DPair +import Data.List +import Data.Nat +import Data.Singleton +import Data.String + +import Decidable.Equality + +import System +import System.File.ReadWrite +import System.File.Virtual + +import Text.Lexer +import Text.Parser +import Text.PrettyPrint.Prettyprinter +import Text.PrettyPrint.Prettyprinter.Render.Terminal + +infix 4 ~~ + +Name : Type +Name = WithBounds String + +(~~) : Name -> Name -> Type +m ~~ n = m.val = n.val + +Context : Type +Context = SnocList Name + +data IsVar : Nat -> Name -> Context -> Type where + Here : (eq : m ~~ n) -> IsVar 0 m (ctx :< n) + There : IsVar k n ctx -> IsVar (S k) n (ctx :< _) + +data Term : Context -> Type where + Var : (k : Nat) -> (0 n : Name) -> (0 prf : IsVar k n ctx) -> Term ctx + Abs : (n : Name) -> Term (ctx :< n) -> Term ctx + App : Term ctx -> Term ctx -> Term ctx + Let : (n : Name) -> Term ctx -> Term (ctx :< n) -> Term ctx + +data Value : Context -> Type +data Neutral : Context -> Type +data Closure : Context -> Type + +data Env : (local, global : Context) -> Type where + Lin : Env [<] global + (:<) : Env local global -> (p : (Name, Lazy (Value global))) -> Env (local :< fst p) global + +data Closure : Context -> Type where + Close : (n : Name) -> Env local global -> Term (local :< n) -> Closure global + +data Value where + Ntrl : Neutral ctx -> Value ctx + VAbs : Closure ctx -> Value ctx + +data Neutral where + VVar : (k : Nat) -> (0 n : Name) -> (0 prf : IsVar k n ctx) -> Neutral ctx + VApp : Neutral ctx -> Lazy (Value ctx) -> Neutral ctx + +%name IsVar prf +%name Term t, u +%name Value v +%name Neutral n +%name Closure close +%name Env env + +-------------------------------------------------------------------------------- + +index : Env local global -> (k : Nat) -> (0 prf : IsVar k n local) -> Value global +index (env :< (n, v)) 0 (Here eq) = v +index (env :< _) (S k) (There prf) = index env k prf + +app : Closure ctx -> Lazy (Value ctx) -> Value ctx +eval : Env local global -> Term local -> Value global + +app (Close n env t) v = assert_total $ eval (env :< (n, v)) t + +eval env (Var k n prf) = index env k prf +eval env (Abs n t) = VAbs (Close n env t) +eval env (App t u) = + case eval env t of + Ntrl n => Ntrl $ VApp n (eval env u) + VAbs close => app close (eval env u) +eval env (Let n t u) = eval (env :< (n, eval env t)) u + +-------------------------------------------------------------------------------- + +data Thins : (src, tgt : Context) -> Type where + Empty : [<] `Thins` [<] + Drop : src `Thins` tgt -> src `Thins` tgt :< n + Keep : src `Thins` tgt -> src :< n `Thins` tgt :< n + +%name Thins thin + +id : {ctx : Context} -> ctx `Thins` ctx +id {ctx = [<]} = Empty +id {ctx = _ :< _} = Keep id + +distance : src `Thins` tgt -> {k : Nat} -> (0 prf : IsVar k n src) -> Nat +distance (Drop thin) prf = S (distance thin prf) +distance (Keep thin) {k = 0} (Here eq) = 0 +distance (Keep thin) {k = S k} (There prf) = distance thin prf + +0 wknIsVar : + (prf : IsVar k n src) -> + (thin : src `Thins` tgt) -> + IsVar (distance thin prf + k) n tgt +wknIsVar prf Empty impossible +wknIsVar prf (Drop thin) = There (wknIsVar prf thin) +wknIsVar (Here eq) (Keep thin) = Here eq +wknIsVar (There {k} prf) (Keep thin) = + rewrite sym $ plusSuccRightSucc (distance thin prf) k in + There (wknIsVar prf thin) + +wknTerm : Term src -> src `Thins` tgt -> Term tgt +wknTerm (Var k n prf) thin = Var (distance thin prf + k) n (wknIsVar prf thin) +wknTerm (Abs n t) thin = Abs n (wknTerm t (Keep thin)) +wknTerm (App t u) thin = App (wknTerm t thin) (wknTerm u thin) +wknTerm (Let n t u) thin = Let n (wknTerm t thin) (wknTerm u (Keep thin)) + +wknNtrl : Neutral src -> src `Thins` tgt -> Neutral tgt +wknVal : Value src -> src `Thins` tgt -> Value tgt +wknClose : Closure src -> src `Thins` tgt -> Closure tgt +wknEnv : Env local src -> src `Thins` tgt -> Env local tgt + +wknNtrl (VVar k n prf) thin = VVar (distance thin prf + k) n (wknIsVar prf thin) +wknNtrl (VApp n v) thin = VApp (wknNtrl n thin) (wknVal v thin) + +wknVal (Ntrl n) thin = Ntrl (wknNtrl n thin) +wknVal (VAbs close) thin = VAbs (wknClose close thin) + +wknClose (Close n env t) thin = Close n (wknEnv env thin) t + +wknEnv [<] thin = [<] +wknEnv (env :< (n, v)) thin = wknEnv env thin :< (n, wknVal v thin) + +-------------------------------------------------------------------------------- + +quote : {ctx : _} -> Value ctx -> Term ctx +quoteNtrl : {ctx : _} -> Neutral ctx -> Term ctx + +quote (Ntrl n) = quoteNtrl n +quote (VAbs close@(Close n _ _)) = + Abs n $ + assert_total $ + quote $ + app (wknClose close (Drop id)) (Ntrl $ VVar 0 n (Here Refl)) + +quoteNtrl (VVar k n prf) = Var k n prf +quoteNtrl (VApp n v) = App (quoteNtrl n) (quote v) + +normalise : {global : _} -> Env local global -> Term local -> Term global +normalise env t = quote $ eval env t + +-------------------------------------------------------------------------------- + +data RawTerm : Type where + RVar : (n : Name) -> RawTerm + RAbs : (n : Name) -> RawTerm -> RawTerm + RApp : RawTerm -> RawTerm -> RawTerm + RLet : (n : Name) -> RawTerm -> RawTerm -> RawTerm + +%name RawTerm t, u + +data NameError : Type -> Type where + Ok : ty -> NameError ty + Err : List1 Name -> NameError ty + +Functor NameError where + map f (Ok v) = Ok (f v) + map f (Err errs) = Err errs + +Applicative NameError where + pure = Ok + + Ok f <*> Ok v = Ok (f v) + Ok f <*> Err ys = Err ys + Err xs <*> Ok v = Err xs + Err xs <*> Err ys = Err (xs ++ ys) + +lookup : (ctx : Context) -> (n : Name) -> NameError $ Subset Nat (\k => IsVar k n ctx) +lookup [<] n = Err (n ::: []) +lookup (ctx :< m) n = + case decEq n.val m.val of + Yes eq => Ok (Element 0 (Here eq)) + No _ => map (\p => Element (S p.fst) (There p.snd)) (lookup ctx n) + +translate : (ctx : Context) -> RawTerm -> NameError (Term ctx) +translate ctx (RVar n) = (\p => Var p.fst n p.snd) <$> lookup ctx n +translate ctx (RAbs n t) = pure (Abs n) <*> translate (ctx :< n) t +translate ctx (RApp t u) = [| App (translate ctx t) (translate ctx u) |] +translate ctx (RLet n t u) = pure (Let n) <*> translate ctx t <*> translate (ctx :< n) u + +-------------------------------------------------------------------------------- + +putErrLn : HasIO io => String -> io () +putErrLn str = do + Right () <- fPutStrLn stderr str + | Left e => pure () + pure () + +putBoundErrLn : HasIO io => Maybe Bounds -> String -> io () +putBoundErrLn Nothing str = putErrLn str +putBoundErrLn (Just bounds) str = + let startLine = padLeft 3 '0' $ show (1 + bounds.startLine) in + let startCol = padLeft 2 '0' $ show bounds.startCol in + let endLine = padLeft 3 '0' $ show (1 + bounds.endLine) in + let endCol = padLeft 2 '0' $ show bounds.endCol in + putErrLn "\{startLine}:\{startCol}--\{endLine}:\{endCol}: \{str}" + +data CCTokenKind + = TIgnore + | TIdent + -- Keywords + | TLet + -- Symbols + | TLambda + | TPOpen + | TPClose + | TComma + | TFatArrow + | TDef + | TSemi + +%name CCTokenKind k + +Eq CCTokenKind where + TIgnore == TIgnore = True + TIdent == TIdent = True + TLet == TLet = True + TLambda == TLambda = True + TPOpen == TPOpen = True + TPClose == TPClose = True + TComma == TComma = True + TFatArrow == TFatArrow = True + TDef == TDef = True + TSemi == TSemi = True + _ == _ = False + +TokenKind CCTokenKind where + TokType TIdent = String + TokType _ = () + + tokValue TIgnore s = () + tokValue TIdent s = s + tokValue TLet s = () + tokValue TLambda s = () + tokValue TPOpen s = () + tokValue TPClose s = () + tokValue TComma s = () + tokValue TFatArrow s = () + tokValue TDef s = () + tokValue TSemi s = () + +CCToken = Token CCTokenKind + +ignored : List Lexer +ignored = [spaces, lineComment (exact "--"), blockComment (exact "{-") (exact "-}")] + +identifier : Lexer +identifier = alpha <+> many alphaNum + +keywords : List (String, CCTokenKind) +keywords = [("let", TLet)] + +tokenMap : TokenMap CCToken +tokenMap = + toTokenMap (map (, TIgnore) ignored) ++ + [(identifier, \s => + case lookup s keywords of + Just kind => Tok kind s + Nothing => Tok TIdent s) + ] ++ toTokenMap [ + (exact "\\", TLambda), + (exact "\x03BB", TLambda), + (exact "(", TPOpen), + (exact ")", TPClose), + (exact ",", TComma), + (exact "=>", TFatArrow), + (exact "=", TDef), + (exact ";", TSemi) + ] + +%tcinline +parens : {b : Bool} -> Grammar state CCToken b ty -> Grammar state CCToken True ty +parens p = match TPOpen >> commit >> p >>= (\t => match TPClose *> commit *> pure t) + +parseTerm : Grammar state CCToken True RawTerm +parseLambda : Grammar state CCToken True RawTerm +parseLet : Grammar state CCToken True RawTerm +parseSpine : Grammar state CCToken True RawTerm +parseAtom : Grammar state CCToken True RawTerm + +parseTerm = parseLambda <|> parseLet <|> parseSpine + +parseLambda = do + match TLambda + commit + names <- sepBy1 (match TComma >> commit) (bounds $ match TIdent) + commit + match TFatArrow + commit + t <- parseTerm + pure $ foldr1By RAbs (flip RAbs t) names + +parseLet = do + match TLet + commit + n <- bounds $ match TIdent + commit + match TDef + commit + t <- parseTerm + match TSemi + commit + u <- parseTerm + pure $ RLet n t u + +parseSpine = foldl1 RApp <$> some parseAtom + +parseAtom = + (RVar <$> (bounds $ match TIdent <* commit)) <|> + (parens parseTerm) + +parseSrc : Grammar state CCToken True RawTerm +parseSrc = parseTerm <* eof + +parseString : String -> Either (List1 (ParsingError CCToken)) RawTerm +parseString str = do + let (tokens, _, _, "") = lex tokenMap str + | (_, line, col, rest) => + let bounds = MkBounds { startCol = col, startLine = line, endCol = col, endLine = line } in + Left (Error "failed to lex input" (Just bounds) ::: []) + (val, []) <- parse parseSrc $ filter (\tok => tok.val.kind /= TIgnore) tokens + | (_, fst :: rest) => Left (Error "unexpected token" (Just fst.bounds) ::: []) + pure val + +parse : String -> IO RawTerm +parse str = + case parseString str of + Left errs => for_ errs putErr >> exitFailure + Right v => pure v + where + putErr : ParsingError CCToken -> IO () + putErr (Error str bounds) = putBoundErrLn bounds ("parse error: \{str}" ) + +partial +parseStdin : IO RawTerm +parseStdin = do + Right str <- fRead stdin + | Left err => putErrLn "failed to read input: \{show err}" >> exitFailure + parse str + +-------------------------------------------------------------------------------- + +recomputeName : {ctx : Context} -> (k : Nat) -> (0 _ : IsVar k n ctx) -> Singleton n.val +recomputeName {ctx = _ :< n} 0 (Here eq) = rewrite eq in Val n.val +recomputeName (S k) (There prf) = recomputeName k prf + +startPrec, leftAppPrec, appPrec : Prec +startPrec = Open +leftAppPrec = Backtick +appPrec = App + +prettySingleton : Pretty a => Singleton {a} x -> Doc ann +prettySingleton (Val x) = pretty x + +prettyTerm : {ctx : Context} -> Prec -> Term ctx -> Doc ann +prettyTerm d (Var k _ prf) = prettySingleton $ recomputeName k prf +prettyTerm d t@(Abs _ _) = + let (names ** t) = getNames t in + parenthesise (d > startPrec) $ group $ + backslash <+> prettyNames names <++> pretty "=>" <+> softline <+> + (assert_total $ prettyTerm startPrec t) + where + getNames : forall ctx. Term ctx -> (local ** Term (ctx <>< local)) + getNames (Abs n t) = let (names ** t) = getNames t in (n :: names ** t) + getNames t = ([] ** t) + + prettyNames : List Name -> Doc ann + prettyNames [] = neutral + prettyNames [n] = pretty n.val + prettyNames (n :: ns) = pretty n.val <+> comma <+> line <+> prettyNames ns +prettyTerm d (App t u) = + parenthesise (d >= appPrec) $ group $ + prettyTerm leftAppPrec t <++> prettyTerm appPrec u +prettyTerm d (Let n t u) = + parenthesise (d > startPrec) $ group $ align $ + pretty "let" <++> + (group $ align $ hang 2 $ pretty n.val <++> pretty "=" <+> line <+> prettyTerm startPrec t) <+> + pretty ";" <+> line <+> + prettyTerm startPrec u + +{ctx : Context} -> Pretty (Term ctx) where + prettyPrec = prettyTerm + +-------------------------------------------------------------------------------- + +-- -- ("\\" works for lambda as well) +-- ex = main' "nf" $ unlines [ +-- "let λ λ 1 (1 (1 (1 (1 0))));", -- five = λ s z. s (s (s (s (s z)))) +-- "let λ λ λ λ 3 1 (2 1 0);", -- add = λ a b s z. a s (b s z) +-- "let λ λ λ λ 3 (2 1) 0;", -- mul = λ a b s z. a (b s) z +-- "let 1 2 2;", -- ten = add five five +-- "let 1 0 0;", -- hundred = mul ten ten +-- "let 2 1 0;", -- thousand = mul ten hundred +-- "0" -- thousand + +helpMsg : String +helpMsg = unlines [ + "usage: cc-eval [--help|nf]", + " --help : display this message", + " nf : read expression from stdin, print its normal form" + ] + +mainWith : IO (List String) -> IO RawTerm -> IO () +mainWith getOpt getTerm = do + [_, "nf"] <- getOpt + | [_, "--help"] => putStrLn helpMsg + | _ => putErrLn helpMsg >> exitFailure + rawTerm <- getTerm + let Ok term = translate [<] rawTerm + | Err errs => for_ errs putErr >> exitFailure + let nf : Term [<] = normalise [<] term + putDoc (pretty nf) + where + putErr : Name -> IO () + putErr n = putBoundErrLn (Just n.bounds) "undefined name: \{n.val}" + +partial +main : IO () +main = mainWith getArgs parseStdin + +main' : String -> String -> IO () +main' mode src = mainWith (pure [mode]) (parse src) |