@@ -1,38 +1,53 @@
1{-# OPTIONS_GHC -Wall #-}
1{-# OPTIONS_GHC -Wall #-}
2−{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE BangPatterns, EmptyDataDecls, FlexibleInstances, UnboxedTuples #-}
3module Elm.Package
3module Elm.Package
5 , Canonical(..)
7 , Canonical(..)
10 , toFilePath
11 , toFilePath
11− , BadName(..)
12+ , toJsonString
13 , dummyName, kernel, core
14 , dummyName, kernel, core
14 , browser, virtualDom, html
15 , browser, virtualDom, html
15 , json, http, url
16 , json, http, url
16 , webgl, linearAlgebra
17 , webgl, linearAlgebra
17 , suggestions
19 , suggestions
24import Control.Monad (liftM2)
31import Control.Monad (liftM2)
25import Data.Binary (Binary, get, put)
32import Data.Binary (Binary, get, put)
26−import qualified Data.Char as Char
33+import qualified Data.Coerce as Coerce
34+import Data.Function (on)
27−import qualified Data.Name as Name
35+import qualified Data.List as List
28import qualified Data.Map as Map
36import qualified Data.Map as Map
37+import qualified Data.Name as Name
29import Data.Monoid ((<>))
38import Data.Monoid ((<>))
30import qualified Data.Utf8 as Utf8
39import qualified Data.Utf8 as Utf8
40+import Data.Word (Word8)
41+import Foreign.Ptr (Ptr, plusPtr, minusPtr)
31import System.FilePath ((</>))
42import System.FilePath ((</>))
33import qualified Elm.Version as V
44import qualified Elm.Version as V
34−import qualified Json.Decode as Decode
45+import qualified Json.Decode as D
35−import qualified Json.Encode as Encode
46+import qualified Json.Encode as E
47+import qualified Json.String as Json
48+import qualified Parse.Primitives as P
49+import Parse.Primitives (Row, Col)
50+import qualified Reporting.Suggest as Suggest
@@ -41,11 +56,18 @@ import qualified Json.Encode as Encode
42data Name =
57data Name =
44− { _author :: !Name.Name
59+ { _author :: !Author
45− , _project :: !Name.Name
60+ , _project :: !Project
64+type Author = Utf8.Utf8 AUTHOR
65+type Project = Utf8.Utf8 PROJECT
49data Canonical =
71data Canonical =
51 { _name :: !Name
73 { _name :: !Name
@@ -60,127 +82,114 @@ data Canonical =
61isKernel :: Name -> Bool
83isKernel :: Name -> Bool
62isKernel (Name author _) =
84isKernel (Name author _) =
63− author == "elm" || author == "elm-explorations"
85+ author == elm || author == elm_explorations
66−toString :: Name -> Utf8.String
67−toString (Name author project) =
68− Utf8.join 0x2F [ Name.toUtf8 author, Name.toUtf8 project ]
71toChars :: Name -> String
88toChars :: Name -> String
72toChars (Name author project) =
89toChars (Name author project) =
73− Name.toChars author <> "/" <> Name.toChars project
90+ Utf8.toChars author <> "/" <> Utf8.toChars project
76toUrl :: Name -> String
93toUrl :: Name -> String
77toUrl (Name author project) =
94toUrl (Name author project) =
78− Name.toChars author ++ "/" ++ Name.toChars project
95+ Utf8.toChars author ++ "/" ++ Utf8.toChars project
81toFilePath :: Name -> FilePath
98toFilePath :: Name -> FilePath
82toFilePath (Name author project) =
99toFilePath (Name author project) =
83− Name.toChars author </> Name.toChars project
100+ Utf8.toChars author </> Utf8.toChars project
86−fromString :: Utf8.String -> Either BadName Name
103+toJsonString :: Name -> Json.String
87−fromString str =
104+toJsonString (Name author project) =
88− case Utf8.split 0x2F {- / -} str of
89− [author, project]
105+ Utf8.join 0x2F {-/-} [ Coerce.coerce author, Coerce.coerce project ]
90− | Utf8.isEmpty author -> Left BadStructure
91− | Utf8.isEmpty project -> Left BadStructure
92− | Utf8.size project >= 256 -> Left TooLong
93− | Utf8.containsDouble 0x2D {- - -} project -> Left HasDoubleDash
94− | Utf8.contains 0x5F {- _ -} project -> Left HasUnderscore
95− | Utf8.contains 0x2E {- . -} project -> Left HasDot
96− | Utf8.any Char.isUpper project -> Left HasUppercase
97− | Utf8.endsWithWord 0x2D {- - -} project -> Left HasDashEnd
98− | Utf8.startsWithChar Char.isLower project -> Right (Name (Name.fromUtf8 author) (Name.fromUtf8 project))
99− | True -> Left HasNonLowercaseStart
105−data BadName
109+-- COMMON PACKAGE NAMES
113− | HasNonLowercaseStart
117−-- COMMON PACKAGE NAMES
112+toName :: Author -> [Char] -> Name
113+toName author project =
114+ Name author (Utf8.fromChars project)
120{-# NOINLINE dummyName #-}
117{-# NOINLINE dummyName #-}
121dummyName :: Name
118dummyName :: Name
122dummyName =
119dummyName =
123− Name "author" "project"
120+ toName (Utf8.fromChars "author") "project"
126{-# NOINLINE kernel #-}
123{-# NOINLINE kernel #-}
127kernel :: Name
124kernel :: Name
129− Name "elm" "kernel"
126+ toName elm "kernel"
132{-# NOINLINE core #-}
129{-# NOINLINE core #-}
133core :: Name
130core :: Name
135− Name "elm" "core"
132+ toName elm "core"
138{-# NOINLINE browser #-}
135{-# NOINLINE browser #-}
139browser :: Name
136browser :: Name
141− Name "elm" "browser"
138+ toName elm "browser"
144{-# NOINLINE virtualDom #-}
141{-# NOINLINE virtualDom #-}
145virtualDom :: Name
142virtualDom :: Name
146virtualDom =
143virtualDom =
147− Name "elm" "virtual-dom"
144+ toName elm "virtual-dom"
150{-# NOINLINE html #-}
147{-# NOINLINE html #-}
151html :: Name
148html :: Name
153− Name "elm" "html"
150+ toName elm "html"
156{-# NOINLINE json #-}
153{-# NOINLINE json #-}
157json :: Name
154json :: Name
159− Name "elm" "json"
156+ toName elm "json"
162{-# NOINLINE http #-}
159{-# NOINLINE http #-}
163http :: Name
160http :: Name
165− Name "elm" "http"
162+ toName elm "http"
168{-# NOINLINE url #-}
165{-# NOINLINE url #-}
169url :: Name
166url :: Name
171− Name "elm" "url"
168+ toName elm "url"
174{-# NOINLINE webgl #-}
171{-# NOINLINE webgl #-}
175webgl :: Name
172webgl :: Name
177− Name "elm-explorations" "webgl"
174+ toName elm_explorations "webgl"
180{-# NOINLINE linearAlgebra #-}
177{-# NOINLINE linearAlgebra #-}
181linearAlgebra :: Name
178linearAlgebra :: Name
182linearAlgebra =
179linearAlgebra =
183− Name "elm-explorations" "linear-algebra"
180+ toName elm_explorations "linear-algebra"
186+ Utf8.fromChars "elm"
189+{-# NOINLINE elm_explorations #-}
190+elm_explorations :: Author
192+ Utf8.fromChars "elm-explorations"
@@ -190,28 +199,63 @@ linearAlgebra =
190suggestions :: Map.Map Name.Name Name
199suggestions :: Map.Map Name.Name Name
191suggestions =
200suggestions =
193− random = Name "elm" "random"
202+ random = toName elm "random"
194− time = Name "elm" "time"
203+ time = toName elm "time"
195− file = Name "elm" "file"
204+ file = toName elm "file"
197 Map.fromList
206 Map.fromList
198− [ ("Browser", browser)
207+ [ "Browser" ==> browser
199− , ("File", file)
208+ , "File" ==> file
200− , ("File.Download", file)
209+ , "File.Download" ==> file
201− , ("File.Select", file)
210+ , "File.Select" ==> file
202− , ("Html", html)
211+ , "Html" ==> html
203− , ("Html.Attributes", html)
212+ , "Html.Attributes" ==> html
204− , ("Html.Events", html)
213+ , "Html.Events" ==> html
205− , ("Http", http)
214+ , "Http" ==> http
206− , ("Json.Decode", json)
215+ , "Json.Decode" ==> json
207− , ("Json.Encode", json)
216+ , "Json.Encode" ==> json
208− , ("Random", random)
217+ , "Random" ==> random
209− , ("Time", time)
218+ , "Time" ==> time
210− , ("Url.Parser", url)
219+ , "Url.Parser" ==> url
211− , ("Url", url)
220+ , "Url" ==> url
224+(==>) :: [Char] -> Name -> (Name.Name, Name)
225+(==>) moduleName package =
226+ ( Utf8.fromChars moduleName, package )
233+nearbyNames :: Name -> [Name] -> [Name]
234+nearbyNames (Name author1 project1) possibleNames =
236+ authorDist = authorDistance (Utf8.toChars author1)
237+ projectDist = projectDistance (Utf8.toChars project1)
239+ addDistance name@(Name author2 project2) =
240+ ( authorDist author2 + projectDist project2, name )
243+ List.sortBy (compare `on` fst) $
244+ map addDistance possibleNames
247+authorDistance :: [Char] -> Author -> Int
248+authorDistance given possibility =
249+ if possibility == elm || possibility == elm_explorations
251+ else abs (Suggest.distance given (Utf8.toChars possibility))
254+projectDistance :: [Char] -> Project -> Int
255+projectDistance given possibility =
256+ abs (Suggest.distance given (Utf8.toChars possibility))
216-- INSTANCES
260-- INSTANCES
@@ -234,9 +278,13 @@ instance Ord Name where
237−instance Binary Name where
285+instance Binary Name where -- TODO try storing as a Word16
238− get = liftM2 Name get get
286+ get = liftM2 Name Utf8.getUnder256 Utf8.getUnder256
239− put (Name a b) = put a >> put b
287+ put (Name a b) = Utf8.putUnder256 a >> Utf8.putUnder256 b
242instance Binary Canonical where
290instance Binary Canonical where
@@ -248,17 +296,89 @@ instance Binary Canonical where
251−decoder :: Decode.Decoder BadName Name
299+decoder :: D.Decoder (Row, Col) Name
253− do str <- Decode.string
301+ D.customString parser (,)
254− case fromString str of
262−encode :: Name -> Encode.Value
304+encode :: Name -> E.Value
263encode name =
305encode name =
264− Encode.string (toString name)
306+ E.chars (toChars name)
309+keyDecoder :: (Row -> Col -> x) -> D.KeyDecoder x Name
313+ P.specialize (\(r,c) _ _ -> toError r c) parser
315+ D.KeyDecoder keyParser toError
322+parser :: P.Parser (Row, Col) Name
324+ do author <- parseName isAlphaOrDigit isAlphaOrDigit
325+ P.word1 0x2F {-/-} (,)
326+ project <- parseName isLower isLowerOrDigit
327+ return (Name author project)
330+parseName :: (Word8 -> Bool) -> (Word8 -> Bool) -> P.Parser (Row, Col) (Utf8.Utf8 t)
331+parseName isGoodStart isGoodInner =
332+ P.Parser $ \(P.State src pos end indent row col) cok _ cerr eerr ->
336+ let !word = P.unsafeIndex pos in
337+ if not (isGoodStart word) then
341+ (# isGood, newPos #) = chompName isGoodInner (plusPtr pos 1) end False
342+ !len = fromIntegral (minusPtr newPos pos)
345+ if isGood && len < 256 then
346+ let !newState = P.State src newPos end indent row newCol in
347+ cok (Utf8.fromPtr pos newPos) newState
352+isLower :: Word8 -> Bool
354+ 0x61 {-a-} <= word && word <= 0x7A {-z-}
357+isLowerOrDigit :: Word8 -> Bool
358+isLowerOrDigit word =
359+ 0x61 {-a-} <= word && word <= 0x7A {-z-}
360+ || 0x30 {-0-} <= word && word <= 0x39 {-9-}
363+isAlphaOrDigit :: Word8 -> Bool
364+isAlphaOrDigit word =
365+ 0x61 {-a-} <= word && word <= 0x7A {-z-}
366+ || 0x41 {-A-} <= word && word <= 0x5A {-Z-}
367+ || 0x30 {-0-} <= word && word <= 0x39 {-9-}
370+chompName :: (Word8 -> Bool) -> Ptr Word8 -> Ptr Word8 -> Bool -> (# Bool, Ptr Word8 #)
371+chompName isGoodChar pos end prevWasDash =
373+ (# not prevWasDash, pos #)
375+ let !word = P.unsafeIndex pos in
376+ if isGoodChar word then
377+ chompName isGoodChar (plusPtr pos 1) end False
378+ else if word == 0x2D {---} then
382+ chompName isGoodChar (plusPtr pos 1) end True