284 lines
9.5 KiB
Agda
284 lines
9.5 KiB
Agda
module Main
|
||
|
||
import Prelude
|
||
import Data.List1
|
||
import Data.String
|
||
import Data.IORef
|
||
import Data.SortedMap
|
||
import Lib.Common
|
||
import Lib.Compile
|
||
import Lib.Parser
|
||
import Lib.Elab
|
||
import Lib.Util
|
||
import Lib.Parser.Impl
|
||
import Lib.Prettier
|
||
import Lib.ProcessDecl
|
||
import Lib.Token
|
||
import Lib.Tokenizer
|
||
import Lib.TopContext
|
||
import Lib.Types
|
||
import Lib.Syntax
|
||
import Node
|
||
import Serialize
|
||
|
||
-- For editors, dump some information about the context (fc, name, type)
|
||
jsonTopContext : M Json
|
||
jsonTopContext = do
|
||
top <- getTop
|
||
let defs = join $ map (\mod => listValues mod.modDefs) $ listValues top.modules
|
||
pure $ JsonObj (("context", JsonArray (map jsonDef $ defs)) :: Nil)
|
||
where
|
||
jsonDef : TopEntry -> Json
|
||
-- There is no FC here...
|
||
jsonDef (MkEntry fc (QN ns name) type def _) = JsonObj
|
||
( ("fc", toJson fc)
|
||
:: ("name", toJson name)
|
||
:: ("type", toJson (render 80 $ pprint Nil type) )
|
||
:: Nil)
|
||
|
||
dumpContext : TopContext -> M Unit
|
||
dumpContext top = do
|
||
putStrLn "Context:"
|
||
go $ listValues top.defs
|
||
putStrLn "---"
|
||
where
|
||
go : List TopEntry -> M Unit
|
||
go Nil = pure MkUnit
|
||
go (x :: xs) = putStrLn " \{show x}" >> go xs
|
||
|
||
writeSource : String -> M Unit
|
||
writeSource fn = do
|
||
docs <- compile
|
||
let src = unlines $
|
||
( "\"use strict\";"
|
||
:: "const bouncer = (f,ini) => { let obj = ini; while (obj.tag) obj = f(obj); return obj.h0 };"
|
||
:: Nil)
|
||
++ map (render 90 ∘ noAlt) docs
|
||
(Right _) <- liftIO {M} $ writeFile fn src
|
||
| Left err => exitFailure (show err)
|
||
-- (Right _) <- chmodRaw fn 493 | Left err => exitFailure (show err)
|
||
pure MkUnit
|
||
|
||
|
||
parseDecls : String -> Operators -> TokenList -> SnocList Decl -> M (List Decl × Operators)
|
||
parseDecls fn ops Nil acc = pure (acc <>> Nil, ops)
|
||
parseDecls fn ops toks@(first :: _) acc =
|
||
case partialParse fn (sameLevel parseDecl) ops toks of
|
||
Left (err, toks) => do
|
||
putStrLn $ showError "" err
|
||
addError err
|
||
parseDecls fn ops (recover toks) acc
|
||
Right (decl,ops,toks) => parseDecls fn ops toks (acc :< decl)
|
||
where
|
||
recover : TokenList -> TokenList
|
||
recover Nil = Nil
|
||
-- skip to top token, but make sure there is progress
|
||
recover (tok :: toks) = if tok.bounds.startCol == 0 && tok.bounds /= first.bounds
|
||
then (tok :: toks)
|
||
else recover toks
|
||
|
||
moduleHash : String → List (List String) → M String
|
||
moduleHash src imports = do
|
||
srcHash <- liftIO $ checksum src
|
||
top <- getTop
|
||
let mods = mapMaybe (\x => lookupMap' x top.modules) imports
|
||
let modHashes = map (\x => x.csum) mods
|
||
liftIO $ checksum $ fastConcat $ srcHash :: modHashes
|
||
|
||
importHints : List TopEntry → M Unit
|
||
importHints Nil = pure MkUnit
|
||
importHints (entry :: entries) = do
|
||
when (elem Hint entry.eflags) $ \ _ => addHint entry.name
|
||
importHints entries
|
||
|
||
-- New style loader, one def at a time
|
||
processModule : FC -> String -> List String -> QName -> M String
|
||
processModule importFC base stk qn@(QN ns nm) = do
|
||
top <- getTop
|
||
-- TODO make top.loaded a List QName
|
||
let modns = (snoc ns nm)
|
||
let name = joinBy "." modns
|
||
let (Nothing) = lookupMap modns top.modules | _ => pure ""
|
||
-- dummy entry for processing
|
||
modifyTop [modules := updateMap modns (emptyModCtx "") top.modules]
|
||
let fn = (joinBy "/" (base :: ns)) ++ "/" ++ nm ++ ".newt"
|
||
(Right src) <- liftIO {M} $ readFile fn
|
||
| Left err => exitFailure "ERROR at \{show importFC}: error reading \{fn}: \{show err}"
|
||
let (Right toks) = tokenise fn src
|
||
| Left err => exitFailure (showError src err)
|
||
|
||
let (Right ((nameFC, modName), ops, toks)) = partialParse fn parseModHeader top.ops toks
|
||
| Left (err, toks) => exitFailure (showError src err)
|
||
|
||
log 1 $ \ _ => "scan imports for module \{modName}"
|
||
let ns = split modName "."
|
||
let (path, modName') = unsnoc $ split1 modName "."
|
||
-- let bparts = split base "/"
|
||
let (True) = qn == QN path modName'
|
||
| _ => exitFailure "ERROR at \{show nameFC}: module name \{show modName} doesn't match file name \{show fn}"
|
||
|
||
let (Right (imports, ops, toks)) = partialParse fn parseImports ops toks
|
||
| Left (err, toks) => exitFailure (showError src err)
|
||
|
||
imported <- for imports $ \case
|
||
MkImport fc name' => do
|
||
let (a,b) = unsnoc $ split1 name' "."
|
||
let qname = QN a b
|
||
-- we could use `fc` if it had a filename in it
|
||
when (elem name' stk) $ \ _ => error emptyFC "import loop \{show name} -> \{show name'}"
|
||
|
||
processModule fc base (name :: stk) qname
|
||
pure $ split name' "."
|
||
let imported = snoc imported primNS
|
||
srcSum <- liftIO $ checksum src
|
||
csum <- moduleHash srcSum imported
|
||
|
||
putStrLn "module \{modName}"
|
||
top <- getTop
|
||
-- TODO we need a flag on this so `make newt3.js` properly tests self-compile
|
||
(Nothing) <- loadModule qn csum
|
||
| Just mod => do
|
||
let modules = updateMap modns mod top.modules
|
||
|
||
-- FIXME - we don't want stray operators in a module.
|
||
-- inject module ops into top
|
||
let ops = foldMap const top.ops $ toList mod.ctxOps
|
||
modifyTop [modules := modules; ops := ops ]
|
||
pure src -- why am I returning this?
|
||
|
||
log 1 $ \ _ => "MODNS " ++ show modns
|
||
top <- getTop
|
||
(decls, ops) <- parseDecls fn top.ops toks Lin
|
||
|
||
top <- getTop
|
||
let freshMC = MC emptyMap Nil 0 CheckAll
|
||
-- set imported, mod, freshMC, ops before processing
|
||
modifyTop (\ top => MkTop top.modules imported emptyMap modns emptyMap freshMC top.verbose top.errors ops)
|
||
for imported $ \ ns => do
|
||
let (Just mod) = lookupMap' ns top.modules | _ => error emptyFC "namespace \{show ns} missing"
|
||
importHints (listValues mod.modDefs)
|
||
|
||
log 1 $ \ _ => "process Decls"
|
||
traverse (tryProcessDecl src ns) (collectDecl decls)
|
||
|
||
-- update modules with result, leave the rest of context in case this is top file
|
||
top <- getTop
|
||
|
||
let mod = MkModCtx csum top.defs top.metaCtx top.ops
|
||
errors <- liftIO {M} $ readIORef top.errors
|
||
if stk /= Nil && length' errors == 0
|
||
then dumpModule qn src mod
|
||
else pure MkUnit
|
||
|
||
let modules = updateMap modns mod top.modules
|
||
modifyTop [modules := modules]
|
||
|
||
(Nil) <- liftIO {M} $ readIORef top.errors
|
||
| errors => do
|
||
-- we're now showing errors when they occur, so they're next to debug messages
|
||
-- traverse (putStrLn ∘ showError src) errors
|
||
exitFailure "Compile failed"
|
||
logMetas $ reverse $ listValues top.metaCtx.metas
|
||
pure src
|
||
where
|
||
tryProcessDecl : String -> List String → Decl -> M Unit
|
||
tryProcessDecl src ns decl = do
|
||
(Left err) <- tryError $ processDecl ns decl | _ => pure MkUnit
|
||
putStrLn $ showError src err
|
||
addError err
|
||
|
||
-- unwind the module part of the path name
|
||
baseDir : SnocList String -> SnocList String -> Either String String
|
||
baseDir dirs Lin = Right $ joinBy "/" (dirs <>> Nil)
|
||
baseDir (dirs :< d) (ns :< n) = if d == n
|
||
then baseDir dirs ns
|
||
else Left "module path doesn't match directory"
|
||
baseDir Lin _ = Left "module path doesn't match directory"
|
||
|
||
showErrors : String -> String -> M Unit
|
||
showErrors fn src = do
|
||
top <- getTop
|
||
-- TODO {M} needed to sort out scrutinee
|
||
(Nil) <- liftIO {M} $ readIORef top.errors
|
||
| errors => do
|
||
traverse (putStrLn ∘ showError src) errors
|
||
exitFailure "Compile failed"
|
||
pure MkUnit
|
||
|
||
-- processFile called on the top level file
|
||
-- it sets up everything and then recurses into processModule
|
||
processFile : String -> M Unit
|
||
processFile fn = do
|
||
putStrLn "*** Process \{fn}"
|
||
let parts = split1 fn "/"
|
||
let (dirs,file) = unsnoc parts
|
||
let dir = if dirs == Nil then "." else joinBy "/" dirs
|
||
let (name, ext) = splitFileName file
|
||
log 1 $ \ _ => "\{show dir} \{show name} \{show ext}"
|
||
|
||
(Right src) <- liftIO {M} $ readFile fn
|
||
| Left err => error (MkFC fn (0,0)) "error reading \{fn}: \{show err}"
|
||
let (Right toks) = tokenise fn src
|
||
| Left err => throwError err
|
||
let (Right ((nameFC, modName), _, _)) = partialParse fn parseModHeader emptyMap toks
|
||
| Left (err,toks) => throwError err
|
||
|
||
(base,qn) <- getBaseDir fn modName
|
||
|
||
-- declare internal primitives
|
||
|
||
processDecl primNS (PType emptyFC "Int" Nothing)
|
||
processDecl primNS (PType emptyFC "String" Nothing)
|
||
processDecl primNS (PType emptyFC "Char" Nothing)
|
||
setDef (QN primNS "PiType") emptyFC (Erased emptyFC) (PrimFn "(h0, h1) => ({ tag: \"PiType\", h0, h1 });" (S (S Z)) Nil) Nil
|
||
|
||
top <- getTop
|
||
let modules = updateMap primNS (MkModCtx "" top.defs (MC emptyMap Nil 0 CheckAll) top.ops) top.modules
|
||
modifyTop (\ top => MkTop modules (primNS :: Nil) emptyMap Nil emptyMap top.metaCtx top.verbose top.errors top.ops)
|
||
|
||
src <- processModule emptyFC base Nil qn
|
||
top <- getTop
|
||
|
||
showErrors fn src
|
||
pure MkUnit
|
||
|
||
cmdLine : List String -> M (Maybe String × List String)
|
||
cmdLine Nil = pure (Nothing, Nil)
|
||
cmdLine ("--top" :: args) = cmdLine args -- handled later
|
||
cmdLine ("-v" :: args) = do
|
||
modifyTop [ verbose $= _+_ 1 ]
|
||
cmdLine args
|
||
cmdLine ("-o" :: fn :: args) = do
|
||
(out, files) <- cmdLine args
|
||
pure ((out <|> Just fn), files)
|
||
|
||
cmdLine (fn :: args) = do
|
||
let (True) = isSuffixOf ".newt" fn
|
||
| _ => error emptyFC "Bad argument \{show fn}"
|
||
(out, files) <- cmdLine args
|
||
pure $ (out, fn :: files)
|
||
|
||
main' : M Unit
|
||
main' = do
|
||
(arg0 :: args) <- liftIO {M} $ getArgs
|
||
| _ => error emptyFC "error reading args"
|
||
(out, files) <- cmdLine args
|
||
traverse processFile files
|
||
|
||
when (elem "--top" args) $ \ _ => do
|
||
json <- jsonTopContext
|
||
putStrLn "TOP:\{renderJson json}"
|
||
|
||
case out of
|
||
Nothing => pure MkUnit
|
||
Just name => writeSource name
|
||
|
||
|
||
main : IO Unit
|
||
main = do
|
||
-- we'll need to reset for each file, etc.
|
||
ctx <- emptyTop
|
||
(Right _) <- .runM main' ctx
|
||
| Left err => exitFailure "ERROR at \{show $ getFC err}: \{errorMsg err}"
|
||
putStrLn "done"
|