caasih.net

Re: Build Your Own Haskell Compiler #0 Appendix

附錄: C 的 Parse.hs

以下程式按閱讀順序重新編排過。

從 Main.hs 來看,該從 getAllModules ,還有 prettyPrintAllModules 看起。 getAllModules 在做的是,從給定的檔案內容中取得 Module 名稱與所 imports 的其他 Modules 名稱,再存到 Map 中。

一直留著 ParseResult ,猜想是把它當 Maybe 用了。

getAllModules :: String -> IO (ParseResult (M.Map String (Module SrcSpanInfo)))
getAllModules src =
  case parseModule src of
    ParseFailed loc msg -> return $ ParseFailed loc msg
    ParseOk mod -> do
      let
        modName = case mod of
          Module _ Nothing _ _ _ -> "Main"
          Module _ (Just (ModuleHead _ (ModuleName _ modName) _ _)) _ _ _ -> modName

        imports = moduleImports mod

      addMoreModules (M.singleton modName mod) imports

存到 Map 中的工作,由 addMoreModules 完成。有趣的是,在 Haskell 中打兩次 M.Map String (Module SrcSpanInfo) 不是什麼問題,但 Java 中就會想用一個 <T> 解決就好。

addMoreModules :: M.Map String (Module SrcSpanInfo) -> [String] -> IO (ParseResult (M.Map String (Module SrcSpanInfo)))
addMoreModules = go where
  -- 覺得常見的 `go acc []` 和 `go acc (m : ms)` 這種 local 遞迴用起來很像
  -- reduce/fold ,不知道作者是怎麼看的?
  go acc [] = return $ pure acc
  go acc (m : ms)
    | M.member m acc = go acc ms -- 已經 import  過了
    | m == "Prelude" = go acc ms -- 略過 Prelude
    | otherwise = do
      res <- getModule m
      case res of
        ParseFailed loc msg -> return (ParseFailed loc{srcFilename = m} msg)
        -- insert 後的 Map 變成下一次 go 的 acc 。 moduleImports 則從 mod 中取得
        -- 更多的 module names ,交給下次的 go 。
        ParseOk mod -> go (M.insert m mod acc) (moduleImports mod ++ ms)

moduleImports[ImportDecl l] 中的 module 一個個拆出來。

moduleImports :: Module l -> [String]
-- Module 長這樣:
--   Module l (Maybe (ModuleHead l)) [ModulePragma l] [ImportDecl l] [Decl l]
-- 這裡只留下 [ImportDecl l]
moduleImports (Module _ _ _ imports _) = map takeOne imports where
  takeOne imp = case importModule imp of
    ModuleName _ modName -> modName

-- getModule 是包裝好的 parseModule
-- 原來 Control.Exception 用在這裡!
getModule :: String -> IO (ParseResult (Module SrcSpanInfo))
getModule modName = do
  let filename = modNameToPath modName
  catch
    ( do
      src <- readFile filename
      return (parseModule src)
    )
    (\e -> return $ ParseFailed (SrcLoc filename 0 0) (show (e :: SomeException)))

modNameToPath 用到 System.FilePath 中的 extSeparatorpathSeparator ,好在不同的平台上生出可以用的 filepath 。

modNameToPath :: String -> FilePath
modNameToPath = go where
  go [] = extSeparator : "hs"
  go ('.' : ns) = pathSeparator : go ns
  go (n : ns) = n : go ns

最後 prettyPrintAllModules 把放在 Map 裡的 module 都印出來。

prettyPrintAllModules :: M.Map String (Module SrcSpanInfo) -> IO ()
prettyPrintAllModules = go . M.toAscList where
  go [] = return ()
  go ((modName, mod) : ms) = do
    putStrLn $ modName ++ ":"
    putStrLn $ prettyPrint mod
    putStrLn ""
    go ms

探しものは何ですか?見つけにくいものですか?

Isaac Huang 發佈於