Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,7 @@ jobs:
run: if [ -f scripts/test ]; then nix develop -c bash ./scripts/test; fi

- name: Luacheck
run: nix develop -c luacheck --quiet --std lua51 --no-unused-args 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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/.*
!/.gitignore
!/.github/
!/.tidyrc.json
!/.lua-format
/output/
10 changes: 10 additions & 0 deletions .lua-format
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions .tidyrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"importSort": "source",
"importWrap": "source",
"indent": 2,
"operatorsFile": null,
"ribbon": 1,
"typeArrowPlacement": "first",
"unicode": "source",
"width": 80
}
16 changes: 13 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,21 @@ A PureScript→Lua FFI fork in the [`purescript-lua`](https://github.com/purescr

## Commands

All commands run inside the nix dev shell:

- 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 src/`
- 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

Expand Down
3 changes: 2 additions & 1 deletion bench/Main.purs
Original file line number Diff line number Diff line change
Expand Up @@ -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."
23 changes: 22 additions & 1 deletion flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 39 additions & 4 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,33 @@
inputs.nixpkgs.follows = "nixpkgs";
};
pslua.url = "github:purescript-lua/purescript-lua";
treefmt-nix = {
url = "github:numtide/treefmt-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
};

outputs = { self, nixpkgs, flake-utils, purescript-overlay, pslua }:
flake-utils.lib.eachDefaultSystem (system:
outputs =
{
self,
nixpkgs,
flake-utils,
purescript-overlay,
pslua,
treefmt-nix,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = import nixpkgs {
inherit system;
overlays = [ purescript-overlay.overlays.default ];
};
in {
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
Expand All @@ -31,8 +48,26 @@
spago-bin.spago-0_21_0
treefmt
];
# Install a content-based pre-commit hook. It compares the working
# tree diff before and after `nix fmt`, so it only objects to changes
# the formatter itself introduces (not the developer's existing
# unstaged work) and is not fooled by formatters that only bump mtime.
# Rewritten each shell entry to stay in sync with this flake.
shellHook = ''
hook=.git/hooks/pre-commit
if [ -d .git ]; then
printf '%s\n' \
'#!/usr/bin/env bash' \
'before=$(git diff)' \
'nix fmt >/dev/null 2>&1 || exit 0' \
'[ "$before" = "$(git diff)" ] || { echo "nix fmt changed files; re-stage them, then commit." >&2; exit 1; }' \
> "$hook"
chmod +x "$hook"
fi
'';
Comment thread
Unisay marked this conversation as resolved.
};
});
}
);

# --- Flake Local Nix Configuration ----------------------------
nixConfig = {
Expand Down
3 changes: 1 addition & 2 deletions spago-test.dhall
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
2 changes: 1 addition & 1 deletion src/Data/Char/Gen.purs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
39 changes: 10 additions & 29 deletions src/Data/String/CodePoints.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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
Expand All @@ -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 = {}
Expand Down Expand Up @@ -136,5 +117,5 @@ return {
local cp = decode(s, 1)
return cp
end
end),
end)
}
61 changes: 39 additions & 22 deletions src/Data/String/CodePoints.purs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Loading
Loading