Files
newt/src/Main.newt

284 lines
9.5 KiB
Agda
Raw Blame History

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"