summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDougal <d.maclaurin@gmail.com>2024-01-05 15:34:29 -0500
committerDougal <d.maclaurin@gmail.com>2024-01-05 15:41:54 -0500
commitfeba99f2c6d61251922418601e39cd7d7deba786 (patch)
tree195728317eb2119756cf3033eac1019c9581dd5c
parenta7d5c6f5502346c7e0e806a18240b300c5ea9c0e (diff)
Get static HTML pages working with the hover-info goodness
-rw-r--r--README.md2
-rw-r--r--dex.cabal21
-rw-r--r--makefile18
-rwxr-xr-xmisc/build-web-index2
-rw-r--r--src/dex.hs129
-rw-r--r--src/lib/Actor.hs10
-rw-r--r--src/lib/Live/Eval.hs32
-rw-r--r--src/lib/Live/Web.hs15
-rw-r--r--src/lib/RenderHtml.hs64
-rw-r--r--src/lib/TopLevel.hs60
-rw-r--r--src/lib/Util.hs5
-rw-r--r--static/dynamic.html24
-rw-r--r--static/index.ts10
13 files changed, 172 insertions, 220 deletions
diff --git a/README.md b/README.md
index fe9f6701..acae25e9 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ To learn more, check out our
our [tutorial](https://google-research.github.io/dex-lang/examples/tutorial.html)
or these example programs:
- * [Dex prelude](https://google-research.github.io/dex-lang/prelude.html)
+ * [Dex prelude](https://google-research.github.io/dex-lang/lib/prelude.html)
* [Mandelbrot set](https://google-research.github.io/dex-lang/examples/mandelbrot.html)
* [Ray tracer](https://google-research.github.io/dex-lang/examples/raytrace.html)
* [Estimating pi](https://google-research.github.io/dex-lang/examples/pi.html)
diff --git a/dex.cabal b/dex.cabal
index 50703eeb..16ff9c26 100644
--- a/dex.cabal
+++ b/dex.cabal
@@ -14,9 +14,9 @@ license-file: LICENSE
build-type: Simple
data-files: lib/*.dx
- , static/*.css
- , static/*.html
- , static/*.js
+ , static/dynamic.html
+ , static/index.js
+ , static/style.css
, src/lib/dexrt.bc
flag cuda
@@ -27,10 +27,6 @@ flag optimized
description: Enables GHC optimizations
default: False
-flag live
- description: Enables live-editing environments (web notebook and terminal)
- default: True
-
flag llvm-head
description: Use the bleeding-edge version of LLVM
default: False
@@ -99,8 +95,7 @@ library
, QueryTypePure
, Util
, Vectorize
- if flag(live)
- exposed-modules: Actor
+ , Actor
, Live.Eval
, Live.Web
, RenderHtml
@@ -136,16 +131,13 @@ library
, time
-- Floating-point pedanticness (correcting for GHC < 9.2.2)
, floating-bits
- if flag(live)
- build-depends: binary
+ , binary
, blaze-html
, blaze-markup
, cmark
, http-types
, wai
, warp
- cpp-options: -DDEX_LIVE
- cxx-options: -DDEX_LIVE
if flag(debug)
cpp-options: -DDEX_DEBUG
default-language: Haskell2010
@@ -252,6 +244,7 @@ executable dex
ghc-options: -threaded
-optP-Wno-nonportable-include-path
-rtsopts
+ -Wall
"-with-rtsopts=-I0 -A16m"
default-extensions: CPP
, DeriveGeneric
@@ -260,8 +253,6 @@ executable dex
, BlockArguments
if flag(cuda)
cpp-options: -DDEX_CUDA
- if flag(live)
- cpp-options: -DDEX_LIVE
if flag(optimized)
ghc-options: -O3
else
diff --git a/makefile b/makefile
index 0112525d..baf3733a 100644
--- a/makefile
+++ b/makefile
@@ -412,6 +412,7 @@ bench-summary:
# --- building docs ---
slow-pages = pages/examples/mnist-nearest-neighbors.html
+static-names = dynamic.html index.js style.css
doc-files = $(doc-names:%=doc/%.dx)
pages-doc-files = $(doc-names:%=pages/%.html)
@@ -420,30 +421,35 @@ pages-example-files = $(example-names:%=pages/examples/%.html)
lib-files = $(filter-out lib/prelude.dx,$(wildcard lib/*.dx))
pages-lib-files = $(patsubst %.dx,pages/%.html,$(lib-files))
+static-files = $(static-names:%=pages/static/%)
-docs: pages-prelude $(pages-doc-files) $(pages-example-files) $(pages-lib-files) $(slow-pages) pages/index.md
+docs: $(static-files) pages-prelude $(pages-doc-files) $(pages-example-files) $(pages-lib-files) $(slow-pages) pages/index.md
+
+pages/static/%: static/%
+ mkdir -p pages/static
+ cp $^ $@
pages-prelude: lib/prelude.dx
- mkdir -p pages
- $(dex) --prelude /dev/null script lib/prelude.dx --outfmt html > pages/prelude.html
+ mkdir -p pages/lib
+ $(dex) --prelude /dev/null generate-html lib/prelude.dx lib/prelude
pages/examples/tutorial.html: tutorial-data
pages/examples/dither.html: dither-data
pages/examples/%.html: examples/%.dx
mkdir -p pages/examples
- $(dex) script $< --outfmt html > $@
+ $(dex) generate-html $< examples/$*
pages/lib/%.html: lib/%.dx
mkdir -p pages/lib
- $(dex) script $^ --outfmt html > $@
+ $(dex) generate-html $^ lib/$*
pages/index.md: $(doc-files) $(example-files) $(lib-files)
python3 misc/build-web-index "$(doc-files)" "$(example-files)" "$(lib-files)" > $@
${pages-doc-files}:pages/%.html: doc/%.dx
mkdir -p pages
- $(dex) script $^ --outfmt html > $@
+ $(dex) generate-html $^ $*
clean:
$(STACK) clean
diff --git a/misc/build-web-index b/misc/build-web-index
index 88a571c5..75c6de1a 100755
--- a/misc/build-web-index
+++ b/misc/build-web-index
@@ -49,7 +49,7 @@ def main():
print(""); print("## Libraries"); print("")
- print("- [lib/prelude.dx](prelude.html): The Dex Prelude (automatically imported)")
+ print("- [lib/prelude.dx](lib/prelude.html): The Dex Prelude (automatically imported)")
file_block(libraries.split())
diff --git a/src/dex.hs b/src/dex.hs
index f16aa12a..53fbe0ac 100644
--- a/src/dex.hs
+++ b/src/dex.hs
@@ -4,8 +4,6 @@
-- license that can be found in the LICENSE file or at
-- https://developers.google.com/open-source/licenses/bsd
-{-# LANGUAGE RecordWildCards #-}
-
import System.Console.Haskeline
import System.Exit
import Control.Monad
@@ -14,88 +12,76 @@ import Options.Applicative hiding (Success, Failure)
import Text.PrettyPrint.ANSI.Leijen (text, hardline)
import System.Posix.Terminal (queryTerminal)
import System.Posix.IO (stdOutput)
-import System.IO (openFile, IOMode (..))
-import qualified Data.ByteString as BS
import Data.List
import qualified Data.Text as T
-import qualified Data.Text.Encoding as T
import qualified Data.Map.Strict as M
import qualified System.Console.ANSI as ANSI
import System.Console.ANSI hiding (Color)
import TopLevel
-import Err
-import Name
import AbstractSyntax (parseTopDeclRepl)
import ConcreteSyntax (keyWordStrs, preludeImportBlock)
-import RenderHtml
--- import Live.Terminal (runTerminal)
-import Live.Web (runWeb)
+import Live.Web
import PPrint hiding (hardline)
import Core
-import Types.Core
import Types.Imp
import Types.Source
import Types.Top
import MonadUtil
+import Util (readFileText)
data DocFmt = ResultOnly
| TextDoc
- | JSONDoc
-#ifdef DEX_LIVE
- | HTMLDoc
-#endif
-data EvalMode = ReplMode String
- | ScriptMode FilePath DocFmt ErrorHandling
- | ExportMode FilePath FilePath -- Dex path, .o path
- | ClearCache
-#ifdef DEX_LIVE
+
+data EvalMode = ReplMode
+ | ScriptMode FilePath DocFmt
| WebMode FilePath
- | WatchMode FilePath
-#endif
+ | GenerateHTML FilePath FilePath
+ | ClearCache
data CmdOpts = CmdOpts EvalMode EvalConfig
-runMode :: EvalMode -> EvalConfig -> IO ()
-runMode evalMode cfg = case evalMode of
- ScriptMode fname fmt onErr -> do
+runMode :: CmdOpts -> IO ()
+runMode (CmdOpts evalMode cfg) = case evalMode of
+ ScriptMode fname fmt -> do
env <- loadCache
- ((), finalEnv) <- runTopperM cfg env do
- source <- liftIO $ T.decodeUtf8 <$> BS.readFile fname
- evalSourceText source $ printIncrementalSource fmt
+ ((), finalEnv) <- runTopperM cfg stdOutLogger env do
+ blocks <- parseSourceBlocks <$> readFileText fname
+ forM_ blocks \block -> do
+ case fmt of
+ ResultOnly -> return ()
+ TextDoc -> liftIO $ putStr $ pprint block
+ evalSourceBlockRepl block
storeCache finalEnv
- ReplMode prompt -> do
+ ReplMode -> do
env <- loadCache
- void $ runTopperM cfg env do
+ void $ runTopperM cfg stdOutLogger env do
void $ evalSourceBlockRepl preludeImportBlock
forever do
- block <- readSourceBlock prompt
+ block <- readSourceBlock
void $ evalSourceBlockRepl block
- ClearCache -> clearCache
-#ifdef DEX_LIVE
WebMode fname -> do
env <- loadCache
runWeb fname cfg env
- WatchMode _ -> error "not implemented"
-#endif
-
-printIncrementalSource :: DocFmt -> SourceBlock -> IO ()
-printIncrementalSource fmt sb = case fmt of
- ResultOnly -> return ()
- JSONDoc -> return ()
- TextDoc -> putStr $ pprint sb
-#ifdef DEX_LIVE
- HTMLDoc -> return ()
-#endif
-
-readSourceBlock :: (MonadIO (m n), EnvReader m) => String -> m n SourceBlock
-readSourceBlock prompt = do
+ GenerateHTML fname dest -> do
+ env <- loadCache
+ generateHTML fname dest cfg env
+ ClearCache -> clearCache
+
+stdOutLogger :: Outputs -> IO ()
+stdOutLogger (Outputs outs) = do
+ isatty <- queryTerminal stdOutput
+ forM_ outs \out -> putStr $ printOutput isatty out
+
+readSourceBlock :: (MonadIO (m n), EnvReader m) => m n SourceBlock
+readSourceBlock = do
sourceMap <- withEnv $ envSourceMap . moduleEnv
let filenameAndDexCompletions =
completeQuotedWord (Just '\\') "\"'" listFiles (dexCompletions sourceMap)
let hasklineSettings = setComplete filenameAndDexCompletions defaultSettings
liftIO $ runInputT hasklineSettings $ readMultiline prompt (parseTopDeclRepl . T.pack)
+ where prompt = ">=> "
dexCompletions :: Monad m => SourceMap n -> CompletionFunc m
dexCompletions sourceMap (line, _) = do
@@ -133,31 +119,18 @@ helpOption optionName options =
parseMode :: Parser EvalMode
parseMode = subparser $
- command "repl" (simpleInfo
- (ReplMode <$> strOption (long "prompt" <> value ">=> "
- <> metavar "STRING" <> help "REPL prompt")))
-#ifdef DEX_LIVE
+ command "repl" (simpleInfo (pure ReplMode))
<> command "web" (simpleInfo (WebMode <$> sourceFileInfo))
- <> command "watch" (simpleInfo (WatchMode <$> sourceFileInfo))
-#endif
+ <> command "generate-html" (simpleInfo (GenerateHTML <$> sourceFileInfo <*> destFileInfo))
<> command "clean" (simpleInfo (pure ClearCache))
- <> command "export" (simpleInfo (ExportMode <$> sourceFileInfo <*> objectFileInfo))
- <> command "script" (simpleInfo (ScriptMode <$> sourceFileInfo
- <*> option
+ <> command "script" (simpleInfo (ScriptMode <$> sourceFileInfo <*> option
(optionList [ ("literate" , TextDoc)
- , ("result-only", ResultOnly)
-#ifdef DEX_LIVE
- , ("html" , HTMLDoc)
-#endif
- , ("json" , JSONDoc)])
+ , ("result-only", ResultOnly)])
(long "outfmt" <> value TextDoc <>
- helpOption "Output format" "literate (default) | result-only | html | json")
- <*> flag ContinueOnErr HaltOnErr (
- long "stop-on-error"
- <> help "Stop program evaluation when an error occurs (type or runtime)")))
+ helpOption "Output format" "literate (default) | result-only | html | json")))
where
- sourceFileInfo = argument str (metavar "FILE" <> help "Source program")
- objectFileInfo = argument str (metavar "OBJFILE" <> help "Output path (.o file)")
+ sourceFileInfo = argument str (metavar "FILE" <> help "Source program")
+ destFileInfo = argument str (metavar "OUTFILE" <> help "Output path")
optionList :: [(String, a)] -> ReadM a
optionList opts = eitherReader \s -> case lookup s opts of
@@ -178,11 +151,7 @@ parseEvalOpts = EvalConfig
<*> optional (strOption $ long "prelude" <> metavar "FILE" <> help "Prelude file")
<*> flag NoOptimize Optimize (short 'O' <> help "Optimize generated code")
<*> enumOption "print" "Print backend" PrintCodegen printBackends
- <*> flag ContinueOnErr HaltOnErr (
- long "stop-on-error"
- <> help "Stop program evaluation when an error occurs (type or runtime)")
<*> enumOption "loglevel" "Log level" NormalLogLevel logLevels
- <*> pure stdOutLogger
where
printBackends = [ ("haskell", PrintHaskell)
, ("dex" , PrintCodegen) ]
@@ -191,21 +160,16 @@ parseEvalOpts = EvalConfig
logLevels = [ ("normal", NormalLogLevel)
, ("debug" , DebugLogLevel ) ]
-stdOutLogger :: Outputs -> IO ()
-stdOutLogger (Outputs outs) = do
- isatty <- queryTerminal stdOutput
- forM_ outs \out -> putStr $ printOutput isatty out
-
printOutput :: Bool -> Output -> String
printOutput isatty out = case out of
Error _ -> addColor isatty Red $ addPrefix ">" $ pprint out
_ -> addPrefix (addColor isatty Cyan ">") $ pprint $ out
addPrefix :: String -> String -> String
-addPrefix prefix str = unlines $ map prefixLine $ lines str
+addPrefix prefix s = unlines $ map prefixLine $ lines s
where prefixLine :: String -> String
- prefixLine s = case s of "" -> prefix
- _ -> prefix ++ " " ++ s
+ prefixLine l = case l of "" -> prefix
+ _ -> prefix ++ " " ++ l
addColor :: Bool -> ANSI.Color -> String -> String
addColor False _ s = s
@@ -213,14 +177,13 @@ addColor True c s =
setSGRCode [SetConsoleIntensity BoldIntensity, SetColor Foreground Vivid c]
++ s ++ setSGRCode [Reset]
-
pathOption :: ReadM [LibPath]
pathOption = splitPaths [] <$> str
where
splitPaths :: [LibPath] -> String -> [LibPath]
splitPaths revAcc = \case
[] -> reverse revAcc
- str -> let (p,t) = break (==':') str in
+ s -> let (p,t) = break (==':') s in
splitPaths (parseLibPath p:revAcc) (dropWhile (==':') t)
parseLibPath = \case
@@ -228,6 +191,4 @@ pathOption = splitPaths [] <$> str
path -> LibDirectory path
main :: IO ()
-main = do
- CmdOpts evalMode opts <- execParser parseOpts
- runMode evalMode opts
+main = execParser parseOpts >>= runMode
diff --git a/src/lib/Actor.hs b/src/lib/Actor.hs
index c722ebdd..6b935f5f 100644
--- a/src/lib/Actor.hs
+++ b/src/lib/Actor.hs
@@ -17,15 +17,14 @@ import Control.Concurrent
import Control.Monad
import Control.Monad.State.Strict
import Control.Monad.Reader
-import qualified Data.ByteString as BS
import Data.IORef
-import Data.Text.Encoding qualified as T
import Data.Text (Text)
import System.Directory (getModificationTime)
import GHC.Generics
import IncState
import MonadUtil
+import Util (readFileText)
-- === Actor implementation ===
@@ -190,9 +189,6 @@ launchClock intervalMicroseconds mailbox =
type SourceFileContents = Text
type FileWatcher = StateServer (Overwritable SourceFileContents)
-readFileContents :: MonadIO m => FilePath -> m Text
-readFileContents path = liftIO $ T.decodeUtf8 <$> BS.readFile path
-
data FileWatcherMsg =
ClockSignal_FW ()
| Subscribe_FW (SubscribeMsg (Overwritable Text))
@@ -203,7 +199,7 @@ launchFileWatcher path = sliceMailbox Subscribe_FW <$> launchActor (fileWatcherI
fileWatcherImpl :: FilePath -> ActorM FileWatcherMsg ()
fileWatcherImpl path = do
- initContents <- readFileContents path
+ initContents <- readFileText path
t0 <- liftIO $ getModificationTime path
launchClock 100000 =<< selfMailbox ClockSignal_FW
modTimeRef <- newRef t0
@@ -213,7 +209,7 @@ fileWatcherImpl path = do
tOld <- readRef modTimeRef
tNew <- liftIO $ getModificationTime path
when (tNew /= tOld) do
- newContents <- readFileContents path
+ newContents <- readFileText path
update $ OverwriteWith newContents
flushDiffs
writeRef modTimeRef tNew
diff --git a/src/lib/Live/Eval.hs b/src/lib/Live/Eval.hs
index 3b1ba69d..7af2c58d 100644
--- a/src/lib/Live/Eval.hs
+++ b/src/lib/Live/Eval.hs
@@ -9,7 +9,7 @@
module Live.Eval (
watchAndEvalFile, EvalServer, CellState (..), CellUpdate (..), CellsState, CellsUpdate,
NodeList (..), NodeListUpdate (..), subscribeIO,
- CellStatus (..), nodeListAsUpdate, NodeId) where
+ CellStatus (..), nodeListAsUpdate, NodeId, evalFileNonInteractive) where
import Control.Concurrent
import Control.Monad
@@ -20,6 +20,7 @@ import Data.Aeson (ToJSON)
import Data.Functor ((<&>))
import Data.Maybe (fromJust)
import Data.Text (Text)
+import Data.IORef
import Prelude hiding (span)
import GHC.Generics
@@ -29,6 +30,7 @@ import Types.Source
import TopLevel
import ConcreteSyntax
import MonadUtil
+import Util (readFileText)
-- === Top-level interface ===
@@ -43,9 +45,22 @@ watchAndEvalFile fname opts env = do
launchDagEvaluator opts parser env
sourceBlockEvalFun :: EvalConfig -> Mailbox Outputs -> TopStateEx -> SourceBlock -> IO (ExitStatus, TopStateEx)
-sourceBlockEvalFun cfg resultChan env block = do
- let cfg' = cfg { cfgLogAction = send resultChan }
- evalSourceBlockIO cfg' env block
+sourceBlockEvalFun cfg resultChan env block =
+ evalSourceBlockIO cfg (send resultChan) env block
+
+-- === Evaluating non-interactively to produce a standalone HTML page ===
+
+evalFileNonInteractive :: FilePath -> EvalConfig -> TopStateEx -> IO CellsState
+evalFileNonInteractive fname cfg initEnv = do
+ envRef <- newIORef initEnv
+ blocks <- parseSourceBlocks <$> readFileText fname
+ cellStates <- forM blocks \block -> do
+ env <- readIORef envRef
+ ((exitStatus, newEnv), outs) <- captureLogs \logger ->
+ evalSourceBlockIO cfg logger env block
+ writeIORef envRef newEnv
+ return $ CellState block (exitStatusAsCellStatus exitStatus) outs
+ runFreshNameT $ buildNodeList cellStates
-- === DAG diff state ===
@@ -253,9 +268,7 @@ processJobUpdate jobId jobUpdate = do
let nodeId = snd jobId
case jobUpdate of
JobComplete (exitStatus, newEnv) -> do
- let newStatus = case exitStatus of
- ExitSuccess -> Complete
- ExitFailure -> CompleteWithErrors
+ let newStatus = exitStatusAsCellStatus exitStatus
update $ UpdateCellState nodeId $ CellUpdate (OverwriteWith newStatus) mempty
update $ UpdateCurJob Nothing
update $ AppendEnv newEnv
@@ -264,6 +277,11 @@ processJobUpdate jobId jobUpdate = do
PartialJobUpdate result -> update $ UpdateCellState nodeId $ CellUpdate NoChange result
Nothing -> return () -- this job is a zombie
+exitStatusAsCellStatus :: ExitStatus -> CellStatus
+exitStatusAsCellStatus = \case
+ ExitSuccess -> Complete
+ ExitFailure -> CompleteWithErrors
+
nextCellIndex :: EvaluatorM Int
nextCellIndex = do
envs <- getl PrevEnvs
diff --git a/src/lib/Live/Web.hs b/src/lib/Live/Web.hs
index dfac48bc..25a62050 100644
--- a/src/lib/Live/Web.hs
+++ b/src/lib/Live/Web.hs
@@ -4,7 +4,7 @@
-- license that can be found in the LICENSE file or at
-- https://developers.google.com/open-source/licenses/bsd
-module Live.Web (runWeb) where
+module Live.Web (runWeb, generateHTML) where
import Control.Concurrent (readChan)
import Control.Monad (forever)
@@ -17,6 +17,7 @@ import Data.Aeson (ToJSON, encode)
import Data.Binary.Builder (fromByteString)
import Data.ByteString.Lazy (toStrict)
import qualified Data.ByteString as BS
+import System.Directory (withCurrentDirectory)
-- import Paths_dex (getDataFileName)
import RenderHtml
@@ -29,6 +30,16 @@ runWeb fname opts env = do
putStrLn "Streaming output to http://localhost:8000/"
run 8000 $ serveResults resultsChan
+pagesDir :: FilePath
+pagesDir = "pages"
+
+generateHTML :: FilePath -> FilePath -> EvalConfig -> TopStateEx -> IO ()
+generateHTML sourcePath destPath cfg env = do
+ finalState <- evalFileNonInteractive sourcePath cfg env
+ results <- renderResults finalState
+ withCurrentDirectory pagesDir do
+ renderStandaloneHTML destPath results
+
serveResults :: EvalServer -> Application
serveResults resultsSubscribe request respond = do
print (pathInfo request)
@@ -52,7 +63,7 @@ resultStream :: EvalServer -> StreamingBody
resultStream resultsServer write flush = do
sendUpdate ("start"::String)
(initResult, resultsChan) <- subscribeIO resultsServer
- (renderedInit, renderUpdateFun) <- renderResults initResult
+ (renderedInit, renderUpdateFun) <- renderResultsInc initResult
sendUpdate renderedInit
forever $ readChan resultsChan >>= renderUpdateFun >>= sendUpdate
where
diff --git a/src/lib/RenderHtml.hs b/src/lib/RenderHtml.hs
index fe4d7703..d1d884ea 100644
--- a/src/lib/RenderHtml.hs
+++ b/src/lib/RenderHtml.hs
@@ -7,15 +7,15 @@
{-# OPTIONS_GHC -Wno-orphans #-}
{-# OPTIONS_GHC -Wno-incomplete-patterns #-}
-module RenderHtml (
- progHtml, pprintHtml, ToMarkup, renderSourceBlock, renderResults,
- RenderedSourceBlock, RenderedOutputs) where
+module RenderHtml (renderResults, renderResultsInc, renderStandaloneHTML) where
import Text.Blaze.Internal (MarkupM)
import Text.Blaze.Html5 as H hiding (map, a, b)
import Text.Blaze.Html5.Attributes as At
import Text.Blaze.Html.Renderer.String
-import Data.Aeson (ToJSON (..))
+import Data.ByteString.Lazy (toStrict)
+import qualified Data.ByteString as BS
+import Data.Aeson (ToJSON (..), encode)
import qualified Data.Map.Strict as M
import Control.Monad.State.Strict
import Data.Foldable (fold)
@@ -23,13 +23,10 @@ import Data.Maybe (fromJust)
import Data.Functor ((<&>))
import Data.String (fromString)
import Data.Text qualified as T
-import Data.Text.IO qualified as T
import CMark (commonmarkToHtml)
-import System.IO.Unsafe
import GHC.Generics
import Err
-import Paths_dex (getDataFileName)
import PPrint
import Types.Source
import Util (unsnoc, foldMapM)
@@ -59,8 +56,11 @@ instance IncState RenderedCellState where
applyDiff (RenderedCellState sb status result) (RenderedCellUpdate status' result') =
RenderedCellState sb (fromOverwritable (applyDiff (Overwritable status) status')) (result <> result')
-renderResults :: CellsState -> IO (RenderingUpdate, CellsUpdate -> IO RenderingUpdate)
-renderResults initState = do
+renderResults :: CellsState -> IO RenderingUpdate
+renderResults cellsState = fst <$> renderResultsInc cellsState
+
+renderResultsInc :: CellsState -> IO (RenderingUpdate, CellsUpdate -> IO RenderingUpdate)
+renderResultsInc initState = do
(initRender, updates) <- runIncM renderCells initState
return (nodeListAsUpdate initRender, updates)
@@ -265,38 +265,26 @@ instance ToJSON HighlightType
-- -----------------
-cssSource :: T.Text
-cssSource = unsafePerformIO $
- T.readFile =<< getDataFileName "static/style.css"
-{-# NOINLINE cssSource #-}
-
-javascriptSource :: T.Text
-javascriptSource = unsafePerformIO $
- T.readFile =<< getDataFileName "static/index.js"
-{-# NOINLINE javascriptSource #-}
-
-pprintHtml :: ToMarkup a => a -> String
-pprintHtml x = renderHtml $ toMarkup x
-
-progHtml :: (ToMarkup a, ToMarkup b) => [(a, b)] -> String
-progHtml blocks = renderHtml $ wrapBody $ map toHtmlBlock blocks
- where toHtmlBlock (block,outputs) = toMarkup block <> toMarkup outputs
+renderStandaloneHTML :: FilePath -> RenderingUpdate -> IO ()
+renderStandaloneHTML pagePath renderingInfo = do
+ let jsonPath = pagePath ++ ".json"
+ let htmlPath = pagePath ++ ".html"
+ BS.writeFile jsonPath $ toStrict $ encode renderingInfo
+ writeFile htmlPath $ renderHtml $ buildMainHtml jsonPath
-wrapBody :: [Html] -> Html
-wrapBody blocks = docTypeHtml $ do
- H.head $ do
+buildMainHtml :: FilePath -> Html
+buildMainHtml jsonPath = docTypeHtml $ do
+ H.head do
H.meta ! charset "UTF-8"
- -- Base CSS stylesheet.
- H.style ! type_ "text/css" $ toHtml cssSource
- -- KaTeX CSS and JavaScript.
- H.link ! rel "stylesheet" ! href "https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css"
- H.script ! defer "" ! src "https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js" $ mempty
- H.script ! defer "" ! src "https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js"
- ! onload jsSource $ mempty
- H.body $ H.div inner ! At.id "main-output"
+ H.link ! rel "stylesheet" ! type_ "text/css" ! href "/static/style.css"
+ H.body ! onload (textValue $ fromString jsSource) $ do
+ H.div mempty ! At.id "minimap"
+ H.div "(hover over code for more information)" ! At.id "hover-info"
+ H.div mempty ! At.id "main-output"
+ H.script ! src "/static/index.js" $ mempty
where
- inner = foldMap (cdiv "cell") blocks
- jsSource = textValue $ javascriptSource <> "render(RENDER_MODE.STATIC);"
+ jsSource :: String
+ jsSource = "render('Static', '/" ++ jsonPath ++ "');"
mdToHtml :: T.Text -> Html
mdToHtml s = preEscapedText $ commonmarkToHtml [] s
diff --git a/src/lib/TopLevel.hs b/src/lib/TopLevel.hs
index 777b7051..aa0f94bd 100644
--- a/src/lib/TopLevel.hs
+++ b/src/lib/TopLevel.hs
@@ -8,13 +8,12 @@
module TopLevel (
EvalConfig (..), Topper, TopperM, runTopperM,
- evalSourceBlockRepl, OptLevel (..),
- evalSourceText, TopStateEx (..), LibPath (..),
+ evalSourceBlockRepl, OptLevel (..), TopStateEx (..), LibPath (..),
evalSourceBlockIO, initTopState, loadCache, storeCache, clearCache,
ensureModuleLoaded, importModule, printCodegen,
loadObject, toCFunction, packageLLVMCallable,
- simpOptimizations, loweredOptimizations, compileTopLevelFun, ErrorHandling (..),
- ExitStatus (..)) where
+ simpOptimizations, loweredOptimizations, compileTopLevelFun,
+ ExitStatus (..), parseSourceBlocks, captureLogs) where
import Data.Functor
import Data.Maybe (catMaybes)
@@ -23,7 +22,8 @@ import Control.Monad.Writer.Strict hiding (pass)
import Control.Monad.State.Strict
import Control.Monad.Reader
import qualified Data.ByteString as BS
-import Data.Text (Text)
+import qualified Data.Text as T
+import Data.IORef
import Data.Text.Prettyprint.Doc
import Data.Store (encode, decode)
import Data.String (fromString)
@@ -82,7 +82,6 @@ import Vectorize
-- === top-level monad ===
data LibPath = LibDirectory FilePath | LibBuiltinPath
-data ErrorHandling = HaltOnErr | ContinueOnErr
data EvalConfig = EvalConfig
{ backendName :: Backend
@@ -90,10 +89,9 @@ data EvalConfig = EvalConfig
, preludeFile :: Maybe FilePath
, optLevel :: OptLevel
, printBackend :: PrintBackend
- , errorHandling :: ErrorHandling
- , cfgLogLevel :: LogLevel
- , cfgLogAction :: Outputs -> IO ()}
+ , cfgLogLevel :: LogLevel }
+type LogAction = Outputs -> IO ()
class Monad m => ConfigReader m where
getConfig :: m EvalConfig
@@ -114,6 +112,7 @@ class ( forall n. Fallible (m n)
data TopperReaderData = TopperReaderData
{ topperEvalConfig :: EvalConfig
+ , topperLogAction :: LogAction
, topperRuntimeEnv :: RuntimeEnv }
newtype TopperM (n::S) a = TopperM
@@ -132,12 +131,12 @@ data TopSerializedStateEx where
TopSerializedStateEx :: Distinct n => SerializedEnv n -> TopSerializedStateEx
runTopperM
- :: EvalConfig -> TopStateEx
+ :: EvalConfig -> LogAction -> TopStateEx
-> (forall n. Mut n => TopperM n a)
-> IO (a, TopStateEx)
-runTopperM opts (TopStateEx env rtEnv) cont = do
+runTopperM opts logger (TopStateEx env rtEnv) cont = do
Abs frag (LiftE result) <-
- flip runReaderT (TopperReaderData opts rtEnv) $
+ flip runReaderT (TopperReaderData opts logger rtEnv) $
runTopBuilderT env $ runTopperM' do
localTopBuilder $ LiftE <$> cont
return (result, extendTopEnv env rtEnv frag)
@@ -157,31 +156,22 @@ allocateDynamicVarKeyPtrs = do
ptr <- createTLSKey
return [(OutStreamDyvar, castPtr ptr)]
+captureLogs :: (LogAction -> IO a) -> IO (a, Outputs)
+captureLogs cont = do
+ ref <- newIORef mempty
+ ans <- cont \outs -> modifyIORef ref (<>outs)
+ finalOuts <- readIORef ref
+ return (ans, finalOuts)
+
-- ======
+parseSourceBlocks :: T.Text -> [SourceBlock]
+parseSourceBlocks source = uModuleSourceBlocks $ parseUModule Main source
+
evalSourceBlockIO
- :: EvalConfig -> TopStateEx -> SourceBlock -> IO (ExitStatus, TopStateEx)
-evalSourceBlockIO opts env block =
- runTopperM opts env $ evalSourceBlockRepl block
-
--- Used for the top-level source file (rather than imported modules)
-evalSourceText :: (Topper m, Mut n) => Text -> (SourceBlock -> IO ()) -> m n ()
-evalSourceText source logSourceBlock = do
- let UModule mname deps sbs = parseUModule Main source
- mapM_ ensureModuleLoaded deps
- evalSourceBlocks mname sbs
- where
- evalSourceBlocks mname = \case
- [] -> return ()
- sb:rest -> do
- liftIO $ logSourceBlock sb
- evalSourceBlock mname sb >>= \case
- Success () -> return ()
- Failure e -> do
- logTop $ Error e
- (errorHandling <$> getConfig) >>= \case
- HaltOnErr -> return ()
- ContinueOnErr -> evalSourceBlocks mname rest
+ :: EvalConfig -> LogAction -> TopStateEx -> SourceBlock -> IO (ExitStatus, TopStateEx)
+evalSourceBlockIO opts logger env block =
+ runTopperM opts logger env $ evalSourceBlockRepl block
data ExitStatus = ExitSuccess | ExitFailure deriving (Show)
@@ -820,7 +810,7 @@ instance Logger Outputs (TopperM n) where
getLogLevel = cfgLogLevel <$> getConfig
instance HasIOLogger Outputs (TopperM n) where
- getIOLogAction = cfgLogAction <$> getConfig
+ getIOLogAction = TopperM $ asks topperLogAction
instance Generic TopStateEx where
type Rep TopStateEx = Rep (Env UnsafeS, RuntimeEnv)
diff --git a/src/lib/Util.hs b/src/lib/Util.hs
index 4dbc43ed..4d7e7241 100644
--- a/src/lib/Util.hs
+++ b/src/lib/Util.hs
@@ -23,6 +23,8 @@ import Data.Maybe (catMaybes, mapMaybe)
import Data.List (sort)
import Data.Hashable (Hashable)
import Data.Store (Store)
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as T
import qualified Data.List.NonEmpty as NE
import qualified Data.ByteString as BS
import Data.Foldable
@@ -360,6 +362,9 @@ instance Pretty a => Pretty (Tree a) where
Leaf x -> pretty x
Branch xs -> pretty xs
+readFileText :: MonadIO m => FilePath -> m T.Text
+readFileText fname = liftIO $ T.decodeUtf8 <$> BS.readFile fname
+
-- === bytestrings paired with their hash digest ===
-- TODO: use something other than a string to store the digest
diff --git a/static/dynamic.html b/static/dynamic.html
index cb4da817..d3dc9891 100644
--- a/static/dynamic.html
+++ b/static/dynamic.html
@@ -4,29 +4,11 @@
<meta charset="UTF-8">
<title>Dex Output</title>
<link rel="stylesheet" href="/style.css" type="text/css" />
- <!-- KaTeX: LaTeX rendering -->
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css" integrity="sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X" crossorigin="anonymous">
- <script defer src="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js" integrity="sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4" crossorigin="anonymous"></script>
- <!-- Do dynamic webpage rendering on-load. -->
- <!-- The `onerror` attribute renders Dex output dynamically even if
- KaTeX fails to load (e.g., if running dex web locally with no
- network); the latex will just remain unrendered. -->
- <script defer src="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js"
- integrity="sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa"
- onload="render('Dynamic');"
- onerror="render('Dynamic');"
- crossorigin="anonymous"></script>
- <!-- jQuery -->
- <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"></script>
</head>
-
-<body>
+<body onload="render('Dynamic');">
<div id="minimap"> </div>
- <div id="hover-info"> (hover over code for more information) </div>
+ <div id="hover-info">(hover over code for more information)</div>
<main id="main-output"></main>
-
-<script src="/index.js""></script>
-
+ <script src="/index.js"></script>
</body>
-
</html>
diff --git a/static/index.ts b/static/index.ts
index 9d712f37..adf19ee6 100644
--- a/static/index.ts
+++ b/static/index.ts
@@ -343,11 +343,15 @@ function highlightTreeNode(isTemporary: boolean, node: TreeNode, highlightType:H
}})}
}
type RenderMode = "Static" | "Dynamic"
-function render(renderMode:RenderMode) {
+function render(renderMode:RenderMode, jsonData:string) {
switch (renderMode) {
case "Static":
- // For static pages, simply call rendering functions once.
- // renderLaTeX(document);
+ const req = new XMLHttpRequest()
+ req.open('GET', jsonData, true)
+ req.responseType = 'json'
+ req.onload = function() {
+ processUpdates(req.response)}
+ req.send()
break
case "Dynamic":
const source = new EventSource("/getnext");