diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 1c6afb9..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "parserOptions": { - "ecmaVersion": 6, - "sourceType": "module" - }, - "extends": "eslint:recommended", - "rules": { - "strict": [2, "global"], - "block-scoped-var": 2, - "consistent-return": 2, - "eqeqeq": [2, "smart"], - "guard-for-in": 2, - "no-caller": 2, - "no-extend-native": 2, - "no-loop-func": 2, - "no-new": 2, - "no-param-reassign": 2, - "no-return-assign": 2, - "no-unused-expressions": 2, - "no-use-before-define": 2, - "radix": [2, "always"], - "indent": [2, 2], - "quotes": [2, "double"], - "semi": [2, "always"] - } -} diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..72cb16f --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# Content-based format gate. CI's `nix fmt && git diff --exit-code` is +# authoritative; this hook is just a local convenience. +# +# Tracked under .githooks/ and wired via `git config core.hooksPath .githooks` +# in the dev-shell shellHook — robust across git worktrees and submodules +# (where .git is a file) and it never clobbers a developer's .git/hooks. +set -u +command -v nix >/dev/null 2>&1 || exit 0 # no nix here → skip; CI still gates + +before=$(git diff) +if ! nix fmt >/dev/null 2>&1; then + echo "pre-commit: 'nix fmt' failed — fix the formatter error, then commit." >&2 + exit 1 +fi +if [ "$before" != "$(git diff)" ]; then + echo "pre-commit: 'nix fmt' reformatted files — re-stage them, then commit." >&2 + exit 1 +fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dff0666..4dc2d20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,12 +14,17 @@ jobs: - uses: cachix/install-nix-action@v27 with: extra_nix_config: | - accept-flake-config = true extra-substituters = https://cache.iog.io https://purescript-lua.cachix.org extra-trusted-public-keys = hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= purescript-lua.cachix.org-1:yLs4ei2HtnuPtzLekOrW3xdfm95+Etw15gwgyIGTayA= + - name: Build + run: nix develop -c ./scripts/build + - name: Test - run: nix develop -c ./scripts/test + run: if [ -f scripts/test ]; then nix develop -c bash ./scripts/test; fi - name: Luacheck - run: nix develop -c luacheck --quiet --std min src/ + run: nix develop -c luacheck --quiet --std lua51 --no-unused-args --max-line-length 130 src/ + + - name: Format check + run: nix fmt && git diff --exit-code diff --git a/.gitignore b/.gitignore index b846b63..b618ba9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,7 @@ /.* !/.gitignore -!/.eslintrc.json !/.github/ -/bower_components/ -/node_modules/ +!/.tidyrc.json +!/.lua-format +!/.githooks/ /output/ -package-lock.json diff --git a/.lua-format b/.lua-format new file mode 100644 index 0000000..2945014 --- /dev/null +++ b/.lua-format @@ -0,0 +1,10 @@ +# LuaFormatter config for the hand-written FFI under src/. +# 2-space indent. Keep simple functions on one line; column_limit sits a few +# columns under luacheck's 130 limit because lua-format under-counts the leading +# indent and trailing comma, so this keeps every emitted line within 130. +indent_width: 2 +use_tab: false +column_limit: 126 +continuation_indent_width: 2 +keep_simple_function_one_line: true +keep_simple_control_block_one_line: true diff --git a/.tidyrc.json b/.tidyrc.json new file mode 100644 index 0000000..8636af8 --- /dev/null +++ b/.tidyrc.json @@ -0,0 +1,10 @@ +{ + "importSort": "source", + "importWrap": "source", + "indent": 2, + "operatorsFile": null, + "ribbon": 1, + "typeArrowPlacement": "first", + "unicode": "source", + "width": 80 +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..a312901 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,55 @@ +# AGENTS.md + +A PureScript→Lua FFI fork in the [`purescript-lua`](https://github.com/purescript-lua/purescript-lua) package set. Generated code targets **Lua 5.1**. + +## Commands + +- Build: `nix develop -c ./scripts/build` +- Test (only if the fork has `scripts/test`): `nix develop -c bash ./scripts/test` +- Lint: `nix develop -c luacheck --quiet --std lua51 --no-unused-args --max-line-length 130 src/` +- Format: `nix fmt` (check: `nix fmt && git diff --exit-code`) + +## Formatting + +`nix fmt` runs treefmt (`treefmt.nix`): nixfmt for Nix, `dhall format`, purs-tidy +for `*.purs` (config in `.tidyrc.json`), and LuaFormatter for the `*.lua` FFI +(config in `.lua-format`). LuaFormatter is used over StyLua because it keeps the +parentheses pslua's foreign-file parser requires. The Lua line budget is 130 +columns, matching the `luacheck --max-line-length` above. The check is +content-based (`nix fmt && git diff --exit-code`) rather than `treefmt --ci`, +since the in-place formatters bump mtime even when content is unchanged, which +trips treefmt's `--fail-on-change`. CI and the pre-commit hook use it. + +## Lua 5.1 target + +The output runs on Lua 5.1, which is stricter than 5.3: + +- No `table.unpack`, `bit32`, `utf8`, or the `//` operator. `math.pow` and `math.atan2` do exist. +- Array-style tables are 1-indexed: the first element is `t[1]`, not `t[0]`. +- `unit` is `{}`, never `nil`: a `nil` table element silently disappears, which would collapse `Array Unit` into an empty table. +- Lua 5.1 mangles some Lua 5.3 string escapes, so keep FFI string escapes 5.1-safe. + +## FFI files (under `src/`) + +pslua's foreign-file parser needs every exported value wrapped in parentheses: + +```lua +return { + identity = (function(x) return x end), + answer = (42), +} +``` + +A bare `function … end` or an unparenthesised expression fails to parse. + +## Toolchain + +`flake.nix` pins everything through [`purescript-overlay`](https://github.com/thomashoneyman/purescript-overlay): purs 0.15.16 (`purs-bin.purs-0_15_16`), spago 0.21.0 (`spago-bin.spago-0_21_0`), Lua 5.1 (`lua51Packages`). The `pslua` input tracks `github:purescript-lua/purescript-lua`; keep `flake.lock` reasonably current, since a long-stale pslua pin won't create the `--lua-output-file` directory and CI fails. + +## Releasing + +Tag-driven, with no GitHub Release or changelog entry. The full conventions live in the [package-set repo](https://github.com/purescript-lua/purescript-lua-package-sets/blob/master/CONTRIBUTING.md): push an annotated tag on `master`, bump this fork's `version` in the package set's `src/packages.dhall`, refresh `latest-compatible-sets.json`, and push a `psc-*` set tag. + +## Decisions + +Cross-cutting decisions are recorded as ADRs in the [package-set repo](https://github.com/purescript-lua/purescript-lua-package-sets/tree/master/docs/adr). Read them before a decision that affects the set, and add one after making such a decision. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/bench/Main.purs b/bench/Main.purs index 87c1ca0..516ca26 100644 --- a/bench/Main.purs +++ b/bench/Main.purs @@ -54,4 +54,5 @@ benchNonEmptyConversions = do bench = benchWith 100000 loremIpsum :: String -loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut aliquet euismod ligula, vitae lacinia lorem imperdiet nec. Nulla volutpat ullamcorper mollis. Proin interdum quam a sem auctor, id tempus nisl pretium. Suspendisse potenti. Quisque ut libero consequat, suscipit sem a, malesuada nisi. Aliquam dictum odio mi, eu laoreet felis scelerisque non. Ut in odio vehicula, cursus augue sed, tincidunt lorem. Vestibulum consequat lectus eu commodo vulputate. Nam vitae faucibus ipsum. Curabitur sit amet neque sed est sagittis vehicula nec nec risus. Phasellus consectetur cursus malesuada. Vestibulum commodo lorem ut mauris mollis faucibus. Integer ut massa auctor, scelerisque nisi nec, rutrum nisl. Integer vel ex sem. Sed purus felis, molestie eget cursus vel, maximus ut augue. Curabitur nunc ligula, lobortis vitae vehicula a, volutpat nec sem. Phasellus non sapien ipsum. Mauris dolor justo, mollis at elit a, sollicitudin commodo quam. Curabitur posuere felis at nunc pharetra, eu convallis lectus dapibus. Aliquam ullamcorper porta fermentum. Donec at tellus metus. Donec pharetra tempor odio sit amet viverra. Nam vel metus libero. Vivamus maximus quis lacus id pharetra. Duis sed diam molestie, sodales leo id, pulvinar justo. In non augue tempor risus consectetur hendrerit. In libero nulla, elementum non ultrices eu, vehicula non ipsum. Maecenas in hendrerit tellus, sodales dignissim turpis. Ut odio diam, convallis in elit non, consequat gravida nisi. Cras egestas metus eleifend sapien efficitur, vel vulputate est porta. Aliquam posuere, magna nec bibendum luctus, quam risus efficitur sapien, id volutpat metus ex non lorem. Praesent velit eros, efficitur sed tortor quis, lobortis eleifend ligula. Sed tellus quam, aliquet vitae sagittis a, egestas eget massa. Etiam odio elit, hendrerit vel dui vel, fermentum pharetra neque. Curabitur quis mauris id lacus consectetur rhoncus non nec mauris. Mauris blandit tempor pretium. Donec non nisi finibus, lobortis dolor vitae, euismod arcu. Nullam scelerisque lacus in dolor volutpat mollis. Nunc vitae consectetur ligula, quis laoreet quam.Proin sit amet nisi eu orci hendrerit imperdiet vitae sit amet leo. Donec sodales id ante eget viverra. Nullam vitae elit in mauris accumsan feugiat id a velit. Nulla facilisi. Cras in turpis efficitur, consectetur justo quis, suscipit tortor. Sed tincidunt pellentesque sapien, in ultricies eros rhoncus sit amet. Integer blandit ornare lobortis. Duis dictum sit amet mauris sit amet cursus. Nullam nec nisl mauris. Praesent cursus imperdiet mi mattis luctus. Donec in tortor fermentum, efficitur turpis vel, facilisis augue. Integer egestas nisl et magna volutpat ornare. Donec pulvinar risus elit, eget viverra est feugiat in.Ut nec ante vestibulum neque pulvinar pretium sit amet eu nisi. Aliquam erat volutpat. Maecenas egestas nisi et mi congue, sed ultricies nibh posuere. Suspendisse potenti. Donec a nulla et velit elementum pretium. Pellentesque gravida imperdiet sem et varius. Praesent ac diam diam. Donec iaculis risus ex, ac eleifend sapien luctus ut. Fusce aliquet, lacus tincidunt porta malesuada, massa augue commodo nulla, ac malesuada tortor est sed eros. Praesent mattis, nisi eget ullamcorper vestibulum, lacus ante placerat metus, ac ullamcorper ante tellus vel nulla. Praesent vehicula in est sit amet varius. Sed facilisis felis sed sem porttitor rutrum. Etiam sollicitudin erat neque, id gravida metus scelerisque quis. Proin venenatis pharetra lectus ac auctor." +loremIpsum = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut aliquet euismod ligula, vitae lacinia lorem imperdiet nec. Nulla volutpat ullamcorper mollis. Proin interdum quam a sem auctor, id tempus nisl pretium. Suspendisse potenti. Quisque ut libero consequat, suscipit sem a, malesuada nisi. Aliquam dictum odio mi, eu laoreet felis scelerisque non. Ut in odio vehicula, cursus augue sed, tincidunt lorem. Vestibulum consequat lectus eu commodo vulputate. Nam vitae faucibus ipsum. Curabitur sit amet neque sed est sagittis vehicula nec nec risus. Phasellus consectetur cursus malesuada. Vestibulum commodo lorem ut mauris mollis faucibus. Integer ut massa auctor, scelerisque nisi nec, rutrum nisl. Integer vel ex sem. Sed purus felis, molestie eget cursus vel, maximus ut augue. Curabitur nunc ligula, lobortis vitae vehicula a, volutpat nec sem. Phasellus non sapien ipsum. Mauris dolor justo, mollis at elit a, sollicitudin commodo quam. Curabitur posuere felis at nunc pharetra, eu convallis lectus dapibus. Aliquam ullamcorper porta fermentum. Donec at tellus metus. Donec pharetra tempor odio sit amet viverra. Nam vel metus libero. Vivamus maximus quis lacus id pharetra. Duis sed diam molestie, sodales leo id, pulvinar justo. In non augue tempor risus consectetur hendrerit. In libero nulla, elementum non ultrices eu, vehicula non ipsum. Maecenas in hendrerit tellus, sodales dignissim turpis. Ut odio diam, convallis in elit non, consequat gravida nisi. Cras egestas metus eleifend sapien efficitur, vel vulputate est porta. Aliquam posuere, magna nec bibendum luctus, quam risus efficitur sapien, id volutpat metus ex non lorem. Praesent velit eros, efficitur sed tortor quis, lobortis eleifend ligula. Sed tellus quam, aliquet vitae sagittis a, egestas eget massa. Etiam odio elit, hendrerit vel dui vel, fermentum pharetra neque. Curabitur quis mauris id lacus consectetur rhoncus non nec mauris. Mauris blandit tempor pretium. Donec non nisi finibus, lobortis dolor vitae, euismod arcu. Nullam scelerisque lacus in dolor volutpat mollis. Nunc vitae consectetur ligula, quis laoreet quam.Proin sit amet nisi eu orci hendrerit imperdiet vitae sit amet leo. Donec sodales id ante eget viverra. Nullam vitae elit in mauris accumsan feugiat id a velit. Nulla facilisi. Cras in turpis efficitur, consectetur justo quis, suscipit tortor. Sed tincidunt pellentesque sapien, in ultricies eros rhoncus sit amet. Integer blandit ornare lobortis. Duis dictum sit amet mauris sit amet cursus. Nullam nec nisl mauris. Praesent cursus imperdiet mi mattis luctus. Donec in tortor fermentum, efficitur turpis vel, facilisis augue. Integer egestas nisl et magna volutpat ornare. Donec pulvinar risus elit, eget viverra est feugiat in.Ut nec ante vestibulum neque pulvinar pretium sit amet eu nisi. Aliquam erat volutpat. Maecenas egestas nisi et mi congue, sed ultricies nibh posuere. Suspendisse potenti. Donec a nulla et velit elementum pretium. Pellentesque gravida imperdiet sem et varius. Praesent ac diam diam. Donec iaculis risus ex, ac eleifend sapien luctus ut. Fusce aliquet, lacus tincidunt porta malesuada, massa augue commodo nulla, ac malesuada tortor est sed eros. Praesent mattis, nisi eget ullamcorper vestibulum, lacus ante placerat metus, ac ullamcorper ante tellus vel nulla. Praesent vehicula in est sit amet varius. Sed facilisis felis sed sem porttitor rutrum. Etiam sollicitudin erat neque, id gravida metus scelerisque quis. Proin venenatis pharetra lectus ac auctor." diff --git a/bower.json b/bower.json deleted file mode 100644 index 85a17c5..0000000 --- a/bower.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "purescript-strings", - "homepage": "https://github.com/purescript/purescript-strings", - "license": "BSD-3-Clause", - "repository": { - "type": "git", - "url": "https://github.com/purescript/purescript-strings.git" - }, - "ignore": [ - "**/.*", - "bower_components", - "node_modules", - "output", - "test", - "bower.json", - "package.json" - ], - "dependencies": { - "purescript-arrays": "^7.0.0", - "purescript-control": "^6.0.0", - "purescript-either": "^6.0.0", - "purescript-enums": "^6.0.1", - "purescript-foldable-traversable": "^6.0.0", - "purescript-gen": "^4.0.0", - "purescript-integers": "^6.0.0", - "purescript-maybe": "^6.0.0", - "purescript-newtype": "^5.0.0", - "purescript-nonempty": "^7.0.0", - "purescript-partial": "^4.0.0", - "purescript-prelude": "^6.0.0", - "purescript-tailrec": "^6.0.0", - "purescript-tuples": "^7.0.0", - "purescript-unfoldable": "^6.0.0", - "purescript-unsafe-coerce": "^6.0.0" - }, - "devDependencies": { - "purescript-assert": "^6.0.0", - "purescript-console": "^6.0.0", - "purescript-minibench": "^4.0.0" - } -} diff --git a/flake.lock b/flake.lock index b1b25c5..21cb4e4 100644 --- a/flake.lock +++ b/flake.lock @@ -84,35 +84,35 @@ "type": "github" } }, - "easyps": { + "flake-compat": { "flake": false, "locked": { - "lastModified": 1763814099, - "narHash": "sha256-YazeA9u0JdxykexV6HHG5DMtsnwqXoiAcWPjncO1XHM=", - "owner": "justinwoo", - "repo": "easy-purescript-nix", - "rev": "8fcd84f54d75d9007b2f1c7c9da5af843105a55f", + "lastModified": 1672831974, + "narHash": "sha256-z9k3MfslLjWQfnjBtEtJZdq3H7kyi2kQtUThfTgdRk0=", + "owner": "input-output-hk", + "repo": "flake-compat", + "rev": "45f2638735f8cdc40fe302742b79f248d23eb368", "type": "github" }, "original": { - "owner": "justinwoo", - "repo": "easy-purescript-nix", + "owner": "input-output-hk", + "ref": "hkm/gitlab-fix", + "repo": "flake-compat", "type": "github" } }, - "flake-compat": { + "flake-compat_2": { "flake": false, "locked": { - "lastModified": 1672831974, - "narHash": "sha256-z9k3MfslLjWQfnjBtEtJZdq3H7kyi2kQtUThfTgdRk0=", - "owner": "input-output-hk", + "lastModified": 1765121682, + "narHash": "sha256-4VBOP18BFeiPkyhy9o4ssBNQEvfvv1kXkasAYd0+rrA=", + "owner": "edolstra", "repo": "flake-compat", - "rev": "45f2638735f8cdc40fe302742b79f248d23eb368", + "rev": "65f23138d8d09a92e30f1e5c87611b23ef451bf3", "type": "github" }, "original": { - "owner": "input-output-hk", - "ref": "hkm/gitlab-fix", + "owner": "edolstra", "repo": "flake-compat", "type": "github" } @@ -547,11 +547,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1775888245, - "narHash": "sha256-nwASzrRDD1JBEu/o8ekKYEXm/oJW6EMCzCRdrwcLe90=", + "lastModified": 1781074563, + "narHash": "sha256-md8WlXOlfnIeHeOScMTTHFyf2d6iaTwPl2apR5EQ3P4=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "13043924aaa7375ce482ebe2494338e058282925", + "rev": "9ae611a455b90cf061d8f332b977e387bda8e1ca", "type": "github" }, "original": { @@ -698,28 +698,51 @@ "pslua", "haskellNix", "nixpkgs-unstable" - ] + ], + "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1781335909, - "narHash": "sha256-gg1K5vcNPYJWWyQz8f/fOxf/8UJG6rJnj2ofQ18tZqQ=", - "owner": "Unisay", + "lastModified": 1781519566, + "narHash": "sha256-ptELm3Q4290+1grRnJS7JJgCbzvcKM6CyL9yt4qEmQM=", + "owner": "purescript-lua", "repo": "purescript-lua", - "rev": "be5956742c67a6a35ab59dbbacafc3eaf5d2fc8c", + "rev": "62e36537ecafd35081f521a7d49a7b1ae2e13ff7", "type": "github" }, "original": { - "owner": "Unisay", + "owner": "purescript-lua", "repo": "purescript-lua", "type": "github" } }, + "purescript-overlay": { + "inputs": { + "flake-compat": "flake-compat_2", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1780972251, + "narHash": "sha256-PwHYPpfLP+WjSwj765zJ1N0Xp4ET3+8vRqxJ031ua3M=", + "owner": "thomashoneyman", + "repo": "purescript-overlay", + "rev": "1cf88ab9d83596db0e0c0d304a16809c410e2917", + "type": "github" + }, + "original": { + "owner": "thomashoneyman", + "repo": "purescript-overlay", + "type": "github" + } + }, "root": { "inputs": { - "easyps": "easyps", "flake-utils": "flake-utils", "nixpkgs": "nixpkgs", - "pslua": "pslua" + "pslua": "pslua", + "purescript-overlay": "purescript-overlay", + "treefmt-nix": "treefmt-nix_2" } }, "stackage": { @@ -782,6 +805,47 @@ "repo": "default", "type": "github" } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "pslua", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1780220602, + "narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "db947814a175b7ca6ded66e21383d938df01c227", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + }, + "treefmt-nix_2": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1780220602, + "narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "db947814a175b7ca6ded66e21383d938df01c227", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index f813607..d89f83d 100644 --- a/flake.nix +++ b/flake.nix @@ -4,33 +4,66 @@ inputs = { flake-utils.url = "github:numtide/flake-utils"; nixpkgs.url = "nixpkgs/nixos-unstable"; - easyps = { - url = "github:justinwoo/easy-purescript-nix"; - flake = false; + purescript-overlay = { + url = "github:thomashoneyman/purescript-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + pslua.url = "github:purescript-lua/purescript-lua"; + treefmt-nix = { + url = "github:numtide/treefmt-nix"; + inputs.nixpkgs.follows = "nixpkgs"; }; - pslua.url = "github:Unisay/purescript-lua"; }; - outputs = { self, nixpkgs, flake-utils, easyps, pslua }: - flake-utils.lib.eachDefaultSystem (system: + outputs = + { + self, + nixpkgs, + flake-utils, + purescript-overlay, + pslua, + treefmt-nix, + }: + flake-utils.lib.eachDefaultSystem ( + system: let - p = nixpkgs.legacyPackages.${system}; - e = import easyps { pkgs = p; }; - l = p.lua51Packages; - in { - devShell = p.mkShell { - buildInputs = [ - p.dhall - l.lua - l.luacheck - p.luaformatter - p.nixfmt - pslua.packages.${system}.default - e.purs-0_15_15 - e.spago - p.treefmt - ]; + pkgs = import nixpkgs { + inherit system; + overlays = [ purescript-overlay.overlays.default ]; }; - }); -} + treefmtEval = treefmt-nix.lib.evalModule pkgs ./treefmt.nix; + in + { + formatter = treefmtEval.config.build.wrapper; + checks.formatting = treefmtEval.config.build.check self; + devShell = pkgs.mkShell { + buildInputs = with pkgs; [ + dhall + lua51Packages.lua + lua51Packages.luacheck + luaformatter + nixfmt-rfc-style + pslua.packages.${system}.default + purs-bin.purs-0_15_16 + spago-bin.spago-0_21_0 + treefmt + ]; + # Robust pre-commit hook: point git at the tracked .githooks/ dir + # (worktree/submodule-safe; never clobbers an existing .git/hooks). + shellHook = "git config core.hooksPath .githooks"; + }; + } + ); + # --- Flake Local Nix Configuration ---------------------------- + nixConfig = { + extra-substituters = [ + "https://cache.iog.io" + "https://purescript-lua.cachix.org" + ]; + extra-trusted-public-keys = [ + "hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ=" + "purescript-lua.cachix.org-1:yLs4ei2HtnuPtzLekOrW3xdfm95+Etw15gwgyIGTayA=" + ]; + }; +} diff --git a/package.json b/package.json deleted file mode 100644 index cffd45e..0000000 --- a/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "private": true, - "scripts": { - "clean": "rimraf output && rimraf .pulp-cache", - "build": "eslint src && pulp build -- --censor-lib --strict", - "test": "pulp test && npm run test:run:without_codePointAt", - "test:run:without_codePointAt": "node -e \"delete String.prototype.codePointAt; import('./output/Test.Main/index.js').then(m => m.main());\"", - "bench:build": "purs compile 'bench/**/*.purs' 'src/**/*.purs' 'bower_components/*/src/**/*.purs'", - "bench:run": "node --expose-gc -e 'require(\"./output/Bench.Main/index.js\").main()'", - "bench": "npm run bench:build && npm run bench:run" - }, - "devDependencies": { - "eslint": "^7.15.0", - "pulp": "16.0.0-0", - "purescript-psa": "^0.8.2", - "rimraf": "^3.0.2" - } -} diff --git a/packages.dhall b/packages.dhall index f240736..3914537 100644 --- a/packages.dhall +++ b/packages.dhall @@ -3,7 +3,7 @@ let upstream-ps = sha256:e48c9b283ca89ec994453459fb74c4b5b5a9432349f83a2e104f39dd869a0f6e let upstream-lua = - https://github.com/Unisay/purescript-lua-package-sets/releases/download/psc-0.15.15-20260613/packages.dhall + https://github.com/purescript-lua/purescript-lua-package-sets/releases/download/psc-0.15.15-20260613/packages.dhall sha256:4cb4784187583587818384ca3c4930f8fe77b15796ff7d487f628ef4590d8058 in upstream-ps // upstream-lua diff --git a/scripts/build b/scripts/build index 2520d0d..1f80243 100755 --- a/scripts/build +++ b/scripts/build @@ -1,4 +1,5 @@ #!/usr/bin/env bash +set -euo pipefail echo "Building..." diff --git a/spago-test.dhall b/spago-test.dhall index b598ded..1101fcc 100644 --- a/spago-test.dhall +++ b/spago-test.dhall @@ -2,8 +2,7 @@ let conf = ./spago.dhall in conf // { sources = conf.sources # [ "test/**/*.purs" ] - , dependencies = - conf.dependencies # [ "assert", "console", "effect" ] + , dependencies = conf.dependencies # [ "assert", "console", "effect" ] , backend = '' pslua \ diff --git a/src/Data/Char/Gen.purs b/src/Data/Char/Gen.purs index 838ff29..0a1bc8e 100644 --- a/src/Data/Char/Gen.purs +++ b/src/Data/Char/Gen.purs @@ -24,7 +24,7 @@ genDigitChar = toEnumWithDefaults bottom top <$> chooseInt 48 57 -- | Generates a character from the basic latin alphabet. genAlpha :: forall m. MonadGen m => m Char -genAlpha = oneOf (genAlphaLowercase :| [genAlphaUppercase]) +genAlpha = oneOf (genAlphaLowercase :| [ genAlphaUppercase ]) -- | Generates a lowercase character from the basic latin alphabet. genAlphaLowercase :: forall m. MonadGen m => m Char diff --git a/src/Data/String/CodePoints.lua b/src/Data/String/CodePoints.lua index b5f5d59..29df9fd 100644 --- a/src/Data/String/CodePoints.lua +++ b/src/Data/String/CodePoints.lua @@ -4,7 +4,6 @@ -- under this representation; every export ignores them. -- -- Lua 5.1: no utf8 library, no bit operators - plain arithmetic only. - -- Decodes the code point starting at byte position i. -- Returns the code point and the position of the next one. -- An invalid leading byte is returned as-is (one byte consumed). @@ -13,22 +12,16 @@ local function decode(s, i) if b1 < 0x80 then return b1, i + 1 end if b1 >= 0xC2 and b1 <= 0xDF then local b2 = s:byte(i + 1) - if b2 and b2 >= 0x80 and b2 <= 0xBF then - return (b1 - 0xC0) * 0x40 + (b2 - 0x80), i + 2 - end + if b2 and b2 >= 0x80 and b2 <= 0xBF then return (b1 - 0xC0) * 0x40 + (b2 - 0x80), i + 2 end elseif b1 >= 0xE0 and b1 <= 0xEF then local b2, b3 = s:byte(i + 1, i + 2) - if b2 and b2 >= 0x80 and b2 <= 0xBF - and b3 and b3 >= 0x80 and b3 <= 0xBF then + if b2 and b2 >= 0x80 and b2 <= 0xBF and b3 and b3 >= 0x80 and b3 <= 0xBF then return (b1 - 0xE0) * 0x1000 + (b2 - 0x80) * 0x40 + (b3 - 0x80), i + 3 end elseif b1 >= 0xF0 and b1 <= 0xF4 then local b2, b3, b4 = s:byte(i + 1, i + 3) - if b2 and b2 >= 0x80 and b2 <= 0xBF - and b3 and b3 >= 0x80 and b3 <= 0xBF - and b4 and b4 >= 0x80 and b4 <= 0xBF then - return (b1 - 0xF0) * 0x40000 + (b2 - 0x80) * 0x1000 - + (b3 - 0x80) * 0x40 + (b4 - 0x80), i + 4 + if b2 and b2 >= 0x80 and b2 <= 0xBF and b3 and b3 >= 0x80 and b3 <= 0xBF and b4 and b4 >= 0x80 and b4 <= 0xBF then + return (b1 - 0xF0) * 0x40000 + (b2 - 0x80) * 0x1000 + (b3 - 0x80) * 0x40 + (b4 - 0x80), i + 4 end end return b1, i + 1 @@ -37,28 +30,16 @@ end -- Encodes a code point as a UTF-8 byte string. local function encode(cp) if cp < 0x80 then return string.char(cp) end - if cp < 0x800 then - return string.char(0xC0 + math.floor(cp / 0x40), 0x80 + cp % 0x40) - end + if cp < 0x800 then return string.char(0xC0 + math.floor(cp / 0x40), 0x80 + cp % 0x40) end if cp < 0x10000 then - return string.char( - 0xE0 + math.floor(cp / 0x1000), - 0x80 + math.floor(cp / 0x40) % 0x40, - 0x80 + cp % 0x40 - ) + return string.char(0xE0 + math.floor(cp / 0x1000), 0x80 + math.floor(cp / 0x40) % 0x40, 0x80 + cp % 0x40) end - return string.char( - 0xF0 + math.floor(cp / 0x40000), - 0x80 + math.floor(cp / 0x1000) % 0x40, - 0x80 + math.floor(cp / 0x40) % 0x40, - 0x80 + cp % 0x40 - ) + return string.char(0xF0 + math.floor(cp / 0x40000), 0x80 + math.floor(cp / 0x1000) % 0x40, + 0x80 + math.floor(cp / 0x40) % 0x40, 0x80 + cp % 0x40) end return { - _singleton = (function(_) - return function(cp) return encode(cp) end - end), + _singleton = (function(_) return function(cp) return encode(cp) end end), _fromCodePointArray = (function(_) return function(cps) local t = {} @@ -136,5 +117,5 @@ return { local cp = decode(s, 1) return cp end - end), + end) } diff --git a/src/Data/String/CodePoints.purs b/src/Data/String/CodePoints.purs index 915023b..a5addb5 100644 --- a/src/Data/String/CodePoints.purs +++ b/src/Data/String/CodePoints.purs @@ -50,7 +50,8 @@ derive instance eqCodePoint :: Eq CodePoint derive instance ordCodePoint :: Ord CodePoint instance showCodePoint :: Show CodePoint where - show (CodePoint i) = "(CodePoint 0x" <> toUpper (toStringAs hexadecimal i) <> ")" + show (CodePoint i) = "(CodePoint 0x" <> toUpper (toStringAs hexadecimal i) <> + ")" instance boundedCodePoint :: Bounded CodePoint where bottom = CodePoint 0 @@ -96,9 +97,13 @@ foreign import _singleton singletonFallback :: CodePoint -> String singletonFallback (CodePoint cp) | cp <= 0xFFFF = fromCharCode cp singletonFallback (CodePoint cp) = - let lead = ((cp - 0x10000) / 0x400) + 0xD800 in - let trail = (cp - 0x10000) `mod` 0x400 + 0xDC00 in - fromCharCode lead <> fromCharCode trail + let + lead = ((cp - 0x10000) / 0x400) + 0xD800 + in + let + trail = (cp - 0x10000) `mod` 0x400 + 0xDC00 + in + fromCharCode lead <> fromCharCode trail -- | Creates a string from an array of code points. Operates in space and time -- | linear to the length of the array. @@ -161,7 +166,10 @@ codePointAt :: Int -> String -> Maybe CodePoint codePointAt n _ | n < 0 = Nothing codePointAt 0 "" = Nothing codePointAt 0 s = Just (unsafeCodePointAt0 s) -codePointAt n s = _codePointAt codePointAtFallback Just Nothing unsafeCodePointAt0 n s +codePointAt n s = _codePointAt codePointAtFallback Just Nothing + unsafeCodePointAt0 + n + s foreign import _codePointAt :: (Int -> String -> Maybe CodePoint) @@ -174,7 +182,8 @@ foreign import _codePointAt codePointAtFallback :: Int -> String -> Maybe CodePoint codePointAtFallback n s = case uncons s of - Just { head, tail } -> if n == 0 then Just head else codePointAtFallback (n - 1) tail + Just { head, tail } -> + if n == 0 then Just head else codePointAtFallback (n - 1) tail _ -> Nothing -- | Returns a record with the first code point and the remaining code points @@ -266,8 +275,10 @@ indexOf p s = (\i -> length (CU.take i s)) <$> CU.indexOf p s -- | indexOf' :: Pattern -> Int -> String -> Maybe Int indexOf' p i s = - let s' = drop i s in - (\k -> i + length (CU.take k s')) <$> CU.indexOf p s' + let + s' = drop i s + in + (\k -> i + length (CU.take k s')) <$> CU.indexOf p s' -- | Returns the number of code points preceding the last match of the given -- | pattern in the string. Returns `Nothing` when no matches are found. @@ -307,8 +318,10 @@ lastIndexOf p s = (\i -> length (CU.take i s)) <$> CU.lastIndexOf p s -- | lastIndexOf' :: Pattern -> Int -> String -> Maybe Int lastIndexOf' p i s = - let i' = CU.length (take i s) in - (\k -> length (CU.take k s)) <$> CU.lastIndexOf' p i' s + let + i' = CU.length (take i s) + in + (\k -> length (CU.take k s)) <$> CU.lastIndexOf' p i' s -- | Returns a string containing the given number of code points from the -- | beginning of the given string. If the string does not have that many code @@ -398,14 +411,17 @@ dropWhile p s = drop (countPrefix p s) s -- | ``` splitAt :: Int -> String -> { before :: String, after :: String } splitAt i s = - let before = take i s in - { before - -- inline drop i s to reuse the result of take i s - , after: CU.drop (CU.length before) s - } + let + before = take i s + in + { before + -- inline drop i s to reuse the result of take i s + , after: CU.drop (CU.length before) s + } unsurrogate :: Int -> Int -> CodePoint -unsurrogate lead trail = CodePoint ((lead - 0xD800) * 0x400 + (trail - 0xDC00) + 0x10000) +unsurrogate lead trail = CodePoint + ((lead - 0xD800) * 0x400 + (trail - 0xDC00) + 0x10000) isLead :: Int -> Boolean isLead cu = 0xD800 <= cu && cu <= 0xDBFF @@ -430,9 +446,10 @@ unsafeCodePointAt0Fallback s = let cu0 = fromEnum (Unsafe.charAt 0 s) in - if isLead cu0 && CU.length s > 1 - then - let cu1 = fromEnum (Unsafe.charAt 1 s) in - if isTrail cu1 then unsurrogate cu0 cu1 else CodePoint cu0 - else - CodePoint cu0 + if isLead cu0 && CU.length s > 1 then + let + cu1 = fromEnum (Unsafe.charAt 1 s) + in + if isTrail cu1 then unsurrogate cu0 cu1 else CodePoint cu0 + else + CodePoint cu0 diff --git a/src/Data/String/CodeUnits.purs b/src/Data/String/CodeUnits.purs index 5fed21f..c8f27fe 100644 --- a/src/Data/String/CodeUnits.purs +++ b/src/Data/String/CodeUnits.purs @@ -45,8 +45,10 @@ import Data.String.Unsafe as U -- | ``` stripPrefix :: Pattern -> String -> Maybe String stripPrefix (Pattern prefix) str = - let { before, after } = splitAt (length prefix) str in - if before == prefix then Just after else Nothing + let + { before, after } = splitAt (length prefix) str + in + if before == prefix then Just after else Nothing -- | If the string ends with the given suffix, return the portion of the -- | string left after removing it, as a `Just` value. Otherwise, return @@ -58,8 +60,10 @@ stripPrefix (Pattern prefix) str = -- | ``` stripSuffix :: Pattern -> String -> Maybe String stripSuffix (Pattern suffix) str = - let { before, after } = splitAt (length str - length suffix) str in - if after == suffix then Just before else Nothing + let + { before, after } = splitAt (length str - length suffix) str + in + if after == suffix then Just before else Nothing -- | Checks whether the pattern appears in the given string. -- | @@ -139,7 +143,7 @@ foreign import _toChar -- | uncons :: String -> Maybe { head :: Char, tail :: String } uncons "" = Nothing -uncons s = Just { head: U.charAt zero s, tail: drop one s } +uncons s = Just { head: U.charAt zero s, tail: drop one s } -- | Returns the number of characters the string is composed of. -- | diff --git a/src/Data/String/Common.lua b/src/Data/String/Common.lua index 93285ef..e36457a 100644 --- a/src/Data/String/Common.lua +++ b/src/Data/String/Common.lua @@ -5,9 +5,8 @@ return { _localeCompare = (function(lt) return function(eq) - return function(gt) - return function(s1) return function(s2) return (s1 < s2) and lt or (s2 < s1) and gt or eq end end - end + return + function(gt) return function(s1) return function(s2) return (s1 < s2) and lt or (s2 < s1) and gt or eq end end end end end), replace = (function(pattern) @@ -24,7 +23,7 @@ return { return function(replacement) return function(s) if pattern == "" then - local out = { replacement } + local out = {replacement} for i = 1, #s do out[#out + 1] = s:sub(i, i) out[#out + 1] = replacement diff --git a/src/Data/String/NonEmpty/CaseInsensitive.purs b/src/Data/String/NonEmpty/CaseInsensitive.purs index d1c1719..f61682b 100644 --- a/src/Data/String/NonEmpty/CaseInsensitive.purs +++ b/src/Data/String/NonEmpty/CaseInsensitive.purs @@ -6,7 +6,8 @@ import Data.Newtype (class Newtype) import Data.String.NonEmpty (NonEmptyString, toLower) -- | A newtype for case insensitive string comparisons and ordering. -newtype CaseInsensitiveNonEmptyString = CaseInsensitiveNonEmptyString NonEmptyString +newtype CaseInsensitiveNonEmptyString = CaseInsensitiveNonEmptyString + NonEmptyString instance eqCaseInsensitiveNonEmptyString :: Eq CaseInsensitiveNonEmptyString where eq (CaseInsensitiveNonEmptyString s1) (CaseInsensitiveNonEmptyString s2) = @@ -17,6 +18,9 @@ instance ordCaseInsensitiveNonEmptyString :: Ord CaseInsensitiveNonEmptyString w compare (toLower s1) (toLower s2) instance showCaseInsensitiveNonEmptyString :: Show CaseInsensitiveNonEmptyString where - show (CaseInsensitiveNonEmptyString s) = "(CaseInsensitiveNonEmptyString " <> show s <> ")" + show (CaseInsensitiveNonEmptyString s) = "(CaseInsensitiveNonEmptyString " + <> show s + <> ")" -derive instance newtypeCaseInsensitiveNonEmptyString :: Newtype CaseInsensitiveNonEmptyString _ +derive instance newtypeCaseInsensitiveNonEmptyString :: + Newtype CaseInsensitiveNonEmptyString _ diff --git a/src/Data/String/NonEmpty/CodePoints.purs b/src/Data/String/NonEmpty/CodePoints.purs index 7b5328a..dd7a0d1 100644 --- a/src/Data/String/NonEmpty/CodePoints.purs +++ b/src/Data/String/NonEmpty/CodePoints.purs @@ -55,7 +55,8 @@ fromCodePointArray = case _ of cs -> Just (toNonEmptyString (CP.fromCodePointArray cs)) fromNonEmptyCodePointArray :: NonEmptyArray CodePoint -> NonEmptyString -fromNonEmptyCodePointArray = unsafePartial fromJust <<< fromCodePointArray <<< NEA.toArray +fromNonEmptyCodePointArray = unsafePartial fromJust <<< fromCodePointArray <<< + NEA.toArray singleton :: CodePoint -> NonEmptyString singleton = toNonEmptyString <<< CP.singleton @@ -73,7 +74,8 @@ toCodePointArray :: NonEmptyString -> Array CodePoint toCodePointArray = CP.toCodePointArray <<< fromNonEmptyString toNonEmptyCodePointArray :: NonEmptyString -> NonEmptyArray CodePoint -toNonEmptyCodePointArray = unsafePartial fromJust <<< NEA.fromArray <<< toCodePointArray +toNonEmptyCodePointArray = unsafePartial fromJust <<< NEA.fromArray <<< + toCodePointArray codePointAt :: Int -> NonEmptyString -> Maybe CodePoint codePointAt = liftS <<< CP.codePointAt @@ -107,9 +109,8 @@ take i nes = let s = fromNonEmptyString nes in - if i < 1 - then Nothing - else Just (toNonEmptyString (CP.take i s)) + if i < 1 then Nothing + else Just (toNonEmptyString (CP.take i s)) takeWhile :: (CodePoint -> Boolean) -> NonEmptyString -> Maybe NonEmptyString takeWhile f = fromString <<< liftS (CP.takeWhile f) @@ -119,9 +120,8 @@ drop i nes = let s = fromNonEmptyString nes in - if i >= CP.length s - then Nothing - else Just (toNonEmptyString (CP.drop i s)) + if i >= CP.length s then Nothing + else Just (toNonEmptyString (CP.drop i s)) dropWhile :: (CodePoint -> Boolean) -> NonEmptyString -> Maybe NonEmptyString dropWhile f = fromString <<< liftS (CP.dropWhile f) diff --git a/src/Data/String/NonEmpty/CodeUnits.purs b/src/Data/String/NonEmpty/CodeUnits.purs index 4e97244..88cd38b 100644 --- a/src/Data/String/NonEmpty/CodeUnits.purs +++ b/src/Data/String/NonEmpty/CodeUnits.purs @@ -209,9 +209,8 @@ take i nes = let s = fromNonEmptyString nes in - if i < 1 - then Nothing - else Just (toNonEmptyString (CU.take i s)) + if i < 1 then Nothing + else Just (toNonEmptyString (CU.take i s)) -- | Returns the last `n` characters of the string. Returns `Nothing` if `n` is -- | less than 1. @@ -225,9 +224,8 @@ takeRight i nes = let s = fromNonEmptyString nes in - if i < 1 - then Nothing - else Just (toNonEmptyString (CU.takeRight i s)) + if i < 1 then Nothing + else Just (toNonEmptyString (CU.takeRight i s)) -- | Returns the longest prefix of characters that satisfy the predicate. -- | `Nothing` is returned if there is no matching prefix. @@ -251,9 +249,8 @@ drop i nes = let s = fromNonEmptyString nes in - if i >= CU.length s - then Nothing - else Just (toNonEmptyString (CU.drop i s)) + if i >= CU.length s then Nothing + else Just (toNonEmptyString (CU.drop i s)) -- | Returns the string without the last `n` characters. Returns `Nothing` if -- | more characters are dropped than the string is long. @@ -267,9 +264,8 @@ dropRight i nes = let s = fromNonEmptyString nes in - if i >= CU.length s - then Nothing - else Just (toNonEmptyString (CU.dropRight i s)) + if i >= CU.length s then Nothing + else Just (toNonEmptyString (CU.dropRight i s)) -- | Returns the suffix remaining after `takeWhile`. -- | diff --git a/src/Data/String/NonEmpty/Internal.purs b/src/Data/String/NonEmpty/Internal.purs index 8722654..af04c56 100644 --- a/src/Data/String/NonEmpty/Internal.purs +++ b/src/Data/String/NonEmpty/Internal.purs @@ -44,7 +44,9 @@ instance showNonEmptyString :: Show NonEmptyString where class MakeNonEmpty (s :: Symbol) where nes :: Proxy s -> NonEmptyString -instance makeNonEmptyBad :: TE.Fail (TE.Text "Cannot create an NonEmptyString from an empty Symbol") => MakeNonEmpty "" where +instance makeNonEmptyBad :: + TE.Fail (TE.Text "Cannot create an NonEmptyString from an empty Symbol") => + MakeNonEmpty "" where nes _ = NonEmptyString "" else instance nonEmptyNonEmpty :: IsSymbol s => MakeNonEmpty s where @@ -55,7 +57,8 @@ newtype NonEmptyReplacement = NonEmptyReplacement NonEmptyString derive newtype instance eqNonEmptyReplacement :: Eq NonEmptyReplacement derive newtype instance ordNonEmptyReplacement :: Ord NonEmptyReplacement -derive newtype instance semigroupNonEmptyReplacement ∷ Semigroup NonEmptyReplacement +derive newtype instance semigroupNonEmptyReplacement ∷ + Semigroup NonEmptyReplacement instance showNonEmptyReplacement :: Show NonEmptyReplacement where show (NonEmptyReplacement s) = "(NonEmptyReplacement " <> show s <> ")" @@ -201,8 +204,8 @@ trim (NonEmptyString s) = fromString (String.trim s) joinWith :: forall f. Foldable f => String -> f NonEmptyString -> String joinWith splice = F.intercalate splice <<< coe where - coe :: f NonEmptyString -> f String - coe = unsafeCoerce + coe :: f NonEmptyString -> f String + coe = unsafeCoerce -- | Joins non-empty strings in a non-empty container together as a new -- | non-empty string, inserting a possibly empty string as separator between @@ -213,7 +216,8 @@ joinWith splice = F.intercalate splice <<< coe -- | join1With ", " [NonEmptyString "apple", NonEmptyString "banana"] == NonEmptyString "apple, banana" -- | join1With "" [NonEmptyString "apple", NonEmptyString "banana"] == NonEmptyString "applebanana" -- | ``` -join1With :: forall f. Foldable1 f => String -> f NonEmptyString -> NonEmptyString +join1With + :: forall f. Foldable1 f => String -> f NonEmptyString -> NonEmptyString join1With splice = NonEmptyString <<< joinWith splice -- | Joins possibly empty strings in a non-empty container together as a new @@ -225,7 +229,8 @@ join1With splice = NonEmptyString <<< joinWith splice -- | joinWith1 (NonEmptyString ", ") ["apple", "banana"] == NonEmptyString "apple, banana" -- | joinWith1 (NonEmptyString "/") ["a", "b", "", "c", ""] == NonEmptyString "a/b//c/" -- | ``` -joinWith1 :: forall f. Foldable1 f => NonEmptyString -> f String -> NonEmptyString +joinWith1 + :: forall f. Foldable1 f => NonEmptyString -> f String -> NonEmptyString joinWith1 (NonEmptyString splice) = NonEmptyString <<< F.intercalate splice liftS :: forall r. (String -> r) -> NonEmptyString -> r diff --git a/test/Test/Data/String.purs b/test/Test/Data/String.purs index 5c73153..b80b9bd 100644 --- a/test/Test/Data/String.purs +++ b/test/Test/Data/String.purs @@ -85,19 +85,19 @@ testString = do } assertEqual { actual: S.split (Pattern "") "a" - , expected: ["a"] + , expected: [ "a" ] } assertEqual { actual: S.split (Pattern "") "ab" - , expected: ["a", "b"] + , expected: [ "a", "b" ] } assertEqual { actual: S.split (Pattern "b") "aabcc" - , expected: ["aa", "cc"] + , expected: [ "aa", "cc" ] } assertEqual { actual: S.split (Pattern "d") "abc" - , expected: ["abc"] + , expected: [ "abc" ] } log "toLower" @@ -124,10 +124,10 @@ testString = do , expected: "" } assertEqual - { actual: S.joinWith "" ["a", "b"] + { actual: S.joinWith "" [ "a", "b" ] , expected: "ab" } assertEqual - { actual: S.joinWith "--" ["a", "b", "c"] + { actual: S.joinWith "--" [ "a", "b", "c" ] , expected: "a--b--c" } diff --git a/test/Test/Data/String/CaseInsensitive.purs b/test/Test/Data/String/CaseInsensitive.purs index a263732..1885f9b 100644 --- a/test/Test/Data/String/CaseInsensitive.purs +++ b/test/Test/Data/String/CaseInsensitive.purs @@ -17,6 +17,7 @@ testCaseInsensitiveString = do log "comparison" assertEqual - { actual: compare (CaseInsensitiveString "qwerty") (CaseInsensitiveString "QWERTY") + { actual: compare (CaseInsensitiveString "qwerty") + (CaseInsensitiveString "QWERTY") , expected: EQ } diff --git a/test/Test/Data/String/CodePoints.purs b/test/Test/Data/String/CodePoints.purs index c5304e7..b3b97c6 100644 --- a/test/Test/Data/String/CodePoints.purs +++ b/test/Test/Data/String/CodePoints.purs @@ -12,7 +12,7 @@ -- -- The single upstream do-block is also split into per-section functions: -- one huge do-block generates Lua nested beyond the parser limit of stock --- Lua 5.1 interpreters (Unisay/purescript-lua#46). +-- Lua 5.1 interpreters (purescript-lua/purescript-lua#46). module Test.Data.String.CodePoints (testStringCodePoints) where import Prelude diff --git a/test/Test/Data/String/CodeUnits.purs b/test/Test/Data/String/CodeUnits.purs index a9cea2d..8ae1d36 100644 --- a/test/Test/Data/String/CodeUnits.purs +++ b/test/Test/Data/String/CodeUnits.purs @@ -222,10 +222,10 @@ testFromCharArray = do log "fromCharArray" assertEqual { actual: SCU.fromCharArray [] - , expected: "" - } + , expected: "" + } assertEqual - { actual: SCU.fromCharArray ['a', 'b'] + { actual: SCU.fromCharArray [ 'a', 'b' ] , expected: "ab" } @@ -494,31 +494,31 @@ testSplitAt = do log "splitAt" assertEqual { actual: SCU.splitAt 1 "" - , expected: {before: "", after: ""} + , expected: { before: "", after: "" } } assertEqual { actual: SCU.splitAt 0 "a" - , expected: {before: "", after: "a"} + , expected: { before: "", after: "a" } } assertEqual { actual: SCU.splitAt 1 "a" - , expected: {before: "a", after: ""} + , expected: { before: "a", after: "" } } assertEqual { actual: SCU.splitAt 1 "ab" - , expected: {before: "a", after: "b"} + , expected: { before: "a", after: "b" } } assertEqual { actual: SCU.splitAt 3 "aabcc" - , expected: {before: "aab", after: "cc"} + , expected: { before: "aab", after: "cc" } } assertEqual { actual: SCU.splitAt (-1) "abc" - , expected: {before: "", after: "abc"} + , expected: { before: "", after: "abc" } } assertEqual { actual: SCU.splitAt 10 "Hi" - , expected: {before: "Hi", after: ""} + , expected: { before: "Hi", after: "" } } testToCharArray :: Effect Unit @@ -530,30 +530,30 @@ testToCharArray = do } assertEqual { actual: SCU.toCharArray "a" - , expected: ['a'] + , expected: [ 'a' ] } assertEqual { actual: SCU.toCharArray "ab" - , expected: ['a', 'b'] + , expected: [ 'a', 'b' ] } testSlice :: Effect Unit testSlice = do log "slice" assertEqual - { actual: SCU.slice 0 0 "purescript" + { actual: SCU.slice 0 0 "purescript" , expected: "" } assertEqual - { actual: SCU.slice 0 1 "purescript" + { actual: SCU.slice 0 1 "purescript" , expected: "p" } assertEqual - { actual: SCU.slice 3 6 "purescript" + { actual: SCU.slice 3 6 "purescript" , expected: "esc" } assertEqual - { actual: SCU.slice 3 10 "purescript" + { actual: SCU.slice 3 10 "purescript" , expected: "escript" } assertEqual @@ -565,11 +565,11 @@ testSlice = do , expected: "rip" } assertEqual - { actual: SCU.slice (-4) 3 "purescript" + { actual: SCU.slice (-4) 3 "purescript" , expected: "" } assertEqual - { actual: SCU.slice 1000 3 "purescript" + { actual: SCU.slice 1000 3 "purescript" , expected: "" } assertEqual diff --git a/test/Test/Data/String/NonEmpty.purs b/test/Test/Data/String/NonEmpty.purs index fe7e923..b6e2c6f 100644 --- a/test/Test/Data/String/NonEmpty.purs +++ b/test/Test/Data/String/NonEmpty.purs @@ -81,22 +81,26 @@ testContains = do assert $ NES.contains (Pattern "") (nes (Proxy :: Proxy "abcd")) assert $ NES.contains (Pattern "bc") (nes (Proxy :: Proxy "abcd")) assert $ not NES.contains (Pattern "cb") (nes (Proxy :: Proxy "abcd")) - assert $ NES.contains (Pattern "needle") (nes (Proxy :: Proxy "haystack with needle")) + assert $ NES.contains (Pattern "needle") + (nes (Proxy :: Proxy "haystack with needle")) assert $ not NES.contains (Pattern "needle") (nes (Proxy :: Proxy "haystack")) testLocaleCompare :: Effect Unit testLocaleCompare = do log "localeCompare" assertEqual - { actual: NES.localeCompare (nes (Proxy :: Proxy "a")) (nes (Proxy :: Proxy "a")) + { actual: NES.localeCompare (nes (Proxy :: Proxy "a")) + (nes (Proxy :: Proxy "a")) , expected: EQ } assertEqual - { actual: NES.localeCompare (nes (Proxy :: Proxy "a")) (nes (Proxy :: Proxy "b")) + { actual: NES.localeCompare (nes (Proxy :: Proxy "a")) + (nes (Proxy :: Proxy "b")) , expected: LT } assertEqual - { actual: NES.localeCompare (nes (Proxy :: Proxy "b")) (nes (Proxy :: Proxy "a")) + { actual: NES.localeCompare (nes (Proxy :: Proxy "b")) + (nes (Proxy :: Proxy "a")) , expected: GT } @@ -104,15 +108,21 @@ testReplace :: Effect Unit testReplace = do log "replace" assertEqual - { actual: NES.replace (Pattern "b") (NES.NonEmptyReplacement (nes (Proxy :: Proxy "!"))) (nes (Proxy :: Proxy "abc")) + { actual: NES.replace (Pattern "b") + (NES.NonEmptyReplacement (nes (Proxy :: Proxy "!"))) + (nes (Proxy :: Proxy "abc")) , expected: nes (Proxy :: Proxy "a!c") } assertEqual - { actual: NES.replace (Pattern "b") (NES.NonEmptyReplacement (nes (Proxy :: Proxy "!"))) (nes (Proxy :: Proxy "abbc")) + { actual: NES.replace (Pattern "b") + (NES.NonEmptyReplacement (nes (Proxy :: Proxy "!"))) + (nes (Proxy :: Proxy "abbc")) , expected: nes (Proxy :: Proxy "a!bc") } assertEqual - { actual: NES.replace (Pattern "d") (NES.NonEmptyReplacement (nes (Proxy :: Proxy "!"))) (nes (Proxy :: Proxy "abc")) + { actual: NES.replace (Pattern "d") + (NES.NonEmptyReplacement (nes (Proxy :: Proxy "!"))) + (nes (Proxy :: Proxy "abc")) , expected: nes (Proxy :: Proxy "abc") } @@ -120,15 +130,21 @@ testReplaceAll :: Effect Unit testReplaceAll = do log "replaceAll" assertEqual - { actual: NES.replaceAll (Pattern "[b]") (NES.NonEmptyReplacement (nes (Proxy :: Proxy "!"))) (nes (Proxy :: Proxy "a[b]c")) + { actual: NES.replaceAll (Pattern "[b]") + (NES.NonEmptyReplacement (nes (Proxy :: Proxy "!"))) + (nes (Proxy :: Proxy "a[b]c")) , expected: nes (Proxy :: Proxy "a!c") } assertEqual - { actual: NES.replaceAll (Pattern "[b]") (NES.NonEmptyReplacement (nes (Proxy :: Proxy "!"))) (nes (Proxy :: Proxy "a[b]c[b]")) + { actual: NES.replaceAll (Pattern "[b]") + (NES.NonEmptyReplacement (nes (Proxy :: Proxy "!"))) + (nes (Proxy :: Proxy "a[b]c[b]")) , expected: nes (Proxy :: Proxy "a!c!") } assertEqual - { actual: NES.replaceAll (Pattern "x") (NES.NonEmptyReplacement (nes (Proxy :: Proxy "!"))) (nes (Proxy :: Proxy "abc")) + { actual: NES.replaceAll (Pattern "x") + (NES.NonEmptyReplacement (nes (Proxy :: Proxy "!"))) + (nes (Proxy :: Proxy "abc")) , expected: nes (Proxy :: Proxy "abc") } @@ -152,11 +168,13 @@ testStripPrefix = do , expected: Nothing } assertEqual - { actual: NES.stripPrefix (Pattern "http:") (nes (Proxy :: Proxy "http://purescript.org")) + { actual: NES.stripPrefix (Pattern "http:") + (nes (Proxy :: Proxy "http://purescript.org")) , expected: Just (nes (Proxy :: Proxy "//purescript.org")) } assertEqual - { actual: NES.stripPrefix (Pattern "http:") (nes (Proxy :: Proxy "https://purescript.org")) + { actual: NES.stripPrefix (Pattern "http:") + (nes (Proxy :: Proxy "https://purescript.org")) , expected: Nothing } assertEqual @@ -216,11 +234,16 @@ testJoinWith = do , expected: "" } assertEqual - { actual: NES.joinWith "" [nes (Proxy :: Proxy "a"), nes (Proxy :: Proxy "b")] + { actual: NES.joinWith "" + [ nes (Proxy :: Proxy "a"), nes (Proxy :: Proxy "b") ] , expected: "ab" } assertEqual - { actual: NES.joinWith "--" [nes (Proxy :: Proxy "a"), nes (Proxy :: Proxy "b"), nes (Proxy :: Proxy "c")] + { actual: NES.joinWith "--" + [ nes (Proxy :: Proxy "a") + , nes (Proxy :: Proxy "b") + , nes (Proxy :: Proxy "c") + ] , expected: "a--b--c" } @@ -228,19 +251,28 @@ testJoin1With :: Effect Unit testJoin1With = do log "join1With" assertEqual - { actual: NES.join1With "" (nea [nes (Proxy :: Proxy "a"), nes (Proxy :: Proxy "b")]) + { actual: NES.join1With "" + (nea [ nes (Proxy :: Proxy "a"), nes (Proxy :: Proxy "b") ]) , expected: nes (Proxy :: Proxy "ab") } assertEqual - { actual: NES.join1With "--" (nea [nes (Proxy :: Proxy "a"), nes (Proxy :: Proxy "b"), nes (Proxy :: Proxy "c")]) + { actual: NES.join1With "--" + ( nea + [ nes (Proxy :: Proxy "a") + , nes (Proxy :: Proxy "b") + , nes (Proxy :: Proxy "c") + ] + ) , expected: nes (Proxy :: Proxy "a--b--c") } assertEqual - { actual: NES.join1With ", " (nea [nes (Proxy :: Proxy "apple"), nes (Proxy :: Proxy "banana")]) + { actual: NES.join1With ", " + (nea [ nes (Proxy :: Proxy "apple"), nes (Proxy :: Proxy "banana") ]) , expected: nes (Proxy :: Proxy "apple, banana") } assertEqual - { actual: NES.join1With "" (nea [nes (Proxy :: Proxy "apple"), nes (Proxy :: Proxy "banana")]) + { actual: NES.join1With "" + (nea [ nes (Proxy :: Proxy "apple"), nes (Proxy :: Proxy "banana") ]) , expected: nes (Proxy :: Proxy "applebanana") } @@ -248,19 +280,21 @@ testJoinWith1 :: Effect Unit testJoinWith1 = do log "joinWith1" assertEqual - { actual: NES.joinWith1 (nes (Proxy :: Proxy " ")) (nea ["a", "b"]) + { actual: NES.joinWith1 (nes (Proxy :: Proxy " ")) (nea [ "a", "b" ]) , expected: nes (Proxy :: Proxy "a b") } assertEqual - { actual: NES.joinWith1 (nes (Proxy :: Proxy "--")) (nea ["a", "b", "c"]) + { actual: NES.joinWith1 (nes (Proxy :: Proxy "--")) (nea [ "a", "b", "c" ]) , expected: nes (Proxy :: Proxy "a--b--c") } assertEqual - { actual: NES.joinWith1 (nes (Proxy :: Proxy ", ")) (nea ["apple", "banana"]) + { actual: NES.joinWith1 (nes (Proxy :: Proxy ", ")) + (nea [ "apple", "banana" ]) , expected: nes (Proxy :: Proxy "apple, banana") } assertEqual - { actual: NES.joinWith1 (nes (Proxy :: Proxy "/")) (nea ["a", "b", "", "c", ""]) + { actual: NES.joinWith1 (nes (Proxy :: Proxy "/")) + (nea [ "a", "b", "", "c", "" ]) , expected: nes (Proxy :: Proxy "a/b//c/") } diff --git a/test/Test/Data/String/NonEmpty/CodeUnits.purs b/test/Test/Data/String/NonEmpty/CodeUnits.purs index 930753e..092659a 100644 --- a/test/Test/Data/String/NonEmpty/CodeUnits.purs +++ b/test/Test/Data/String/NonEmpty/CodeUnits.purs @@ -49,7 +49,7 @@ testFromCharArray = do , expected: Nothing } assertEqual - { actual: NESCU.fromCharArray ['a', 'b'] + { actual: NESCU.fromCharArray [ 'a', 'b' ] , expected: Just (nes (Proxy :: Proxy "ab")) } @@ -97,11 +97,11 @@ testFromFoldable1 :: Effect Unit testFromFoldable1 = do log "fromFoldable1" assertEqual - { actual: NESCU.fromFoldable1 (nea ['a']) + { actual: NESCU.fromFoldable1 (nea [ 'a' ]) , expected: nes (Proxy :: Proxy "a") } assertEqual - { actual: NESCU.fromFoldable1 (nea ['a', 'b', 'c']) + { actual: NESCU.fromFoldable1 (nea [ 'a', 'b', 'c' ]) , expected: nes (Proxy :: Proxy "abc") } @@ -188,18 +188,18 @@ testToCharArray = do log "toCharArray" assertEqual { actual: NESCU.toCharArray (nes (Proxy :: Proxy "a")) - , expected: ['a'] + , expected: [ 'a' ] } assertEqual { actual: NESCU.toCharArray (nes (Proxy :: Proxy "ab")) - , expected: ['a', 'b'] + , expected: [ 'a', 'b' ] } -- pslua: a Char is a byte, so the upstream "Hello☺\n" case (with a -- multi-byte '☺' Char) is not expressible; an ASCII string keeps the -- intent (letters plus a control character). assertEqual { actual: NESCU.toCharArray (nes (Proxy :: Proxy "Hello!\n")) - , expected: ['H','e','l','l','o','!','\n'] + , expected: [ 'H', 'e', 'l', 'l', 'o', '!', '\n' ] } testToNonEmptyCharArray :: Effect Unit @@ -207,7 +207,7 @@ testToNonEmptyCharArray = do log "toNonEmptyCharArray" assertEqual { actual: NESCU.toNonEmptyCharArray (nes (Proxy :: Proxy "ab")) - , expected: nea ['a', 'b'] + , expected: nea [ 'a', 'b' ] } testUncons :: Effect Unit @@ -238,7 +238,8 @@ testTakeWhile = do , expected: Just (nes (Proxy :: Proxy "aa")) } assertEqual - { actual: NESCU.takeWhile (_ /= ':') (nes (Proxy :: Proxy "http://purescript.org")) + { actual: NESCU.takeWhile (_ /= ':') + (nes (Proxy :: Proxy "http://purescript.org")) , expected: Just (nes (Proxy :: Proxy "http")) } assertEqual @@ -515,11 +516,17 @@ testSplitAt = do } assertEqual { actual: NESCU.splitAt 1 (nes (Proxy :: Proxy "ab")) - , expected: { before: Just (nes (Proxy :: Proxy "a")), after: Just (nes (Proxy :: Proxy "b")) } + , expected: + { before: Just (nes (Proxy :: Proxy "a")) + , after: Just (nes (Proxy :: Proxy "b")) + } } assertEqual { actual: NESCU.splitAt 3 (nes (Proxy :: Proxy "aabcc")) - , expected: { before: Just (nes (Proxy :: Proxy "aab")), after: Just (nes (Proxy :: Proxy "cc")) } + , expected: + { before: Just (nes (Proxy :: Proxy "aab")) + , after: Just (nes (Proxy :: Proxy "cc")) + } } assertEqual { actual: NESCU.splitAt (-1) (nes (Proxy :: Proxy "abc")) diff --git a/treefmt.nix b/treefmt.nix new file mode 100644 index 0000000..0f57573 --- /dev/null +++ b/treefmt.nix @@ -0,0 +1,43 @@ +{ pkgs, ... }: +{ + projectRootFile = "flake.nix"; + + # Nix — RFC 166 formatter. + programs.nixfmt.enable = true; + + # Dhall — spago.dhall / packages.dhall layout. + programs.dhall.enable = true; + + # PureScript — purs-tidy is not a first-class treefmt program, so wire it via + # the generic mechanism. It picks up `.tidyrc.json` from the project root. + settings.formatter.purs-tidy = { + command = "${pkgs.purs-tidy}/bin/purs-tidy"; + options = [ "format-in-place" ]; + includes = [ "*.purs" ]; + }; + + # Lua FFI — LuaFormatter keeps the parentheses pslua's foreign-file parser + # requires (unlike StyLua, which strips them). Config in `.lua-format`. + settings.formatter.lua-format = { + command = "${pkgs.luaformatter}/bin/lua-format"; + options = [ + "-i" + "-c" + ".lua-format" + ]; + includes = [ "*.lua" ]; + }; + + # Never format generated output or vendored trees. + settings.global.excludes = [ + "dist/*" + "output/*" + ".spago/*" + "node_modules/*" + "*.lock" + "flake.lock" + "spago.lock" + ".tidyrc.json" + ".lua-format" + ]; +}