Skip to content

Commit cabc4b9

Browse files
committed
Add support for compiling to wasm.
This builds on the excellent work by @TerrorJack at haskell-wasm/pandoc-wasm. However, we can now make use of a new http cabal flag that simplify things, in addition to a small API change (FromJSON for LogMessage). The interface of `wasm_main` now taes a JSON-encoded Opts as input, instead of a command-line argument string. Input/output files can now be specified (these will have to come from the virtual file system). The Makefile has a new pandoc.wasm target. Note that the environment variable `OPTIMIZE_WASM` may be set to 0 for faster builds. Log messages and errors are rendered to `/stderr`. Regular output goes to `/stdout`, and input is taken from `/stdin`. Another file `/warnings` collects warnings (using `--log-file`); these are then rendered to `/stderr`.
1 parent 51b459f commit cabc4b9

File tree

4 files changed

+128
-4
lines changed

4 files changed

+128
-4
lines changed

Makefile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ WEBSITE=../../web/pandoc.org
1717
REVISION?=1
1818
BENCHARGS?=--csv bench_$(TIMESTAMP).csv $(BASELINECMD) --timeout=6 +RTS -T --nonmoving-gc -RTS $(if $(PATTERN),--pattern "$(PATTERN)",)
1919
pandoc=$(shell cabal list-bin $(CABALOPTS) pandoc-cli)
20+
OPTIMIZE_WASM?=1
2021

2122
all: build test binpath ## build executable and run tests
2223
.PHONY: all
@@ -326,3 +327,11 @@ release-checklist-$(VERSION).org: RELEASE-CHECKLIST-TEMPLATE.org
326327
hie.yaml: ## regenerate hie.yaml
327328
gen-hie > $@
328329
.PHONY: hie.yaml
330+
331+
pandoc.wasm:
332+
rm $@
333+
ifeq ($(OPTIMIZE_WASM),1)
334+
echo 'wasm32-wasi-cabal build pandoc-cli && wasm-opt --low-memory-unused --converge --gufa --flatten --rereloop -Oz $$(wasm32-wasi-cabal list-bin pandoc-cli) -o $@' | nix shell 'gitlab:haskell-wasm/ghc-wasm-meta?host=gitlab.haskell.org'
335+
else
336+
echo 'wasm32-wasi-cabal build pandoc-cli && cp $$(wasm32-wasi-cabal list-bin pandoc-cli) $@' | nix shell 'gitlab:haskell-wasm/ghc-wasm-meta?host=gitlab.haskell.org'
337+
endif

cabal.project

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ packages: .
22
pandoc-lua-engine
33
pandoc-server
44
pandoc-cli
5-
tests: True
6-
flags: +embed_data_files
75
constraints: skylighting-format-blaze-html >= 0.1.1.3,
86
skylighting-format-context >= 0.1.0.2
97

8+
package pandoc
9+
flags: +embed_data_files +http
10+
1011
source-repository-package
1112
type: git
1213
location: https://github.com/jgm/asciidoc-hs.git
@@ -31,3 +32,66 @@ source-repository-package
3132
type: git
3233
location: https://github.com/jgm/djoths.git
3334
tag: a6fc625fd58aa39df05dd1d82ee005e681815095
35+
36+
if arch(wasm32)
37+
tests: False
38+
39+
package aeson
40+
flags: -ordered-keymap
41+
42+
package crypton
43+
ghc-options: -optc-DARGON2_NO_THREADS
44+
45+
package digest
46+
flags: -pkg-config
47+
48+
package pandoc
49+
flags: +embed_data_files -http
50+
51+
package pandoc-cli
52+
flags: -lua -server
53+
54+
allow-newer:
55+
all:base,
56+
all:binary,
57+
all:bytestring,
58+
all:containers,
59+
all:ghc-bignum,
60+
all:template-haskell,
61+
all:text,
62+
all:time
63+
64+
source-repository-package
65+
type: git
66+
location: https://github.com/haskell-wasm/conduit.git
67+
tag: ff33329247f2ef321dcab836e98c1bcfaff2bd13
68+
subdir: conduit-extra
69+
70+
source-repository-package
71+
type: git
72+
location: https://github.com/haskell-wasm/foundation.git
73+
tag: 8e6dd48527fb429c1922083a5030ef88e3d58dd3
74+
subdir: basement
75+
76+
source-repository-package
77+
type: git
78+
location: https://github.com/haskell-wasm/hs-memory.git
79+
tag: a198a76c584dc2cfdcde6b431968de92a5fed65e
80+
81+
source-repository-package
82+
type: git
83+
location: https://github.com/haskell-wasm/streaming-commons.git
84+
tag: 7e9c38b2fd55ce50d3f74fe708ca47db8c9bb315
85+
86+
source-repository-package
87+
type: git
88+
location: https://github.com/haskell-wasm/xml.git
89+
tag: bc793dc9bc29c92245d3482a54d326abd3ae1403
90+
subdir: xml-conduit
91+
92+
-- https://github.com/haskellari/splitmix/pull/73
93+
source-repository-package
94+
type: git
95+
location: https://github.com/amesgen/splitmix
96+
tag: 5f5b766d97dc735ac228215d240a3bb90bc2ff75
97+

pandoc-cli/pandoc-cli.cabal

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ common common-options
6161

6262
common common-executable
6363
import: common-options
64-
ghc-options: -rtsopts -with-rtsopts=-A8m -threaded
64+
ghc-options: -rtsopts -with-rtsopts=-H64m
6565

6666
executable pandoc
6767
import: common-executable
@@ -74,6 +74,11 @@ executable pandoc
7474
text
7575
other-modules: PandocCLI.Lua
7676
, PandocCLI.Server
77+
78+
if arch(wasm32)
79+
build-depends: aeson, bytestring
80+
ghc-options: -optl-Wl,--export=__wasm_call_ctors,--export=hs_init_with_rtsopts,--export=malloc,--export=wasm_main
81+
7782
if flag(nightly)
7883
cpp-options: -DNIGHTLY
7984
build-depends: template-haskell,

pandoc-cli/src/pandoc.hs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{-# LANGUAGE CPP #-}
2+
{-# LANGUAGE ScopedTypeVariables #-}
23
{-# LANGUAGE TemplateHaskell #-}
4+
35
{- |
46
Module : Main
57
Copyright : Copyright (C) 2006-2024 John MacFarlane
@@ -15,7 +17,8 @@ writers.
1517
module Main where
1618
import qualified Control.Exception as E
1719
import System.Environment (getArgs, getProgName)
18-
import Text.Pandoc.App ( convertWithOpts, defaultOpts, options
20+
import Data.Maybe (fromMaybe)
21+
import Text.Pandoc.App ( convertWithOpts, defaultOpts, options, Opt(..)
1922
, parseOptionsFromArgs, handleOptInfo, versionInfo )
2023
import Text.Pandoc.Error (handleError)
2124
import Data.Monoid (Any(..))
@@ -29,6 +32,20 @@ import qualified Language.Haskell.TH as TH
2932
import Data.Time
3033
#endif
3134

35+
#if defined(wasm32_HOST_ARCH)
36+
import Control.Exception
37+
import Foreign
38+
import Foreign.C
39+
import qualified Data.Aeson as Aeson
40+
import qualified Text.Pandoc.UTF8 as UTF8
41+
import qualified Data.Text.IO as TIO
42+
import qualified Data.ByteString.Lazy as BL
43+
import Text.Pandoc.Error (renderError, PandocError)
44+
import Text.Pandoc.Logging (showLogMessage)
45+
#else
46+
import System.Exit
47+
#endif
48+
3249
#ifdef NIGHTLY
3350
versionSuffix :: String
3451
versionSuffix = "-nightly-" ++
@@ -39,6 +56,35 @@ versionSuffix :: String
3956
versionSuffix = ""
4057
#endif
4158

59+
#if defined(wasm32_HOST_ARCH)
60+
61+
foreign export ccall "wasm_main" wasm_main :: Ptr CChar -> Int -> IO ()
62+
63+
wasm_main :: Ptr CChar -> Int -> IO ()
64+
wasm_main raw_args_ptr raw_args_len =
65+
catch act (\(err :: SomeException) -> writeFile "/stderr" (show err))
66+
where
67+
act = do
68+
args <- peekCStringLen (raw_args_ptr, raw_args_len)
69+
free raw_args_ptr
70+
engine <- getEngine
71+
let aesonRes = Aeson.eitherDecode (UTF8.fromStringLazy args)
72+
case aesonRes of
73+
Left e -> error e
74+
Right opts -> do
75+
let opts' = opts{ optInputFiles = Just $ fromMaybe ["/stdin"] (optInputFiles opts)
76+
, optOutputFile = Just $ fromMaybe "/stdout" (optOutputFile opts)
77+
, optLogFile = Just $ fromMaybe "/warnings" (optLogFile opts)
78+
}
79+
E.catch (convertWithOpts engine opts') $
80+
\(e :: PandocError) -> TIO.writeFile "/stderr" (renderError e)
81+
res <- Aeson.eitherDecode <$> BL.readFile "/warnings"
82+
case res of
83+
Left e -> writeFile "/stderr" e
84+
Right msgs -> TIO.writeFile "/stderr" (T.unlines $ map showLogMessage msgs)
85+
86+
#endif
87+
4288
main :: IO ()
4389
main = E.handle (handleError . Left) $ do
4490
prg <- getProgName

0 commit comments

Comments
 (0)