Skip to content

Commit d13702f

Browse files
committed
WIP
0 parents  commit d13702f

File tree

21 files changed

+860
-0
lines changed

21 files changed

+860
-0
lines changed

.ghcid

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
--command "wasm32-wasi-cabal repl --enable-multi-repl"
2+
--restart cabal.project
3+
--restart canary.cabal

.github/workflows/ci.yml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: ci
2+
3+
on:
4+
merge_group:
5+
pull_request:
6+
push:
7+
branches:
8+
- master
9+
workflow_dispatch:
10+
11+
jobs:
12+
ci:
13+
name: ci-${{ matrix.flavour }}
14+
runs-on: ubuntu-24.04
15+
continue-on-error: ${{ matrix.experimental }}
16+
strategy:
17+
fail-fast: false
18+
matrix:
19+
flavour:
20+
- "9.12"
21+
- "9.10"
22+
experimental:
23+
- false
24+
include:
25+
- flavour: "gmp"
26+
experimental: true
27+
steps:
28+
29+
- name: checkout
30+
uses: actions/checkout@v4
31+
32+
- name: ghc-wasm-meta
33+
run: |
34+
pushd "$(mktemp -d)"
35+
curl -f -L https://gitlab.haskell.org/haskell-wasm/ghc-wasm-meta/-/archive/master/ghc-wasm-meta-master.tar.gz | tar xz --strip-components=1
36+
PLAYWRIGHT=1 ./setup.sh
37+
~/.ghc-wasm/add_to_github_path.sh
38+
cp cabal.project.local ${{ github.workspace }}
39+
popd
40+
env:
41+
FLAVOUR: ${{ matrix.flavour }}
42+
43+
- name: gen-plan-json
44+
run: |
45+
wasm32-wasi-cabal build --dry-run
46+
47+
- name: cabal-cache
48+
uses: actions/cache@v4
49+
with:
50+
key: ${{ matrix.flavour }}-${{ hashFiles('dist-newstyle/cache/plan.json') }}
51+
restore-keys: ${{ matrix.flavour }}-
52+
path: |
53+
~/.ghc-wasm/.cabal/store
54+
dist-newstyle
55+
56+
- name: ci
57+
run: |
58+
./util/ci

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dist-newstyle

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Revision history for canary
2+
3+
## 0.1.0.0 -- YYYY-mm-dd
4+
5+
* First version. Released on an unsuspecting world.

LICENSE

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
Copyright (c) 2025, Cheng Shao
2+
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions are met:
6+
7+
* Redistributions of source code must retain the above copyright
8+
notice, this list of conditions and the following disclaimer.
9+
10+
* Redistributions in binary form must reproduce the above
11+
copyright notice, this list of conditions and the following
12+
disclaimer in the documentation and/or other materials provided
13+
with the distribution.
14+
15+
* Neither the name of the copyright holder nor the names of its
16+
contributors may be used to endorse or promote products derived
17+
from this software without specific prior written permission.
18+
19+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23+
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

cabal.project

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
packages: .
2+
3+
tests: True
4+
5+
source-repository-package
6+
type: git
7+
location: https://github.com/amesgen/splitmix.git
8+
tag: cea9e31bdd849eb0c17611bb99e33d590e126164
9+
10+
package *
11+
optimization: 2
12+
13+
package tasty
14+
flags: -unix
15+
16+
package vector
17+
flags: -boundschecks

canary.cabal

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
cabal-version: 3.14
2+
name: canary
3+
version: 0.1.0.0
4+
license: BSD-3-Clause
5+
license-file: LICENSE
6+
author: Cheng Shao
7+
maintainer: [email protected]
8+
build-type: Simple
9+
extra-doc-files: CHANGELOG.md
10+
11+
common deps
12+
build-depends:
13+
aeson,
14+
async,
15+
base,
16+
bytestring,
17+
ghc-experimental,
18+
primitive,
19+
servant-client-core,
20+
text,
21+
vector,
22+
vector-stream,
23+
24+
default-language: GHC2024
25+
ghc-options:
26+
-Weverything
27+
-Wno-all-missed-specialisations
28+
-Wno-implicit-prelude
29+
-Wno-missing-export-lists
30+
-Wno-missing-import-lists
31+
-Wno-missing-kind-signatures
32+
-Wno-missing-local-signatures
33+
-Wno-missing-poly-kind-signatures
34+
-Wno-missing-role-annotations
35+
-Wno-missing-safe-haskell-mode
36+
-Wno-prepositive-qualified-module
37+
-Wno-unsafe
38+
39+
library
40+
import: deps
41+
exposed-modules:
42+
GHC.Wasm.JS.Array
43+
GHC.Wasm.JS.AsyncIterator
44+
GHC.Wasm.JS.ESM
45+
GHC.Wasm.JS.Free
46+
GHC.Wasm.JS.JSON
47+
GHC.Wasm.JS.Promise
48+
GHC.Wasm.JS.ReadableStream
49+
GHC.Wasm.JS.RuntimeFFI
50+
GHC.Wasm.JS.String
51+
GHC.Wasm.JS.Uint8Array
52+
53+
hs-source-dirs: src
54+
55+
test-suite canary-test
56+
import: deps
57+
type: exitcode-stdio-1.0
58+
hs-source-dirs: test
59+
main-is: Main.hs
60+
build-depends:
61+
canary,
62+
quickcheck-instances,
63+
tasty,
64+
tasty-bench,
65+
tasty-hunit,
66+
tasty-quickcheck,

src/GHC/Wasm/JS/Array.hs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
module GHC.Wasm.JS.Array (
2+
JSArray (..),
3+
fromJSVals,
4+
toJSVals,
5+
) where
6+
7+
import qualified Data.Vector.Strict as V
8+
import GHC.Wasm.Prim
9+
import System.IO.Unsafe
10+
11+
newtype JSArray = JSArray { unJSArray :: JSVal }
12+
13+
fromJSVals :: V.Vector JSVal -> JSArray
14+
fromJSVals vs = unsafePerformIO $ do
15+
arr <- arrayNew
16+
V.forM_ vs $ arrayPush arr
17+
pure arr
18+
19+
toJSVals :: JSArray -> V.Vector JSVal
20+
toJSVals arr = V.generate (arrayLength arr) (arrayIndex arr)
21+
22+
foreign import javascript unsafe
23+
"[]"
24+
arrayNew :: IO JSArray
25+
26+
foreign import javascript unsafe
27+
"$1.push($2)"
28+
arrayPush :: JSArray -> JSVal -> IO ()
29+
30+
foreign import javascript unsafe
31+
"$1.length"
32+
arrayLength :: JSArray -> Int
33+
34+
foreign import javascript unsafe
35+
"$1[$2]"
36+
arrayIndex :: JSArray -> Int -> JSVal

src/GHC/Wasm/JS/AsyncIterator.hs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
module GHC.Wasm.JS.AsyncIterator (
2+
JSAsyncIterable (..),
3+
JSAsyncIterator (..),
4+
fromJSAsyncIterable,
5+
toVectorStream,
6+
toLazyByteString,
7+
) where
8+
9+
import Control.Exception
10+
import qualified Data.ByteString.Lazy.Internal as LBS
11+
import Data.Coerce
12+
import qualified Data.Stream.Monadic as VS
13+
import GHC.Wasm.JS.Free
14+
import GHC.Wasm.JS.Uint8Array
15+
import GHC.Wasm.Prim
16+
import System.IO.Unsafe
17+
18+
newtype JSAsyncIterable = JSAsyncIterable { unJSAsyncIterable :: JSVal }
19+
20+
newtype JSAsyncIterator = JSAsyncIterator { unJSAsyncIterator :: JSVal }
21+
22+
foreign import javascript unsafe
23+
"$1[Symbol.asyncIterator] ? $1[Symbol.asyncIterator]() : $1[Symbol.iterator]()"
24+
fromJSAsyncIterable :: JSAsyncIterable -> IO JSAsyncIterator
25+
26+
newtype JSIteratorResult = IteratorResult JSVal
27+
28+
toVectorStream :: JSAsyncIterator -> VS.Stream IO JSVal
29+
toVectorStream iter = flip VS.unfoldrM () $ \_ -> do
30+
r <- jsAsyncIteratorNext iter
31+
if jsIteratorResultDone r
32+
then do
33+
free r
34+
pure Nothing
35+
else do
36+
v <- jsIteratorResultValue r
37+
free r
38+
pure $ Just (v, ())
39+
40+
toLazyByteString :: JSAsyncIterator -> IO LBS.ByteString
41+
toLazyByteString iter = w
42+
where
43+
w = unsafeInterleaveIO $ do
44+
r <- jsAsyncIteratorNext iter
45+
if jsIteratorResultDone r
46+
then do
47+
free r
48+
pure LBS.Empty
49+
else do
50+
v <- coerce <$> jsIteratorResultValue r
51+
free r
52+
c <- evaluate $ toStrictByteString v
53+
free v
54+
LBS.chunk c <$> w
55+
56+
foreign import javascript safe
57+
"$1.next()"
58+
jsAsyncIteratorNext :: JSAsyncIterator -> IO JSIteratorResult
59+
60+
foreign import javascript unsafe
61+
"$1.done"
62+
jsIteratorResultDone :: JSIteratorResult -> Bool
63+
64+
foreign import javascript unsafe
65+
"$1.value"
66+
jsIteratorResultValue :: JSIteratorResult -> IO JSVal

src/GHC/Wasm/JS/ESM.hs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
{-# LANGUAGE OverloadedStrings #-}
2+
3+
module GHC.Wasm.JS.ESM
4+
( PkgSpec (..),
5+
JSModule (..),
6+
jsImport,
7+
JSElement (..),
8+
cssImport,
9+
elementRemove,
10+
npmESM,
11+
npmFile,
12+
jsrESM,
13+
)
14+
where
15+
16+
import Control.Exception
17+
import Data.String
18+
import qualified Data.Text as T
19+
import GHC.Wasm.JS.Free
20+
import qualified GHC.Wasm.JS.String as String
21+
import GHC.Wasm.Prim
22+
23+
newtype PkgSpec = PkgSpec { unPkgSpec :: T.Text }
24+
deriving newtype (IsString)
25+
26+
newtype JSModule = JSModule { unJSModule :: JSVal }
27+
28+
jsImport :: T.Text -> IO JSModule
29+
jsImport url = do
30+
let url' = String.fromStrictText url
31+
m <- jsLazyImport url'
32+
free url'
33+
evaluate m
34+
35+
newtype JSElement = JSElement { unJSElement :: JSVal }
36+
37+
cssImport :: T.Text -> IO JSElement
38+
cssImport url = do
39+
let url' = String.fromStrictText url
40+
n <- cssLazyImport url'
41+
free url'
42+
evaluate n
43+
44+
foreign import javascript unsafe
45+
"$1.remove()"
46+
elementRemove :: JSElement -> IO ()
47+
48+
npmESM :: PkgSpec -> T.Text -> T.Text
49+
npmESM pkg rest = "https://cdn.jsdelivr.net/npm/" <> unPkgSpec pkg <> rest <> "/+esm"
50+
51+
npmFile :: PkgSpec -> T.Text -> T.Text
52+
npmFile pkg f =
53+
"https://cdn.jsdelivr.net/npm/" <> unPkgSpec pkg <> "/" <> f
54+
55+
jsrESM :: PkgSpec -> T.Text
56+
jsrESM pkg = "https://esm.sh/jsr/" <> unPkgSpec pkg
57+
58+
foreign import javascript safe
59+
"import($1)"
60+
jsLazyImport :: JSString -> IO JSModule
61+
62+
foreign import javascript safe
63+
"new Promise((res, rej) => { const l = document.createElement('link'); l.rel = 'stylesheet'; l.href = $1; l.addEventListener('load', () => res(l)); l.addEventListener('error', rej); document.head.append(l); })"
64+
cssLazyImport :: JSString -> IO JSElement

0 commit comments

Comments
 (0)