diff --git a/cabal.project b/cabal.project index 861dc68..9fba498 100644 --- a/cabal.project +++ b/cabal.project @@ -1,5 +1,20 @@ packages: - packages/*/*.cabal + packages/diagnostician + packages/diagnostician-terminal + packages/diagnostician-html + packages/fnotation + packages/coln-compiler +if !os(wasi) + packages: + packages/coln-manual-dev + packages/coln-do + packages/coln-repl + packages/coln-ls + packages/coln-cli +else + packages: + packages/coln-compiler-wasm + jobs: $ncpus semaphore: true tests: True @@ -9,3 +24,10 @@ constraints: -- https://github.com/haskell/lsp/issues/641 lsp >= 2.8.0.0, lsp-types >= 2.4, + +if impl(ghc >= 9.14) + allow-newer: + -- https://github.com/dmwit/ordered-containers/pull/36 + ordered-containers:containers, + -- https://github.com/georgefst/prettyprinter-lucid/issues/7 + prettyprinter-lucid:base, diff --git a/flake.lock b/flake.lock index 664ba68..145fa01 100644 --- a/flake.lock +++ b/flake.lock @@ -18,7 +18,62 @@ "type": "github" } }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "ghc-wasm-meta": { + "inputs": { + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs" + }, + "locked": { + "host": "gitlab.haskell.org", + "lastModified": 1782204359, + "narHash": "sha256-iC+iPfZOCTrB/WZaNHb9CQsZ+9u78UO/dyVZ/3OZB+M=", + "owner": "haskell-wasm", + "repo": "ghc-wasm-meta", + "rev": "9480caba4d6e622a67298d81307086582cc3ffe6", + "type": "gitlab" + }, + "original": { + "host": "gitlab.haskell.org", + "owner": "haskell-wasm", + "repo": "ghc-wasm-meta", + "type": "gitlab" + } + }, "nixpkgs": { + "locked": { + "lastModified": 1781472460, + "narHash": "sha256-dqwpb1o0xIwb1rv3PPbpY7RBm8heFiOLAubszNLBefc=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "8c14fa3ccacddaec887134e523083b63c8ea57ac", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-26.05-darwin", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { "locked": { "lastModified": 1780145889, "narHash": "sha256-md0zn0RnwNvPyASas1yG5YUuwQ4ALA6ucL50l0DvqCo=", @@ -34,7 +89,7 @@ "type": "github" } }, - "nixpkgs_2": { + "nixpkgs_3": { "locked": { "lastModified": 1744536153, "narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=", @@ -53,13 +108,14 @@ "root": { "inputs": { "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs", + "ghc-wasm-meta": "ghc-wasm-meta", + "nixpkgs": "nixpkgs_2", "rust-overlay": "rust-overlay" } }, "rust-overlay": { "inputs": { - "nixpkgs": "nixpkgs_2" + "nixpkgs": "nixpkgs_3" }, "locked": { "lastModified": 1782184651, @@ -89,6 +145,21 @@ "repo": "default", "type": "github" } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index f6a52aa..6575e34 100644 --- a/flake.nix +++ b/flake.nix @@ -3,6 +3,7 @@ nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05"; flake-utils.url = "github:numtide/flake-utils"; rust-overlay.url = "github:oxalica/rust-overlay"; + ghc-wasm-meta.url = "gitlab:haskell-wasm/ghc-wasm-meta?host=gitlab.haskell.org"; }; outputs = inputs@{ @@ -156,6 +157,7 @@ }; inherit (packages) forester coln-manual-dev; + haskell-wasm = inputs.ghc-wasm-meta.packages.${system}.default; in { inherit packages; @@ -177,9 +179,11 @@ forester fourmolu esbuild + haskell-wasm haskell.compiler.ghc912 haskell.packages.ghc912.haskell-language-server haskellPackages.cabal-gild + jq just nodejs pnpm @@ -190,6 +194,7 @@ openssl pkg-config reuse + simple-http-server tectonic typescript zlib diff --git a/packages/coln-compiler-wasm/Lib.hs b/packages/coln-compiler-wasm/Lib.hs new file mode 100644 index 0000000..51bcc54 --- /dev/null +++ b/packages/coln-compiler-wasm/Lib.hs @@ -0,0 +1,71 @@ +-- SPDX-FileCopyrightText: 2026 Coln contributors +-- +-- SPDX-License-Identifier: Apache-2.0 OR MIT + +module Lib () where + +import Coln.Backend.IR qualified as I +import Coln.Backend.Lower +import Coln.Core.Globals +import Coln.Diagnostics +import Coln.Frontend.Driver +import Control.Monad +import Data.Aeson.Text qualified as Aeson +import Data.Foldable +import Data.IORef +import Data.Map.Ordered qualified as OMap +import Data.Text (Text) +import Data.Text qualified as T +import Data.Text.Lazy qualified as TL +import Diagnostician +import Diagnostician.HTML (diagnosticToHtml) +import Foreign.StablePtr +import GHC.Wasm.Prim +import Lucid qualified +import Prettyprinter +import Prettyprinter.Render.Text qualified as Text + +data CompileResult = CompileResult + { ir :: [I.FlatRealm] + , diagnostics :: [Diagnostic ColnCode] + } +foreign export javascript "freeCompileResult" freeStablePtr :: StablePtr CompileResult -> IO () + +compile :: JSString -> IO (StablePtr CompileResult) +compile src = do + ref <- newIORef [] + globals <- topFromText (pureReporter ref) (newFile "" $ textFromJSString src) + let ir = map (uncurry lowerRealm) $ OMap.assocs globals.realms + diagnostics <- reverse <$> readIORef ref + newStablePtr CompileResult{ir, diagnostics} +foreign export javascript "compile" compile :: JSString -> IO (StablePtr CompileResult) + +getDiagnostics :: Bool -> StablePtr CompileResult -> IO JSVal +getDiagnostics asHtml = jsStringArray . map (textToJSString . TL.toStrict . render) . (.diagnostics) <=< deRefStablePtr + where + render = + if asHtml + then Lucid.renderText . diagnosticToHtml + else Text.renderLazy . layoutPretty defaultLayoutOptions . dpretty +foreign export javascript "getDiagnostics" getDiagnostics :: Bool -> StablePtr CompileResult -> IO JSVal + +prettyIr :: StablePtr CompileResult -> IO JSVal +prettyIr = jsStringArray . map (textToJSString . T.show) . (.ir) <=< deRefStablePtr +foreign export javascript "prettyIr" prettyIr :: StablePtr CompileResult -> IO JSVal + +irToJson :: StablePtr CompileResult -> IO JSString +irToJson = fmap (textToJSString . TL.toStrict . Aeson.encodeToLazyText . (.ir)) . deRefStablePtr +foreign export javascript "irToJson" irToJson :: StablePtr CompileResult -> IO JSString + +textToJSString :: Text -> JSString +textToJSString = toJSString . T.unpack +textFromJSString :: JSString -> Text +textFromJSString = T.pack . fromJSString + +foreign import javascript unsafe "[]" js_new_array :: IO JSVal +foreign import javascript unsafe "$1.push($2)" js_push_string :: JSVal -> JSString -> IO () +jsStringArray :: [JSString] -> IO JSVal +jsStringArray ss = do + arr <- js_new_array + for_ ss $ js_push_string arr + pure arr diff --git a/packages/coln-compiler-wasm/README.md b/packages/coln-compiler-wasm/README.md new file mode 100644 index 0000000..7b424c2 --- /dev/null +++ b/packages/coln-compiler-wasm/README.md @@ -0,0 +1,3 @@ +# Coln compiler Wasm library + +This package can be compiled with the [GHC WebAssembly backend](https://downloads.haskell.org/ghc/latest/docs/users_guide/wasm.html) to produce a [WASI reactor module](https://github.com/WebAssembly/WASI/blob/wasi-0.1/application-abi.md#current-unstable-abi). See [this example](./example.html) for how to call it from JavaScript. The example app can be launched with `just serve-example`. All necessary tools, including a Wasm-targeting GHC and Cabal, are provided by this repository's top-level Nix shell. diff --git a/packages/coln-compiler-wasm/coln-compiler-wasm.cabal b/packages/coln-compiler-wasm/coln-compiler-wasm.cabal new file mode 100644 index 0000000..3f5272f --- /dev/null +++ b/packages/coln-compiler-wasm/coln-compiler-wasm.cabal @@ -0,0 +1,31 @@ +cabal-version: 3.4 +name: coln-compiler-wasm +version: 0.1 + +executable coln-compiler-wasm + main-is: Lib.hs + hs-source-dirs: . + default-language: GHC2024 + default-extensions: + NoFieldSelectors + OverloadedRecordDot + OverloadedStrings + + build-depends: + aeson, + base, + coln-compiler, + containers, + diagnostician, + diagnostician-html, + fnotation, + ghc-experimental, + lucid, + ordered-containers, + prettyprinter, + text, + + ghc-options: + -Wall + -no-hs-main + -optl-mexec-model=reactor diff --git a/packages/coln-compiler-wasm/example.html b/packages/coln-compiler-wasm/example.html new file mode 100644 index 0000000..834f910 --- /dev/null +++ b/packages/coln-compiler-wasm/example.html @@ -0,0 +1,246 @@ + + + + + Coln + + + +
+
+

Coln

+ +
+

Source

+ +

Diagnostics

+
+

IR

+
+

JSON

+

+    
+ + + + diff --git a/packages/coln-compiler-wasm/justfile b/packages/coln-compiler-wasm/justfile new file mode 100644 index 0000000..d67454d --- /dev/null +++ b/packages/coln-compiler-wasm/justfile @@ -0,0 +1,21 @@ +build_dir := "../../_build/wasm" +dist_dir := build_dir / "dist" + +build: + #!/usr/bin/env bash + set -euxo pipefail + mkdir -p {{ dist_dir }} + wasm32-wasi-cabal build coln-compiler-wasm + cp "$(wasm32-wasi-cabal list-bin coln-compiler-wasm)" {{ dist_dir }}/coln.wasm + libdir="$(wasm32-wasi-ghc --print-libdir)" + "$libdir/post-link.mjs" --input {{ dist_dir }}/coln.wasm --output {{ dist_dir }}/ghc_wasm_jsffi.js + +serve-example port="8000": build + #!/usr/bin/env bash + set -euxo pipefail + cp example.html {{ build_dir }}/index.html + examples_dir="{{ build_dir }}/examples" + mkdir -p "$examples_dir" + cp ../../packages/coln-compiler/test/*.coln "$examples_dir/" + ls -1 "$examples_dir"/*.coln | xargs -n1 basename | jq -nR '[inputs]' > "$examples_dir/index.json" + simple-http-server --nocache --index --open --port {{ port }} {{ build_dir }} diff --git a/packages/diagnostician-html/diagnostician-html.cabal b/packages/diagnostician-html/diagnostician-html.cabal index a351c57..8845823 100644 --- a/packages/diagnostician-html/diagnostician-html.cabal +++ b/packages/diagnostician-html/diagnostician-html.cabal @@ -21,7 +21,7 @@ library -Wall build-depends: - base ^>=4.21.0.0, + base ^>=4.21.0.0 || ^>=4.22, diagnostician, lucid, prettyprinter, diff --git a/packages/diagnostician-html/src/Diagnostician/HTML.hs b/packages/diagnostician-html/src/Diagnostician/HTML.hs index cb29929..4b878b8 100644 --- a/packages/diagnostician-html/src/Diagnostician/HTML.hs +++ b/packages/diagnostician-html/src/Diagnostician/HTML.hs @@ -9,7 +9,7 @@ module Diagnostician.HTML ( import Data.Text (Text) import Diagnostician import Lucid (Html, class_, div_, span_) -import Prettyprinter (defaultLayoutOptions, layoutPretty) +import Prettyprinter (LayoutOptions (LayoutOptions, layoutPageWidth), PageWidth (Unbounded), layoutPretty) import Prettyprinter.Lucid (renderHtml) import Prettyprinter.Render.Util.SimpleDocTree (treeForm) @@ -21,7 +21,7 @@ diagnosticToHtml d = . renderHtml . treeForm . fmap (span_ . pure @[] . class_ . ("ann-" <>) . annClassSuffix) - . layoutPretty defaultLayoutOptions + . layoutPretty LayoutOptions{layoutPageWidth = Unbounded} $ dpretty d severityClassSuffix :: Severity -> Text diff --git a/packages/diagnostician-terminal/diagnostician-terminal.cabal b/packages/diagnostician-terminal/diagnostician-terminal.cabal index df97d0d..43ac98c 100644 --- a/packages/diagnostician-terminal/diagnostician-terminal.cabal +++ b/packages/diagnostician-terminal/diagnostician-terminal.cabal @@ -23,7 +23,7 @@ library build-depends: ansi-terminal, - base ^>=4.21.0.0, + base ^>=4.21.0.0 || ^>=4.22, diagnostician, prettyprinter, prettyprinter-ansi-terminal, diff --git a/packages/diagnostician/diagnostician.cabal b/packages/diagnostician/diagnostician.cabal index e9cac21..3cede7d 100644 --- a/packages/diagnostician/diagnostician.cabal +++ b/packages/diagnostician/diagnostician.cabal @@ -37,7 +37,7 @@ library ghc-options: -Wall build-depends: - base ^>=4.21.0.0, + base ^>=4.21.0.0 || ^>=4.22, containers, prettyprinter, text, @@ -73,6 +73,6 @@ test-suite diagnostician-test ghc-options: -Wall build-depends: - base ^>=4.21.0.0, + base ^>=4.21.0.0 || ^>=4.22, containers, diagnostician, diff --git a/packages/fnotation/fnotation.cabal b/packages/fnotation/fnotation.cabal index e9c8b24..6f076f3 100644 --- a/packages/fnotation/fnotation.cabal +++ b/packages/fnotation/fnotation.cabal @@ -48,7 +48,7 @@ library ghc-options: -Wall build-depends: - base ^>=4.21.0.0, + base ^>=4.21.0.0 || ^>=4.22, containers, diagnostician, hashable, @@ -94,7 +94,7 @@ test-suite fnotation-test ghc-options: -Wall build-depends: QuickCheck, - base ^>=4.21.0.0, + base ^>=4.21.0.0 || ^>=4.22, bytestring, containers, diagnostician,