git.iko.soy/rowtype-yoga/purescript-docs-search
on master help sign in
added: splitting index in parts
commit9e4f2943a3bf3d6bec816153cf466bc24e404e9e authorklntsky <klntsky@gmail.com> dateMon 1 Jul 2019, 13:10 (7 years ago) parent22e4fa0
19 files changed+1046−746
spago.dhall+1 src/Spago/Search/App.purs+1−2 src/Spago/Search/App/SearchField.purs+3−3 src/Spago/Search/App/SearchResults.purs+106−125 src/Spago/Search/Config.purs+25 src/Spago/Search/Declarations.purs+266 src/Spago/Search/DocsJson.js−24 src/Spago/Search/DocsJson.purs+117−121 src/Spago/Search/Extra.purs+1 src/Spago/Search/Index.js+30 src/Spago/Search/Index.purs+87−301 src/Spago/Search/IndexBuilder.purs+172−59 src/Spago/Search/SearchResult.purs+72 src/Spago/Search/TypeIndex.js+26 src/Spago/Search/TypeIndex.purs+114 src/Spago/Search/TypeQuery.purs+7−1 test/Main.purs+13−16 test/Test/TypeQuery.purs+5−4 test/Test/TypeShape.purs−90
spago.dhall+1
@@ -4,6 +4,7 @@
44 [ "aff-promise"
55 , "argonaut-codecs"
66 , "argonaut-core"
7+ , "argonaut-generic"
78 , "arrays"
89 , "console"
910 , "control"
src/Spago/Search/App.purs+1−2
@@ -36,8 +36,7 @@ main = do
3636 sfio <- runUI SearchField.component unit searchField
3737 srio <- runUI (SearchResults.mkComponent pageContents) unit searchResults
3838 sfio.subscribe $
39− Coroutine.consumer \message ->
40− srio.query $ H.tell $ SearchResults.SearchFieldMessage message
39+ Coroutine.consumer (srio.query <<< H.tell <<< SearchResults.MessageFromSearchField)
4140
4241insertStyle :: Document.Document -> Effect Unit
4342insertStyle doc = do
src/Spago/Search/App/SearchField.purs+3−3
@@ -20,13 +20,13 @@ data Action
2020 | EscapePressed
2121 | FocusChanged Boolean
2222
23−data Message
23+data SearchFieldMessage
2424 = InputUpdated String
2525 | InputCleared
2626 | Focused
2727 | LostFocus
2828
29−component :: forall q i. H.Component HH.HTML q i Message Aff
29+component :: forall q i. H.Component HH.HTML q i SearchFieldMessage Aff
3030component =
3131 H.mkComponent
3232 { initialState
@@ -37,7 +37,7 @@ component =
3737initialState :: forall i. i -> State
3838initialState _ = { input: "" }
3939
40−handleAction :: forall m. Action -> H.HalogenM State Action () Message m Unit
40+handleAction :: forall m. Action -> H.HalogenM State Action () SearchFieldMessage m Unit
4141handleAction = case _ of
4242 InputAction input -> do
4343 H.modify_ $ const { input }
src/Spago/Search/App/SearchResults.purs+106−125
@@ -2,47 +2,52 @@ module Spago.Search.App.SearchResults where
22
33import Prelude
44
5−import Spago.Search.Index
6−import Spago.Search.TypeDecoder
7−import Spago.Search.TypeQuery
8−import Spago.Search.TypeShape
5+import Spago.Search.App.SearchField (SearchFieldMessage(..))
6+import Spago.Search.Declarations (DeclLevel(..), declLevelToHashAnchor)
7+import Spago.Search.DocsJson (DataDeclType(..))
8+import Spago.Search.Extra ((>#>))
9+import Spago.Search.SearchResult (ResultInfo(..), SearchResult)
10+import Spago.Search.TypeDecoder (Constraint(..), FunDeps, Kind(..), QualifiedName(..), Type(..), TypeArgument(..))
11+import Spago.Search.TypeQuery (TypeQuery(..), parseTypeQuery)
12+import Spago.Search.TypeShape (joinForAlls, joinRows)
13+import Spago.Search.Config (config)
914
1015import CSS (textWhitespace, whitespacePreWrap)
1116import Data.Array ((!!))
1217import Data.Array as Array
13−import Data.Either (Either(..))
18+import Data.Either (hush)
1419import Data.List as List
15−import Data.Maybe (Maybe(..), isJust, isNothing)
20+import Data.Maybe (Maybe(..), isJust, maybe)
1621import Data.Newtype (unwrap, wrap)
17−import Data.Search.Trie as Trie
1822import Data.String (length) as String
19−import Data.String.CodeUnits (toCharArray, stripSuffix) as String
23+import Data.String.CodeUnits (stripSuffix) as String
2024import Data.String.Common (toLower, trim) as String
2125import Data.String.Pattern (Pattern(..)) as String
2226import Effect.Aff (Aff)
23−import Effect.Console (error)
2427import Halogen as H
2528import Halogen.HTML as HH
2629import Halogen.HTML.CSS as HS
2730import Halogen.HTML.Events as HE
2831import Halogen.HTML.Properties as HP
29−import Spago.Search.App.SearchField (Message(..))
30−import Spago.Search.DocsJson (DataDeclType(..), loadDeclarations)
31−import Spago.Search.Extra (whenJust, (>#>))
32+import Spago.Search.Index (Index)
33+import Spago.Search.Index as Index
34+import Spago.Search.TypeIndex (TypeIndex)
35+import Spago.Search.TypeIndex as TypeIndex
3236import Web.DOM.Element (Element)
3337import Web.DOM.Element as Element
3438import Web.HTML as HTML
3539import Web.HTML.Location as Location
3640import Web.HTML.Window as Window
3741
38−data Mode = Off | Loading | Active
42+data Mode = Off | Loading | Active | InputTooShort
3943
4044derive instance eqMode :: Eq Mode
4145
4246-- | Is it a search by type or by name?
4347data ResultsType = TypeResults TypeQuery | DeclResults
4448
45−type State = { mbIndex :: Maybe SearchIndex
49+type State = { index :: Index
50+ , typeIndex :: TypeIndex
4651 , results :: Array SearchResult
4752 , resultsType :: ResultsType
4853 , input :: String
@@ -52,7 +57,7 @@ type State = { mbIndex :: Maybe SearchIndex
5257 }
5358
5459data Query a
55− = SearchFieldMessage Message a
60+ = MessageFromSearchField SearchFieldMessage a
5661
5762data Action
5863 = SearchResultClicked String
@@ -64,12 +69,13 @@ mkComponent
6469 -> H.Component HH.HTML Query i o Aff
6570mkComponent contents =
6671 H.mkComponent
67− { initialState: const { mbIndex: Nothing
72+ { initialState: const { index: mempty
73+ , typeIndex: mempty
6874 , results: []
6975 , resultsType: DeclResults
7076 , input: ""
7177 , contents
72− , resultsCount: 25
78+ , resultsCount: config.resultsCount
7379 , mode: Off
7480 }
7581 , render
@@ -81,82 +87,49 @@ handleQuery
8187 :: forall o a
8288 . Query a
8389 -> H.HalogenM State Action () o Aff (Maybe a)
84−handleQuery (SearchFieldMessage Focused next) = do
90+handleQuery (MessageFromSearchField Focused next) = do
8591 pure Nothing
86−handleQuery (SearchFieldMessage LostFocus next) = do
92+handleQuery (MessageFromSearchField LostFocus next) = do
8793 pure Nothing
88−handleQuery (SearchFieldMessage InputCleared next) = do
94+handleQuery (MessageFromSearchField InputCleared next) = do
8995 H.modify_ (_ { results = [], input = "", mode = Off })
90− updateSearchResults
9196 showPageContents
9297 pure Nothing
93−handleQuery (SearchFieldMessage (InputUpdated input) next) = do
94− H.modify_ (_ { input = String.trim input })
98+handleQuery (MessageFromSearchField (InputUpdated input_) next) = do
99+ let input = String.trim input_
95100
96− H.get >>= \state -> when (isNothing state.mbIndex && state.mode == Off) do
97− H.modify_ (_ { mode = Loading })
98− void $ H.fork do
99− eiDeclarations <-
100− H.liftAff $ loadDeclarations "../spago-search-index.js"
101− case eiDeclarations of
102− Left err -> do
103− H.liftEffect do
104− error $ "spago-search: couldn't load search index: " <> err
105− Right declarations -> do
106− H.modify_ (_ { mbIndex = Just $ mkSearchIndex declarations
107− , mode = Active })
108− updateSearchResults
109−
110− updateSearchResults
111− pure Nothing
112−
113−updateSearchResults
114− :: forall o
115− . H.HalogenM State Action () o Aff Unit
116−updateSearchResults = do
117− { input } <- H.get
101+ state <- H.modify (_ { input = input })
118102
119103 if String.length input < 2
120104 then do
121− showPageContents
122− H.modify_ (_ { results = [] })
123−
105+ if input == ""
106+ then do
107+ H.modify_ (_ { mode = Off })
108+ showPageContents
109+ else do
110+ H.modify_ (_ { mode = InputTooShort })
111+ hidePageContents
124112 else do
113+ H.modify_ (_ { mode = Loading, resultsCount = config.resultsCount })
114+
115+ void $ H.fork do
116+ let resultsType =
117+ maybe DeclResults TypeResults (hush (parseTypeQuery state.input)
118+ >>= isValuableTypeQuery)
119+
120+ case resultsType of
121+
122+ DeclResults -> do
123+ { index, results } <- H.liftAff $ Index.query state.index (String.toLower state.input)
124+ H.modify_ (_ { results = results, mode = Active, index = index })
125+
126+ TypeResults query -> do
127+ { index, results } <- H.liftAff $ TypeIndex.query state.typeIndex query
128+ H.modify_ (_ { results = results, mode = Active, typeIndex = index })
129+
125130 hidePageContents
126− state <- H.get
127−
128− whenJust (unwrap <$> state.mbIndex) \index -> do
129− let path = List.fromFoldable $
130− String.toCharArray $
131− String.toLower input
132−
133− eiTypeQuery = parseTypeQuery input
134−
135− resultsType =
136− case eiTypeQuery of
137− Left _ -> DeclResults
138− Right query
139− | isValuableTypeQuery query -> TypeResults query
140− | otherwise -> DeclResults
141−
142− results =
143− case resultsType of
144− DeclResults ->
145− Array.concat $
146− List.toUnfoldable $
147− map List.toUnfoldable $
148− Trie.queryValues path $
149− index.decls
150− TypeResults query ->
151− List.toUnfoldable $
152− join $
153− Trie.queryValues shape index.types
154− where
155− shape = shapeOfTypeQuery query
156−
157− H.modify_ (_ { results = results
158− , resultsType = resultsType
159− , resultsCount = 25 })
131+
132+ pure Nothing
160133
161134handleAction
162135 :: forall o
@@ -201,52 +174,59 @@ render
201174 :: forall m
202175 . State
203176 -> H.ComponentHTML Action () m
177+render { mode: Off } = HH.div_ []
204178render { mode: Loading } =
205− HH.div [ HP.classes [ wrap "container", wrap "clearfix" ] ] $
206− pure $
207− HH.div [ HP.classes [ wrap "col", wrap "col--main" ] ] $
179+ renderContainer $
208180 [ HH.h1_ [ HH.text "Loading..." ] ]
209−
181+render { mode: InputTooShort } =
182+ renderContainer $
183+ [ HH.h1_ [ HH.text "Error" ] ] <>
184+ [ HH.div [ HP.classes [ wrap "result", wrap "result--empty" ] ]
185+ [ HH.text "Search query is too short." ]
186+ ]
187+render state@{ mode: Active, results: [] } =
188+ renderContainer $
189+
190+ [ HH.h1_ [ HH.text "Search results" ]
191+ , HH.div [ HP.classes [ wrap "result", wrap "result--empty" ] ]
192+ [ HH.text "Your search for "
193+ , HH.strong_ [ HH.text state.input ]
194+ , HH.text " did not yield any results."
195+ ]
196+ ]
210197render state@{ mode: Active } =
211− HH.div [ HP.classes [ wrap "container", wrap "clearfix" ] ] $
212− pure $
213−
214− HH.div [ HP.classes [ wrap "col", wrap "col--main" ] ] $
215−
216− [ HH.h1_ [ HH.text "Search results" ] ] <>
217−
218− if Array.null state.results
219− then
220− [ HH.div [ HP.classes [ wrap "result", wrap "result--empty" ] ]
221− [ HH.text "Your search for "
222− , HH.strong_ [ HH.text state.input ]
223− , HH.text " did not yield any results."
224− ]
198+ renderContainer $
199+ [ HH.h1_ [ HH.text "Search results" ]
200+
201+ , HH.div [ HP.classes [ wrap "result" ] ] $
202+ [ HH.text "Found "
203+ , HH.strong_ [ HH.text $ show $ Array.length state.results ]
204+ , HH.text $
205+ case state.resultsType of
206+ DeclResults -> " definitions."
207+ TypeResults _ -> " definitions with similar types."
225208 ]
226− else
227− let selectedResults = Array.take state.resultsCount state.results in
228− [ HH.div [ HP.classes [ wrap "result" ] ] $
229− [ HH.text "Found "
230− , HH.strong_ [ HH.text $ show $ Array.length state.results ]
231− , HH.text $
232− case state.resultsType of
233− DeclResults -> " definitions."
234− TypeResults _ -> " definitions with similar types."
235− ]
236209
237− , HH.div [ HP.id_ "spage-search-results-container" ] $
238− Array.concat $ selectedResults <#> renderResult
210+ , HH.div_ $
211+ Array.concat $ shownResults <#> renderResult
239212
240− , HH.div [ HP.class_ (wrap "load_more"), HP.id_ "load-more" ]
241− [ if Array.length selectedResults < Array.length state.results
242− then HH.a [ HP.id_ "load-more-link"
243− , HE.onClick $ const $ Just MoreResultsRequested ]
244− [ HH.text "Show more results" ]
245− else HH.p_
246− [ HH.text "No further results." ]
247− ]
213+ , HH.div [ HP.class_ (wrap "load_more"), HP.id_ "load-more" ]
214+ [ if Array.length shownResults < Array.length state.results
215+ then HH.a [ HP.id_ "load-more-link"
216+ , HE.onClick $ const $ Just MoreResultsRequested ]
217+ [ HH.text "Show more results" ]
218+ else HH.p_
219+ [ HH.text "No further results." ]
248220 ]
249−render { mode: Off } = HH.div_ []
221+ ]
222+ where
223+ shownResults = Array.take state.resultsCount state.results
224+
225+renderContainer :: forall a b. Array (HH.HTML b a) -> HH.HTML b a
226+renderContainer =
227+ HH.div [ HP.classes [ wrap "container", wrap "clearfix" ] ] <<<
228+ pure <<<
229+ HH.div [ HP.classes [ wrap "col", wrap "col--main" ] ]
250230
251231renderSummary
252232 :: forall a b
@@ -338,6 +318,7 @@ renderResultType result =
338318 wrapSignature signature =
339319 [ HH.pre [ HP.class_ (wrap "result__signature") ] [ HH.code_ signature ] ]
340320
321+-- TODO: render fundeps
341322renderTypeClassSignature
342323 :: forall a rest
343324 . { fundeps :: FunDeps
@@ -617,7 +598,7 @@ syntax str = HH.span [ HP.class_ (wrap "syntax") ] [ HH.text str ]
617598space :: forall a b. HH.HTML a b
618599space = HH.text " "
619600
620−isValuableTypeQuery :: TypeQuery -> Boolean
621−isValuableTypeQuery (QVar _) = false
622−isValuableTypeQuery (QConst _) = false
623−isValuableTypeQuery _ = true
601+isValuableTypeQuery :: TypeQuery -> Maybe TypeQuery
602+isValuableTypeQuery (QVar _) = Nothing
603+isValuableTypeQuery (QConst _) = Nothing
604+isValuableTypeQuery query = Just query
src/Spago/Search/Config.pursnew file+25
@@ -0,0 +1,25 @@
1+module Spago.Search.Config where
2+
3+import Prelude
4+
5+config =
6+ { outputDirectory: "output"
7+ , requiredDirectories:
8+ [ "generated-docs"
9+ , "output"
10+ ]
11+ -- ^ Directories required by IndexBuilder
12+ , indexDirectory: "generated-docs/index"
13+ , declIndexDirectory: "generated-docs/index/declarations"
14+ , typeIndexDirectory: "generated-docs/index/types"
15+ , mkShapeScriptPath:
16+ \shape -> "../index/types/" <> shape <> ".js"
17+ , countOfIndexParts: 50
18+ -- ^ In how many parts the index should be splitted?
19+ , mkIndexPartPath:
20+ \(partId :: Int) -> "generated-docs/index/declarations/" <> show partId <> ".js"
21+ , mkIndexPartLoadPath:
22+ \(partId :: Int) -> "../index/declarations/" <> show partId <> ".js"
23+ , resultsCount: 25
24+ -- ^ How many results to show by default?
25+ }
src/Spago/Search/Declarations.pursnew file+266
@@ -0,0 +1,266 @@
1+module Spago.Search.Declarations where
2+
3+import Prelude
4+
5+import Spago.Search.SearchResult (ResultInfo(..), SearchResult(..))
6+import Spago.Search.DocsJson (ChildDeclType(..), ChildDeclaration(..), DeclType(..), Declaration(..), DocsJson(..))
7+import Spago.Search.TypeDecoder (Constraint(..), QualifiedName(..), Type(..))
8+import Spago.Search.TypeShape (joinForAlls)
9+
10+import Control.Alt ((<|>))
11+import Data.Array ((!!))
12+import Data.Foldable (foldr)
13+import Data.List (List, (:))
14+import Data.List as List
15+import Data.Maybe (Maybe(..), fromMaybe)
16+import Data.Newtype (class Newtype, unwrap, wrap)
17+import Data.Search.Trie (Trie, alter)
18+import Data.String.CodeUnits (stripPrefix, stripSuffix, toCharArray)
19+import Data.String.Common (toLower)
20+import Data.String.Common (split) as String
21+import Data.String.Pattern (Pattern(..))
22+
23+type ModuleName = String
24+type PackageName = String
25+
26+newtype Declarations
27+ = Declarations (Trie Char (List SearchResult))
28+
29+derive instance newtypeDeclarations :: Newtype Declarations _
30+derive newtype instance semigroupDeclarations :: Semigroup Declarations
31+derive newtype instance monoidDeclarations :: Monoid Declarations
32+
33+mkDeclarations :: Array DocsJson -> Declarations
34+mkDeclarations docsJson = Declarations trie
35+ where
36+ trie = foldr insertDocsJson mempty docsJson
37+
38+insertDocsJson
39+ :: DocsJson
40+ -> Trie Char (List SearchResult)
41+ -> Trie Char (List SearchResult)
42+insertDocsJson (DocsJson { name, declarations }) trie
43+ = foldr (insertDeclaration name) trie declarations
44+
45+insertDeclaration
46+ :: ModuleName
47+ -> Declaration
48+ -> Trie Char (List SearchResult)
49+ -> Trie Char (List SearchResult)
50+insertDeclaration moduleName entry@(Declaration { title }) trie
51+ = foldr insertSearchResult trie (resultsForEntry moduleName entry)
52+
53+insertSearchResult
54+ :: { path :: String
55+ , result :: SearchResult
56+ }
57+ -> Trie Char (List SearchResult)
58+ -> Trie Char (List SearchResult)
59+insertSearchResult { path, result } trie =
60+ let path' = List.fromFoldable $ toCharArray $ toLower path in
61+ alter path' updateResults trie
62+ where
63+ updateResults mbOldResults =
64+ case mbOldResults of
65+ Just oldResults ->
66+ Just $ result : oldResults
67+ Nothing ->
68+ Just $ List.singleton result
69+
70+resultsForEntry
71+ :: ModuleName
72+ -> Declaration
73+ -> List { path :: String
74+ , result :: SearchResult
75+ }
76+resultsForEntry moduleName indexEntry@(Declaration entry) =
77+ let { info, title, sourceSpan, comments, children } = entry
78+ { name, declLevel } = getLevelAndName info.declType title
79+ packageName = extractPackageName sourceSpan.name
80+ in case mkInfo declLevel indexEntry of
81+ Nothing -> mempty
82+ Just info' ->
83+ let result = SearchResult { name: title
84+ , comments
85+ , hashAnchor: declLevelToHashAnchor declLevel
86+ , moduleName
87+ , sourceSpan: Just sourceSpan
88+ , packageName
89+ , info: info'
90+ }
91+ in
92+ ( List.singleton $
93+ { path: name
94+ , result
95+ }
96+ ) <>
97+ ( List.fromFoldable children >>=
98+ resultsForChildDeclaration packageName moduleName result
99+ )
100+
101+mkInfo :: DeclLevel -> Declaration -> Maybe ResultInfo
102+mkInfo declLevel (Declaration { info, title }) =
103+ case info.declType of
104+
105+ DeclValue ->
106+ info.type <#>
107+ \ty -> ValueResult { type: ty }
108+
109+ DeclData ->
110+ make <$> info.typeArguments <*> info.dataDeclType
111+ where
112+ make typeArguments dataDeclType =
113+ DataResult { typeArguments, dataDeclType }
114+
115+ DeclExternData ->
116+ info.kind <#>
117+ \kind -> ExternDataResult { kind }
118+
119+ DeclTypeSynonym ->
120+ make <$> info.type <*> info.arguments
121+ where
122+ make ty args = TypeSynonymResult { type: ty, arguments: args }
123+
124+ DeclTypeClass ->
125+ case info.fundeps, info.arguments, info.superclasses of
126+ Just fundeps, Just arguments, Just superclasses ->
127+ Just $ TypeClassResult { fundeps, arguments, superclasses }
128+ _, _, _ -> Nothing
129+
130+ DeclAlias ->
131+ case declLevel of
132+ TypeLevel -> Just TypeAliasResult
133+ ValueLevel -> Just ValueAliasResult
134+ _ -> Nothing
135+
136+ DeclExternKind ->
137+ Just ExternKindResult
138+
139+-- | Level of a declaration, used to determine which URI hash anchor to use in
140+-- | links ("v", "t" or "k").
141+data DeclLevel = ValueLevel | TypeLevel | KindLevel
142+
143+declLevelToHashAnchor :: DeclLevel -> String
144+declLevelToHashAnchor = case _ of
145+ ValueLevel -> "v"
146+ TypeLevel -> "t"
147+ KindLevel -> "k"
148+
149+getLevelAndName
150+ :: DeclType
151+ -> String
152+ -> { declLevel :: DeclLevel
153+ , name :: String
154+ }
155+getLevelAndName DeclValue name = { name, declLevel: ValueLevel }
156+getLevelAndName DeclData name = { name, declLevel: TypeLevel }
157+getLevelAndName DeclTypeSynonym name = { name, declLevel: TypeLevel }
158+getLevelAndName DeclTypeClass name = { name, declLevel: ValueLevel }
159+-- "declType": "alias" does not specify the level of the declaration.
160+-- But for type aliases, name of the declaration is always wrapped into
161+-- "type (" and ")".
162+getLevelAndName DeclAlias title =
163+ fromMaybe (withAnchor ValueLevel title) $
164+ (withAnchor ValueLevel <$>
165+ (stripPrefix (Pattern "(") >=>
166+ stripSuffix (Pattern ")")) title) <|>
167+ (withAnchor TypeLevel <$>
168+ (stripPrefix (Pattern "type (") >=>
169+ stripSuffix (Pattern ")")) title)
170+ where
171+ withAnchor declLevel name = { declLevel, name }
172+getLevelAndName DeclExternData name = { name, declLevel: TypeLevel }
173+getLevelAndName DeclExternKind name = { name, declLevel: KindLevel }
174+
175+-- | Extract package name from `sourceSpan.name`, which contains path to
176+-- | the source file.
177+extractPackageName :: String -> String
178+extractPackageName name =
179+ let chunks = String.split (Pattern "/") name in
180+ fromMaybe "<unknown>" $
181+ chunks !! 0 >>= \dir ->
182+ -- TODO: is it safe to assume that directory name is ".spago"?
183+ if dir == ".spago" then
184+ chunks !! 1
185+ else
186+ Just "<local package>"
187+
188+resultsForChildDeclaration
189+ :: PackageName
190+ -> ModuleName
191+ -> SearchResult
192+ -> ChildDeclaration
193+ -> List { path :: String, result :: SearchResult }
194+resultsForChildDeclaration packageName moduleName parentResult
195+ child@(ChildDeclaration { title, info, comments, mbSourceSpan }) =
196+ case mkChildInfo parentResult child of
197+ Nothing -> mempty
198+ Just resultInfo ->
199+ { path: title
200+ , result: SearchResult { name: title
201+ , comments
202+ , hashAnchor: "v"
203+ , moduleName
204+ , sourceSpan: mbSourceSpan
205+ , packageName
206+ , info: resultInfo
207+ }
208+ } # List.singleton
209+
210+mkChildInfo
211+ :: SearchResult
212+ -> ChildDeclaration
213+ -> Maybe ResultInfo
214+mkChildInfo parentResult (ChildDeclaration { info } ) =
215+ case info.declType of
216+ ChildDeclDataConstructor ->
217+ info.arguments <#>
218+ \arguments -> DataConstructorResult { arguments }
219+ ChildDeclTypeClassMember ->
220+ case (unwrap parentResult).info of
221+ TypeClassResult { arguments } ->
222+ -- We need to reconstruct a "real" type of a type class member.
223+ -- For example, if `unconstrainedType` is the type of `pure`, i.e. `forall a. a -> m a`,
224+ -- `restoredType` should be `forall m a. Control.Applicative.Applicative m => a -> m a`.
225+ info.type <#>
226+ \(unconstrainedType :: Type) ->
227+ let
228+ -- First, we get a list of nested `forall` quantifiers for `unconstrainedType`
229+ -- and a version of `unconstrainedType` without them (`ty`).
230+ { ty, binders } = joinForAlls unconstrainedType
231+
232+ -- Then we construct a qualified name of the type class.
233+ constraintClass =
234+ QualifiedName { moduleName:
235+ String.split (wrap ".")
236+ (unwrap parentResult).moduleName
237+ , name: (unwrap parentResult).name }
238+
239+ typeClassArguments = arguments <#> unwrap >>> _.name
240+
241+ -- We concatenate two lists:
242+ -- * list of type parameters of the type class, and
243+ -- * list of quantified variables of the unconstrained type
244+ allArguments :: Array String
245+ allArguments =
246+ typeClassArguments <> (List.toUnfoldable binders <#> (_.var))
247+
248+ restoreType :: Type -> Type
249+ restoreType =
250+ foldr
251+ (\arg -> compose (\type'' -> ForAll arg type'' Nothing))
252+ identity allArguments
253+
254+ restoredType = restoreType $
255+ ConstrainedType (Constraint { constraintClass
256+ , constraintArgs: typeClassArguments <#> TypeVar
257+ }) ty
258+
259+ in TypeClassMemberResult
260+ { type: restoredType
261+ , typeClass: constraintClass
262+ , typeClassArguments
263+ }
264+
265+ _ -> Nothing
266+ ChildDeclInstance -> Nothing
src/Spago/Search/DocsJson.jsdeleted−24
@@ -1,24 +0,0 @@
1−/* global exports */
2−
3−exports.loadDeclarations_ = function (url) {
4− return function () {
5− if (typeof window.spagoSearchIndex == 'undefined') {
6− return new Promise(function(resolve, reject) {
7− var script = document.createElement('script');
8− script.type = 'text/javascript';
9− script.src = url;
10− script.addEventListener('load', function () {
11− if (typeof window.spagoSearchIndex == 'undefined') {
12− reject();
13− } else {
14− resolve(window.spagoSearchIndex);
15− }
16− });
17− script.addEventListener('error', reject);
18− document.body.appendChild(script);
19− });
20− } else {
21− return Promise.resolve(window.spagoSearchIndex);
22− }
23− };
24−};
src/Spago/Search/DocsJson.purs+117−121
@@ -16,6 +16,109 @@ import Data.Newtype (class Newtype, unwrap)
1616import Effect (Effect)
1717import Effect.Aff (Aff)
1818
19+newtype DocsJson
20+ = DocsJson { name :: String
21+ , declarations :: Array Declaration
22+ }
23+
24+derive instance eqDocsJson :: Eq DocsJson
25+derive instance genericDocsJson :: Generic DocsJson _
26+derive instance newtypeDocsJson :: Newtype DocsJson _
27+
28+instance showDocsJson :: Show DocsJson where
29+ show = genericShow
30+
31+instance decodeJsonDocsJson :: DecodeJson DocsJson where
32+ decodeJson json = DocsJson <$> decodeJson json
33+
34+instance encodeJsonDocsJson :: EncodeJson DocsJson where
35+ encodeJson = encodeJson <<< unwrap
36+
37+newtype Declaration
38+ = Declaration { title :: String
39+ , comments :: Maybe String
40+ , info :: { declType :: DeclType
41+ , dataDeclType :: Maybe DataDeclType
42+ , kind :: Maybe Kind
43+ , typeArguments :: Maybe (Array TypeArgument)
44+ , type :: Maybe Type
45+ , superclasses :: Maybe (Array Constraint)
46+ , arguments :: Maybe (Array TypeArgument)
47+ , fundeps :: Maybe FunDeps
48+ }
49+ , sourceSpan :: { start :: Array Int
50+ , end :: Array Int
51+ , name :: String
52+ }
53+ , children :: Array ChildDeclaration
54+ }
55+
56+derive instance eqDeclaration :: Eq Declaration
57+derive instance genericDeclaration :: Generic Declaration _
58+derive instance newtypeDeclaration :: Newtype Declaration _
59+
60+instance showDeclaration :: Show Declaration where
61+ show = genericShow
62+
63+instance decodeJsonDeclaration :: DecodeJson Declaration where
64+ decodeJson json = Declaration <$> do
65+ handle <- decodeJson json
66+ title <- handle .: "title"
67+ comments <- handle .:? "comments"
68+ children <- handle .: "children"
69+ info <- handle .: "info" >>= \info -> do
70+ ty <- info .:? "type"
71+ kind <- info .:? "kind"
72+ typeArguments <- info .:? "typeArguments"
73+ arguments <- info .:? "arguments"
74+ superclasses <- info .:? "superclasses"
75+ fundeps <- info .:? "fundeps"
76+ declType <- info .: "declType"
77+ dataDeclType <- info .:? "dataDeclType"
78+ pure { type: ty, kind, declType, typeArguments, superclasses, fundeps
79+ , arguments, dataDeclType }
80+ sourceSpan <- handle .: "sourceSpan"
81+ pure { title, comments, info, sourceSpan, children }
82+
83+instance encodeJsonDeclaration :: EncodeJson Declaration where
84+ encodeJson = encodeJson <<< unwrap
85+
86+newtype ChildDeclaration
87+ = ChildDeclaration { title :: String
88+ , comments :: Maybe String
89+ , info :: { declType :: ChildDeclType
90+ , arguments :: Maybe (Array Type)
91+ , type :: Maybe Type
92+ }
93+ , mbSourceSpan :: Maybe { start :: Array Int
94+ , end :: Array Int
95+ , name :: String
96+ }
97+ }
98+
99+derive instance eqChildDeclaration :: Eq ChildDeclaration
100+derive instance genericChildDeclaration :: Generic ChildDeclaration _
101+derive instance newtypeChildDeclaration :: Newtype ChildDeclaration _
102+
103+instance showChildDeclaration :: Show ChildDeclaration where
104+ show = genericShow
105+
106+instance decodeJsonChildDeclaration :: DecodeJson ChildDeclaration where
107+ decodeJson json = ChildDeclaration <$> do
108+ handle <- decodeJson json
109+ title <- handle .: "title"
110+ comments <- handle .:? "comments"
111+ info <- handle .: "info" >>= \info -> do
112+ arguments <- info .:? "arguments"
113+ ty <- info .:? "type"
114+ declType <- info .: "declType"
115+ pure { arguments, declType, type: ty }
116+ mbSourceSpan <- handle .:? "sourceSpan"
117+ pure { title, comments, info, mbSourceSpan }
118+
119+instance encodeJsonChildDeclaration :: EncodeJson ChildDeclaration where
120+ encodeJson = encodeJson <<< unwrap
121+
19122-- See `src/Language/Purescript/Docs/Types.hs`
20123data DeclType
21124 = DeclValue
@@ -34,17 +137,18 @@ instance showDeclType :: Show DeclType where
34137
35138instance encodeJsonDeclType :: EncodeJson DeclType where
36139 encodeJson = fromString <<< case _ of
37− DeclValue -> "value"
38− DeclData -> "data"
39− DeclExternData -> "externData"
140+ DeclValue -> "value"
141+ DeclData -> "data"
142+ DeclExternData -> "externData"
40143 DeclTypeSynonym -> "typeSynonym"
41− DeclTypeClass -> "typeClass"
42− DeclAlias -> "alias"
43− DeclExternKind -> "kind"
144+ DeclTypeClass -> "typeClass"
145+ DeclAlias -> "alias"
146+ DeclExternKind -> "kind"
44147
45148instance decodeJsonDeclType :: DecodeJson DeclType where
46149 decodeJson json =
47150 case toString json of
151+ Nothing -> Left $ "Couldn't decode DeclType: " <> stringify json
48152 Just string ->
49153 case string of
50154 "value" -> Right DeclValue
@@ -55,7 +159,6 @@ instance decodeJsonDeclType :: DecodeJson DeclType where
55159 "alias" -> Right DeclAlias
56160 "kind" -> Right DeclExternKind
57161 _ -> Left $ "Couldn't decode DeclType: " <> string
58− Nothing -> Left $ "Couldn't decode DeclType: " <> stringify json
59162
60163data ChildDeclType
61164 = ChildDeclInstance
@@ -77,13 +180,13 @@ instance encodeJsonChildDeclType :: EncodeJson ChildDeclType where
77180instance decodeJsonChildDeclType :: DecodeJson ChildDeclType where
78181 decodeJson json =
79182 case toString json of
80− Just string ->
81− case string of
183+ Nothing -> Left $ "Couldn't decode ChildDeclType: " <> stringify json
184+ Just tag ->
185+ case tag of
82186 "instance" -> Right ChildDeclInstance
83187 "dataConstructor" -> Right ChildDeclDataConstructor
84188 "typeClassMember" -> Right ChildDeclTypeClassMember
85− _ -> Left $ "Couldn't decode ChildDeclType: " <> string
86− Nothing -> Left $ "Couldn't decode ChildDeclType: " <> stringify json
189+ _ -> Left $ "Couldn't decode ChildDeclType: " <> tag
87190
88191data DataDeclType
89192 = NewtypeDataDecl
@@ -103,116 +206,9 @@ instance encodeJsonDataDeclType :: EncodeJson DataDeclType where
103206instance decodeJsonDataDeclType :: DecodeJson DataDeclType where
104207 decodeJson json =
105208 case toString json of
106− Just string ->
107− case string of
209+ Just tag ->
210+ case tag of
108211 "newtype" -> Right NewtypeDataDecl
109212 "data" -> Right DataDataDecl
110− _ -> Left $ "Couldn't decode DataDeclType: " <> string
213+ _ -> Left $ "Couldn't decode DataDeclType: " <> tag
111214 Nothing -> Left $ "Couldn't decode DataDeclType: " <> stringify json
112−
113−newtype IndexEntry
114− = IndexEntry { title :: String
115− , comments :: Maybe String
116− , info :: { declType :: DeclType
117− , dataDeclType :: Maybe DataDeclType
118− , kind :: Maybe Kind
119− , typeArguments :: Maybe (Array TypeArgument)
120− , type :: Maybe Type
121− , superclasses :: Maybe (Array Constraint)
122− , arguments :: Maybe (Array TypeArgument)
123− , fundeps :: Maybe FunDeps
124− }
125− , sourceSpan :: { start :: Array Int
126− , end :: Array Int
127− , name :: String
128− }
129− , children :: Array ChildIndexEntry
130− }
131−
132−derive instance eqIndexEntry :: Eq IndexEntry
133−derive instance genericIndexEntry :: Generic IndexEntry _
134−derive instance newtypeIndexEntry :: Newtype IndexEntry _
135−
136−instance showIndexEntry :: Show IndexEntry where
137− show = genericShow
138−
139−instance decodeJsonIndexEntry :: DecodeJson IndexEntry where
140− decodeJson json = IndexEntry <$> do
141− handle <- decodeJson json
142− title <- handle .: "title"
143− comments <- handle .:? "comments"
144− children <- handle .: "children"
145− info <- handle .: "info" >>= \info -> do
146− ty <- info .:? "type"
147− kind <- info .:? "kind"
148− typeArguments <- info .:? "typeArguments"
149− arguments <- info .:? "arguments"
150− superclasses <- info .:? "superclasses"
151− fundeps <- info .:? "fundeps"
152− declType <- info .: "declType"
153− dataDeclType <- info .:? "dataDeclType"
154− pure { type: ty, kind, declType, typeArguments, superclasses, fundeps
155− , arguments, dataDeclType }
156− sourceSpan <- handle .: "sourceSpan"
157− pure { title, comments, info, sourceSpan, children }
158−
159−instance encodeJsonIndexEntry :: EncodeJson IndexEntry where
160− encodeJson = encodeJson <<< unwrap
161−
162−newtype ChildIndexEntry
163− = ChildIndexEntry { title :: String
164− , comments :: Maybe String
165− , info :: { declType :: ChildDeclType
166− , arguments :: Maybe (Array Type)
167− , type :: Maybe Type
168− }
169− , mbSourceSpan :: Maybe { start :: Array Int
170− , end :: Array Int
171− , name :: String
172− }
173− }
174−
175−derive instance eqChildIndexEntry :: Eq ChildIndexEntry
176−derive instance genericChildIndexEntry :: Generic ChildIndexEntry _
177−derive instance newtypeChildIndexEntry :: Newtype ChildIndexEntry _
178−instance showChildIndexEntry :: Show ChildIndexEntry where
179− show = genericShow
180−
181−instance decodeJsonChildIndexEntry :: DecodeJson ChildIndexEntry where
182− decodeJson json = ChildIndexEntry <$> do
183− handle <- decodeJson json
184− title <- handle .: "title"
185− comments <- handle .:? "comments"
186− info <- handle .: "info" >>= \info -> do
187− arguments <- info .:? "arguments"
188− ty <- info .:? "type"
189− declType <- info .: "declType"
190− pure { arguments, declType, type: ty }
191− mbSourceSpan <- handle .:? "sourceSpan"
192− pure { title, comments, info, mbSourceSpan }
193−
194−instance encodeJsonChildIndexEntry :: EncodeJson ChildIndexEntry where
195− encodeJson = encodeJson <<< unwrap
196−
197−newtype Declarations
198− = Declarations { name :: String
199− , declarations :: Array IndexEntry
200− }
201−
202−derive instance eqDeclarations :: Eq Declarations
203−derive instance genericDeclarations :: Generic Declarations _
204−derive instance newtypeDeclarations :: Newtype Declarations _
205−
206−instance showDeclarations :: Show Declarations where
207− show = genericShow
208−
209−instance decodeJsonDeclarations :: DecodeJson Declarations where
210− decodeJson json = Declarations <$> decodeJson json
211−
212−instance encodeJsonDeclarations :: EncodeJson Declarations where
213− encodeJson = encodeJson <<< unwrap
214−
215−loadDeclarations :: String -> Aff (Either String (Array Declarations))
216−loadDeclarations string = decodeJson <$> toAffE (loadDeclarations_ string)
217−
218−foreign import loadDeclarations_ :: String -> Effect (Promise Json)
src/Spago/Search/Extra.purs+1
@@ -4,6 +4,7 @@ import Prelude
44
55import Data.Foldable (class Foldable, foldMap)
66import Data.Maybe (Maybe(..))
7+import Data.Array as Array
78
89whenJust :: forall a m. Monad m => Maybe a -> (a -> m Unit) -> m Unit
910whenJust (Just a) f = f a
src/Spago/Search/Index.jsnew file+30
@@ -0,0 +1,30 @@
1+/* global exports */
2+
3+exports.loadIndex_ = function (partId) {
4+ return function (url) {
5+ return function () {
6+ return new Promise(function(resolve, reject) {
7+ if (typeof window.spagoIndex[partId] === 'undefined') {
8+
9+ var script = document.createElement('script');
10+ script.type = 'text/javascript';
11+ script.src = url;
12+
13+ script.addEventListener('load', function () {
14+ if (typeof window.spagoIndex[partId] == 'undefined') {
15+ reject();
16+ } else {
17+ resolve(window.spagoIndex[partId]);
18+ }
19+ });
20+
21+ script.addEventListener('error', reject);
22+
23+ document.body.appendChild(script);
24+ } else {
25+ resolve(window.spagoIndex[partId]);
26+ }
27+ });
28+ };
29+ };
30+};
src/Spago/Search/Index.purs+87−301
@@ -2,317 +2,103 @@ module Spago.Search.Index where
22
33import Prelude
44
5−import Spago.Search.DocsJson (ChildDeclType(..), ChildIndexEntry(..), DataDeclType, DeclType(..), Declarations(..), IndexEntry(..))
6−import Spago.Search.TypeDecoder (Constraint(..), FunDeps, Kind, QualifiedName(..), Type(..), TypeArgument)
7−import Spago.Search.TypeShape (ShapeChunk, joinForAlls, shapeOfType)
8−
9−import Control.Alt ((<|>))
10−import Data.Array ((!!))
11−import Data.Foldable (foldr)
5+import Spago.Search.Config (config)
6+import Spago.Search.SearchResult (SearchResult)
7+
8+import Control.Promise (Promise, toAffE)
9+import Data.Argonaut.Core (Json)
10+import Data.Argonaut.Decode (decodeJson)
11+import Data.Array as Array
12+import Data.Char as Char
13+import Data.Either (hush)
1214import Data.List (List, (:))
1315import Data.List as List
14−import Data.Maybe (Maybe(..), fromMaybe)
15−import Data.Newtype (class Newtype, unwrap, wrap)
16−import Data.Search.Trie (Trie, alter, entriesUnordered)
17−import Data.String.CodeUnits (stripPrefix, stripSuffix, toCharArray)
18−import Data.String.Common (toLower)
19−import Data.String.Common as String
20−import Data.String.Pattern (Pattern(..))
16+import Data.Map (Map)
17+import Data.Map as Map
18+import Data.Maybe (Maybe(..))
19+import Data.Newtype (class Newtype)
20+import Data.Search.Trie (Trie)
21+import Data.Search.Trie as Trie
22+import Data.String.CodeUnits as String
2123import Data.Tuple (Tuple(..))
24+import Effect (Effect)
25+import Effect.Aff (Aff, try)
2226
23−newtype SearchIndex
24− = SearchIndex { decls :: Trie Char (List SearchResult)
25− , types :: Trie ShapeChunk (List SearchResult)
26− }
27−
28−derive instance newtypeSearchIndex :: Newtype SearchIndex _
29−
30−data ResultInfo
31− = DataResult { typeArguments :: Array TypeArgument
32− , dataDeclType :: DataDeclType }
33− | ExternDataResult { kind :: Kind }
34− | TypeSynonymResult { arguments :: Array TypeArgument
35− , type :: Type }
36− | DataConstructorResult { arguments :: Array Type }
37− | TypeClassMemberResult { type :: Type
38− , typeClass :: QualifiedName
39− , typeClassArguments :: Array String }
40− | TypeClassResult { fundeps :: FunDeps
41− , arguments :: Array TypeArgument
42− , superclasses :: Array Constraint }
43− | ValueResult { type :: Type }
44− | ValueAliasResult
45− | TypeAliasResult
46− | ExternKindResult
47−
48−newtype SearchResult
49− = SearchResult { name :: String
50− , comments :: Maybe String
51− , hashAnchor :: String
52− , moduleName :: String
53− , packageName :: String
54− , sourceSpan :: Maybe { start :: Array Int
55− , end :: Array Int
56− , name :: String
57− }
58− , info :: ResultInfo
59− }
60−
61−derive instance newtypeSearchResult :: Newtype SearchResult _
27+newtype Index
28+ = Index (Map Int (Trie Char (List SearchResult)))
6229
63−mkSearchIndex :: Array Declarations -> SearchIndex
64−mkSearchIndex decls =
65− SearchIndex { decls: trie
66− , types
67− }
68− where
69− trie = foldr insertDeclarations mempty decls
70− types = foldr insertTypes mempty do
71− Tuple _ results <- entriesUnordered trie
72− result <- results
73− case (unwrap result).info of
74− ValueResult dict ->
75− insertTypeResultsFor dict.type result
76− TypeClassMemberResult dict ->
77− -- TODO: fix missing foralls for type class members
78− insertTypeResultsFor dict.type result
79−
80− TypeSynonymResult dict ->
81− insertTypeResultsFor dict.type result
82− _ -> mempty
30+derive instance newtypeIndex :: Newtype Index _
31+derive newtype instance semigroupIndex :: Semigroup Index
32+derive newtype instance monoidIndex :: Monoid Index
8333
84− insertTypeResultsFor ty result =
85− let path = shapeOfType ty in
86− pure $ Tuple path result
34+query
35+ :: Index
36+ -> String
37+ -> Aff { index :: Index, results :: Array SearchResult }
38+query index@(Index mp) input = do
39+ let
40+ path :: List Char
41+ path =
42+ List.fromFoldable $
43+ String.toCharArray $
44+ input
45+
46+ partId :: Int
47+ partId = getPartId path
48+
49+ case Map.lookup partId mp of
50+ Just trie ->
51+ pure { index, results: flatten $ Trie.queryValues path trie }
52+ Nothing -> do
53+
54+
55+ eiPartJson <-
56+ try $ toAffE $ loadIndex_ partId $ config.mkIndexPartLoadPath partId
57+
58+ let
59+ mbNewTrie :: Maybe (Trie Char (List SearchResult))
60+ mbNewTrie = do
61+ json <- hush eiPartJson
62+ results <- hush (decodeJson json) :: Maybe (Array (Tuple String (Array SearchResult)))
63+ pure $ Array.foldr insertResults mempty results
64+
65+ case mbNewTrie of
66+ Just trie -> do
67+ pure { index: Index $ Map.insert partId trie mp
68+ , results: flatten $ Trie.queryValues path trie
69+ }
70+ Nothing -> do
71+ pure { index, results: mempty }
8772
88−insertTypes
89− :: Tuple (List ShapeChunk) SearchResult
90− -> Trie ShapeChunk (List SearchResult)
91− -> Trie ShapeChunk (List SearchResult)
92−insertTypes (Tuple path result) trie =
93− alter path updateResults trie
9473 where
95− updateResults mbOldResults =
96− case mbOldResults of
97− Just oldResults ->
98− Just $ result : oldResults
99− Nothing ->
100− Just $ List.singleton result
74+ flatten = Array.concat <<< Array.fromFoldable <<< map Array.fromFoldable
10175
102−insertDeclarations
103− :: Declarations
76+insertResults
77+ :: Tuple String (Array SearchResult)
10478 -> Trie Char (List SearchResult)
10579 -> Trie Char (List SearchResult)
106−insertDeclarations (Declarations { name, declarations }) trie
107− = foldr (insertIndexEntry name) trie declarations
108−
109−insertIndexEntry
110− :: String
111− -> IndexEntry
112− -> Trie Char (List SearchResult)
113− -> Trie Char (List SearchResult)
114−insertIndexEntry moduleName entry@(IndexEntry { title }) trie
115− = foldr insertSearchResult trie (resultsForEntry moduleName entry)
116−
117−insertSearchResult
118− :: { path :: String
119− , result :: SearchResult
120− }
121− -> Trie Char (List SearchResult)
122− -> Trie Char (List SearchResult)
123−insertSearchResult { path, result } trie =
124− let path' = List.fromFoldable $ toCharArray $ toLower path in
125− alter path' updateResults trie
126− where
127− updateResults mbOldResults =
128− case mbOldResults of
129− Just oldResults ->
130− Just $ result : oldResults
131− Nothing ->
132− Just $ List.singleton result
133−
134−resultsForEntry
135− :: String
136− -> IndexEntry
137− -> List { path :: String
138− , result :: SearchResult
139− }
140−resultsForEntry moduleName indexEntry@(IndexEntry entry) =
141− let { info, title, sourceSpan, comments, children } = entry
142− { name, declLevel } = getLevelAndName info.declType title
143− packageName = extractPackageName sourceSpan.name
144− in case mkInfo declLevel indexEntry of
145− Nothing -> mempty
146− Just info' ->
147− let result = SearchResult { name: title
148− , comments
149− , hashAnchor: declLevelToHashAnchor declLevel
150− , moduleName
151− , sourceSpan: Just sourceSpan
152− , packageName
153− , info: info'
154− }
155− in
156− ( List.singleton $
157− { path: name
158− , result
159− }
160− ) <>
161− ( List.fromFoldable children >>=
162− resultsForChildIndexEntry packageName moduleName result
163− )
164−
165−mkInfo :: DeclLevel -> IndexEntry -> Maybe ResultInfo
166−mkInfo declLevel (IndexEntry { info, title }) =
167− case info.declType of
168−
169− DeclValue ->
170− info.type <#>
171− \ty -> ValueResult { type: ty }
172−
173− DeclData ->
174− make <$> info.typeArguments <*> info.dataDeclType
175− where
176− make typeArguments dataDeclType =
177− DataResult { typeArguments, dataDeclType }
178−
179− DeclExternData ->
180− info.kind <#>
181− \kind -> ExternDataResult { kind }
182−
183− DeclTypeSynonym ->
184− make <$> info.type <*> info.arguments
185− where
186− make ty args = TypeSynonymResult { type: ty, arguments: args }
187−
188− DeclTypeClass ->
189− case info.fundeps, info.arguments, info.superclasses of
190− Just fundeps, Just arguments, Just superclasses ->
191− Just $ TypeClassResult { fundeps, arguments, superclasses }
192− _, _, _ -> Nothing
193−
194− DeclAlias ->
195− case declLevel of
196− TypeLevel -> Just TypeAliasResult
197− ValueLevel -> Just ValueAliasResult
198− _ -> Nothing
199−
200− DeclExternKind ->
201− Just ExternKindResult
202−
203−-- | Level of a declaration, used to determine which URI hash anchor to use in
204−-- | links ("v", "t" or "k").
205−data DeclLevel = ValueLevel | TypeLevel | KindLevel
206−
207−declLevelToHashAnchor :: DeclLevel -> String
208−declLevelToHashAnchor = case _ of
209− ValueLevel -> "v"
210− TypeLevel -> "t"
211− KindLevel -> "k"
212−
213−getLevelAndName
214− :: DeclType
215− -> String
216− -> { declLevel :: DeclLevel
217− , name :: String
218− }
219−getLevelAndName DeclValue name = { name, declLevel: ValueLevel }
220−getLevelAndName DeclData name = { name, declLevel: TypeLevel }
221−getLevelAndName DeclTypeSynonym name = { name, declLevel: TypeLevel }
222−getLevelAndName DeclTypeClass name = { name, declLevel: ValueLevel }
223−-- "declType": "alias" does not specify the level of the declaration.
224−-- But for type aliases, name of the declaration is always wrapped into
225−-- "type (" and ")".
226−getLevelAndName DeclAlias title =
227− fromMaybe (withAnchor ValueLevel title) $
228− (withAnchor ValueLevel <$>
229− (stripPrefix (Pattern "(") >=>
230− stripSuffix (Pattern ")")) title) <|>
231− (withAnchor TypeLevel <$>
232− (stripPrefix (Pattern "type (") >=>
233− stripSuffix (Pattern ")")) title)
80+insertResults (Tuple path newResults) =
81+ Trie.alter pathList insert
23482 where
235− withAnchor declLevel name = { declLevel, name }
236−getLevelAndName DeclExternData name = { name, declLevel: TypeLevel }
237−getLevelAndName DeclExternKind name = { name, declLevel: KindLevel }
83+ pathList = List.fromFoldable $ String.toCharArray path
23884
239−-- | Extract package name from `sourceSpan.name`, which contains path to
240−-- | the source file.
241−extractPackageName :: String -> String
242−extractPackageName name =
243− let chunks = String.split (Pattern "/") name in
244− fromMaybe "<unknown>" $
245− chunks !! 0 >>= \dir ->
246− -- TODO: is it safe to assume that directory name is ".spago"?
247− if dir == ".spago" then
248− chunks !! 1
249− else
250− Just "<local package>"
251−
252−resultsForChildIndexEntry
253− :: String
254− -> String
255− -> SearchResult
256− -> ChildIndexEntry
257− -> List { path :: String, result :: SearchResult }
258−resultsForChildIndexEntry packageName moduleName parentResult
259− child@(ChildIndexEntry { title, info, comments, mbSourceSpan }) =
260− case mkChildInfo parentResult child of
261− Nothing -> mempty
262− Just resultInfo ->
263− { path: title
264− , result: SearchResult { name: title
265− , comments
266− , hashAnchor: "v"
267− , moduleName
268− , sourceSpan: mbSourceSpan
269− , packageName
270− , info: resultInfo
271− }
272− } # List.singleton
273−
274−mkChildInfo :: SearchResult -> ChildIndexEntry -> Maybe ResultInfo
275−mkChildInfo parentResult (ChildIndexEntry { info } ) =
276− case info.declType of
277− ChildDeclDataConstructor ->
278− info.arguments <#>
279− \arguments -> DataConstructorResult { arguments }
280− ChildDeclTypeClassMember ->
281− case (unwrap parentResult).info of
282− TypeClassResult { arguments } ->
283− -- We need to reconstruct a "real" type of a type class member.
284− -- For example, if `unconstrainedType` is the type of `pure`, i.e. `forall a. a -> m a`,
285− -- `restoredType` should be `forall m a. Control.Applicative.Applicative m => a -> m a`.
286− info.type <#>
287− \(unconstrainedType :: Type) ->
288− let
289− -- First, we get a list of nested `forall` quantifiers for `unconstrainedType`
290− -- and a version of `unconstrainedType` without them (`ty`).
291− { ty, binders } = joinForAlls unconstrainedType
292−
293− -- Then we construct a qualified name of the type class.
294− parentClassName =
295− QualifiedName { moduleName: String.split (wrap ".") (unwrap parentResult).moduleName
296− , name: (unwrap parentResult).name }
297−
298− typeClassArguments = arguments <#> unwrap >>> _.name
299−
300− -- We concatenate two lists:
301− -- * list of type parameters of the type class, and
302− -- * list of quantified variables of the unconstrained type
303− allArguments =
304− typeClassArguments <> (List.toUnfoldable binders <#> (_.var))
305−
306− restoredType =
307− foldr (\arg -> compose (\type'' -> ForAll arg type'' Nothing)) identity allArguments $
308− ConstrainedType (Constraint { constraintClass: parentClassName
309− , constraintArgs: typeClassArguments <#> TypeVar
310− }) ty
311−
312− in TypeClassMemberResult
313− { type: restoredType
314− , typeClass: parentClassName
315− , typeClassArguments
316− }
317− _ -> Nothing
318− ChildDeclInstance -> Nothing
85+ insert
86+ :: Maybe (List SearchResult)
87+ -> Maybe (List SearchResult)
88+ insert mbOldResults =
89+ case mbOldResults of
90+ Nothing -> Just $ List.fromFoldable newResults
91+ Just old -> Just $ List.fromFoldable newResults <> old
92+
93+-- | Find in which part of the index this path can be found.
94+getPartId :: List Char -> Int
95+getPartId (a : b : _) =
96+ (Char.toCharCode a + Char.toCharCode b) `mod` config.countOfIndexParts
97+getPartId (a : _) =
98+ Char.toCharCode a `mod` config.countOfIndexParts
99+getPartId _ = 0
100+
101+foreign import loadIndex_
102+ :: Int
103+ -> String
104+ -> Effect (Promise Json)
src/Spago/Search/IndexBuilder.purs+172−59
@@ -1,108 +1,221 @@
11module Spago.Search.IndexBuilder where
22
3+import Data.Tuple
4+import Node.FS.Stats
35import Prelude
4−
5−import Spago.Search.DocsJson (Declarations)
6−import Spago.Search.Index (mkSearchIndex)
6+import Spago.Search.Config
7+import Spago.Search.Declarations
8+import Spago.Search.Extra
9+import Spago.Search.Index
10+import Spago.Search.SearchResult
11+import Spago.Search.TypeIndex
712
813import Data.Argonaut.Core (stringify)
914import Data.Argonaut.Decode (decodeJson)
1015import Data.Argonaut.Encode (encodeJson)
1116import Data.Argonaut.Parser (jsonParser)
1217import Data.Array as Array
13−import Data.Either (hush, isLeft)
18+import Data.Char as Char
19+import Data.Either (Either(..), hush, isLeft)
20+import Data.List (List(..), (:))
1421import Data.List as List
22+import Data.Map (Map)
23+import Data.Map as Map
1524import Data.Maybe (Maybe(..))
1625import Data.Newtype (unwrap)
1726import Data.Search.Trie as Trie
27+import Data.Set as Set
1828import Data.String.CodePoints (contains) as String
29+import Data.String.CodeUnits (singleton) as String
1930import Data.String.Common (replace) as String
2031import Data.String.Pattern (Pattern(..)) as String
2132import Data.String.Pattern (Pattern(..), Replacement(..))
2233import Data.Traversable (for, for_)
23−import Data.Tuple
2434import Effect (Effect)
25−import Effect.Aff (Aff, launchAff_)
35+import Effect.Aff (Aff, launchAff_, parallel, sequential)
2636import Effect.Class (liftEffect)
27−import Effect.Console (log)
37+import Effect.Console (log, logShow)
2838import Node.Encoding (Encoding(UTF8))
29−import Node.FS.Aff (exists, readTextFile, readdir, writeTextFile)
39+import Node.FS.Aff (exists, mkdir, readTextFile, readdir, stat, writeTextFile)
3040import Node.Process as Process
41+import Spago.Search.DocsJson (DocsJson)
42+
43+main :: Effect Unit
44+main = launchAff_ mainAff
45+
46+mainAff :: Aff Unit
47+mainAff = do
48+ let outputDir = "output"
49+
50+ checkDirectories
51+
52+ docsJsons <- collectDocsJsons outputDir
53+
54+ liftEffect $ log $
55+ "Found " <> show (Array.length docsJsons) <> " modules."
56+
57+ let index = mkDeclarations docsJsons
58+ typeIndex = mkTypeIndex index
59+
60+ createDirectories
61+
62+ void $ sequential do
63+ ignore <$> parallel (writeIndex index)
64+ <*> parallel (writeTypeIndex typeIndex)
65+ <*> parallel patchDocs
66+
67+ liftEffect $ log $
68+ "Loaded " <>
69+ show (Trie.size $ unwrap index) <>
70+ " definitions and " <>
71+ show (List.length $ join $ map snd $ Trie.entriesUnordered (unwrap index)) <>
72+ " type definitions"
73+ where ignore _ _ _ = unit
74+
75+-- | Exit early if something is missing.
76+checkDirectories :: Aff Unit
77+checkDirectories = do
78+ for_ config.requiredDirectories \dir -> do
79+ whenM (not <$> directoryExists dir) $
80+ liftEffect $ do
81+ log "Run `spago build && spago docs` first!"
82+ Process.exit 1
83+
84+-- | Read and decode all `docs.json` files in the `outputDir`.
85+collectDocsJsons :: String -> Aff (Array DocsJson)
86+collectDocsJsons outputDir = do
87+ paths <- readdir outputDir
3188
32−collectDeclarations :: Array String -> Aff (Array Declarations)
33−collectDeclarations paths = do
3489 mbs <- for paths \moduleName -> do
3590 let jsonFile = "output/" <> moduleName <> "/docs.json"
36− doesExist <- exists jsonFile
37−
91+ doesExist <- fileExists jsonFile
3892 if doesExist then do
93+
3994 contents <- readTextFile UTF8 jsonFile
40− let mbResult = jsonParser contents >>= decodeJson
95+ let eiResult = jsonParser contents >>= decodeJson
4196
42− when (isLeft mbResult) do
43− liftEffect $ log $
44− "Index build failed for module " <>
45− moduleName <>
46− ": " <> show mbResult
97+ case eiResult of
98+ Left error -> do
99+ liftEffect $ log $
100+ "`docs.json` decoding failed failed for module " <> moduleName <> ": " <> show error
101+ pure Nothing
102+ Right result -> pure result
47103
48− pure $ hush mbResult
49104 else do
50105 liftEffect $ do
51106 log $
52107 "Couldn't find docs.json for " <> moduleName
53108 pure Nothing
109+
54110 pure $ Array.catMaybes mbs
55111
56−writeDeclarations :: Array Declarations -> Aff Unit
57−writeDeclarations decls = do
58− let filename = "generated-docs/spago-search-index.js"
59− dirname = "generated-docs/"
112+writeTypeIndex :: TypeIndex -> Aff Unit
113+writeTypeIndex typeIndex =
114+ for_ entries \(Tuple typeShape results) -> do
115+ writeTextFile UTF8 ("generated-docs/index/types/" <> typeShape <> ".js")
116+ (mkHeader typeShape <> stringify (encodeJson results))
117+ where
118+ mkHeader typeShape =
119+ "// This file was generated by spago\n" <>
120+ "window.spagoTypeIndex[\"" <> typeShape <> "\"] = "
121+ entries :: Array _
122+ entries = Map.toUnfoldableUnordered (unwrap typeIndex)
123+
124+writeIndex :: Declarations -> Aff Unit
125+writeIndex (Declarations trie) = do
126+ let
127+ prefixes :: Array (List Char)
128+ prefixes =
129+ Set.toUnfoldable $
130+ List.foldr (\entry -> Set.insert (List.take 2 $ fst entry)) mempty $
131+ Trie.entriesUnordered trie
60132
61− whenM (not <$> exists dirname) $
62− liftEffect $ do
63− log "Run `spago docs` first!"
64− Process.exit 1
133+ parts
134+ :: Array { prefix :: List Char
135+ , results :: Array (Tuple String (Array SearchResult))
136+ }
137+ parts = prefixes <#> \prefix ->
138+ let results =
139+ Array.fromFoldable $
140+ Trie.query prefix trie <#>
141+ \(Tuple path value) ->
142+ Tuple (path >#> String.singleton) (Array.fromFoldable value)
143+ in
144+ { prefix, results }
65145
66− let header =
67− "// This file was generated by spago.\n" <>
68− "window.spagoSearchIndex = "
146+ insert { prefix, results } mbOldValue =
147+ case mbOldValue of
148+ Nothing -> Just $ results
149+ Just oldValue -> Just $ oldValue <> results
69150
70− writeTextFile UTF8 filename (header <> (stringify $ encodeJson decls))
151+ mp = Array.foldr (\part -> Map.alter (insert part) (getPartId part.prefix)) mempty parts
152+
153+ for_ (Map.toUnfoldableUnordered mp :: Array _)
154+ \(Tuple indexPartId results) -> do
155+ let header =
156+ "// This file was generated by spago.\n" <>
157+ "window.spagoIndex[\"" <> show indexPartId <> "\"] = "
158+
159+ writeTextFile UTF8 (config.mkIndexPartPath indexPartId) $
160+ header <> stringify (encodeJson results)
71161
72162patchDocs :: Aff Unit
73163patchDocs = do
74164 let dirname = "generated-docs/"
75− let patch = "<!-- Spago search index. -->" <>
165+ pattern = Pattern "</body>"
166+ patch = "<!-- Spago search index. -->" <>
76167 "<script type=\"text/javascript\" src=\"../spago-search-app.js\"></script>" <>
168+ "<script type=\"text/javascript\">" <>
169+ "window.spagoTypeIndex = {};" <>
170+ "window.spagoIndex = {};" <>
171+ "</script>" <>
77172 "</body>"
78173
79− whenM (not <$> exists dirname) $
80− liftEffect $ do
81− log "Run `spago docs` first!"
82− Process.exit 1
174+ files <- readdir (dirname <> "html")
83175
84− paths <- readdir (dirname <> "html")
85− for_ paths \file -> do
176+ for_ files \file -> do
86177 let path = dirname <> "html/" <> file
87− contents <- readTextFile UTF8 path
88− when (not $ String.contains (String.Pattern patch) contents) do
89− let patched = String.replace (Pattern "</body>") (Replacement patch) contents
90− writeTextFile UTF8 path patched
91178
92−main :: Effect Unit
93−main = do
94− launchAff_ do
95− paths <- readdir "output"
96− declarations <- collectDeclarations paths
97− liftEffect $ log $ "Found " <> show (Array.length declarations) <> " modules."
98− writeDeclarations declarations
99− patchDocs
100− let index = mkSearchIndex declarations
101− liftEffect $ log $
102− "Loaded " <>
103− show (Trie.size $ (unwrap index).decls) <>
104− " definitions and " <>
105− show (List.length $ join $ map snd $ Trie.entriesUnordered (unwrap index).types) <>
106− " type definitions with " <>
107− show (Trie.size $ (unwrap index).types) <>
108− " different shapes."
179+ whenM (fileExists path) do
180+ contents <- readTextFile UTF8 path
181+ when (not $ String.contains (String.Pattern patch) contents) do
182+ writeTextFile UTF8 path $
183+ String.replace pattern (Replacement patch) contents
184+
185+createDirectories :: Aff Unit
186+createDirectories = do
187+ let generatedDocsDir = "generated-docs/"
188+ indexDir = "generated-docs/index"
189+ declIndexDir = "generated-docs/index/declarations"
190+ typeIndexDir = "generated-docs/index/types"
191+
192+ whenM (not <$> directoryExists generatedDocsDir) $ liftEffect do
193+ logAndExit "Run `spago docs` first!"
194+
195+ whenM (not <$> directoryExists indexDir) do
196+ mkdir indexDir
197+
198+ whenM (not <$> directoryExists declIndexDir) do
199+ mkdir declIndexDir
200+
201+ whenM (not <$> directoryExists typeIndexDir) do
202+ mkdir typeIndexDir
203+
204+directoryExists :: String -> Aff Boolean
205+directoryExists path = do
206+ doesExist <- exists path
207+ case doesExist of
208+ false -> pure false
209+ true -> isDirectory <$> stat path
210+
211+fileExists :: String -> Aff Boolean
212+fileExists path = do
213+ doesExist <- exists path
214+ case doesExist of
215+ false -> pure false
216+ true -> isFile <$> stat path
217+
218+logAndExit :: forall a. String -> Effect a
219+logAndExit err = do
220+ log err
221+ Process.exit 1
src/Spago/Search/SearchResult.pursnew file+72
@@ -0,0 +1,72 @@
1+module Spago.Search.SearchResult where
2+
3+import Spago.Search.DocsJson (ChildDeclType(..), ChildDeclaration(..), DataDeclType, DeclType(..), Declaration(..), DocsJson(..))
4+import Spago.Search.TypeDecoder (Constraint(..), FunDeps, Kind, QualifiedName(..), Type(..), TypeArgument)
5+import Spago.Search.TypeShape (ShapeChunk, joinForAlls, shapeOfType)
6+
7+import Control.Alt ((<|>))
8+import Data.Array ((!!))
9+import Data.Foldable (foldr)
10+import Data.List (List, (:))
11+import Data.List as List
12+import Data.Maybe (Maybe(..), fromMaybe)
13+import Data.Newtype (class Newtype, unwrap, wrap)
14+import Data.Search.Trie (Trie, alter)
15+import Data.String.CodeUnits (stripPrefix, stripSuffix, toCharArray)
16+import Data.String.Common (toLower)
17+import Data.String.Common as String
18+import Data.String.Pattern (Pattern(..))
19+
20+import Data.Generic.Rep (class Generic)
21+import Data.Argonaut.Decode (class DecodeJson)
22+import Data.Argonaut.Encode (class EncodeJson)
23+import Data.Argonaut.Encode.Generic.Rep (genericEncodeJson)
24+import Data.Argonaut.Decode.Generic.Rep (genericDecodeJson)
25+
26+data ResultInfo
27+ = DataResult { typeArguments :: Array TypeArgument
28+ , dataDeclType :: DataDeclType }
29+ | ExternDataResult { kind :: Kind }
30+ | TypeSynonymResult { arguments :: Array TypeArgument
31+ , type :: Type }
32+ | DataConstructorResult { arguments :: Array Type }
33+ | TypeClassMemberResult { type :: Type
34+ , typeClass :: QualifiedName
35+ , typeClassArguments :: Array String }
36+ | TypeClassResult { fundeps :: FunDeps
37+ , arguments :: Array TypeArgument
38+ , superclasses :: Array Constraint }
39+ | ValueResult { type :: Type }
40+ | ValueAliasResult
41+ | TypeAliasResult
42+ | ExternKindResult
43+
44+derive instance genericResultInfo :: Generic ResultInfo _
45+
46+instance encodeJsonResultInfo :: EncodeJson ResultInfo where
47+ encodeJson = genericEncodeJson
48+
49+instance decodeJsonResultInfo :: DecodeJson ResultInfo where
50+ decodeJson = genericDecodeJson
51+
52+newtype SearchResult
53+ = SearchResult { name :: String
54+ , comments :: Maybe String
55+ , hashAnchor :: String
56+ , moduleName :: String
57+ , packageName :: String
58+ , sourceSpan :: Maybe { start :: Array Int
59+ , end :: Array Int
60+ , name :: String
61+ }
62+ , info :: ResultInfo
63+ }
64+
65+derive instance newtypeSearchResult :: Newtype SearchResult _
66+derive instance genericSearchResult :: Generic SearchResult _
67+
68+instance encodeJsonSearchResult :: EncodeJson SearchResult where
69+ encodeJson = genericEncodeJson
70+
71+instance decodeJsonSearchResult :: DecodeJson SearchResult where
72+ decodeJson = genericDecodeJson
src/Spago/Search/TypeIndex.jsnew file+26
@@ -0,0 +1,26 @@
1+/* global exports */
2+
3+exports.lookup_ = function (shape) {
4+ return function (url) {
5+ return function () {
6+ return new Promise(function (resolve, reject) {
7+ if (typeof window.spagoTypeIndex[shape] === 'undefined') {
8+ var script = document.createElement('script');
9+ script.type = 'text/javascript';
10+ script.src = url;
11+ script.addEventListener('load', function () {
12+ if (typeof window.spagoTypeIndex[shape] === 'undefined') {
13+ reject(new Error("Couldn't load index for type shape " + shape));
14+ } else {
15+ resolve(window.spagoTypeIndex[shape]);
16+ }
17+ });
18+ script.addEventListener('error', reject);
19+ document.body.appendChild(script);
20+ } else {
21+ resolve(window.spagoTypeIndex[shape]);
22+ }
23+ });
24+ };
25+ };
26+};
src/Spago/Search/TypeIndex.pursnew file+114
@@ -0,0 +1,114 @@
1+module Spago.Search.TypeIndex where
2+
3+import Data.Map
4+import Data.Maybe
5+import Data.Newtype
6+import Data.Tuple
7+import Effect.Aff
8+import Prelude
9+import Spago.Search.Index
10+import Spago.Search.TypeQuery
11+import Spago.Search.TypeShape
12+import Spago.Search.Config
13+import Spago.Search.SearchResult
14+import Spago.Search.Declarations
15+
16+import Control.Promise (Promise, toAffE)
17+import Data.Argonaut.Core (Json, fromString, stringify, toString)
18+import Data.Argonaut.Decode (class DecodeJson, decodeJson, (.:), (.:?))
19+import Data.Argonaut.Encode (class EncodeJson, encodeJson)
20+import Data.Array as Array
21+import Data.Either (Either(..), hush)
22+import Data.Generic.Rep (class Generic)
23+import Data.Generic.Rep.Show (genericShow)
24+import Data.List (List(..), (:))
25+import Data.List as List
26+import Data.Map as Map
27+import Data.Maybe (Maybe(..))
28+import Data.Newtype (class Newtype, unwrap)
29+import Data.Search.Trie as Trie
30+import Effect (Effect)
31+
32+newtype TypeIndex = TypeIndex (Map String (Maybe (Array SearchResult)))
33+
34+derive newtype instance semigroupTypeIndex :: Semigroup TypeIndex
35+derive newtype instance monoidTypeIndex :: Monoid TypeIndex
36+derive instance newtypeTypeIndex :: Newtype TypeIndex _
37+
38+insert
39+ :: String
40+ -> Maybe (Array SearchResult)
41+ -> TypeIndex
42+ -> TypeIndex
43+insert key value = unwrap >>> Map.insert key value >>> wrap
44+
45+insertTypes
46+ :: Tuple String SearchResult
47+ -> Map String (List SearchResult)
48+ -> Map String (List SearchResult)
49+insertTypes (Tuple path results) mp =
50+ Map.alter updateResults path mp
51+ where
52+ updateResults mbOldResults =
53+ case mbOldResults of
54+ Just oldResults ->
55+ Just $ results : oldResults
56+ Nothing ->
57+ Just $ List.singleton results
58+
59+mkTypeIndex :: Declarations -> TypeIndex
60+mkTypeIndex (Declarations trie) = TypeIndex $ map (Array.fromFoldable >>> Just) types
61+ where
62+ types = List.foldr insertTypes mempty do
63+
64+ results <- Trie.entriesUnordered trie >>= snd
65+
66+ case (unwrap results).info of
67+ ValueResult dict ->
68+ insertTypeResultsFor dict.type results
69+
70+ TypeClassMemberResult dict ->
71+ insertTypeResultsFor dict.type results
72+
73+ TypeSynonymResult dict ->
74+ insertTypeResultsFor dict.type results
75+ _ -> mempty
76+
77+ insertTypeResultsFor ty results =
78+ let path = stringifyShape (shapeOfType ty) in
79+ pure $ Tuple path results
80+
81+lookup
82+ :: String
83+ -> TypeIndex
84+ -> Aff { index :: TypeIndex, results :: Array SearchResult }
85+lookup key index@(TypeIndex map) =
86+ case Map.lookup key map of
87+ Just results -> pure { index, results: Array.fold results }
88+ Nothing -> do
89+ eiJson <- try (toAffE (lookup_ key $ config.mkShapeScriptPath key))
90+ let mbResult = do
91+ json <- hush eiJson
92+ results <- hush (decodeJson json)
93+ pure { index: insert key (Just results) index, results }
94+ case mbResult of
95+ Nothing -> pure { index: insert key Nothing index, results: [] }
96+ Just results -> pure results
97+
98+-- TODO: flip arguments
99+query
100+ :: TypeIndex
101+ -> TypeQuery
102+ -> Aff { index :: TypeIndex, results :: Array SearchResult }
103+query typeIndex typeQuery = do
104+ res <- lookup (stringifyShape $ shapeOfTypeQuery typeQuery) typeIndex
105+ pure $ res { results = sortByRelevance typeQuery res.results }
106+
107+-- | TODO
108+sortByRelevance :: TypeQuery -> Array SearchResult -> Array SearchResult
109+sortByRelevance typeQuery = identity
110+
111+foreign import lookup_
112+ :: String
113+ -> String
114+ -> Effect (Promise Json)
src/Spago/Search/TypeQuery.purs+7−1
@@ -1,4 +1,10 @@
1−module Spago.Search.TypeQuery where
1+module Spago.Search.TypeQuery
2+ ( TypeQuery(..)
3+ , parseTypeQuery
4+ , typeQueryParser
5+ , getFreeVariables
6+ )
7+where
28
39import Prelude
410
test/Main.purs+13−16
@@ -1,30 +1,27 @@
11module Test.Main where
22
3−import Data.Argonaut.Decode
4−import Data.Argonaut.Encode
5−import Data.Argonaut.Parser
6−import Data.Either
7−import Data.Maybe
8−import Effect.Aff
9−import Partial.Unsafe
103import Prelude
11−import Spago.Search.DocsJson
12−import Spago.Search.TypeDecoder
134
14−import Effect (Effect)
15−import Effect.Aff (Aff, launchAff_)
16−import Effect.Class (liftEffect)
17−import Effect.Console (log)
5+import Spago.Search.TypeDecoder (Constraint(..), FunDep(..), FunDeps(..), Kind(..), QualifiedName(..), Type(..))
186import Test.TypeQuery as TypeQuery
19−import Test.TypeShape as TypeShape
20−import Test.Unit (suite, test)
7+
8+import Data.Argonaut.Decode (decodeJson)
9+import Data.Argonaut.Encode (encodeJson)
10+import Data.Argonaut.Parser (jsonParser)
11+import Data.Either (Either(..), fromRight)
12+import Data.Maybe (Maybe(..))
13+import Effect (Effect)
14+import Effect.Aff (Aff)
15+import Partial.Unsafe (unsafePartial)
16+import Test.Unit (TestSuite, suite, test)
2117import Test.Unit.Assert as Assert
22−import Test.Unit.Main (run, runTest)
18+import Test.Unit.Main (runTest)
2319
2420main :: Effect Unit
2521main = do
2622 runTest mainTest
2723
24+mainTest :: TestSuite
2825mainTest = do
2926 TypeQuery.tests
3027
test/Test/TypeQuery.purs+5−4
@@ -1,11 +1,12 @@
11module Test.TypeQuery where
22
33import Prelude
4−import Spago.Search.DocsJson
5−import Spago.Search.TypeQuery
6−import Spago.Search.TypeShape
74
8−import Data.Either (Either(..), isRight)
5+
6+import Spago.Search.TypeQuery (TypeQuery(..), getFreeVariables, parseTypeQuery)
7+import Spago.Search.TypeShape (ShapeChunk(..), shapeOfTypeQuery)
8+
9+import Data.Either (Either(..))
910import Data.Foldable (class Foldable)
1011import Data.List (List(..), (:))
1112import Data.List as List
test/Test/TypeShape.pursdeleted−90
@@ -1,90 +0,0 @@
1−module Test.TypeShape where
2−
3−import Data.Either
4−import Data.Either
5−import Data.Maybe
6−import Effect.Aff
7−import Partial.Unsafe
8−import Prelude
9−import Spago.Search.DocsJson
10−import Spago.Search.Index
11−import Spago.Search.IndexBuilder
12−import Spago.Search.TypeDecoder
13−import Spago.Search.TypeQuery
14−import Spago.Search.TypeShape
15−
16−import Data.Array as Array
17−import Data.List as List
18−import Data.Maybe (Maybe(..))
19−import Data.Newtype (unwrap)
20−import Data.Search.Trie as Trie
21−import Data.Traversable (for, for_)
22−import Effect (Effect)
23−import Effect.Aff (Aff, launchAff_)
24−import Effect.Class (liftEffect)
25−import Effect.Console (log, logShow)
26−import Node.Encoding (Encoding(UTF8))
27−import Node.FS.Aff (exists, readTextFile, readdir, writeTextFile)
28−import Node.Process as Process
29−import Test.TypeQuery as TypeQuery
30−import Test.Unit (suite, test)
31−import Test.Unit.Assert as Assert
32−import Test.Unit.Main (runTest)
33−
34−-- | Crash-testing query parser on existing types.
35−run :: Aff Unit
36−run = do
37− paths <- readdir "output"
38− declarations <- collectDeclarations paths
39− -- liftEffect $ log $ "Found " <> show (Array.length declarations) <> " modules"
40− let index = mkSearchIndex declarations
41− let types = Trie.values ((unwrap index).types)
42− for_ types \tys -> do
43− for_ tys \ty -> do
44− case (unwrap ty).info of
45− ValueResult { type: ty } -> do
46− let shown = showType ty
47− case parseTypeQuery shown of
48− Left err ->
49− liftEffect $ log $ show err <> ": \n" <> shown
50− _ ->
51− pure unit
52− _ -> pure unit
53−
54−showType :: Type -> String
55−showType = case _ of
56− TypeVar _String -> _String
57− TypeLevelString _String ->
58− _String
59− TypeWildcard ->
60− "_"
61− TypeConstructor _QualifiedName ->
62− showQualifiedName _QualifiedName
63− TypeOp _QualifiedName ->
64− showQualifiedName _QualifiedName
65− TypeApp (TypeApp (TypeConstructor (QualifiedName { moduleName: ["Prim"]
66− , name: "Function" })) t1) t2 ->
67− showType t1 <> " -> " <> showType t2
68−
69− TypeApp _Type1 _Type2 ->
70− showType _Type1 <> " " <> showType _Type2
71− foralls@(ForAll _String _Type _Maybe_Kind) ->
72− let joined = joinForAlls foralls in
73− "forall " <> (List.intercalate " " $ joined.binders <#> (_.var)) <> ". " <> showType joined.ty -- <> " " <> show _Maybe_Kind
74− ConstrainedType _Constraint _Type ->
75− showConstraint _Constraint <> " => " <> showType _Type
76− REmpty ->
77− "{}"
78− RCons _String _Type1 _Type2 ->
79− "(RConsLabel " <> _String <> " " <> showType _Type1 <> " " <> showType _Type2 <> ")"
80− BinaryNoParensType _TypeOp _Type1 _Type2 ->
81− showType _Type1 <> " " <> showType _TypeOp <> " " <> showType _Type2
82− ParensInType _Type ->
83− "(" <> showType _Type <> ")"
84−
85−showQualifiedName :: QualifiedName -> String
86−showQualifiedName = unwrap >>> (_.name)
87−
88−showConstraint :: Constraint -> String
89−showConstraint (Constraint { constraintClass, constraintArgs }) =
90− showQualifiedName constraintClass <> " " <> Array.intercalate " " (constraintArgs <#> showType)