Skip to content

Commit 8366ece

Browse files
committed
Merge branch 'AFPP'
2 parents ca2dc3a + a79dda3 commit 8366ece

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+9778
-2456
lines changed

.github/workflows/test.yaml

+65-33
Original file line numberDiff line numberDiff line change
@@ -14,43 +14,45 @@ jobs:
1414
fail-fast: false
1515
matrix:
1616
os: [ubuntu-latest, macOS-latest, windows-latest]
17-
ghc: ['8.0.2', '8.2.2', '8.4.4', '8.6.5', '8.8.4', '8.10.7', '9.0.2', '9.2.1']
18-
cabal: ['3.6.2.0']
17+
ghc: ['8.0', '8.2', '8.4', '8.6', '8.8', '8.10', '9.0', '9.2']
18+
cabal: ['latest']
1919
include:
20-
- os: ubuntu-latest
21-
ghc: 'HEAD'
22-
experimental: true
2320
- os: ubuntu-latest
2421
ghc: 'recommended'
2522
experimental: true
2623
- os: ubuntu-latest
2724
ghc: 'latest'
2825
experimental: true
26+
- os: windows-latest
27+
ghc: '9.2.1'
28+
experimental: true
2929
exclude:
3030
- os: macOS-latest
31-
ghc: '8.0.2'
31+
ghc: '8.0'
3232
- os: macOS-latest
33-
ghc: '8.2.2'
33+
ghc: '8.2'
3434
- os: macOS-latest
35-
ghc: '8.4.4'
35+
ghc: '8.4'
3636
- os: macOS-latest
37-
ghc: '8.6.5'
37+
ghc: '8.6'
3838
- os: macOS-latest
39-
ghc: '8.8.4'
39+
ghc: '8.8'
4040
- os: macOS-latest
41-
ghc: '9.0.2'
41+
ghc: '9.0'
42+
- os: windows-latest
43+
ghc: '8.0'
4244
- os: windows-latest
43-
ghc: '8.0.2'
45+
ghc: '8.2'
4446
- os: windows-latest
45-
ghc: '8.2.2'
47+
ghc: '8.4'
4648
- os: windows-latest
47-
ghc: '8.4.4'
49+
ghc: '8.6'
4850
- os: windows-latest
49-
ghc: '8.6.5'
51+
ghc: '8.8'
5052
- os: windows-latest
51-
ghc: '8.8.4'
53+
ghc: '9.0'
5254
- os: windows-latest
53-
ghc: '9.0.2'
55+
ghc: '9.2'
5456

5557
steps:
5658
- uses: actions/checkout@v2
@@ -59,22 +61,20 @@ jobs:
5961
run: |
6062
set -eux
6163
if [ "${{ matrix.ghc }}" == 'HEAD' ] ; then
62-
ghcup install ghc -u 'https://gitlab.haskell.org/api/v4/projects/1/jobs/artifacts/master/raw/ghc-x86_64-linux-deb10-unreg-validate.tar.xz?job=x86_64-linux-deb10-unreg-validate' head
63-
ghcup set ghc head
64+
ghcup install ghc -u 'https://gitlab.haskell.org/api/v4/projects/1/jobs/artifacts/master/raw/ghc-x86_64-linux-deb10-unreg-validate.tar.xz?job=x86_64-linux-deb10-unreg-validate' --set head
6465
else
65-
ghcup install ghc ${{ matrix.ghc }}
66-
ghcup set ghc ${{ matrix.ghc }}
66+
ghcup install ghc --set ${{ matrix.ghc }}
6767
fi
6868
ghcup install cabal ${{ matrix.cabal }}
6969
shell: bash
7070

7171
- name: Build
7272
run: |
7373
set -eux
74-
[ "${{ matrix.ghc }}" == 'HEAD' ] ||
75-
[ "${{ matrix.ghc }}" == 'recommended' ] ||
76-
[ "${{ matrix.ghc }}" == 'latest' ] ||
77-
[ "$(ghc --numeric-version)" = "${{ matrix.ghc }}" ]
74+
[[ "${{ matrix.ghc }}" == 'HEAD' ]] ||
75+
[[ "${{ matrix.ghc }}" == 'recommended' ]] ||
76+
[[ "${{ matrix.ghc }}" == 'latest' ]] ||
77+
[[ "$(ghc --numeric-version)" =~ "${{ matrix.ghc }}" ]]
7878
cabal update
7979
cabal build --enable-tests --enable-benchmarks
8080
cabal test
@@ -88,7 +88,6 @@ jobs:
8888
run: |
8989
set -eux
9090
export "PATH=$HOME/.cabal/bin:$PATH"
91-
cabal install --overwrite-policy=always --install-method=copy cpphs
9291
make all
9392
git diff --exit-code
9493
@@ -98,37 +97,70 @@ jobs:
9897
strategy:
9998
fail-fast: true
10099
matrix:
101-
arch: ['s390x', 'ppc64le', 'armv7', 'aarch64']
100+
# arch: ['s390x', 'ppc64le', 'armv7', 'aarch64']
101+
arch: ['ppc64le', 'armv7', 'aarch64']
102102
steps:
103103
- uses: actions/checkout@v2
104104
- uses: uraimo/[email protected]
105-
timeout-minutes: 60
105+
timeout-minutes: 180
106106
with:
107107
arch: ${{ matrix.arch }}
108108
distro: ubuntu20.04
109109
githubToken: ${{ github.token }}
110110
install: |
111111
apt-get update -y
112-
apt-get install -y ghc libghc-quickcheck2-dev cpphs git make
112+
apt-get install -y ghc libghc-quickcheck2-dev libghc-tasty-dev libghc-tasty-quickcheck-dev git make curl
113113
run: |
114114
ghc --version
115-
ghc --make -o Main tests/Test.hs -itests/ +RTS -s
115+
runhaskell --ghc-arg=-DGHC_MAKE Generate.hs
116+
ghc --make -o Main tests/Test.hs -DGHC_MAKE -itests/ -O0 +RTS -s
117+
./Main 100 500 +RTS -s
118+
./Main 100 -500 +RTS -s
119+
ghc --make -o Main tests/Main.hs -DGHC_MAKE -itests/ -O0 +RTS -s
116120
./Main +RTS -s
117121
118122
emulated-i386:
119123
runs-on: ubuntu-latest
120124
container:
121-
image: i386/ubuntu:bionic
125+
image: i386/debian:sid
122126
steps:
123127
- name: install
124128
run: |
125129
apt-get update -y
126-
apt-get install -y ghc libghc-quickcheck2-dev cpphs git make
130+
apt-get install -y ghc libghc-quickcheck2-dev libghc-tasty-dev libghc-tasty-quickcheck-dev git make curl libghc-exceptions-dev
127131
shell: bash
128132
- uses: actions/checkout@v1
129133
- name: test
130134
run: |
131135
ghc --version
132-
ghc --make -o Main tests/Test.hs -itests/ +RTS -s
136+
runhaskell --ghc-arg=-DGHC_MAKE Generate.hs
137+
ghc --make -o Main tests/Test.hs -DGHC_MAKE -itests/ +RTS -s
138+
./Main +RTS -s
139+
ghc --make -o Main tests/Main.hs -DGHC_MAKE -itests/ +RTS -s
133140
./Main +RTS -s
134141
shell: bash
142+
143+
bounds-checking:
144+
needs: build
145+
runs-on: ubuntu-latest
146+
steps:
147+
- uses: actions/checkout@v2
148+
- name: Test
149+
run: |
150+
ghcup install ghc --set 9.2.2
151+
ghcup install cabal latest
152+
cabal update
153+
cabal run -w ghc-9.2.2 --ghc-options='-fcheck-prim-bounds -fno-ignore-asserts' bytestring-tests
154+
155+
sdist:
156+
runs-on: ubuntu-latest
157+
steps:
158+
- uses: actions/checkout@v2
159+
- name: Test
160+
run: |
161+
rm cabal.project
162+
cabal update
163+
cabal sdist
164+
tar xf dist-newstyle/sdist/filepath-*.tar.gz
165+
cd filepath-*
166+
cabal build

Generate.hs

+91-12
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
{-# LANGUAGE RecordWildCards, ViewPatterns #-}
1+
{-# LANGUAGE CPP, RecordWildCards, ViewPatterns #-}
22

33
module Generate(main) where
44

55
import Control.Exception
66
import Control.Monad
7+
import Data.Semigroup
78
import Data.Char
89
import Data.List
910
import System.Directory
@@ -16,19 +17,41 @@ main = do
1617
let tests = map renderTest $ concatMap parseTest $ lines src
1718
writeFileBinaryChanged "tests/TestGen.hs" $ unlines $
1819
["-- GENERATED CODE: See ../Generate.hs"
20+
#ifndef GHC_MAKE
21+
, "{-# LANGUAGE OverloadedStrings #-}"
22+
, "{-# LANGUAGE ViewPatterns #-}"
23+
#endif
1924
,"module TestGen(tests) where"
2025
,"import TestUtil"
26+
,"import Prelude as P"
27+
,"import Data.Semigroup"
28+
,"import qualified Data.Char as C"
29+
,"import qualified System.AbstractFilePath.Data.ByteString.Short as SBS"
30+
,"import qualified System.AbstractFilePath.Data.ByteString.Short.Word16 as SBS16"
2131
,"import qualified System.FilePath.Windows as W"
2232
,"import qualified System.FilePath.Posix as P"
23-
,"{-# ANN module \"HLint: ignore\" #-}"
33+
#ifdef GHC_MAKE
34+
,"import qualified System.AbstractFilePath.Windows.Internal as AFP_W"
35+
,"import qualified System.AbstractFilePath.Posix.Internal as AFP_P"
36+
#else
37+
,"import System.AbstractFilePath.Types"
38+
,"import qualified System.AbstractFilePath.Windows as AFP_W"
39+
,"import qualified System.AbstractFilePath.Posix as AFP_P"
40+
#endif
41+
, "import System.AbstractFilePath.Data.ByteString.Short.Encode"
2442
,"tests :: [(String, Property)]"
2543
,"tests ="] ++
2644
[" " ++ c ++ "(" ++ show t1 ++ ", " ++ t2 ++ ")" | (c,(t1,t2)) <- zip ("[":repeat ",") tests] ++
2745
[" ]"]
2846

2947

3048

31-
data PW = P | W deriving Show -- Posix or Windows
49+
data PW = P -- legacy posix
50+
| W -- legacy windows
51+
| AFP_P -- abstract-filepath posix
52+
| AFP_W -- abstract-filepath windows
53+
deriving Show
54+
3255
data Test = Test
3356
{testPlatform :: PW
3457
,testVars :: [(String,String)] -- generator constructor, variable
@@ -39,19 +62,22 @@ data Test = Test
3962
parseTest :: String -> [Test]
4063
parseTest (stripPrefix "-- > " -> Just x) = platform $ toLexemes x
4164
where
42-
platform ("Windows":":":x) = [valid W x]
43-
platform ("Posix" :":":x) = [valid P x]
44-
platform x = [valid P x, valid W x]
65+
platform ("Windows":":":x) = [valid W x, valid AFP_W x]
66+
platform ("Posix" :":":x) = [valid P x, valid AFP_P x]
67+
platform x = [valid P x, valid W x, valid AFP_P x, valid AFP_W x]
4568

4669
valid p ("Valid":x) = free p a $ drop 1 b
4770
where (a,b) = break (== "=>") x
4871
valid p x = free p [] x
4972

5073
free p val x = Test p [(ctor v, v) | v <- vars] x
5174
where vars = nub $ sort [v | v@[c] <- x, isAlpha c]
52-
ctor v | v < "x" = ""
75+
ctor v | v < "x" = ""
5376
| v `elem` val = "QFilePathValid" ++ show p
54-
| otherwise = "QFilePath"
77+
| otherwise = case p of
78+
AFP_P -> if v == "z" then "QFilePathsAFP_P" else "QFilePathAFP_P"
79+
AFP_W -> if v == "z" then "QFilePathsAFP_W" else "QFilePathAFP_W"
80+
_ -> if v == "z" then "" else "QFilePath"
5581
parseTest _ = []
5682

5783

@@ -80,14 +106,67 @@ renderTest Test{..} = (body, code)
80106
body = fromLexemes $ map (qualify testPlatform) testBody
81107

82108

109+
83110
qualify :: PW -> String -> String
84111
qualify pw str
85-
| str `elem` fpops || (all isAlpha str && length str > 1 && str `notElem` prelude) = show pw ++ "." ++ str
86-
| otherwise = str
112+
| str `elem` fpops || (all isAlpha str && length str > 1 && str `notElem` prelude)
113+
= if str `elem` bs then qualifyBS str else show pw ++ "." ++ str
114+
| otherwise = encode str
87115
where
88-
prelude = ["elem","uncurry","snd","fst","not","null","if","then","else"
89-
,"True","False","Just","Nothing","fromJust","concat","isPrefixOf","isSuffixOf","any","foldr"]
116+
bs = ["null", "concat", "isPrefixOf", "isSuffixOf", "any"]
117+
prelude = ["elem","uncurry","snd","fst","not","if","then","else"
118+
,"True","False","Just","Nothing","fromJust","foldr"]
90119
fpops = ["</>","<.>","-<.>"]
120+
#ifdef GHC_MAKE
121+
encode v
122+
| isString' v = case pw of
123+
AFP_P -> "(encodeUtf8 " <> v <> ")"
124+
AFP_W -> "(encodeUtf16LE " <> v <> ")"
125+
_ -> v
126+
| isChar' v = case pw of
127+
AFP_P -> "(fromIntegral . C.ord $ " <> v <> ")"
128+
AFP_W -> "(fromIntegral . C.ord $ " <> v <> ")"
129+
_ -> v
130+
| otherwise = v
131+
isString' xs@('"':_:_) = last xs == '"'
132+
isString' _ = False
133+
isChar' xs@('\'':_:_) = last xs == '\''
134+
isChar' _ = False
135+
qualifyBS v = case pw of
136+
AFP_P -> "SBS." <> v
137+
AFP_W -> "SBS16." <> v
138+
_ -> v
139+
#else
140+
encode v
141+
| isString' v = case pw of
142+
AFP_P -> "(" <> v <> ")"
143+
AFP_W -> "(" <> v <> ")"
144+
_ -> v
145+
| isChar' v = case pw of
146+
AFP_P -> "(PW . fromIntegral . C.ord $ " <> v <> ")"
147+
AFP_W -> "(WW . fromIntegral . C.ord $ " <> v <> ")"
148+
_ -> v
149+
| otherwise = v
150+
isString' xs@('"':_:_) = last xs == '"'
151+
isString' _ = False
152+
isChar' xs@('\'':_:_) = last xs == '\''
153+
isChar' _ = False
154+
qualifyBS v = case pw of
155+
AFP_P
156+
| v == "concat" -> "(PS . SBS." <> v <> " . fmap unPFP)"
157+
| v == "any" -> "(\\f (unPFP -> x) -> SBS." <> v <> " (f . PW) x)"
158+
| v == "isPrefixOf" -> "(\\(unPFP -> x) (unPFP -> y) -> SBS." <> v <> " x y)"
159+
| v == "isSuffixOf" -> "(\\(unPFP -> x) (unPFP -> y) -> SBS." <> v <> " x y)"
160+
| otherwise -> "(SBS." <> v <> " . unPFP)"
161+
AFP_W
162+
| v == "concat" -> "(WS . SBS16." <> v <> " . fmap unWFP)"
163+
| v == "any" -> "(\\f (unWFP -> x) -> SBS16." <> v <> " (f . WW) x)"
164+
| v == "isPrefixOf" -> "(\\(unWFP -> x) (unWFP -> y) -> SBS16." <> v <> " x y)"
165+
| v == "isSuffixOf" -> "(\\(unWFP -> x) (unWFP -> y) -> SBS16." <> v <> " x y)"
166+
| otherwise -> "(SBS16." <> v <> " . unWFP)"
167+
_ -> v
168+
#endif
169+
91170

92171

93172
---------------------------------------------------------------------

Makefile

+3-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
all: cpp gen
2-
3-
cpp:
4-
cpphs --noline -DIS_WINDOWS=False -DMODULE_NAME=Posix -OSystem/FilePath/Posix.hs System/FilePath/Internal.hs
5-
cpphs --noline -DIS_WINDOWS=True -DMODULE_NAME=Windows -OSystem/FilePath/Windows.hs System/FilePath/Internal.hs
1+
all: gen
62

73
gen:
84
runhaskell Generate.hs
95

10-
.PHONY: all cpp gen
6+
7+
.PHONY: all gen

README.md

+7-5
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,21 @@ All three modules provide the same API, and the same documentation (calling out
1414

1515
### What is a `FilePath`?
1616

17-
In Haskell, the definition is `type FilePath = String` as of now. A Haskell `String` is a list of Unicode code points.
17+
In Haskell, the legacy definition (used in `base` and Prelude) is `type FilePath = String`,
18+
where a Haskell `String` is a list of Unicode code points.
19+
20+
The new definition is (simplified) `newtype AbstractFilePath = AFP ShortByteString`, where
21+
`ShortByteString` is an unpinned byte array and follows syscall conventions, preserving the encoding.
1822

1923
On unix, filenames don't have a predefined encoding as per the
2024
[POSIX specification](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_170)
2125
and are passed as `char[]` to syscalls.
2226

2327
On windows (at least the API used by `Win32`) filepaths are UTF-16 strings.
2428

25-
This means that Haskell filepaths have to be converted to C-strings on unix
26-
(utilizing the current filesystem encoding) and to UTF-16 strings
27-
on windows.
29+
You are encouraged to use `AbstractFilePath` whenever possible, because it is more correct.
2830

29-
Further, this is a low-level library and it makes no attempt at providing a more
31+
Also note that this is a low-level library and it makes no attempt at providing a more
3032
type safe variant for filepaths (e.g. by distinguishing between absolute and relative
3133
paths) and ensures no invariants (such as filepath validity).
3234

0 commit comments

Comments
 (0)