full-text-search 0.2.0.0 → 0.2.1.0
raw patch · 27 files changed
+3692/−465 lines, 27 filesdep +Cabaldep +QuickCheckdep +bytestringdep ~arraydep ~basedep ~containersnew-component:exe:search-demo
Dependencies added: Cabal, QuickCheck, bytestring, directory, filepath, full-text-search, mtl, snowball, split, tar, tasty, tasty-quickcheck, time, tokenize, transformers
Dependency ranges changed: array, base, containers, text, vector
Files
- Data/SearchEngine.hs +10/−354
- Data/SearchEngine/Autosuggest.hs +501/−0
- Data/SearchEngine/BM25F.hs +51/−1
- Data/SearchEngine/DocIdSet.hs +43/−62
- Data/SearchEngine/DocTermIds.hs +22/−3
- Data/SearchEngine/Query.hs +257/−0
- Data/SearchEngine/SearchIndex.hs +87/−28
- Data/SearchEngine/TermBag.hs +145/−7
- Data/SearchEngine/Types.hs +124/−0
- Data/SearchEngine/Update.hs +90/−0
- LICENSE +1/−0
- changelog +19/−0
- demo/ExtractDescriptionTerms.hs +113/−0
- demo/ExtractNameTerms.hs +181/−0
- demo/HaddockHtml.hs +84/−0
- demo/HaddockLex.x +189/−0
- demo/HaddockParse.y +117/−0
- demo/HaddockTypes.hs +48/−0
- demo/Main.hs +93/−0
- demo/PackageIndexUtils.hs +86/−0
- demo/PackageSearch.hs +106/−0
- dist/build/search-demo/search-demo-tmp/HaddockLex.hs +368/−0
- dist/build/search-demo/search-demo-tmp/HaddockParse.hs +732/−0
- full-text-search.cabal +77/−10
- tests/Main.hs +38/−0
- tests/Test/Data/SearchEngine/DocIdSet.hs +55/−0
- tests/Test/Data/SearchEngine/TermBag.hs +55/−0
Data/SearchEngine.hs view
@@ -1,5 +1,3 @@-{-# LANGUAGE BangPatterns, NamedFieldPuns, RecordWildCards #-}- module Data.SearchEngine ( -- * Basic interface@@ -8,12 +6,16 @@ Term, query, + -- *** Query auto-completion \/ auto-suggestion+ queryAutosuggest,+ ResultsFilter(..),+ -- ** Making a search engine instance initSearchEngine, SearchEngine, SearchConfig(..), SearchRankParameters(..),- BM25F.FeatureFunction(..),+ FeatureFunction(..), -- ** Helper type for non-term features NoFeatures,@@ -26,361 +28,15 @@ -- * Explain mode for query result rankings queryExplain,- BM25F.Explanation(..),+ Explanation(..), setRankParams, -- * Internal sanity check invariant, ) where -import Data.SearchEngine.SearchIndex (SearchIndex, Term, TermId)-import qualified Data.SearchEngine.SearchIndex as SI-import Data.SearchEngine.DocIdSet (DocIdSet, DocId)-import qualified Data.SearchEngine.DocIdSet as DocIdSet-import Data.SearchEngine.DocTermIds (DocTermIds)-import qualified Data.SearchEngine.DocTermIds as DocTermIds-import Data.SearchEngine.DocFeatVals (DocFeatVals)-import qualified Data.SearchEngine.DocFeatVals as DocFeatVals-import qualified Data.SearchEngine.BM25F as BM25F--import Data.Ix-import Data.Array.Unboxed-import Data.List-import Data.Function-import Data.Maybe------------------------ Doc layer------ That is, at the layer of documents, so covering the issues of:--- - inserting/removing whole documents--- - documents having multiple fields--- - documents having multiple terms--- - transformations (case-fold/normalisation/stemming) on the doc terms--- - transformations on the search terms-----data SearchConfig doc key field feature = SearchConfig {- documentKey :: doc -> key,- extractDocumentTerms :: doc -> field -> [Term],- transformQueryTerm :: Term -> field -> Term,- documentFeatureValue :: doc -> feature -> Float- }--data SearchRankParameters field feature = SearchRankParameters {- paramK1 :: !Float,- paramB :: field -> Float,- paramFieldWeights :: field -> Float,- paramFeatureWeights :: feature -> Float,- paramFeatureFunctions :: feature -> BM25F.FeatureFunction,- paramResultsetSoftLimit :: !Int,- paramResultsetHardLimit :: !Int- }--data SearchEngine doc key field feature = SearchEngine {- searchIndex :: !(SearchIndex key field feature),- searchConfig :: !(SearchConfig doc key field feature),- searchRankParams :: !(SearchRankParameters field feature),-- -- cached info- sumFieldLengths :: !(UArray field Int),- bm25Context :: BM25F.Context TermId field feature- }--initSearchEngine :: (Ix field, Bounded field, Ix feature, Bounded feature) =>- SearchConfig doc key field feature ->- SearchRankParameters field feature ->- SearchEngine doc key field feature-initSearchEngine config params =- cacheBM25Context- SearchEngine {- searchIndex = SI.emptySearchIndex,- searchConfig = config,- searchRankParams = params,- sumFieldLengths = listArray (minBound, maxBound) (repeat 0),- bm25Context = undefined- }--setRankParams :: SearchRankParameters field feature ->- SearchEngine doc key field feature ->- SearchEngine doc key field feature-setRankParams params@SearchRankParameters{..} se =- se {- searchRankParams = params,- bm25Context = (bm25Context se) {- BM25F.paramK1 = paramK1,- BM25F.paramB = paramB,- BM25F.fieldWeight = paramFieldWeights,- BM25F.featureWeight = paramFeatureWeights,- BM25F.featureFunction = paramFeatureFunctions- }- }--invariant :: (Ord key, Ix field, Bounded field) =>- SearchEngine doc key field feature -> Bool-invariant SearchEngine{searchIndex} =- SI.invariant searchIndex--- && check caches--cacheBM25Context :: Ix field =>- SearchEngine doc key field feature ->- SearchEngine doc key field feature-cacheBM25Context- se@SearchEngine {- searchRankParams = SearchRankParameters{..},- searchIndex,- sumFieldLengths- }- = se { bm25Context = bm25Context' }- where- bm25Context' = BM25F.Context {- BM25F.numDocsTotal = SI.docCount searchIndex,- BM25F.avgFieldLength = \f -> fromIntegral (sumFieldLengths ! f)- / fromIntegral (SI.docCount searchIndex),- BM25F.numDocsWithTerm = DocIdSet.size . SI.lookupTermId searchIndex,- BM25F.paramK1 = paramK1,- BM25F.paramB = paramB,- BM25F.fieldWeight = paramFieldWeights,- BM25F.featureWeight = paramFeatureWeights,- BM25F.featureFunction = paramFeatureFunctions- }--updateCachedFieldLengths :: (Ix field, Bounded field) =>- Maybe (DocTermIds field) -> Maybe (DocTermIds field) ->- SearchEngine doc key field feature ->- SearchEngine doc key field feature-updateCachedFieldLengths Nothing (Just newDoc) se@SearchEngine{sumFieldLengths} =- se {- sumFieldLengths =- array (bounds sumFieldLengths)- [ (i, n + DocTermIds.fieldLength newDoc i)- | (i, n) <- assocs sumFieldLengths ]- }-updateCachedFieldLengths (Just oldDoc) (Just newDoc) se@SearchEngine{sumFieldLengths} =- se {- sumFieldLengths =- array (bounds sumFieldLengths)- [ (i, n - DocTermIds.fieldLength oldDoc i- + DocTermIds.fieldLength newDoc i)- | (i, n) <- assocs sumFieldLengths ]- }-updateCachedFieldLengths (Just oldDoc) Nothing se@SearchEngine{sumFieldLengths} =- se {- sumFieldLengths =- array (bounds sumFieldLengths)- [ (i, n - DocTermIds.fieldLength oldDoc i)- | (i, n) <- assocs sumFieldLengths ]- }-updateCachedFieldLengths Nothing Nothing se = se--insertDocs :: (Ord key, Ix field, Bounded field, Ix feature, Bounded feature) =>- [doc] ->- SearchEngine doc key field feature ->- SearchEngine doc key field feature-insertDocs docs se = foldl' (\se' doc -> insertDoc doc se') se docs--insertDoc :: (Ord key, Ix field, Bounded field, Ix feature, Bounded feature) =>- doc ->- SearchEngine doc key field feature ->- SearchEngine doc key field feature-insertDoc doc se@SearchEngine{ searchConfig = SearchConfig {- documentKey, - extractDocumentTerms,- documentFeatureValue- }- , searchIndex } =- let key = documentKey doc- searchIndex' = SI.insertDoc key (extractDocumentTerms doc)- (documentFeatureValue doc)- searchIndex- oldDoc = SI.lookupDocKey searchIndex key- newDoc = SI.lookupDocKey searchIndex' key-- in cacheBM25Context $- updateCachedFieldLengths oldDoc newDoc $- se { searchIndex = searchIndex' }--deleteDoc :: (Ord key, Ix field, Bounded field) =>- key ->- SearchEngine doc key field feature ->- SearchEngine doc key field feature-deleteDoc key se@SearchEngine{searchIndex} =- let searchIndex' = SI.deleteDoc key searchIndex- oldDoc = SI.lookupDocKey searchIndex key-- in cacheBM25Context $- updateCachedFieldLengths oldDoc Nothing $- se { searchIndex = searchIndex' }--query :: (Ix field, Bounded field, Ix feature, Bounded feature) =>- SearchEngine doc key field feature ->- [Term] -> [key]-query se@SearchEngine{ searchIndex,- searchConfig = SearchConfig{transformQueryTerm},- searchRankParams = SearchRankParameters{..} }- terms =-- let -- Start by transforming/normalising all the query terms.- -- This can be done differently for each field we search by.- lookupTerms :: [Term]- lookupTerms = [ term'- | term <- terms- , let transformForField = transformQueryTerm term- , term' <- nub [ transformForField field- | field <- range (minBound, maxBound) ]- ]-- -- Then we look up all the normalised terms in the index.- rawresults :: [Maybe (TermId, DocIdSet)] - rawresults = map (SI.lookupTerm searchIndex) lookupTerms-- -- For the terms that occur in the index, this gives us the term's id- -- and the set of documents that the term occurs in.- termids :: [TermId]- docidsets :: [DocIdSet]- (termids, docidsets) = unzip (catMaybes rawresults)-- -- We looked up the documents that *any* of the term occur in (not all)- -- so this could be rather a lot of docs if the user uses a few common- -- terms. Scoring these result docs is a non-trivial cost so we want to- -- limit the number that we have to score. The standard trick is to- -- consider the doc sets in the order of size, smallest to biggest. Once- -- we have gone over a certain threshold of docs then don't bother with- -- the doc sets for the remaining terms. This tends to work because the- -- scoring gives lower weight to terms that occur in many documents.- unrankedResults :: DocIdSet- unrankedResults = pruneRelevantResults- paramResultsetSoftLimit- paramResultsetHardLimit- docidsets-- --TODO: technically this isn't quite correct. Because each field can- -- be normalised differently, we can end up with different termids for- -- the same original search term, and then we score those as if they- -- were different terms, which makes a difference when the term appears- -- in multiple fields (exactly the case BM25F is supposed to deal with).- -- What we ought to have instead is an Array (Int, field) TermId, and- -- make the scoring use the appropriate termid for each field, but to- -- consider them the "same" term.- in rankResults se termids (DocIdSet.toList unrankedResults)--rankResults :: (Ix field, Bounded field, Ix feature, Bounded feature) =>- SearchEngine doc key field feature ->- [TermId] -> [DocId] -> [key]-rankResults se@SearchEngine{searchIndex} queryTerms docids =- map snd- $ sortBy (flip compare `on` fst)- [ (relevanceScore se queryTerms doctermids docfeatvals, dockey)- | docid <- docids- , let (dockey, doctermids, docfeatvals) = SI.lookupDocId searchIndex docid ]--relevanceScore :: (Ix field, Bounded field, Ix feature, Bounded feature) =>- SearchEngine doc key field feature ->- [TermId] -> DocTermIds field -> DocFeatVals feature -> Float-relevanceScore SearchEngine{bm25Context} queryTerms doctermids docfeatvals =- BM25F.score bm25Context doc queryTerms- where- doc = indexDocToBM25Doc doctermids docfeatvals--indexDocToBM25Doc :: (Ix field, Bounded field, Ix feature, Bounded feature) =>- DocTermIds field ->- DocFeatVals feature ->- BM25F.Doc TermId field feature-indexDocToBM25Doc doctermids docfeatvals =- BM25F.Doc {- BM25F.docFieldLength = DocTermIds.fieldLength doctermids,- BM25F.docFieldTermFrequency = DocTermIds.fieldTermCount doctermids,- BM25F.docFeatureValue = DocFeatVals.featureValue docfeatvals- }--pruneRelevantResults :: Int -> Int -> [DocIdSet] -> DocIdSet-pruneRelevantResults softLimit hardLimit =- -- Look at the docsets starting with the smallest ones. Smaller docsets- -- correspond to the rarer terms, which are the ones that score most highly.- go DocIdSet.empty . sortBy (compare `on` DocIdSet.size)- where- go !acc [] = acc- go !acc (d:ds)- -- If this is the first one, we add it anyway, otherwise we're in- -- danger of returning no results at all.- | DocIdSet.null acc = go d ds- -- We consider the size our docset would be if we add this extra one...- -- If it puts us over the hard limit then stop.- | size > hardLimit = acc- -- If it puts us over soft limit then we add it and stop- | size > softLimit = DocIdSet.union acc d- -- Otherwise we can add it and carry on to consider the remainder- | otherwise = go (DocIdSet.union acc d) ds- where- size = DocIdSet.size acc + DocIdSet.size d---------------------------------queryExplain :: (Ix field, Bounded field, Ix feature, Bounded feature) =>- SearchEngine doc key field feature ->- [Term] -> [(BM25F.Explanation field feature Term, key)]-queryExplain se@SearchEngine{ searchIndex,- searchConfig = SearchConfig{transformQueryTerm},- searchRankParams = SearchRankParameters{..} }- terms =-- -- See 'query' above for explanation. Really we ought to combine them.- let lookupTerms :: [Term]- lookupTerms = [ term'- | term <- terms- , let transformForField = transformQueryTerm term- , term' <- nub [ transformForField field- | field <- range (minBound, maxBound) ]- ]-- rawresults :: [Maybe (TermId, DocIdSet)] - rawresults = map (SI.lookupTerm searchIndex) lookupTerms-- termids :: [TermId]- docidsets :: [DocIdSet]- (termids, docidsets) = unzip (catMaybes rawresults)-- unrankedResults :: DocIdSet- unrankedResults = pruneRelevantResults- paramResultsetSoftLimit- paramResultsetHardLimit- docidsets-- in rankExplainResults se termids (DocIdSet.toList unrankedResults)--rankExplainResults :: (Ix field, Bounded field, Ix feature, Bounded feature) =>- SearchEngine doc key field feature -> - [TermId] ->- [DocId] -> - [(BM25F.Explanation field feature Term, key)]-rankExplainResults se@SearchEngine{searchIndex} queryTerms docids =- sortBy (flip compare `on` (BM25F.overallScore . fst))- [ (explainRelevanceScore se queryTerms doctermids docfeatvals, dockey)- | docid <- docids- , let (dockey, doctermids, docfeatvals) = SI.lookupDocId searchIndex docid ]--explainRelevanceScore :: (Ix field, Bounded field, Ix feature, Bounded feature) =>- SearchEngine doc key field feature ->- [TermId] ->- DocTermIds field ->- DocFeatVals feature -> - BM25F.Explanation field feature Term-explainRelevanceScore SearchEngine{bm25Context, searchIndex}- queryTerms doctermids docfeatvals =- fmap (SI.getTerm searchIndex) (BM25F.explain bm25Context doc queryTerms)- where- doc = indexDocToBM25Doc doctermids docfeatvals---------------------------------data NoFeatures = NoFeatures- deriving (Eq, Ord, Bounded)--instance Ix NoFeatures where- range _ = []- inRange _ _ = False- index _ _ = -1--noFeatures :: NoFeatures -> a-noFeatures _ = error "noFeatures"+import Data.SearchEngine.Types+import Data.SearchEngine.Update+import Data.SearchEngine.Query+import Data.SearchEngine.Autosuggest
+ Data/SearchEngine/Autosuggest.hs view
@@ -0,0 +1,501 @@+{-# LANGUAGE BangPatterns, NamedFieldPuns, RecordWildCards,+ ScopedTypeVariables #-}++module Data.SearchEngine.Autosuggest (++ -- * Query auto-completion \/ auto-suggestion+ queryAutosuggest,+ ResultsFilter(..),++ ) where++import Data.SearchEngine.Types+import Data.SearchEngine.Query (ResultsFilter(..))+import qualified Data.SearchEngine.Query as Query+import qualified Data.SearchEngine.SearchIndex as SI+import qualified Data.SearchEngine.DocIdSet as DocIdSet+import qualified Data.SearchEngine.DocTermIds as DocTermIds+import qualified Data.SearchEngine.BM25F as BM25F++import Data.Ix+import Data.Ord+import Data.List+import Data.Maybe+import qualified Data.Map as Map+import qualified Data.IntSet as IntSet+import qualified Data.Vector.Unboxed as Vec+++-- | Execute an \"auto-suggest\" query. This is where one of the search terms+-- is an incomplete prefix and we are looking for possible completions of that+-- search term, and result documents to go with the possible completions.+--+-- An auto-suggest query only gives useful results when the 'SearchEngine' is+-- configured to use a non-term feature score. That is, when we can give+-- documents an importance score independent of what terms we are looking for.+-- This is because an auto-suggest query is backwards from a normal query: we+-- are asking for relevant terms occurring in important or popular documents+-- so we need some notion of important or popular. Without this we would just+-- be ranking based on term frequency which while it makes sense for normal+-- \"forward\" queries is pretty meaningless for auto-suggest \"reverse\"+-- queries. Indeed for single-term auto-suggest queries the ranking function+-- we use will assign 0 for all documents and completions if there is no +-- non-term feature scores.+--+queryAutosuggest :: (Ix field, Bounded field, Ix feature, Bounded feature) =>+ SearchEngine doc key field feature ->+ ResultsFilter key ->+ [Term] -> Term -> ([(Term, Float)], [(key, Float)])+queryAutosuggest se resultsFilter precedingTerms partialTerm =++ step_external+ . step_rank+ . step_scoreDs+ . step_scoreTs+ . step_cache+ . step_postfilterlimit+ . step_filter+ . step_prefilterlimit+ . step_process+ $ step_prep+ precedingTerms partialTerm++ where+ -- Construct the auto-suggest query from the query terms+ step_prep pre_ts t = mkAutosuggestQuery se pre_ts t++ -- Find the appropriate subset of ts and ds+ -- and an intermediate result that will be useful later:+ -- { (t, ds ∩ ds_t) | t ∈ ts, ds ∩ ds_t ≠ ∅ }+ step_process (ts, ds, pre_ts) = (ts', ds', tdss', pre_ts)+ where+ (tdss', ts', ds') = processAutosuggestQuery (ts, ds, pre_ts)++ -- If the number of docs results is huge then we may not want to bother+ -- and just return no results. Even the filtering of a huge number of+ -- docs can be expensive.+ step_prefilterlimit args@(_, ds, _, _)+ | withinPrefilterLimit se ds = args+ | otherwise = ([], DocIdSet.empty, [], [])++ -- Filter ds to those that are visible for this query+ -- and at the same time, do the docid -> docinfo lookup+ -- (needed at this step anyway to do the filter)+ step_filter (ts, ds, tdss, pre_ts) = (ts, ds_info, tdss, pre_ts)+ where+ ds_info = filterAutosuggestQuery se resultsFilter ds++ -- If the number of docs results is huge then we may not want to bother+ -- and just return no results. Scoring a large number of docs is expensive.+ step_postfilterlimit args@(_, ds_info, _, _)+ | withinPostfilterLimit se ds_info = args+ | otherwise = ([], [], [], [])++ -- For all ds, calculate and cache a couple bits of info needed+ -- later for scoring completion terms and doc results+ step_cache (ts, ds_info, tdss, pre_ts) = (ds_info', tdss)+ where+ ds_info' = cacheDocScoringInfo se ts ds_info pre_ts++ -- Score the completion terms+ step_scoreTs (ds_info, tdss) = (ds_info, tdss, ts_scored)+ where+ ts_scored = scoreAutosuggestQueryCompletions tdss ds_info++ -- Score the doc results (making use of the completion scores)+ step_scoreDs (ds_info, tdss, ts_scored) = (ts_scored, ds_scored)+ where+ ds_scored = scoreAutosuggestQueryResults tdss ds_info ts_scored++ -- Rank the completions and results based on their scores+ step_rank = sortResults++ -- Convert from internal Ids into external forms: Term and doc key+ step_external = convertIdsToExternal se+++-- We apply hard limits both before and after filtering.+-- The post-filter limit is to avoid scoring 1000s of documents.+-- The pre-filter limit is to avoid filtering 1000s of docs (which in some+-- apps may be expensive itself)++withinPrefilterLimit :: SearchEngine doc key field feature ->+ DocIdSet -> Bool+withinPrefilterLimit SearchEngine{searchRankParams} ds =+ DocIdSet.size ds <= paramAutosuggestPrefilterLimit searchRankParams++withinPostfilterLimit :: SearchEngine doc key field feature ->+ [a] -> Bool+withinPostfilterLimit SearchEngine{searchRankParams} ds_info =+ length ds_info <= paramAutosuggestPostfilterLimit searchRankParams+++sortResults :: (Ord av, Ord bv) => ([(a,av)], [(b,bv)]) -> ([(a,av)], [(b,bv)])+sortResults (xs, ys) =+ ( sortBySndDescending xs+ , sortBySndDescending ys )+ where+ sortBySndDescending :: Ord v => [(x,v)] -> [(x,v)]+ sortBySndDescending = sortBy (flip (comparing snd))++convertIdsToExternal :: SearchEngine doc key field feature ->+ ([(TermId, v)], [(DocId, v)]) -> ([(Term, v)], [(key, v)])+convertIdsToExternal SearchEngine{searchIndex} (termids, docids) =+ ( [ (SI.getTerm searchIndex termid, s) | (termid, s) <- termids ]+ , [ (SI.getDocKey searchIndex docid, s) | (docid, s) <- docids ]+ )+++-- From Bast and Weber:+--+-- An autocompletion query is a pair (T, D), where T is a range of terms+-- (all possible completions of the last term which the user has started+-- typing) and D is a set of documents (the hits for the preceding part of+-- the query).+--+-- We augment this with the preceding terms because we will need these to+-- score the set of documents D.+--+-- Note that the set D will be the entire collection in the case that the+-- preceding part of the query is empty. For efficiency we represent that+-- case specially with Maybe.++type AutosuggestQuery = (Map.Map TermId DocIdSet, Maybe DocIdSet, [TermId])++mkAutosuggestQuery :: (Ix field, Bounded field) =>+ SearchEngine doc key field feature ->+ [Term] -> Term -> AutosuggestQuery+mkAutosuggestQuery se@SearchEngine{ searchIndex }+ precedingTerms partialTerm =+ (completionTerms, precedingDocHits, precedingTerms')+ where+ completionTerms =+ Map.unions+ [ Map.fromList (SI.lookupTermsByPrefix searchIndex partialTerm')+ | partialTerm' <- Query.expandTransformedQueryTerm se partialTerm+ ]++ (precedingTerms', precedingDocHits)+ | null precedingTerms = ([], Nothing)+ | otherwise = fmap (Just . DocIdSet.unions)+ (lookupRawResults precedingTerms)++ lookupRawResults :: [Term] -> ([TermId], [DocIdSet])+ lookupRawResults ts =+ unzip $ catMaybes+ [ SI.lookupTerm searchIndex t'+ | t <- ts+ , t' <- Query.expandTransformedQueryTerm se t+ ]++++-- From Bast and Weber:+--+-- To process the query means to compute the subset T' ⊆ T of terms that+-- occur in at least one document from D, as well as the subset D' ⊆ D of+-- documents that contain at least one of these words.+--+-- The obvious way to use an inverted index to process an autocompletion+-- query (T, D) is to compute, for each t ∈ T, the intersections D ∩ Dt.+-- Then, T' is simply the set of all t for which the intersection was+-- non-empty, and D' is the union of all (non-empty) intersections.+--+-- We will do this but additionally we will return all the non-empty+-- intersections because they will be useful when scoring.++processAutosuggestQuery :: AutosuggestQuery ->+ ([(TermId, DocIdSet)], [TermId], DocIdSet)+processAutosuggestQuery (completionTerms, precedingDocHits, _) =+ ( completionTermAndDocSets+ , completionTerms'+ , allTermDocSet+ )+ where+ -- We look up each candidate completion to find the set of documents+ -- it appears in, and filtering (intersecting) down to just those+ -- appearing in the existing partial query results (if any).+ -- Candidate completions not appearing at all within the existing+ -- partial query results are excluded at this stage.+ --+ -- We have to keep these doc sets for the whole process, so we keep+ -- them as the compact DocIdSet type.+ --+ completionTermAndDocSets :: [(TermId, DocIdSet)]+ completionTermAndDocSets =+ [ (t, ds_t')+ | (t, ds_t) <- Map.toList completionTerms+ , let ds_t' = case precedingDocHits of+ Just ds -> ds `DocIdSet.intersection` ds_t+ Nothing -> ds_t+ , not (DocIdSet.null ds_t')+ ]++ -- The remaining candidate completions+ completionTerms' = [ w | (w, _ds_w) <- completionTermAndDocSets ]++ -- The union of all these is this set of documents that form the results.+ allTermDocSet :: DocIdSet+ allTermDocSet =+ DocIdSet.unions [ ds_t | (_t, ds_t) <- completionTermAndDocSets ]+++filterAutosuggestQuery :: SearchEngine doc key field feature ->+ ResultsFilter key ->+ DocIdSet ->+ [(DocId, (key, DocTermIds field, DocFeatVals feature))]+filterAutosuggestQuery SearchEngine{ searchIndex } resultsFilter ds =+ case resultsFilter of+ NoFilter ->+ [ (docid, doc)+ | docid <- DocIdSet.toList ds+ , let doc = SI.lookupDocId searchIndex docid ]++ FilterPredicate predicate ->+ [ (docid, doc)+ | docid <- DocIdSet.toList ds+ , let doc@(k,_,_) = SI.lookupDocId searchIndex docid+ , predicate k ]++ FilterBulkPredicate bulkPredicate ->+ [ (docid, doc)+ | let docids = DocIdSet.toList ds+ docinf = map (SI.lookupDocId searchIndex) docids+ keep = bulkPredicate [ k | (k,_,_) <- docinf ]+ , (docid, doc, True) <- zip3 docids docinf keep ]+++-- Scoring+-------------+--+-- From Bast and Weber:+-- In practice, only a selection of items from these lists can and will be+-- presented to the user, and it is of course crucial that the most relevant+-- completions and hits are selected.+--+-- A standard approach for this task in ad-hoc retrieval is to have a+-- precomputed score for each term-in-document pair, and when a query is+-- being processed, to aggregate these scores for each candidate document,+-- and return documents with the highest such aggregated scores.+--+-- Both INV and HYB can be easily adapted to implement any such scoring and+-- aggregation scheme: store by each term-in-document pair its precomputed+-- score, and when intersecting, aggregate the scores. A decision has to be+-- made on how to reconcile scores from different completions within the+-- same document. We suggest the following: when merging the intersections+-- (which gives the set D' according to Definition 1), compute for each+-- document in D' the maximal score achieved for some completion in T'+-- contained in that document, and compute for each completion in T' the+-- maximal score achieved for a hit from D' achieved for this completion.+--+-- So firstly let us explore what this means and then discuss why it does not+-- work for BM25.+--+-- The "precomputed score for each term-in-document pair" refers to the bm25+-- score for this term in this document (and obviously doesn't have to be+-- precomputed, though that'd be faster).+--+-- So the score for a document d ∈ D' is:+-- maximum of score for d ∈ D ∩ Dt, for any t ∈ T'+--+-- While the score for a completion t ∈ T' is:+-- maximum of score for d ∈ D ∩ Dt+--+-- So for documents we say their score is their best score for any of the+-- completion terms they contain. While for completions we say their score+-- is their best score for any of the documents they appear in.+--+-- For a scoring function like BM25 this appears to be not a good method, both+-- in principle and in practice. Consider what terms get high BM25 scores:+-- very rare ones. So this means we're going to score highly documents that+-- contain the least frequent terms, and completions that are themselves very+-- rare. This is silly.+--+-- Another important thing to note is that if we use this scoring method then+-- we are using the BM25 score in a way that makes no sense. The BM25 score+-- for different documents for the /same/ set of terms are comparable. The+-- score for the same for different document with different terms are simply+-- not comparable.+--+-- This also makes sense if you consider what question the BM25 score is+-- answering: "what is the likelihood that this document is relevant given that+-- I judge these terms to be relevant". However an auto-suggest query is+-- different: "what is the likelihood that this term is relevant given the+-- importance/popularity of the documents (and any preceding terms I've judged+-- to be relevant)". They are both conditional likelihood questions but with+-- different premises.+--+-- More generally, term frequency information simply isn't useful for+-- auto-suggest queries. We don't want results that have the most obscure terms+-- nor the most common terms, not even something in-between. Term frequency+-- just doesn't tell us anything unless we've already judged terms to be+-- relevant, and in an auto-suggest query we've not done that yet.+--+-- What we really need is information on the importance/popularity of the+-- documents. We can actually do something with that.+--+-- So, instead we follow a different strategy. We require that we have+-- importance/popularity info for the documents.+--+-- A first approximation would be to rank result documents by their importance+-- and completion terms by the sum of the importance of the documents each+-- term appears in.+--+-- Score for a document d ∈ D'+-- importance score for d+--+-- Score for a completion t ∈ T'+-- sum of importance score for d ∈ D ∩ Dt+--+-- The only problem with this is that just because a term appears in an+-- important document, doesn't mean that term is /about/ that document, or to+-- put it another way, that term may not be relevant for that document. For+-- example common words like "the" likely appear in all important documents+-- but this doesn't really tell us anything because "the" isn't an important+-- keyword.+--+-- So what we want to do is to weight the document importance by the relevance+-- of the keyword to the document. So now if we have an important document and+-- a relevant keyword for that document then we get a high score, but an+-- irrelevant term like "the" would get a very low weighting and so would not+-- contribute much to the score, even for very important documents.+--+-- The intuition is that we will score term completions by taking the+-- document importance weighted by the relevance of that term to that document+-- and summing over all the documents where the term occurs.+--+-- We define document importance (for the set D') to be the BM25F score for+-- the documents with any preceding terms. So this includes the non-term+-- feature score for the importance/popularity, and also takes account of+-- preceding terms if there were any.+--+-- We define term relevance (for terms in documents) to be the BM25F score for+-- that term in that document as a fraction of the total BM25F score for all+-- terms in the document. Thus the relevance of all terms in a document sums+-- to 1.+--+-- Now we can re-weight the document importance by the term relevance:+--+-- Score for a completion t ∈ T'+-- sum (for d ∈ D ∩ Dt) of ( importance for d * relevance for t in d )+--+-- And now for document result scores. We don't want to just stick with the+-- innate document importance. We want to re-weight by the completion term+-- scores:+--+-- Score for a document d ∈ D'+-- sum (for t ∈ T' ∩ d) (importance score for d * score for completion t)+--+-- Clear as mud?++type DocImportance = Float+type TermRelevanceBreakdown = Map.Map TermId Float++-- | Precompute the document importance and the term relevance breakdown for+-- all the documents. This will be used in scoring the term completions+-- and the result documents. They will all be used and some used many+-- times so it's better to compute up-front and share.+--+-- This is actually the expensive bit (which is why we've filtered already).+--+cacheDocScoringInfo :: (Ix field, Bounded field, Ix feature, Bounded feature) =>+ SearchEngine doc key field feature ->+ [TermId] ->+ [(DocId, (key, DocTermIds field, DocFeatVals feature))] ->+ [TermId] ->+ Map.Map DocId (DocImportance, TermRelevanceBreakdown)+cacheDocScoringInfo se completionTerms allTermDocInfo precedingTerms =+ Map.fromList+ [ (docid, (docImportance, termRelevances))+ | (docid, (_dockey, doctermids, docfeatvals)) <- allTermDocInfo+ , let docImportance = Query.relevanceScore se precedingTerms+ doctermids docfeatvals+ termRelevances = relevanceBreakdown se doctermids docfeatvals+ completionTerms+ ]++-- | Calculate the relevance of each of a given set of terms to the given+-- document.+--+-- We define the \"relevance\" of each term in a document to be its+-- term-in-document score as a fraction of the total of the scores for all+-- terms in the document. Thus the sum of all the relevance values in the+-- document is 1.+--+-- Note: we have to calculate the relevance for all terms in the document+-- but we only keep the relevance value for the terms of interest.+--+relevanceBreakdown :: forall doc key field feature.+ (Ix field, Bounded field, Ix feature, Bounded feature) =>+ SearchEngine doc key field feature ->+ DocTermIds field -> DocFeatVals feature ->+ [TermId] -> TermRelevanceBreakdown+relevanceBreakdown SearchEngine{ bm25Context } doctermids docfeatvals ts =+ let -- We'll calculate the bm25 score for each term in this document+ bm25Doc = Query.indexDocToBM25Doc doctermids docfeatvals++ -- Cache the info that depends only on this doc, not the terms+ termScore :: (TermId -> (field -> Int) -> Float)+ termScore = BM25F.scoreTermsBulk bm25Context bm25Doc++ -- The DocTermIds has the info we need to do bulk scoring, but it's+ -- a sparse representation, so we first convert it to a dense table+ term :: Int -> TermId+ count :: Int -> field -> Int+ (!numTerms, term, count) = DocTermIds.denseTable doctermids++ -- We generate the vector of scores for all terms, based on looking up+ -- the termid and the per-field counts in the dense table+ termScores :: Vec.Vector Float+ !termScores = Vec.generate numTerms $ \i ->+ termScore (term i) (\f -> count i f)++ -- We keep only the values for the terms we're interested in+ -- and normalise so we get the relevence fraction+ !scoreSum = Vec.sum termScores+ !tset = IntSet.fromList (map fromEnum ts)+ in Map.fromList+ . Vec.toList+ . Vec.map (\(t,s) -> (t, s/scoreSum))+ . Vec.filter (\(t,_) -> fromEnum t `IntSet.member` tset)+ . Vec.imap (\i s -> (term i, s))+ $ termScores+++scoreAutosuggestQueryCompletions :: [(TermId, DocIdSet)]+ -> Map.Map DocId (Float, Map.Map TermId Float)+ -> [(TermId, Float)]+scoreAutosuggestQueryCompletions completionTermAndDocSets allTermDocInfo =+ [ (t, candidateScore t ds_t)+ | (t, ds_t) <- completionTermAndDocSets ]+ where+ -- The score for a completion is the sum of the importance of the+ -- documents in which that completion occurs, weighted by the relevance+ -- of the term to each document. For example we can have a very+ -- important document and our completion term is highly relevant to it+ -- or we could have a large number of moderately important documents+ -- that our term is quite relevant to. In either example the completion+ -- term would score highly.+ candidateScore :: TermId -> DocIdSet -> Float+ candidateScore t ds_t =+ sum [ docImportance * termRelevence+ | Just (docImportance, termRelevances) <-+ map (`Map.lookup` allTermDocInfo) (DocIdSet.toList ds_t)+ , let Just termRelevence = Map.lookup t termRelevances+ ]+++scoreAutosuggestQueryResults :: [(TermId, DocIdSet)] ->+ Map.Map DocId (Float, Map.Map TermId Float) ->+ [(TermId, Float)] ->+ [(DocId, Float)]+scoreAutosuggestQueryResults completionTermAndDocSets allTermDocInfo+ scoredCandidates =+ Map.toList $ Map.fromListWith (+)+ [ (docid, docImportance * score_t)+ | ((_, ds_t), (_, score_t)) <- zip completionTermAndDocSets scoredCandidates+ , let docids = DocIdSet.toList ds_t+ docinfo = map (`Map.lookup` allTermDocInfo) docids+ , (docid, Just (docImportance, _)) <- zip docids docinfo+ ]+
Data/SearchEngine/BM25F.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE RecordWildCards, BangPatterns, ScopedTypeVariables #-} -- | An implementation of BM25F ranking. See: --@@ -16,6 +16,8 @@ Context(..), FeatureFunction(..), Doc(..),+ -- ** Specialised variants+ scoreTermsBulk, -- * Explaining the score Explanation(..),@@ -23,6 +25,7 @@ ) where import Data.Ix+import Data.Array.Unboxed data Context term field feature = Context { numDocsTotal :: !Int,@@ -117,6 +120,53 @@ applyFeatureFunction (LogarithmicFunction p1) = \fi -> log (p1 + fi) applyFeatureFunction (RationalFunction p1) = \fi -> fi / (p1 + fi) applyFeatureFunction (SigmoidFunction p1 p2) = \fi -> 1 / (p1 + exp (-fi * p2))+++-----------------------------+-- Bulk scoring of many terms+--++-- | Most of the time we want to score several different documents for the same+-- set of terms, but sometimes we want to score one document for many terms+-- and in that case we can save a bit of work by doing it in bulk. It lets us+-- calculate once and share things that depend only on the document, and not+-- the term.+--+-- To take advantage of the sharing you must partially apply and name the+-- per-doc score functon, e.g.+--+-- > let score :: term -> (field -> Int) -> Float+-- > score = BM25.bulkScorer ctx doc+-- > in sum [ score t (\f -> counts ! (t, f)) | t <- ts ]+--+scoreTermsBulk :: forall field term feature. (Ix field, Bounded field) =>+ Context term field feature ->+ Doc term field feature ->+ (term -> (field -> Int) -> Float)+scoreTermsBulk ctx doc = + -- This is just a rearrangement of weightedTermScore and+ -- weightedDocTermFrequency above, with the doc-constant bits hoisted out.++ \t tFreq ->+ let !tf' = sum [ w!f * tf_f / _B!f+ | f <- range (minBound, maxBound)+ , let tf_f = fromIntegral (tFreq f)+ ]++ in weightIDF ctx t * tf'+ / (k1 + tf')+ where+ -- So long as the caller does the partial application thing then these+ -- values can all be shared between many calls with different terms.++ !k1 = paramK1 ctx+ w, _B :: UArray field Float+ !w = array (minBound, maxBound)+ [ (field, fieldWeight ctx field)+ | field <- range (minBound, maxBound) ]+ !_B = array (minBound, maxBound)+ [ (field, lengthNorm ctx doc field)+ | field <- range (minBound, maxBound) ] ------------------
Data/SearchEngine/DocIdSet.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE BangPatterns, GeneralizedNewtypeDeriving #-} module Data.SearchEngine.DocIdSet (- DocId, - DocIdSet,+ DocId(..),+ DocIdSet(..), null, size, empty,@@ -12,6 +12,8 @@ insert, delete, union,+ unions,+ intersection, invariant, ) where @@ -24,11 +26,11 @@ import Control.Monad.ST import Data.Set (Set) import qualified Data.Set as Set+import Data.List (foldl', sortBy)+import Data.Function (on) import Prelude hiding (null) ---import Test.QuickCheck---import qualified Data.List as List newtype DocId = DocId Word32 deriving (Eq, Ord, Show, Enum, Bounded, Vec.Unbox,@@ -94,17 +96,22 @@ EQ -> (mid, True) GT -> binarySearch vec (mid+1) b key +unions :: [DocIdSet] -> DocIdSet+unions = foldl' union empty+ -- a bit more effecient if we merge small ones first+ . sortBy (compare `on` size)+ union :: DocIdSet -> DocIdSet -> DocIdSet union x y | null x = y | null y = x union (DocIdSet xs) (DocIdSet ys) =- DocIdSet (Vec.create (MVec.new sizeBound >>= writeMerged xs ys))+ DocIdSet (Vec.create (MVec.new sizeBound >>= writeMergedUnion xs ys)) where sizeBound = Vec.length xs + Vec.length ys -writeMerged :: Vec.Vector DocId -> Vec.Vector DocId ->- MVec.MVector s DocId -> ST s (MVec.MVector s DocId)-writeMerged xs0 ys0 out = do+writeMergedUnion :: Vec.Vector DocId -> Vec.Vector DocId ->+ MVec.MVector s DocId -> ST s (MVec.MVector s DocId)+writeMergedUnion xs0 ys0 !out = do i <- go xs0 ys0 0 return $! MVec.take i out where@@ -115,60 +122,34 @@ return (i + Vec.length xs) | otherwise = let x = Vec.head xs; y = Vec.head ys in case compare x y of- GT -> do MVec.write out i y- go xs (Vec.tail ys) (i+1) - EQ -> do MVec.write out i x- go (Vec.tail xs) (Vec.tail ys) (i+1)- LT -> do MVec.write out i x - go (Vec.tail xs) ys (i+1)------------------- tests----{--instance Arbitrary DocIdSet where- arbitrary = fromList `fmap` (listOf arbitrary)--instance Arbitrary DocId where- arbitrary = DocId `fmap` choose (0,15)---prop_insert :: DocIdSet -> DocId -> Bool-prop_insert dset x =- let dset' = insert x dset- in invariant dset && invariant dset'- && all (`member` dset') (x : toList dset)--prop_delete :: DocIdSet -> DocId -> Bool-prop_delete dset x =- let dset' = DocIdSet.delete x dset- in invariant dset && invariant dset'- && all (`member` dset') (List.delete x (toList dset))- && not (x `member` dset')--prop_delete' :: DocIdSet -> Bool-prop_delete' dset =- all (prop_delete dset) (toList dset)--prop_union :: DocIdSet -> DocIdSet -> Bool-prop_union dset1 dset2 =- let dset = union dset1 dset2- dset' = fromList (List.union (toList dset1) (toList dset2))+ GT -> do MVec.write out i y+ go xs (Vec.tail ys) (i+1)+ EQ -> do MVec.write out i x+ go (Vec.tail xs) (Vec.tail ys) (i+1)+ LT -> do MVec.write out i x+ go (Vec.tail xs) ys (i+1) - in invariant dset && invariant dset'- && dset == dset'+intersection :: DocIdSet -> DocIdSet -> DocIdSet+intersection x y | null x = empty+ | null y = empty+intersection (DocIdSet xs) (DocIdSet ys) =+ DocIdSet (Vec.create (MVec.new sizeBound >>= writeMergedIntersection xs ys))+ where+ sizeBound = max (Vec.length xs) (Vec.length ys) -prop_union' :: DocIdSet -> DocIdSet -> Bool-prop_union' dset1 dset2 =- let dset = union dset1 dset2- dset' = List.foldl' (\s i -> insert i s) dset1 (toList dset2)- dset'' = List.foldl' (\s i -> insert i s) dset2 (toList dset1)- in invariant dset && invariant dset' && invariant dset''- && dset == dset'- && dset' == dset''+writeMergedIntersection :: Vec.Vector DocId -> Vec.Vector DocId ->+ MVec.MVector s DocId -> ST s (MVec.MVector s DocId)+writeMergedIntersection xs0 ys0 !out = do+ i <- go xs0 ys0 0+ return $! MVec.take i out+ where+ go !xs !ys !i+ | Vec.null xs = return i+ | Vec.null ys = return i+ | otherwise = let x = Vec.head xs; y = Vec.head ys+ in case compare x y of+ GT -> go xs (Vec.tail ys) i+ EQ -> do MVec.write out i x+ go (Vec.tail xs) (Vec.tail ys) (i+1)+ LT -> go (Vec.tail xs) ys i -member :: DocId -> DocIdSet -> Bool-member x (DocIdSet vec) =- x `List.elem` Vec.toList vec--}
Data/SearchEngine/DocTermIds.hs view
@@ -6,6 +6,7 @@ fieldTermCount, fieldElems, create,+ denseTable, vecIndexIx, vecCreateIx, ) where@@ -15,6 +16,7 @@ import Data.Vector (Vector, (!)) import qualified Data.Vector as Vec+import qualified Data.Vector.Unboxed as UVec import Data.Ix (Ix) import qualified Data.Ix as Ix @@ -38,14 +40,31 @@ fieldLength docterms field = TermBag.size (getField docterms field) --- | The frequency of a particular term in a field within the document.-fieldTermCount :: (Ix field, Bounded field) => DocTermIds field -> field -> TermId -> Int+-- | /O(log n)/ The frequency of a particular term in a field within the document.+--+fieldTermCount :: (Ix field, Bounded field) =>+ DocTermIds field -> field -> TermId -> Int fieldTermCount docterms field termid =- TermBag.termCount (getField docterms field) termid+ fromIntegral (TermBag.termCount (getField docterms field) termid) fieldElems :: (Ix field, Bounded field) => DocTermIds field -> field -> [TermId] fieldElems docterms field = TermBag.elems (getField docterms field)++-- | The 'DocTermIds' is really a sparse 2d array, and doing lookups with+-- 'fieldTermCount' has a O(log n) cost. This function converts to a dense+-- tabular representation which then enables linear scans.+--+denseTable :: (Ix field, Bounded field) => DocTermIds field ->+ (Int, Int -> TermId, Int -> field -> Int)+denseTable (DocTermIds fieldVec) =+ let (!termids, !termcounts) = TermBag.denseTable (Vec.toList fieldVec)+ !numTerms = UVec.length termids+ in ( numTerms+ , \i -> termids UVec.! i+ , \i ix -> let j = Ix.index (minBound, maxBound) ix+ in fromIntegral (termcounts UVec.! (j * numTerms + i))+ ) --------------------------------- -- Vector indexed by Ix Bounded
+ Data/SearchEngine/Query.hs view
@@ -0,0 +1,257 @@+{-# LANGUAGE BangPatterns, NamedFieldPuns, RecordWildCards #-}++module Data.SearchEngine.Query (++ -- * Querying+ query,+ ResultsFilter(..),++ -- * Explain mode for query result rankings+ queryExplain,+ BM25F.Explanation(..),+ setRankParams,++ -- ** Utils used by autosuggest+ relevanceScore,+ indexDocToBM25Doc,+ expandTransformedQueryTerm,+ ) where++import Data.SearchEngine.Types+import qualified Data.SearchEngine.SearchIndex as SI+import qualified Data.SearchEngine.DocIdSet as DocIdSet+import qualified Data.SearchEngine.DocTermIds as DocTermIds+import qualified Data.SearchEngine.DocFeatVals as DocFeatVals+import qualified Data.SearchEngine.BM25F as BM25F++import Data.Ix+import Data.List+import Data.Function+import Data.Maybe+++-- | Execute a normal query. Find the documents in which one or more of+-- the search terms appear and return them in ranked order.+--+-- The number of documents returned is limited by the 'paramResultsetSoftLimit'+-- and 'paramResultsetHardLimit' paramaters. This also limits the cost of the+-- query (which is primarily the cost of scoring each document).+--+-- The given terms are all assumed to be complete (as opposed to prefixes+-- like with 'queryAutosuggest').+--+query :: (Ix field, Bounded field, Ix feature, Bounded feature) =>+ SearchEngine doc key field feature ->+ [Term] -> [key]+query se@SearchEngine{ searchIndex,+ searchRankParams = SearchRankParameters{..} }+ terms =++ let -- Start by transforming/normalising all the query terms.+ -- This can be done differently for each field we search by.+ lookupTerms :: [Term]+ lookupTerms = concatMap (expandTransformedQueryTerm se) terms++ -- Then we look up all the normalised terms in the index.+ rawresults :: [Maybe (TermId, DocIdSet)]+ rawresults = map (SI.lookupTerm searchIndex) lookupTerms++ -- For the terms that occur in the index, this gives us the term's id+ -- and the set of documents that the term occurs in.+ termids :: [TermId]+ docidsets :: [DocIdSet]+ (termids, docidsets) = unzip (catMaybes rawresults)++ -- We looked up the documents that *any* of the term occur in (not all)+ -- so this could be rather a lot of docs if the user uses a few common+ -- terms. Scoring these result docs is a non-trivial cost so we want to+ -- limit the number that we have to score. The standard trick is to+ -- consider the doc sets in the order of size, smallest to biggest. Once+ -- we have gone over a certain threshold of docs then don't bother with+ -- the doc sets for the remaining terms. This tends to work because the+ -- scoring gives lower weight to terms that occur in many documents.+ unrankedResults :: DocIdSet+ unrankedResults = pruneRelevantResults+ paramResultsetSoftLimit+ paramResultsetHardLimit+ docidsets++ --TODO: technically this isn't quite correct. Because each field can+ -- be normalised differently, we can end up with different termids for+ -- the same original search term, and then we score those as if they+ -- were different terms, which makes a difference when the term appears+ -- in multiple fields (exactly the case BM25F is supposed to deal with).+ -- What we ought to have instead is an Array (Int, field) TermId, and+ -- make the scoring use the appropriate termid for each field, but to+ -- consider them the "same" term.+ in rankResults se termids (DocIdSet.toList unrankedResults)++-- | Before looking up a term in the main index we need to normalise it+-- using the 'transformQueryTerm'. Of course the transform can be different+-- for different fields, so we have to collect all the forms (eliminating+-- duplicates).+--+expandTransformedQueryTerm :: (Ix field, Bounded field) =>+ SearchEngine doc key field feature ->+ Term -> [Term]+expandTransformedQueryTerm SearchEngine{searchConfig} term =+ nub [ transformForField field+ | let transformForField = transformQueryTerm searchConfig term+ , field <- range (minBound, maxBound) ]+++rankResults :: (Ix field, Bounded field, Ix feature, Bounded feature) =>+ SearchEngine doc key field feature ->+ [TermId] -> [DocId] -> [key]+rankResults se@SearchEngine{searchIndex} queryTerms docids =+ map snd+ $ sortBy (flip compare `on` fst)+ [ (relevanceScore se queryTerms doctermids docfeatvals, dockey)+ | docid <- docids+ , let (dockey, doctermids, docfeatvals) = SI.lookupDocId searchIndex docid ]++relevanceScore :: (Ix field, Bounded field, Ix feature, Bounded feature) =>+ SearchEngine doc key field feature ->+ [TermId] -> DocTermIds field -> DocFeatVals feature -> Float+relevanceScore SearchEngine{bm25Context} queryTerms doctermids docfeatvals =+ BM25F.score bm25Context doc queryTerms+ where+ doc = indexDocToBM25Doc doctermids docfeatvals++indexDocToBM25Doc :: (Ix field, Bounded field, Ix feature, Bounded feature) =>+ DocTermIds field ->+ DocFeatVals feature ->+ BM25F.Doc TermId field feature+indexDocToBM25Doc doctermids docfeatvals =+ BM25F.Doc {+ BM25F.docFieldLength = DocTermIds.fieldLength doctermids,+ BM25F.docFieldTermFrequency = DocTermIds.fieldTermCount doctermids,+ BM25F.docFeatureValue = DocFeatVals.featureValue docfeatvals+ }++pruneRelevantResults :: Int -> Int -> [DocIdSet] -> DocIdSet+pruneRelevantResults softLimit hardLimit =+ -- Look at the docsets starting with the smallest ones. Smaller docsets+ -- correspond to the rarer terms, which are the ones that score most highly.+ go DocIdSet.empty . sortBy (compare `on` DocIdSet.size)+ where+ go !acc [] = acc+ go !acc (d:ds)+ -- If this is the first one, we add it anyway, otherwise we're in+ -- danger of returning no results at all.+ | DocIdSet.null acc = go d ds+ -- We consider the size our docset would be if we add this extra one...+ -- If it puts us over the hard limit then stop.+ | size > hardLimit = acc+ -- If it puts us over soft limit then we add it and stop+ | size > softLimit = DocIdSet.union acc d+ -- Otherwise we can add it and carry on to consider the remainder+ | otherwise = go (DocIdSet.union acc d) ds+ where+ size = DocIdSet.size acc + DocIdSet.size d+++--------------------------------+-- Normal query with explanation+--++queryExplain :: (Ix field, Bounded field, Ix feature, Bounded feature) =>+ SearchEngine doc key field feature ->+ [Term] -> [(BM25F.Explanation field feature Term, key)]+queryExplain se@SearchEngine{ searchIndex,+ searchConfig = SearchConfig{transformQueryTerm},+ searchRankParams = SearchRankParameters{..} }+ terms =++ -- See 'query' above for explanation. Really we ought to combine them.+ let lookupTerms :: [Term]+ lookupTerms = [ term'+ | term <- terms+ , let transformForField = transformQueryTerm term+ , term' <- nub [ transformForField field+ | field <- range (minBound, maxBound) ]+ ]++ rawresults :: [Maybe (TermId, DocIdSet)]+ rawresults = map (SI.lookupTerm searchIndex) lookupTerms++ termids :: [TermId]+ docidsets :: [DocIdSet]+ (termids, docidsets) = unzip (catMaybes rawresults)++ unrankedResults :: DocIdSet+ unrankedResults = pruneRelevantResults+ paramResultsetSoftLimit+ paramResultsetHardLimit+ docidsets++ in rankExplainResults se termids (DocIdSet.toList unrankedResults)++rankExplainResults :: (Ix field, Bounded field, Ix feature, Bounded feature) =>+ SearchEngine doc key field feature ->+ [TermId] ->+ [DocId] ->+ [(BM25F.Explanation field feature Term, key)]+rankExplainResults se@SearchEngine{searchIndex} queryTerms docids =+ sortBy (flip compare `on` (BM25F.overallScore . fst))+ [ (explainRelevanceScore se queryTerms doctermids docfeatvals, dockey)+ | docid <- docids+ , let (dockey, doctermids, docfeatvals) = SI.lookupDocId searchIndex docid ]+++explainRelevanceScore :: (Ix field, Bounded field, Ix feature, Bounded feature) =>+ SearchEngine doc key field feature ->+ [TermId] ->+ DocTermIds field ->+ DocFeatVals feature ->+ BM25F.Explanation field feature Term+explainRelevanceScore SearchEngine{bm25Context, searchIndex}+ queryTerms doctermids docfeatvals =+ fmap (SI.getTerm searchIndex) (BM25F.explain bm25Context doc queryTerms)+ where+ doc = indexDocToBM25Doc doctermids docfeatvals+++setRankParams :: SearchRankParameters field feature ->+ SearchEngine doc key field feature ->+ SearchEngine doc key field feature+setRankParams params@SearchRankParameters{..} se =+ se {+ searchRankParams = params,+ bm25Context = (bm25Context se) {+ BM25F.paramK1 = paramK1,+ BM25F.paramB = paramB,+ BM25F.fieldWeight = paramFieldWeights,+ BM25F.featureWeight = paramFeatureWeights,+ BM25F.featureFunction = paramFeatureFunctions+ }+ }+++--------------------------------+-- Results filter+--++-- | In some applications it is necessary to enforce some security or+-- visibility rule about the query results (e.g. in a typical DB-based+-- application different users can see different data items). Typically+-- it would be too expensive to build different search indexes for the+-- different contexts and so the strategy is to use one index containing+-- everything and filter for visibility in the results. This means the+-- filter condition is different for different queries (e.g. performed+-- on behalf of different users).+--+-- Filtering the results after a query is possible but not the most efficient+-- thing to do because we've had to score all the not-visible documents.+-- The better thing to do is to filter as part of the query, this way we can+-- filter before the expensive scoring.+--+-- We provide one further optimisation: bulk predicates. In some applications+-- it can be quicker to check the security\/visibility of a whole bunch of+-- results all in one go.+--+data ResultsFilter key = NoFilter+ | FilterPredicate (key -> Bool)+ | FilterBulkPredicate ([key] -> [Bool])+--TODO: allow filtering & non-feature score lookup in one bulk op+
Data/SearchEngine/SearchIndex.hs view
@@ -12,12 +12,14 @@ docCount, lookupTerm,+ lookupTermsByPrefix, lookupTermId, lookupDocId, lookupDocKey,- + getTerm,- + getDocKey,+ invariant, ) where @@ -36,6 +38,7 @@ import qualified Data.IntMap as IntMap import qualified Data.Set as Set import Data.Text (Text)+import qualified Data.Text as T import Data.List (foldl') import Control.Exception (assert)@@ -59,21 +62,24 @@ -- So the mappings we maintain can be depicted as: -- -- > Term <-- 1:1 --> TermId--- > ^--- > |--- > many:many--- > |--- > v+-- > \ ^+-- > \ |+-- > 1:many many:many+-- > \ |+-- > \-> v -- > DocKey <-- 1:1 --> DocId -- -- For efficiency, these details are exposed in the interface. In particular -- the mapping from TermId to many DocIds is exposed via a 'DocIdSet', -- and the mapping from DocIds to TermIds is exposed via 'DocTermIds'. --+-- The main reason we need to keep the DocId -> TermId is to allow for+-- efficient incremental updates.+-- data SearchIndex key field feature = SearchIndex { -- the indexes termMap :: !(Map Term TermInfo),- termIdMap :: !(IntMap Term),+ termIdMap :: !(IntMap TermIdInfo), docIdMap :: !(IntMap (DocInfo key field feature)), docKeyMap :: !(Map key DocId), @@ -86,6 +92,9 @@ data TermInfo = TermInfo !TermId !DocIdSet deriving Show +data TermIdInfo = TermIdInfo !Term !DocIdSet+ deriving (Show, Eq)+ data DocInfo key field feature = DocInfo !key !(DocTermIds field) !(DocFeatVals feature) deriving Show@@ -112,12 +121,14 @@ invariant :: (Ord key, Ix field, Bounded field) => SearchIndex key field feature -> Bool invariant SearchIndex{termMap, termIdMap, docKeyMap, docIdMap} =- and [ IntMap.lookup (fromEnum termId) termIdMap == Just term- | (term, (TermInfo termId _)) <- Map.assocs termMap ]+ and [ IntMap.lookup (fromEnum termId) termIdMap+ == Just (TermIdInfo term docidset)+ | (term, (TermInfo termId docidset)) <- Map.assocs termMap ] && and [ case Map.lookup term termMap of- Just (TermInfo termId' _) -> toEnum termId == termId'- Nothing -> False- | (termId, term) <- IntMap.assocs termIdMap ]+ Just (TermInfo termId' docidset') -> toEnum termId == termId'+ && docidset == docidset'+ Nothing -> False+ | (termId, (TermIdInfo term docidset)) <- IntMap.assocs termIdMap ] && and [ case IntMap.lookup (fromEnum docId) docIdMap of Just (DocInfo docKey' _ _) -> docKey == docKey' Nothing -> False@@ -151,14 +162,17 @@ Nothing -> Nothing Just (TermInfo termid docidset) -> Just (termid, docidset) +lookupTermsByPrefix :: SearchIndex key field feature ->+ Term -> [(TermId, DocIdSet)]+lookupTermsByPrefix SearchIndex{termMap} term =+ [ (termid, docidset)+ | (TermInfo termid docidset) <- lookupPrefix term termMap ]+ lookupTermId :: SearchIndex key field feature -> TermId -> DocIdSet-lookupTermId SearchIndex{termIdMap, termMap} termid =+lookupTermId SearchIndex{termIdMap} termid = case IntMap.lookup (fromEnum termid) termIdMap of- Nothing -> error $ "lookupTermId: not found " ++ show termid- Just term ->- case Map.lookup term termMap of- Nothing -> error "lookupTermId: internal error"- Just (TermInfo _ docidset) -> docidset+ Nothing -> error $ "lookupTermId: not found " ++ show termid+ Just (TermIdInfo _ docidset) -> docidset lookupDocId :: SearchIndex key field feature -> DocId -> (key, DocTermIds field, DocFeatVals feature)@@ -169,7 +183,8 @@ where errNotFound = error $ "lookupDocId: not found " ++ show docid -lookupDocKey :: Ord key => SearchIndex key field feature -> key -> Maybe (DocTermIds field)+lookupDocKey :: Ord key => SearchIndex key field feature ->+ key -> Maybe (DocTermIds field) lookupDocKey SearchIndex{docKeyMap, docIdMap} key = do case Map.lookup key docKeyMap of Nothing -> Nothing@@ -181,12 +196,17 @@ getTerm :: SearchIndex key field feature -> TermId -> Term getTerm SearchIndex{termIdMap} termId =- termIdMap IntMap.! fromEnum termId+ case termIdMap IntMap.! fromEnum termId of TermIdInfo term _ -> term getTermId :: SearchIndex key field feature -> Term -> TermId getTermId SearchIndex{termMap} term = case termMap Map.! term of TermInfo termid _ -> termid +getDocKey :: SearchIndex key field feature -> DocId -> key+getDocKey SearchIndex{docIdMap} docid =+ case docIdMap IntMap.! fromEnum docid of+ DocInfo dockey _ _ -> dockey+ getDocTermIds :: SearchIndex key field feature -> DocId -> DocTermIds field getDocTermIds SearchIndex{docIdMap} docid = case docIdMap IntMap.! fromEnum docid of@@ -315,14 +335,22 @@ insertTermToDocIdEntry term !docid si@SearchIndex{termMap, termIdMap, nextTermId} = case Map.lookup term termMap of Nothing ->- let !termInfo' = TermInfo nextTermId (DocIdSet.singleton docid)+ let docIdSet' = DocIdSet.singleton docid+ !termInfo' = TermInfo nextTermId docIdSet'+ !termIdInfo' = TermIdInfo term docIdSet' in si { termMap = Map.insert term termInfo' termMap- , termIdMap = IntMap.insert (fromEnum nextTermId) term termIdMap+ , termIdMap = IntMap.insert (fromEnum nextTermId)+ termIdInfo' termIdMap , nextTermId = succ nextTermId } Just (TermInfo termId docIdSet) ->- let !termInfo' = TermInfo termId (DocIdSet.insert docid docIdSet)- in si { termMap = Map.insert term termInfo' termMap }+ let docIdSet' = DocIdSet.insert docid docIdSet+ !termInfo' = TermInfo termId docIdSet'+ !termIdInfo' = TermIdInfo term docIdSet'+ in si { termMap = Map.insert term termInfo' termMap+ , termIdMap = IntMap.insert (fromEnum termId)+ termIdInfo' termIdMap+ } -- | Add multiple entries into the 'Term' to 'DocId' mapping: many terms that -- map to the same document.@@ -340,12 +368,16 @@ case Map.lookup term termMap of Nothing -> si Just (TermInfo termId docIdSet) ->- let docIdSet' = DocIdSet.delete docid docIdSet- termInfo' = TermInfo termId docIdSet'+ let docIdSet' = DocIdSet.delete docid docIdSet+ !termInfo' = TermInfo termId docIdSet'+ !termIdInfo' = TermIdInfo term docIdSet' in if DocIdSet.null docIdSet' then si { termMap = Map.delete term termMap , termIdMap = IntMap.delete (fromEnum termId) termIdMap }- else si { termMap = Map.insert term termInfo' termMap }+ else si { termMap = Map.insert term termInfo' termMap+ , termIdMap = IntMap.insert (fromEnum termId)+ termIdInfo' termIdMap+ } -- | Delete multiple entries from the 'Term' to 'DocId' mapping: many terms -- that map to the same document.@@ -390,4 +422,31 @@ deleteDocEntry docid key si@SearchIndex{docIdMap, docKeyMap} = si { docIdMap = IntMap.delete (fromEnum docid) docIdMap , docKeyMap = Map.delete key docKeyMap }++--+-- Data.Map utils+--++-- Data.Map does not support prefix lookups directly (unlike a trie)+-- but we can implement it reasonably efficiently using split:++-- | Lookup values for a range of keys (inclusive lower bound and exclusive+-- upper bound)+--+lookupRange :: Ord k => (k, k) -> Map k v -> [v]+lookupRange (lb, ub) m =+ let (_, mv, gt) = Map.splitLookup lb m+ (between, _) = Map.split ub gt+ in case mv of+ Just v -> v : Map.elems between+ Nothing -> Map.elems between++lookupPrefix :: Text -> Map Text v -> [v]+lookupPrefix t _ | T.null t = []+lookupPrefix t m = lookupRange (t, prefixUpperBound t) m++prefixUpperBound :: Text -> Text+prefixUpperBound = succLast . T.dropWhileEnd (== maxBound)+ where+ succLast t = T.init t `T.snoc` succ (T.last t)
Data/SearchEngine/TermBag.hs view
@@ -1,20 +1,32 @@ {-# LANGUAGE BangPatterns, GeneralizedNewtypeDeriving #-} module Data.SearchEngine.TermBag (- TermId,+ TermId(..), TermCount, TermBag, size, fromList,+ toList, elems, termCount,+ denseTable,+ invariant ) where -import qualified Data.Vector.Unboxed as Vec+import qualified Data.Vector.Unboxed as Vec+import qualified Data.Vector.Unboxed.Mutable as MVec+import qualified Data.Vector.Generic.Base as VecGen+import qualified Data.Vector.Unboxed.Base as VecBase+import qualified Data.Vector.Generic.Mutable as VecMut+import Control.Monad.ST import qualified Data.Map as Map-import Data.Word (Word32)+import Data.Word (Word32, Word8) import Data.Bits+import Data.List (sortBy, foldl')+import Data.Function (on) newtype TermId = TermId Word32- deriving (Eq, Ord, Show, Enum)+ deriving (Eq, Ord, Show, Enum,+ Vec.Unbox, VecGen.Vector VecBase.Vector,+ VecMut.MVector VecBase.MVector) instance Bounded TermId where minBound = TermId 0@@ -25,6 +37,7 @@ -- We sneakily stuff both the TermId and the bag count into one 32bit word type TermIdAndCount = Word32+type TermCount = Word8 -- Bottom 24 bits is the TermId, top 8 bits is the bag count termIdAndCount :: TermId -> Int -> TermIdAndCount@@ -35,9 +48,16 @@ getTermId :: TermIdAndCount -> TermId getTermId word = TermId (word .&. 0x00FFFFFF) -getTermCount :: TermIdAndCount -> Int+getTermCount :: TermIdAndCount -> TermCount getTermCount word = fromIntegral (word `shiftR` 24) +invariant :: TermBag -> Bool+invariant (TermBag _ vec) =+ strictlyAscending (Vec.toList vec)+ where+ strictlyAscending (a:xs@(b:_)) = getTermId a < getTermId b+ && strictlyAscending xs+ strictlyAscending _ = True size :: TermBag -> Int size (TermBag sz _) = sz@@ -45,11 +65,15 @@ elems :: TermBag -> [TermId] elems (TermBag _ vec) = map getTermId (Vec.toList vec) -termCount :: TermBag -> TermId -> Int+toList :: TermBag -> [(TermId, TermCount)]+toList (TermBag _ vec) = [ (getTermId x, getTermCount x)+ | x <- Vec.toList vec ]++termCount :: TermBag -> TermId -> TermCount termCount (TermBag _ vec) = binarySearch 0 (Vec.length vec - 1) where- binarySearch :: Int -> Int -> TermId -> Int+ binarySearch :: Int -> Int -> TermId -> TermCount binarySearch !a !b !key | a > b = 0 | otherwise =@@ -69,3 +93,117 @@ | (termid, freq) <- Map.toAscList bag ] in TermBag sz vec +-- | Given a bunch of term bags, merge them into a table for easier subsequent+-- processing. This is bascially a sparse to dense conversion. Missing entries+-- are filled in with 0. We represent the table as one vector for the+-- term ids and a 2d array for the counts.+--+-- Unfortunately vector does not directly support 2d arrays and array does+-- not make it easy to trim arrays.+--+denseTable :: [TermBag] -> (Vec.Vector TermId, Vec.Vector TermCount)+denseTable termbags = + (tids, tcts)+ where+ -- First merge the TermIds into one array+ -- then make a linear pass to create the counts array+ -- filling in 0s or the counts as we find them+ !numBags = length termbags+ !tids = unionsTermId termbags+ !numTerms = Vec.length tids+ !numCounts = numTerms * numBags+ !tcts = Vec.create (do+ out <- MVec.new numCounts+ sequence_+ [ writeMergedTermCounts tids bag out i+ | (n, TermBag _ bag) <- zip [0..] termbags+ , let i = n * numTerms ]+ return out+ )++writeMergedTermCounts :: Vec.Vector TermId -> Vec.Vector TermIdAndCount ->+ MVec.MVector s TermCount -> Int -> ST s ()+writeMergedTermCounts xs0 ys0 !out i0 =+ -- assume xs & ys are sorted, and ys contains a subset of xs+ go xs0 ys0 i0+ where+ go !xs !ys !i+ | Vec.null ys = MVec.set (MVec.slice i (Vec.length xs) out) 0+ | Vec.null xs = return ()+ | otherwise = let x = Vec.head xs+ ytc = Vec.head ys+ y = getTermId ytc+ c = getTermCount ytc+ in case x == y of+ True -> do MVec.write out i c+ go (Vec.tail xs) (Vec.tail ys) (i+1)+ False -> do MVec.write out i 0+ go (Vec.tail xs) ys (i+1)++-- | Given a set of term bags, form the set of TermIds+--+unionsTermId :: [TermBag] -> Vec.Vector TermId+unionsTermId tbs =+ case sortBy (compare `on` bagVecLength) tbs of+ [] -> Vec.empty+ [TermBag _ xs] -> (Vec.map getTermId xs)+ (x0:x1:xs) -> foldl' union3 (union2 x0 x1) xs+ where+ bagVecLength (TermBag _ vec) = Vec.length vec++union2 :: TermBag -> TermBag -> Vec.Vector TermId+union2 (TermBag _ xs) (TermBag _ ys) =+ Vec.create (MVec.new sizeBound >>= writeMergedUnion2 xs ys)+ where+ sizeBound = Vec.length xs + Vec.length ys++writeMergedUnion2 :: Vec.Vector TermIdAndCount -> Vec.Vector TermIdAndCount ->+ MVec.MVector s TermId -> ST s (MVec.MVector s TermId)+writeMergedUnion2 xs0 ys0 !out = do+ i <- go xs0 ys0 0+ return $! MVec.take i out+ where+ go !xs !ys !i+ | Vec.null xs = do Vec.copy (MVec.slice i (Vec.length ys) out)+ (Vec.map getTermId ys)+ return (i + Vec.length ys)+ | Vec.null ys = do Vec.copy (MVec.slice i (Vec.length xs) out)+ (Vec.map getTermId xs)+ return (i + Vec.length xs)+ | otherwise = let x = getTermId (Vec.head xs)+ y = getTermId (Vec.head ys)+ in case compare x y of+ GT -> do MVec.write out i y+ go xs (Vec.tail ys) (i+1)+ EQ -> do MVec.write out i x+ go (Vec.tail xs) (Vec.tail ys) (i+1)+ LT -> do MVec.write out i x+ go (Vec.tail xs) ys (i+1)++union3 :: Vec.Vector TermId -> TermBag -> Vec.Vector TermId+union3 xs (TermBag _ ys) =+ Vec.create (MVec.new sizeBound >>= writeMergedUnion3 xs ys)+ where+ sizeBound = Vec.length xs + Vec.length ys++writeMergedUnion3 :: Vec.Vector TermId -> Vec.Vector TermIdAndCount ->+ MVec.MVector s TermId -> ST s (MVec.MVector s TermId)+writeMergedUnion3 xs0 ys0 !out = do+ i <- go xs0 ys0 0+ return $! MVec.take i out+ where+ go !xs !ys !i+ | Vec.null xs = do Vec.copy (MVec.slice i (Vec.length ys) out)+ (Vec.map getTermId ys)+ return (i + Vec.length ys)+ | Vec.null ys = do Vec.copy (MVec.slice i (Vec.length xs) out) xs+ return (i + Vec.length xs)+ | otherwise = let x = Vec.head xs+ y = getTermId (Vec.head ys)+ in case compare x y of+ GT -> do MVec.write out i y+ go xs (Vec.tail ys) (i+1)+ EQ -> do MVec.write out i x+ go (Vec.tail xs) (Vec.tail ys) (i+1)+ LT -> do MVec.write out i x+ go (Vec.tail xs) ys (i+1)
+ Data/SearchEngine/Types.hs view
@@ -0,0 +1,124 @@+{-# LANGUAGE NamedFieldPuns, RecordWildCards #-}++module Data.SearchEngine.Types (+ -- * Search engine types and helper functions+ SearchEngine(..),+ SearchConfig(..),+ SearchRankParameters(..),+ BM25F.FeatureFunction(..),+ initSearchEngine,+ cacheBM25Context,++ -- ** Helper type for non-term features+ NoFeatures,+ noFeatures,++ -- * Re-export SearchIndex and other types+ SearchIndex, Term, TermId,+ DocIdSet, DocId,+ DocTermIds, DocFeatVals,++ -- * Internal sanity check+ invariant,+ ) where++import Data.SearchEngine.SearchIndex (SearchIndex, Term, TermId)+import qualified Data.SearchEngine.SearchIndex as SI+import Data.SearchEngine.DocIdSet (DocIdSet, DocId)+import qualified Data.SearchEngine.DocIdSet as DocIdSet+import Data.SearchEngine.DocFeatVals (DocFeatVals)+import Data.SearchEngine.DocTermIds (DocTermIds)+import qualified Data.SearchEngine.BM25F as BM25F++import Data.Ix+import Data.Array.Unboxed++++data SearchConfig doc key field feature = SearchConfig {+ documentKey :: doc -> key,+ extractDocumentTerms :: doc -> field -> [Term],+ transformQueryTerm :: Term -> field -> Term,+ documentFeatureValue :: doc -> feature -> Float+ }++data SearchRankParameters field feature = SearchRankParameters {+ paramK1 :: !Float,+ paramB :: field -> Float,+ paramFieldWeights :: field -> Float,+ paramFeatureWeights :: feature -> Float,+ paramFeatureFunctions :: feature -> BM25F.FeatureFunction,++ paramResultsetSoftLimit :: !Int,+ paramResultsetHardLimit :: !Int,+ paramAutosuggestPrefilterLimit :: !Int,+ paramAutosuggestPostfilterLimit :: !Int+ }++data SearchEngine doc key field feature = SearchEngine {+ searchIndex :: !(SearchIndex key field feature),+ searchConfig :: !(SearchConfig doc key field feature),+ searchRankParams :: !(SearchRankParameters field feature),++ -- cached info+ sumFieldLengths :: !(UArray field Int),+ bm25Context :: BM25F.Context TermId field feature+ }++invariant :: (Ord key, Ix field, Bounded field) =>+ SearchEngine doc key field feature -> Bool+invariant SearchEngine{searchIndex} =+ SI.invariant searchIndex+-- && check caches++initSearchEngine :: (Ix field, Bounded field, Ix feature, Bounded feature) =>+ SearchConfig doc key field feature ->+ SearchRankParameters field feature ->+ SearchEngine doc key field feature+initSearchEngine config params =+ cacheBM25Context+ SearchEngine {+ searchIndex = SI.emptySearchIndex,+ searchConfig = config,+ searchRankParams = params,+ sumFieldLengths = listArray (minBound, maxBound) (repeat 0),+ bm25Context = undefined+ }++cacheBM25Context :: Ix field =>+ SearchEngine doc key field feature ->+ SearchEngine doc key field feature+cacheBM25Context+ se@SearchEngine {+ searchRankParams = SearchRankParameters{..},+ searchIndex,+ sumFieldLengths+ }+ = se { bm25Context = bm25Context' }+ where+ bm25Context' = BM25F.Context {+ BM25F.numDocsTotal = SI.docCount searchIndex,+ BM25F.avgFieldLength = \f -> fromIntegral (sumFieldLengths ! f)+ / fromIntegral (SI.docCount searchIndex),+ BM25F.numDocsWithTerm = DocIdSet.size . SI.lookupTermId searchIndex,+ BM25F.paramK1 = paramK1,+ BM25F.paramB = paramB,+ BM25F.fieldWeight = paramFieldWeights,+ BM25F.featureWeight = paramFeatureWeights,+ BM25F.featureFunction = paramFeatureFunctions+ }+++-----------------------------++data NoFeatures = NoFeatures+ deriving (Eq, Ord, Bounded)++instance Ix NoFeatures where+ range _ = []+ inRange _ _ = False+ index _ _ = -1++noFeatures :: NoFeatures -> a+noFeatures _ = error "noFeatures"+
+ Data/SearchEngine/Update.hs view
@@ -0,0 +1,90 @@+{-# LANGUAGE BangPatterns, NamedFieldPuns, RecordWildCards #-}++module Data.SearchEngine.Update (++ -- * Managing documents to be searched+ insertDoc,+ insertDocs,+ deleteDoc,++ ) where++import Data.SearchEngine.Types+import qualified Data.SearchEngine.SearchIndex as SI+import qualified Data.SearchEngine.DocTermIds as DocTermIds++import Data.Ix+import Data.Array.Unboxed+import Data.List+++insertDocs :: (Ord key, Ix field, Bounded field, Ix feature, Bounded feature) =>+ [doc] ->+ SearchEngine doc key field feature ->+ SearchEngine doc key field feature+insertDocs docs se = foldl' (\se' doc -> insertDoc doc se') se docs+++insertDoc :: (Ord key, Ix field, Bounded field, Ix feature, Bounded feature) =>+ doc ->+ SearchEngine doc key field feature ->+ SearchEngine doc key field feature+insertDoc doc se@SearchEngine{ searchConfig = SearchConfig {+ documentKey,+ extractDocumentTerms,+ documentFeatureValue+ }+ , searchIndex } =+ let key = documentKey doc+ searchIndex' = SI.insertDoc key (extractDocumentTerms doc)+ (documentFeatureValue doc)+ searchIndex+ oldDoc = SI.lookupDocKey searchIndex key+ newDoc = SI.lookupDocKey searchIndex' key++ in cacheBM25Context $+ updateCachedFieldLengths oldDoc newDoc $+ se { searchIndex = searchIndex' }+++deleteDoc :: (Ord key, Ix field, Bounded field) =>+ key ->+ SearchEngine doc key field feature ->+ SearchEngine doc key field feature+deleteDoc key se@SearchEngine{searchIndex} =+ let searchIndex' = SI.deleteDoc key searchIndex+ oldDoc = SI.lookupDocKey searchIndex key++ in cacheBM25Context $+ updateCachedFieldLengths oldDoc Nothing $+ se { searchIndex = searchIndex' }+++updateCachedFieldLengths :: (Ix field, Bounded field) =>+ Maybe (DocTermIds field) -> Maybe (DocTermIds field) ->+ SearchEngine doc key field feature ->+ SearchEngine doc key field feature+updateCachedFieldLengths Nothing (Just newDoc) se@SearchEngine{sumFieldLengths} =+ se {+ sumFieldLengths =+ array (bounds sumFieldLengths)+ [ (i, n + DocTermIds.fieldLength newDoc i)+ | (i, n) <- assocs sumFieldLengths ]+ }+updateCachedFieldLengths (Just oldDoc) (Just newDoc) se@SearchEngine{sumFieldLengths} =+ se {+ sumFieldLengths =+ array (bounds sumFieldLengths)+ [ (i, n - DocTermIds.fieldLength oldDoc i+ + DocTermIds.fieldLength newDoc i)+ | (i, n) <- assocs sumFieldLengths ]+ }+updateCachedFieldLengths (Just oldDoc) Nothing se@SearchEngine{sumFieldLengths} =+ se {+ sumFieldLengths =+ array (bounds sumFieldLengths)+ [ (i, n - DocTermIds.fieldLength oldDoc i)+ | (i, n) <- assocs sumFieldLengths ]+ }+updateCachedFieldLengths Nothing Nothing se = se+
LICENSE view
@@ -1,4 +1,5 @@ Copyright (c) 2013-2014 Duncan Coutts, 2014 Well-Typed LLP+ 2014 IRIS Connect Ltd. All rights reserved.
+ changelog view
@@ -0,0 +1,19 @@+0.2.1.0 Duncan Coutts <duncan@well-typed.com> March 2014+ * Add auto-complete / auto-suggest feature+ * Add demo program+ * Moved QC props into a separate test suite+ * Work sponsored by IRIS Connect Ltd.++0.2.0.0 Duncan Coutts <duncan@well-typed.com> Feb 2014+ * Initial version as a separate library++0.1.1.0 Duncan Coutts <duncan@community.haskell.org> Nov 2013+ * Add "explain" mode for query+ * Add non-term feature scores++0.1.0.1 Duncan Coutts <duncan@community.haskell.org> Sept 2013+ * Fix bug in index update (thanks to Matthew Gruen)++0.1.0.0 Duncan Coutts <duncan@community.haskell.org> July 2013+ * Initial version (included in hackage-server)+
+ demo/ExtractDescriptionTerms.hs view
@@ -0,0 +1,113 @@++module ExtractDescriptionTerms (+ extractSynopsisTerms,+ extractDescriptionTerms+ ) where++import Data.Text (Text)+import qualified Data.Text as T+import Data.Set (Set)+import qualified Data.Set as Set+import Data.Char+import qualified NLP.Tokenize as NLP+import qualified NLP.Snowball as NLP+import Control.Monad ((>=>))+import Data.Maybe++import HaddockTypes as Haddock+import HaddockHtml as Haddock (markup)+import qualified HaddockParse as Haddock (parseHaddockParagraphs)+import qualified HaddockLex as Haddock (tokenise)+++extractSynopsisTerms :: Set Text -> String -> [Text]+extractSynopsisTerms stopWords =+ NLP.stems NLP.English+ . filter (`Set.notMember` stopWords)+ . map (T.toCaseFold . T.pack)+ . concatMap splitTok+ . filter (not . ignoreTok)+ . NLP.tokenize+++ignoreTok :: String -> Bool +ignoreTok = all isPunctuation++splitTok :: String -> [String]+splitTok tok =+ case go tok of+ toks@(_:_:_) -> tok:toks+ toks -> toks+ where+ go remaining =+ case break (\c -> c == ')' || c == '-' || c == '/') remaining of+ ([], _:trailing) -> go trailing+ (leading, _:trailing) -> leading : go trailing+ ([], []) -> []+ (leading, []) -> leading : []+++extractDescriptionTerms :: Set Text -> String -> [Text]+extractDescriptionTerms stopWords =+ NLP.stems NLP.English+ . filter (`Set.notMember` stopWords)+ . map (T.toCaseFold . T.pack)+ . maybe+ [] --TODO: something here+ ( filter (not . ignoreTok)+ . NLP.tokenize+ . concat . Haddock.markup termsMarkup)+ . (Haddock.tokenise >=> Haddock.parseHaddockParagraphs)++termsMarkup :: DocMarkup String [String]+termsMarkup = Markup {+ markupEmpty = [],+ markupString = \s -> [s],+ markupParagraph = id,+ markupAppend = (++),+ markupIdentifier = \s -> [s],+ markupModule = const [], -- i.e. filter these out+ markupEmphasis = id,+ markupMonospaced = \s -> if length s > 1 then [] else s,+ markupUnorderedList = concat,+ markupOrderedList = concat,+ markupDefList = concatMap (\(d,t) -> d ++ t),+ markupCodeBlock = const [],+ markupHyperlink = \(Hyperlink _url mLabel) -> maybeToList mLabel,+ --TODO: extract main part of hostname+ markupPic = const [],+ markupAName = const []+ }++{-+-------------------+-- Main experiment+--++main = do+ pkgsFile <- readFile "pkgs"+ let mostFreq :: [String]+ pkgs :: [PackageDescription]+ (mostFreq, pkgs) = read pkgsFile+ + stopWordsFile <- T.readFile "stopwords.txt"+-- wordsFile <- T.readFile "/usr/share/dict/words"+-- let ws = Set.fromList (map T.toLower $ T.lines wordsFile)+++ print "reading file"+ evaluate (length mostFreq + length pkgs)+ print "done"++ let stopWords = Set.fromList $ T.lines stopWordsFile+ print stopWords++ sequence_+ [ putStrLn $ display (packageName pkg) ++ ": "+ ++ --intercalate ", "+ (description pkg) ++ "\n" + ++ intercalate ", "+ (map T.unpack $ extractDescriptionTerms stopWords (description pkg)) ++ "\n"+ | pkg <- pkgs+ , let pkgname = display (packageName pkg) ]+-}
+ demo/ExtractNameTerms.hs view
@@ -0,0 +1,181 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module ExtractNameTerms (+ extractPackageNameTerms,+ extractModuleNameTerms,+ ) where++import Data.Text (Text)+import qualified Data.Text as T+import Data.Char (isUpper, isDigit)+import Data.List+import Data.List.Split hiding (Splitter)+import Data.Maybe (maybeToList)++import Data.Functor.Identity+import Control.Monad+import Control.Monad.List+import Control.Monad.Writer+import Control.Monad.State+++extractModuleNameTerms :: String -> [Text]+extractModuleNameTerms modname =+ map T.toCaseFold $+ nub $+ map T.pack $+ flip runSplitter modname $ do+ _ <- forEachPart splitDot+ _ <- forEachPart splitCamlCase+ satisfy (not . singleChar)+ get >>= emit++extractPackageNameTerms :: String -> [Text]+extractPackageNameTerms pkgname =+ map T.toCaseFold $+ nub $+ map T.pack $+ flip runSplitter pkgname $ do++ fstComponentHyphen <- forEachPart splitHyphen++ satisfy (`notElem` ["hs", "haskell"])++ _ <- forEachPart stripPrefixH++ fstComponentCaml <- forEachPart splitCamlCase++ fstComponent2 <- forEachPart splitOn2++ when (fstComponentHyphen && fstComponentCaml && fstComponent2) $ do+ forEachPartAndWhole stripPrefix_h+ _ <- forEachPart (maybeToList . stripPrefix "lib")+ _ <- forEachPart (maybeToList . stripSuffix "lib")+ _ <- forEachPart stripSuffixNum+ satisfy (not . singleChar)++ get >>= emit++newtype Split a = Split (StateT String (ListT (WriterT [String] Identity)) a)+ deriving (Monad, MonadPlus, MonadState String)++emit :: String -> Split ()+emit x = Split (lift (lift (tell [x])))++forEach :: [a] -> Split a+forEach = msum . map return++runSplitter :: Split () -> String -> [String]+runSplitter (Split m) s = snd (runWriter (runListT (runStateT m s)))++singleChar :: String -> Bool+singleChar [_] = True+singleChar _ = False++satisfy :: (String -> Bool) -> Split ()+satisfy p = get >>= guard . p++forEachPart :: (String -> [String]) -> Split Bool+forEachPart parts = do+ t <- get+ case parts t of+ [] -> return True+ [t'] | t == t' -> return True+ ts -> do emit t+ (t', n) <- forEach (zip ts [1::Int ..])+ put t'+ return (n==1)++forEachPartAndWhole :: (String -> [String]) -> Split ()+forEachPartAndWhole parts = do+ t <- get+ case parts t of+ [] -> return ()+ ts -> forEach (t:ts) >>= put+ ++splitDot :: String -> [String]+splitDot = split (dropBlanks $ dropDelims $ whenElt (=='.'))++splitHyphen :: String -> [String]+splitHyphen = split (dropBlanks $ dropDelims $ whenElt (=='-'))++splitCamlCase :: String -> [String]+splitCamlCase = split (dropInitBlank $ condense $ keepDelimsL $ whenElt isUpper)++stripPrefixH :: String -> [String]+stripPrefixH ('H':'S':frag) | all isUpper frag = [frag]+stripPrefixH "HTTP" = []+stripPrefixH ('H':frag@(c:_)) | isUpper c = [frag]+stripPrefixH _ = []++stripPrefix_h :: String -> [String]+stripPrefix_h "http" = []+stripPrefix_h "html" = []+stripPrefix_h ('h':'s':frag) = ['s':frag, frag]+stripPrefix_h ('h':frag) {- | Set.notMember (T.pack w) ws -} = [frag]+stripPrefix_h _ = []++stripSuffix :: String -> String -> Maybe String+stripSuffix s t = fmap reverse (stripPrefix (reverse s) (reverse t))++stripSuffixNum :: String -> [String]+stripSuffixNum s+ | null rd || null rs' = []+ | otherwise = [s', d]+ where+ rs = reverse s+ (rd, rs') = span isDigit rs+ d = reverse rd+ s' = reverse rs'++splitOn2 :: String -> [String]+splitOn2 t =+ case break (=='2') t of+ (from@(_:_), '2':to@(c:_))+ | not (isDigit c)+ , not (length from == 1 && length to == 1)+ -> [from, to]+ _ -> []++{-+-------------------+-- Main experiment+--++main = do+ pkgsFile <- readFile "pkgs"+ let mostFreq :: [String]+ pkgs :: [PackageDescription]+ (mostFreq, pkgs) = read pkgsFile+ +-- wordsFile <- T.readFile "/usr/share/dict/words"+-- let ws = Set.fromList (map T.toLower $ T.lines wordsFile)+++ print "reading file"+ evaluate (length mostFreq + length pkgs)+ print "done"++ sequence_+ [ putStrLn $ display (packageName pkg) ++ " -> "+ ++ intercalate ", " (map T.unpack $ extractNameTerms pkgname)+ | pkg <- pkgs+ , let pkgname = display (packageName pkg) ]++main = do+ pkgsFile <- readFile "pkgs3"+ let pkgs :: [PackageDescription]+ pkgs = map read (lines pkgsFile)++-- print "forcing pkgs..."+-- evaluate (foldl' (\a p -> seq p a) () pkgs)++ sequence_+ [ putStrLn $ display (packageName pkg) ++ ": " ++ display mod ++ " -> "+ ++ intercalate ", " (map T.unpack $ extractModuleNameTerms (display mod))+ | pkg <- pkgs+ , Just lib <- [library pkg]+ , let mods = exposedModules lib+ , mod <- mods ]+-}
+ demo/HaddockHtml.hs view
@@ -0,0 +1,84 @@+-- stolen from Haddock's Util.hs and Doc.hs+module HaddockHtml (markup, docAppend, docParagraph) where++import HaddockTypes+import Data.Char (isSpace)++markup :: DocMarkup id a -> Doc id -> a+markup m DocEmpty = markupEmpty m+markup m (DocAppend d1 d2) = markupAppend m (markup m d1) (markup m d2)+markup m (DocString s) = markupString m s+markup m (DocParagraph d) = markupParagraph m (markup m d)+markup m (DocIdentifier x) = markupIdentifier m x+markup m (DocModule mod0) = markupModule m mod0+markup m (DocEmphasis d) = markupEmphasis m (markup m d)+markup m (DocMonospaced d) = markupMonospaced m (markup m d)+markup m (DocUnorderedList ds) = markupUnorderedList m (map (markup m) ds)+markup m (DocOrderedList ds) = markupOrderedList m (map (markup m) ds)+markup m (DocDefList ds) = markupDefList m (map (markupPair m) ds)+markup m (DocCodeBlock d) = markupCodeBlock m (markup m d)+markup m (DocHyperlink l) = markupHyperlink m l+markup m (DocAName ref) = markupAName m ref+markup m (DocPic img) = markupPic m img++markupPair :: DocMarkup id a -> (Doc id, Doc id) -> (a, a)+markupPair m (a,b) = (markup m a, markup m b)++-- -----------------------------------------------------------------------------+-- ** Smart constructors++-- used to make parsing easier; we group the list items later+docAppend :: Doc id -> Doc id -> Doc id+docAppend (DocUnorderedList ds1) (DocUnorderedList ds2)+ = DocUnorderedList (ds1++ds2)+docAppend (DocUnorderedList ds1) (DocAppend (DocUnorderedList ds2) d)+ = DocAppend (DocUnorderedList (ds1++ds2)) d+docAppend (DocOrderedList ds1) (DocOrderedList ds2)+ = DocOrderedList (ds1++ds2)+docAppend (DocOrderedList ds1) (DocAppend (DocOrderedList ds2) d)+ = DocAppend (DocOrderedList (ds1++ds2)) d+docAppend (DocDefList ds1) (DocDefList ds2)+ = DocDefList (ds1++ds2)+docAppend (DocDefList ds1) (DocAppend (DocDefList ds2) d)+ = DocAppend (DocDefList (ds1++ds2)) d+docAppend DocEmpty d = d+docAppend d DocEmpty = d+docAppend d1 d2+ = DocAppend d1 d2+++-- again to make parsing easier - we spot a paragraph whose only item+-- is a DocMonospaced and make it into a DocCodeBlock+docParagraph :: Doc id -> Doc id+docParagraph (DocMonospaced p)+ = DocCodeBlock (docCodeBlock p)+docParagraph (DocAppend (DocString s1) (DocMonospaced p))+ | all isSpace s1+ = DocCodeBlock (docCodeBlock p)+docParagraph (DocAppend (DocString s1)+ (DocAppend (DocMonospaced p) (DocString s2)))+ | all isSpace s1 && all isSpace s2+ = DocCodeBlock (docCodeBlock p)+docParagraph (DocAppend (DocMonospaced p) (DocString s2))+ | all isSpace s2+ = DocCodeBlock (docCodeBlock p)+docParagraph p+ = DocParagraph p+++-- Drop trailing whitespace from @..@ code blocks. Otherwise this:+--+-- -- @+-- -- foo+-- -- @+--+-- turns into (DocCodeBlock "\nfoo\n ") which when rendered in HTML+-- gives an extra vertical space after the code block. The single space+-- on the final line seems to trigger the extra vertical space.+--+docCodeBlock :: Doc id -> Doc id+docCodeBlock (DocString s)+ = DocString (reverse $ dropWhile (`elem` " \t") $ reverse s)+docCodeBlock (DocAppend l r)+ = DocAppend l (docCodeBlock r)+docCodeBlock d = d
+ demo/HaddockLex.x view
@@ -0,0 +1,189 @@+--+-- Haddock - A Haskell Documentation Tool+--+-- (c) Simon Marlow 2002+--++{+-- Disable warnings that the generated code causes+{-# OPTIONS_GHC -fno-warn-deprecated-flags+ -fno-warn-unused-binds+ -fno-warn-unused-imports+ -fno-warn-unused-matches+ -fno-warn-missing-signatures+ -fno-warn-tabs #-}+module HaddockLex (+ Token(..),+ tokenise+ ) where++import Data.Char+import Data.Word (Word8)+import qualified Data.Bits+import Numeric+import Control.Monad (liftM)+import HaddockTypes (RdrName)+}++$ws = $white # \n+$digit = [0-9]+$hexdigit = [0-9a-fA-F]+$special = [\"\@]+$alphanum = [A-Za-z0-9]+$ident = [$alphanum \'\_\.\!\#\$\%\&\*\+\/\<\=\>\?\@\\\\\^\|\-\~\:]++:-++-- beginning of a paragraph+<0,para> {+ $ws* \n ;+ $ws* \> { begin birdtrack }+ $ws* [\*\-] { token TokBullet `andBegin` string }+ $ws* \[ { token TokDefStart `andBegin` def }+ $ws* \( $digit+ \) { token TokNumber `andBegin` string }+ $ws* { begin string }+}++-- beginning of a line+<line> {+ $ws* \> { begin birdtrack }+ $ws* \n { token TokPara `andBegin` para }+ -- Here, we really want to be able to say+ -- $ws* (\n | <eof>) { token TokPara `andBegin` para}+ -- because otherwise a trailing line of whitespace will result in+ -- a spurious TokString at the end of a docstring. We don't have <eof>,+ -- though (NOW I realise what it was for :-). To get around this, we always+ -- append \n to the end of a docstring.+ () { begin string }+}++<birdtrack> .* \n? { strtokenNL TokBirdTrack `andBegin` line }++<string,def> {+ $special { strtoken $ \s -> TokSpecial (head s) }+ \<\< [^\<\>]* \>\> { strtoken $ \s -> TokPic (init $ init $ tail $ tail s) }+ \< [^\<\>]* \> { strtoken $ \s -> TokURL (init (tail s)) }+ \# [^\#]* \# { strtoken $ \s -> TokAName (init (tail s)) }+ \/ [^\/]* \/ { strtoken $ \s -> TokEmphasis (init (tail s)) }+ [\'\`] $ident+ [\'\`] { ident }+ \\ . { strtoken (TokString . tail) }+ "&#" $digit+ \; { strtoken $ \s -> TokString [chr (read (init (drop 2 s)))] }+ "&#" [xX] $hexdigit+ \; { strtoken $ \s -> case readHex (init (drop 3 s)) of [(n,_)] -> TokString [chr n]; _ -> error "hexParser: Can't happen" }+ -- allow special characters through if they don't fit one of the previous+ -- patterns.+ [\/\'\`\<\#\&\\] { strtoken TokString }+ [^ $special \/ \< \# \n \'\` \& \\ \]]* \n { strtokenNL TokString `andBegin` line }+ [^ $special \/ \< \# \n \'\` \& \\ \]]+ { strtoken TokString }+}++<def> {+ \] { token TokDefEnd `andBegin` string }+}++-- ']' doesn't have any special meaning outside of the [...] at the beginning+-- of a definition paragraph.+<string> {+ \] { strtoken TokString }+}++{+data Token+ = TokPara+ | TokNumber+ | TokBullet+ | TokDefStart+ | TokDefEnd+ | TokSpecial Char+ | TokIdent RdrName+ | TokString String+ | TokURL String+ | TokPic String+ | TokEmphasis String+ | TokAName String+ | TokBirdTrack String+ deriving Show++-- -----------------------------------------------------------------------------+-- Alex support stuff++type StartCode = Int+type Action = String -> StartCode -> (StartCode -> Maybe [Token]) -> Maybe [Token]++--TODO: we ought to switch to ByteString input.+type AlexInput = (Char, [Word8], String)++-- | For alex >= 3+--+-- See also alexGetChar+alexGetByte :: AlexInput -> Maybe (Word8,AlexInput)+alexGetByte (c,(b:bs),s) = Just (b,(c,bs,s))+alexGetByte (c,[],[]) = Nothing+alexGetByte (_,[],(c:s)) = case utf8Encode c of+ (b:bs) -> Just (b, (c, bs, s))++-- | Encode a Haskell String to a list of Word8 values, in UTF8 format.+utf8Encode :: Char -> [Word8]+utf8Encode = map fromIntegral . go . ord+ where+ go oc+ | oc <= 0x7f = [oc]++ | oc <= 0x7ff = [ 0xc0 + (oc `Data.Bits.shiftR` 6)+ , 0x80 + oc Data.Bits..&. 0x3f+ ]++ | oc <= 0xffff = [ 0xe0 + (oc `Data.Bits.shiftR` 12)+ , 0x80 + ((oc `Data.Bits.shiftR` 6) Data.Bits..&. 0x3f)+ , 0x80 + oc Data.Bits..&. 0x3f+ ]+ | otherwise = [ 0xf0 + (oc `Data.Bits.shiftR` 18)+ , 0x80 + ((oc `Data.Bits.shiftR` 12) Data.Bits..&. 0x3f)+ , 0x80 + ((oc `Data.Bits.shiftR` 6) Data.Bits..&. 0x3f)+ , 0x80 + oc Data.Bits..&. 0x3f+ ]++-- | For alex < 3+--+-- See also alexGetByte+alexGetChar :: AlexInput -> Maybe (Char, AlexInput)+alexGetChar (_, _, []) = Nothing+alexGetChar (_, _, c:cs) = Just (c, (c,[],cs))++alexInputPrevChar (c,_) = c++tokenise :: String -> Maybe [Token]+tokenise str =+ go ('\n', [], eofHack str) para+ where+ go inp@(_,_,str') sc =+ case alexScan inp sc of+ AlexEOF -> Just []+ AlexError _ -> Nothing+ AlexSkip inp' _ -> go inp' sc+ AlexToken inp' len act -> act (take len str') sc (\sc' -> go inp' sc')++-- NB. we add a final \n to the string, (see comment in the beginning of line+-- production above).+eofHack str = str++"\n"++andBegin :: Action -> StartCode -> Action+andBegin act new_sc = \str _ cont -> act str new_sc cont++token :: Token -> Action+token t = \_ sc cont -> liftM (t :) (cont sc)++strtoken, strtokenNL :: (String -> Token) -> Action+strtoken t = \str sc cont -> liftM (t str :) (cont sc)+strtokenNL t = \str sc cont -> liftM (t (filter (/= '\r') str) :) (cont sc)+-- ^ We only want LF line endings in our internal doc string format, so we+-- filter out all CRs.++begin :: StartCode -> Action+begin sc = \_ _ cont -> cont sc++-- -----------------------------------------------------------------------------+-- Lex a string as a Haskell identifier++ident :: Action+ident str sc cont = liftM (TokIdent str :) (cont sc)+}
+ demo/HaddockParse.y view
@@ -0,0 +1,117 @@+{+-- Disable warnings that the generated code causes+{-# OPTIONS_GHC -fno-warn-deprecated-flags+ -fno-warn-missing-signatures+ -fno-warn-unused-binds+ -fno-warn-unused-matches+ -fno-warn-lazy-unlifted-bindings+ -fno-warn-name-shadowing+ -fno-warn-incomplete-patterns+ -fno-warn-tabs #-}+module HaddockParse (parseHaddockParagraphs) where++import HaddockLex+import HaddockHtml+import HaddockTypes+import Data.Char (isSpace)+}++%expect 0++%tokentype { Token }++%token+ '@' { TokSpecial '@' }+ '[' { TokDefStart }+ ']' { TokDefEnd }+ DQUO { TokSpecial '\"' }+ URL { TokURL $$ }+ PIC { TokPic $$ }+ ANAME { TokAName $$ }+ '/../' { TokEmphasis $$ }+ '-' { TokBullet }+ '(n)' { TokNumber }+ '>..' { TokBirdTrack $$ }+ IDENT { TokIdent $$ }+ PARA { TokPara }+ STRING { TokString $$ }++%monad { Maybe }++%name parseHaddockParagraphs doc+%name parseHaddockString seq++%%++doc :: { Doc RdrName }+ : apara PARA doc { docAppend $1 $3 }+ | PARA doc { $2 }+ | apara { $1 }+ | {- empty -} { DocEmpty }++apara :: { Doc RdrName }+ : ulpara { DocUnorderedList [$1] }+ | olpara { DocOrderedList [$1] }+ | defpara { DocDefList [$1] }+ | para { $1 }++ulpara :: { Doc RdrName }+ : '-' para { $2 }++olpara :: { Doc RdrName }+ : '(n)' para { $2 }++defpara :: { (Doc RdrName, Doc RdrName) }+ : '[' seq ']' seq { ($2, $4) }++para :: { Doc RdrName }+ : seq { docParagraph $1 }+ | codepara { DocCodeBlock $1 }++codepara :: { Doc RdrName }+ : '>..' codepara { docAppend (DocString $1) $2 }+ | '>..' { DocString $1 }++seq :: { Doc RdrName }+ : elem seq { docAppend $1 $2 }+ | elem { $1 }++elem :: { Doc RdrName }+ : elem1 { $1 }+ | '@' seq1 '@' { DocMonospaced $2 }++seq1 :: { Doc RdrName }+ : PARA seq1 { docAppend (DocString "\n") $2 }+ | elem1 seq1 { docAppend $1 $2 }+ | elem1 { $1 }++elem1 :: { Doc RdrName }+ : STRING { DocString $1 }+ | '/../' { DocEmphasis (DocString $1) }+ | URL { DocHyperlink (makeHyperlink $1) }+ | PIC { DocPic $1 }+ | ANAME { DocAName $1 }+ | IDENT { DocIdentifier $1 }+ | DQUO strings DQUO { DocModule $2 }++strings :: { String }+ : STRING { $1 }+ | STRING strings { $1 ++ $2 }++{+happyError :: [Token] -> Maybe a+happyError toks = Nothing++-- | Create a `Hyperlink` from given string.+--+-- A hyperlink consists of a URL and an optional label. The label is separated+-- from the url by one or more whitespace characters.+makeHyperlink :: String -> Hyperlink+makeHyperlink input = case break isSpace $ strip input of+ (url, "") -> Hyperlink url Nothing+ (url, label) -> Hyperlink url (Just . dropWhile isSpace $ label)++-- | Remove all leading and trailing whitespace+strip :: String -> String+strip = dropWhile isSpace . reverse . dropWhile isSpace . reverse+}
+ demo/HaddockTypes.hs view
@@ -0,0 +1,48 @@+-- stolen from Haddock's Types.hs+module HaddockTypes where++data Doc id+ = DocEmpty+ | DocAppend (Doc id) (Doc id)+ | DocString String+ | DocParagraph (Doc id)+ | DocIdentifier id+ | DocModule String+ | DocEmphasis (Doc id)+ | DocMonospaced (Doc id)+ | DocUnorderedList [Doc id]+ | DocOrderedList [Doc id]+ | DocDefList [(Doc id, Doc id)]+ | DocCodeBlock (Doc id)+ | DocHyperlink Hyperlink+ | DocPic String+ | DocAName String++data Hyperlink = Hyperlink+ { hyperlinkUrl :: String+ , hyperlinkLabel :: Maybe String+ } deriving (Eq, Show)++-- | DocMarkup is a set of instructions for marking up documentation.+-- In fact, it's really just a mapping from 'Doc' to some other+-- type [a], where [a] is usually the type of the output (HTML, say).++data DocMarkup id a = Markup {+ markupEmpty :: a,+ markupString :: String -> a,+ markupParagraph :: a -> a,+ markupAppend :: a -> a -> a,+ markupIdentifier :: id -> a,+ markupModule :: String -> a,+ markupEmphasis :: a -> a,+ markupMonospaced :: a -> a,+ markupUnorderedList :: [a] -> a,+ markupOrderedList :: [a] -> a,+ markupDefList :: [(a,a)] -> a,+ markupCodeBlock :: a -> a,+ markupHyperlink :: Hyperlink -> a,+ markupAName :: String -> a,+ markupPic :: String -> a+ }++type RdrName = String
+ demo/Main.hs view
@@ -0,0 +1,93 @@+module Main where++import Data.SearchEngine+import PackageSearch++import PackageIndexUtils++import Data.List+import qualified Data.Map as Map+import qualified Data.Text as T+import qualified Data.Text.IO as T+import Data.Time++import Control.Monad+import Control.Exception+import System.IO+import System.Directory+import System.Exit++import Distribution.PackageDescription (PackageDescription)+import Distribution.PackageDescription.Configuration (flattenPackageDescription)+import Distribution.Package (packageVersion, packageName)+import Distribution.Text (display)+++-------------------+-- Main experiment+--++main :: IO ()+main = do+ putStrLn "reading 00-index.tar..."+ pkgs <- readPackages++ putStrLn "forcing pkgs..."+ evaluate (foldl' (\a p -> seq p a) () pkgs)++ let searchengine = insertDocs pkgs initialPkgSearchEngine++ putStrLn "constructing index..."+ printTiming "done" $+ evaluate searchengine >> return ()+ putStrLn $ "search engine invariant: " ++ show (invariant searchengine)++-- print [ avgFieldLength ctx s | s <- [minBound..maxBound] ]++-- print $ take 100 $ sortBy (flip compare) $ map Set.size $ Map.elems (termMap searchindex)+-- T.putStr $ T.unlines $ Map.keys (termMap searchindex)+-- let SearchEngine{searchIndex=SearchIndex{termMap, termIdMap, docKeyMap, docIdMap}} = searchengine+-- print (Map.size termMap, IntMap.size termIdMap, Map.size docKeyMap, IntMap.size docIdMap)++ let loop = do+ putStr "search term> "+ hFlush stdout + t <- T.getLine+ unless (T.null t) $ do+ putStrLn "Ranked results:"+ let rankedResults = query searchengine (T.words t)++ putStr $ unlines+ [ {-show rank ++ ": " ++ -}display pkgname+ | ({-rank, -}pkgname) <- take 10 rankedResults ]++ loop+ return ()+ loop++printTiming :: String -> IO () -> IO ()+printTiming msg action = do+ t <- getCurrentTime+ action+ t' <- getCurrentTime+ putStrLn (msg ++ ". time: " ++ show (diffUTCTime t' t))++readPackages :: IO [PackageDescription]+readPackages = do+ exists <- doesFileExist "00-index.tar"+ when (not exists) $ do+ putStrLn "This program needs a 00-index.tar package index."+ putStrLn "Please grab 00-index.tar.gz from hackage and gunzip it."+ exitFailure++ pkgs <- PackageIndexUtils.readPackageIndexFile "00-index.tar"+ let latestPkgs = Map.fromListWith+ (\a b -> if packageVersion (fst a) > packageVersion (fst b)+ then a else b)+ [ (packageName pkgid, (pkgid, pkg))+ | (pkgid, pkg) <- pkgs ]++ return . map (flattenPackageDescription . snd)+ . Map.elems+ $ latestPkgs+
+ demo/PackageIndexUtils.hs view
@@ -0,0 +1,86 @@+-----------------------------------------------------------------------------+-- |+-- Module : Distribution.Client.IndexUtils+-- Copyright : (c) Duncan Coutts 2008+-- License : BSD-like+--+-- Maintainer : duncan@community.haskell.org+-- Stability : provisional+-- Portability : portable+--+-- Extra utils related to the package indexes.+-----------------------------------------------------------------------------+module PackageIndexUtils (+ readPackageIndexFile+ ) where++import qualified Codec.Archive.Tar as Tar++import Distribution.Package+ ( PackageId, PackageIdentifier(..), PackageName(..) )+import Distribution.PackageDescription+ ( GenericPackageDescription )+import Distribution.PackageDescription.Parse+ ( parsePackageDescription )+import Distribution.ParseUtils+ ( ParseResult(..) )+import Distribution.Text+ ( simpleParse )+import Distribution.Simple.Utils+ ( fromUTF8 )++import Data.Maybe (fromMaybe)+import qualified Data.ByteString.Lazy as BS+import qualified Data.ByteString.Lazy.Char8 as BS.Char8+import Data.ByteString.Lazy (ByteString)+import System.FilePath (takeExtension, splitDirectories, normalise)+++readPackageIndexFile :: FilePath -> IO [(PackageId, GenericPackageDescription)]+readPackageIndexFile indexFile =+ either fail return+ . parsePackageIndex+ =<< BS.readFile indexFile++-- | Parse an uncompressed \"00-index.tar\" repository index file represented+-- as a 'ByteString'.+--+parsePackageIndex :: ByteString+ -> Either String [(PackageId, GenericPackageDescription)]+parsePackageIndex = accum [] . Tar.read+ where+ accum pkgs es = case es of+ Tar.Fail err -> Left (show err)+ Tar.Done -> Right (reverse pkgs)+ Tar.Next e es' -> accum pkgs' es'+ where+ pkgs' = extract pkgs e++ extract pkgs entry =+ fromMaybe pkgs $ tryExtractPkg+ where+ tryExtractPkg = do+ (pkgid, pkg) <- extractPkg entry+ return ((pkgid, pkg):pkgs)+++extractPkg :: Tar.Entry -> Maybe (PackageId, GenericPackageDescription)+extractPkg entry = case Tar.entryContent entry of+ Tar.NormalFile content _+ | takeExtension fileName == ".cabal"+ -> case splitDirectories (normalise fileName) of+ [pkgname,vers,_] -> case simpleParse vers of+ Just ver -> Just (pkgid, descr)+ where+ pkgid = PackageIdentifier (PackageName pkgname) ver+ parsed = parsePackageDescription . fromUTF8 . BS.Char8.unpack+ $ content+ descr = case parsed of+ ParseOk _ d -> d+ _ -> error $ "Couldn't read cabal file "+ ++ show fileName+ _ -> Nothing+ _ -> Nothing+ _ -> Nothing+ where+ fileName = Tar.entryPath entry
+ demo/PackageSearch.hs view
@@ -0,0 +1,106 @@+{-# LANGUAGE OverloadedStrings, NamedFieldPuns #-}+module PackageSearch (+ PkgSearchEngine,+ initialPkgSearchEngine,+ defaultSearchRankParameters,+ PkgDocField(..),+ ) where++import Data.SearchEngine++import ExtractNameTerms+import ExtractDescriptionTerms++import Data.Ix+import Data.Set (Set)+import qualified Data.Set as Set+import Data.Text (Text)+import qualified Data.Text as T+import NLP.Snowball++import Distribution.Package+import Distribution.PackageDescription+import Distribution.Text (display)+++type PkgSearchEngine = SearchEngine+ PackageDescription+ PackageName+ PkgDocField+ NoFeatures++data PkgDocField = NameField+ | SynopsisField+ | DescriptionField+ deriving (Eq, Ord, Enum, Bounded, Ix, Show)++initialPkgSearchEngine :: PkgSearchEngine+initialPkgSearchEngine =+ initSearchEngine pkgSearchConfig defaultSearchRankParameters++pkgSearchConfig :: SearchConfig PackageDescription+ PackageName PkgDocField NoFeatures+pkgSearchConfig =+ SearchConfig {+ documentKey = packageName,+ extractDocumentTerms = extractTokens,+ transformQueryTerm = normaliseQueryToken,+ documentFeatureValue = const noFeatures+ }+ where+ extractTokens :: PackageDescription -> PkgDocField -> [Text]+ extractTokens pkg NameField = extractPackageNameTerms+ (display $ packageName pkg)+ extractTokens pkg SynopsisField = extractSynopsisTerms+ stopWords (synopsis pkg)+ extractTokens pkg DescriptionField = extractDescriptionTerms+ stopWords (description pkg)++ normaliseQueryToken :: Text -> PkgDocField -> Text+ normaliseQueryToken tok =+ let tokFold = T.toCaseFold tok+ tokStem = stem English tokFold+ in \field -> case field of+ NameField -> tokFold+ SynopsisField -> tokStem+ DescriptionField -> tokStem++defaultSearchRankParameters :: SearchRankParameters PkgDocField NoFeatures+defaultSearchRankParameters =+ SearchRankParameters {+ paramK1,+ paramB,+ paramFieldWeights,+ paramFeatureWeights = noFeatures,+ paramFeatureFunctions = noFeatures,+ paramResultsetSoftLimit = 200,+ paramResultsetHardLimit = 400,+ paramAutosuggestPrefilterLimit = 500,+ paramAutosuggestPostfilterLimit = 500+ }+ where+ paramK1 :: Float+ paramK1 = 1.5++ paramB :: PkgDocField -> Float+ paramB NameField = 0.9+ paramB SynopsisField = 0.5+ paramB DescriptionField = 0.5++ paramFieldWeights :: PkgDocField -> Float+ paramFieldWeights NameField = 20+ paramFieldWeights SynopsisField = 5+ paramFieldWeights DescriptionField = 1+++stopWords :: Set Term+stopWords =+ Set.fromList+ ["haskell","library","simple","using","interface","functions",+ "implementation","package","support","'s","based","for","a","and","the",+ "to","of","with","in","an","on","from","that","as","into","by","is",+ "some","which","or","like","your","other","can","at","over","be","it",+ "within","their","this","but","are","get","one","all","you","so","only",+ "now","how","where","when","up","has","been","about","them","then","see",+ "no","do","than","should","out","off","much","if","i","have","also"]+
+ dist/build/search-demo/search-demo-tmp/HaddockLex.hs view
@@ -0,0 +1,368 @@+{-# LANGUAGE CPP,MagicHash #-}+{-# LINE 7 "demo/HaddockLex.x" #-}++-- Disable warnings that the generated code causes+{-# OPTIONS_GHC -fno-warn-deprecated-flags+ -fno-warn-unused-binds+ -fno-warn-unused-imports+ -fno-warn-unused-matches+ -fno-warn-missing-signatures+ -fno-warn-tabs #-}+module HaddockLex (+ Token(..),+ tokenise+ ) where++import Data.Char+import Data.Word (Word8)+import qualified Data.Bits+import Numeric+import Control.Monad (liftM)+import HaddockTypes (RdrName)++#if __GLASGOW_HASKELL__ >= 603+#include "ghcconfig.h"+#elif defined(__GLASGOW_HASKELL__)+#include "config.h"+#endif+#if __GLASGOW_HASKELL__ >= 503+import Data.Array+import Data.Char (ord)+import Data.Array.Base (unsafeAt)+#else+import Array+import Char (ord)+#endif+#if __GLASGOW_HASKELL__ >= 503+import GHC.Exts+#else+import GlaExts+#endif+alex_base :: AlexAddr+alex_base = AlexA# "\xf8\xff\xff\xff\xfc\xff\xff\xff\xf2\x00\x00\x00\xfe\xff\xff\xff\x03\x00\x00\x00\xe8\x01\x00\x00\x12\x00\x00\x00\xde\x02\x00\x00\xd4\x03\x00\x00\xd5\x02\x00\x00\x24\x00\x00\x00\xf4\x00\x00\x00\x54\x04\x00\x00\xd4\x04\x00\x00\x54\x05\x00\x00\xd4\x05\x00\x00\x54\x06\x00\x00\xd4\x06\x00\x00\x54\x07\x00\x00\xd4\x07\x00\x00\x54\x08\x00\x00\xd4\x08\x00\x00\x54\x09\x00\x00\xd4\x09\x00\x00\x54\x0a\x00\x00\xd4\x0a\x00\x00\x00\x00\x00\x00\x45\x0b\x00\x00\x00\x00\x00\x00\xb6\x0b\x00\x00\x00\x00\x00\x00\x27\x0c\x00\x00\x00\x00\x00\x00\x98\x0c\x00\x00\x00\x00\x00\x00\x45\x03\x00\x00\x00\x00\x00\x00\x09\x0d\x00\x00\x00\x00\x00\x00\x7a\x0d\x00\x00\x00\x00\x00\x00\xbb\x0d\x00\x00\x00\x00\x00\x00\xfc\x0d\x00\x00\x00\x00\x00\x00\x3d\x0e\x00\x00\x3d\x0f\x00\x00\xfd\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x6e\x0f\x00\x00\x00\x00\x00\x00\xaf\x0f\x00\x00\xda\x00\x00\x00\xc3\x01\x00\x00\x00\x00\x00\x00\xf0\x0f\x00\x00\xf0\x10\x00\x00\xb0\x10\x00\x00\x00\x00\x00\x00\xb0\x11\x00\x00\x70\x11\x00\x00\x00\x00\x00\x00\x70\x12\x00\x00\x30\x12\x00\x00\x00\x00\x00\x00\x26\x13\x00\x00\x00\x00\x00\x00\xa6\x12\x00\x00\x30\x00\x00\x00\x26\x14\x00\x00\xe6\x13\x00\x00\x00\x00\x00\x00\xe6\x14\x00\x00\xa6\x14\x00\x00\x00\x00\x00\x00\x9c\x15\x00\x00\xd3\xff\xff\xff\x9c\x16\x00\x00\x1b\x15\x00\x00\x00\x00\x00\x00\xf6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfb\x16\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x13\x17\x00\x00\x09\x18\x00\x00\xfd\x17\x00\x00\xef\xff\xff\xff\xff\x18\x00\x00\xf5\x19\x00\x00\x00\x00\x00\x00\xeb\x1a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"#++alex_table :: AlexAddr+alex_table = AlexA# "\x00\x00\x04\x00\x52\x00\x04\x00\x04\x00\x04\x00\x59\x00\x51\x00\x58\x00\x51\x00\x51\x00\x51\x00\x04\x00\x52\x00\x04\x00\x04\x00\x04\x00\x5b\x00\x36\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x51\x00\x00\x00\x45\x00\x00\x00\x54\x00\x04\x00\x00\x00\x54\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x45\x00\x00\x00\x54\x00\x00\x00\x00\x00\x54\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x53\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x00\x57\x00\x00\x00\x00\x00\x00\x00\x00\x00\x53\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x55\x00\x0a\x00\x0a\x00\x0a\x00\x0a\x00\x0a\x00\x0a\x00\x0a\x00\x0a\x00\x0a\x00\x0a\x00\x55\x00\x62\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x4e\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x4f\x00\x0c\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x44\x00\x0f\x00\x31\x00\x31\x00\x31\x00\x32\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x6a\x00\x00\x00\x00\x00\x51\x00\x58\x00\x51\x00\x51\x00\x51\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x5a\x00\x64\x00\x51\x00\x00\x00\x67\x00\x66\x00\x00\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x65\x00\x00\x00\x00\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x69\x00\x63\x00\x00\x00\x00\x00\x5a\x00\x00\x00\x57\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x68\x00\x6c\x00\x00\x00\x00\x00\x66\x00\x00\x00\x00\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x46\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x47\x00\x0e\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x34\x00\x14\x00\x24\x00\x24\x00\x24\x00\x25\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x6a\x00\x0a\x00\x0a\x00\x0a\x00\x0a\x00\x0a\x00\x0a\x00\x0a\x00\x0a\x00\x0a\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x5a\x00\x64\x00\x00\x00\x00\x00\x67\x00\x66\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x65\x00\x00\x00\x00\x00\x00\x00\x35\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x69\x00\x00\x00\x00\x00\x00\x00\x5a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x35\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x68\x00\x6d\x00\x00\x00\x00\x00\x66\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x46\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x47\x00\x0e\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x34\x00\x14\x00\x24\x00\x24\x00\x24\x00\x25\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x09\x00\x09\x00\x09\x00\x09\x00\x5f\x00\x00\x00\x00\x00\x09\x00\x09\x00\x5d\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x00\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x00\x00\x09\x00\x00\x00\x09\x00\x09\x00\x60\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x00\x00\x09\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x3c\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3d\x00\x11\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2b\x00\x17\x00\x1e\x00\x1e\x00\x1e\x00\x1f\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x5e\x00\x2f\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x39\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3a\x00\x12\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x29\x00\x18\x00\x1c\x00\x1c\x00\x1c\x00\x1d\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x4e\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x50\x00\x49\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x46\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x0c\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x43\x00\x3f\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x3c\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x39\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x0d\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x0e\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x2e\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x10\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x11\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x12\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x15\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x23\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x29\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x2b\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x2d\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x34\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x38\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x3a\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x3d\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x40\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x44\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x47\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x4a\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x4d\x00\x4f\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x2e\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x2f\x00\x15\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x22\x00\x23\x00\x19\x00\x1a\x00\x1a\x00\x1a\x00\x1b\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x5c\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x3f\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x40\x00\x10\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2d\x00\x16\x00\x20\x00\x20\x00\x20\x00\x21\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x09\x00\xff\xff\x09\x00\x09\x00\x09\x00\x09\x00\x5f\x00\x00\x00\x00\x00\x09\x00\x09\x00\x00\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x5d\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x00\x00\x09\x00\x00\x00\x09\x00\x09\x00\x60\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x00\x00\x09\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x3c\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3e\x00\x3d\x00\x11\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2a\x00\x2b\x00\x17\x00\x1e\x00\x1e\x00\x1e\x00\x1f\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x00\x00\x00\x00\x09\x00\x09\x00\x00\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x5e\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x00\x00\x09\x00\x00\x00\x09\x00\x09\x00\x00\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x00\x00\x09\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x39\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3b\x00\x3a\x00\x12\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x28\x00\x29\x00\x18\x00\x1c\x00\x1c\x00\x1c\x00\x1d\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x49\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4b\x00\x4a\x00\x0d\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x37\x00\x38\x00\x13\x00\x26\x00\x26\x00\x26\x00\x27\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x42\x00\x00\x00\x5c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x3f\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x41\x00\x40\x00\x10\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2c\x00\x2d\x00\x16\x00\x20\x00\x20\x00\x20\x00\x21\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x6a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x46\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x48\x00\x47\x00\x0e\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x33\x00\x34\x00\x14\x00\x24\x00\x24\x00\x24\x00\x25\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"#++alex_check :: AlexAddr+alex_check = AlexA# "\xff\xff\x09\x00\x0a\x00\x0b\x00\x0c\x00\x0d\x00\x0a\x00\x09\x00\x0a\x00\x0b\x00\x0c\x00\x0d\x00\x09\x00\x0a\x00\x0b\x00\x0c\x00\x0d\x00\x3e\x00\x23\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x20\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x20\x00\xff\xff\x28\x00\xff\xff\x2a\x00\x20\x00\xff\xff\x2d\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x28\x00\xff\xff\x2a\x00\xff\xff\xff\xff\x2d\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x3e\x00\xff\xff\xff\xff\xff\xff\xff\xff\x29\x00\x3e\x00\xff\xff\xff\xff\xff\xff\xff\xff\x3e\x00\x30\x00\x31\x00\x32\x00\x33\x00\x34\x00\x35\x00\x36\x00\x37\x00\x38\x00\x39\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x5b\x00\x30\x00\x31\x00\x32\x00\x33\x00\x34\x00\x35\x00\x36\x00\x37\x00\x38\x00\x39\x00\x5b\x00\x3b\x00\x30\x00\x31\x00\x32\x00\x33\x00\x34\x00\x35\x00\x36\x00\x37\x00\x38\x00\x39\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x0a\x00\xff\xff\xff\xff\x09\x00\x0a\x00\x0b\x00\x0c\x00\x0d\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x30\x00\x31\x00\x32\x00\x33\x00\x34\x00\x35\x00\x36\x00\x37\x00\x38\x00\x39\x00\x22\x00\x23\x00\x20\x00\xff\xff\x26\x00\x27\x00\xff\xff\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x2f\x00\xff\xff\xff\xff\x30\x00\x31\x00\x32\x00\x33\x00\x34\x00\x35\x00\x36\x00\x37\x00\x38\x00\x39\x00\x3c\x00\x3b\x00\xff\xff\xff\xff\x40\x00\xff\xff\x3e\x00\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x61\x00\x62\x00\x63\x00\x64\x00\x65\x00\x66\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x5c\x00\x5d\x00\xff\xff\xff\xff\x60\x00\xff\xff\xff\xff\x61\x00\x62\x00\x63\x00\x64\x00\x65\x00\x66\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x0a\x00\x30\x00\x31\x00\x32\x00\x33\x00\x34\x00\x35\x00\x36\x00\x37\x00\x38\x00\x39\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x22\x00\x23\x00\xff\xff\xff\xff\x26\x00\x27\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x2f\x00\xff\xff\xff\xff\xff\xff\x58\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x3c\x00\xff\xff\xff\xff\xff\xff\x40\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x78\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x5c\x00\x5d\x00\xff\xff\xff\xff\x60\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x0a\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x21\x00\xff\xff\x23\x00\x24\x00\x25\x00\x26\x00\x27\x00\xff\xff\xff\xff\x2a\x00\x2b\x00\x23\x00\x2d\x00\x2e\x00\x2f\x00\x30\x00\x31\x00\x32\x00\x33\x00\x34\x00\x35\x00\x36\x00\x37\x00\x38\x00\x39\x00\x3a\x00\xff\xff\x3c\x00\x3d\x00\x3e\x00\x3f\x00\x40\x00\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x47\x00\x48\x00\x49\x00\x4a\x00\x4b\x00\x4c\x00\x4d\x00\x4e\x00\x4f\x00\x50\x00\x51\x00\x52\x00\x53\x00\x54\x00\x55\x00\x56\x00\x57\x00\x58\x00\x59\x00\x5a\x00\xff\xff\x5c\x00\xff\xff\x5e\x00\x5f\x00\x60\x00\x61\x00\x62\x00\x63\x00\x64\x00\x65\x00\x66\x00\x67\x00\x68\x00\x69\x00\x6a\x00\x6b\x00\x6c\x00\x6d\x00\x6e\x00\x6f\x00\x70\x00\x71\x00\x72\x00\x73\x00\x74\x00\x75\x00\x76\x00\x77\x00\x78\x00\x79\x00\x7a\x00\xff\xff\x7c\x00\xff\xff\x7e\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x0a\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x2f\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x09\x00\x0a\x00\x0b\x00\x0c\x00\x0d\x00\x0e\x00\x0f\x00\x10\x00\x11\x00\x12\x00\x13\x00\x14\x00\x15\x00\x16\x00\x17\x00\x18\x00\x19\x00\x1a\x00\x1b\x00\x1c\x00\x1d\x00\x1e\x00\x1f\x00\x20\x00\x21\x00\x22\x00\x23\x00\x24\x00\x25\x00\x26\x00\x27\x00\x28\x00\x29\x00\x2a\x00\x2b\x00\x2c\x00\x2d\x00\x2e\x00\x2f\x00\x30\x00\x31\x00\x32\x00\x33\x00\x34\x00\x35\x00\x36\x00\x37\x00\x38\x00\x39\x00\x3a\x00\x3b\x00\x3c\x00\x3d\x00\x3e\x00\x3f\x00\x40\x00\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x47\x00\x48\x00\x49\x00\x4a\x00\x4b\x00\x4c\x00\x4d\x00\x4e\x00\x4f\x00\x50\x00\x51\x00\x52\x00\x53\x00\x54\x00\x55\x00\x56\x00\x57\x00\x58\x00\x59\x00\x5a\x00\x5b\x00\x5c\x00\x5d\x00\x5e\x00\x5f\x00\x60\x00\x61\x00\x62\x00\x63\x00\x64\x00\x65\x00\x66\x00\x67\x00\x68\x00\x69\x00\x6a\x00\x6b\x00\x6c\x00\x6d\x00\x6e\x00\x6f\x00\x70\x00\x71\x00\x72\x00\x73\x00\x74\x00\x75\x00\x76\x00\x77\x00\x78\x00\x79\x00\x7a\x00\x7b\x00\x7c\x00\x7d\x00\x7e\x00\x7f\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x09\x00\x0a\x00\x0b\x00\x0c\x00\x0d\x00\x0e\x00\x0f\x00\x10\x00\x11\x00\x12\x00\x13\x00\x14\x00\x15\x00\x16\x00\x17\x00\x18\x00\x19\x00\x1a\x00\x1b\x00\x1c\x00\x1d\x00\x1e\x00\x1f\x00\x20\x00\x21\x00\x22\x00\x23\x00\x24\x00\x25\x00\x26\x00\x27\x00\x28\x00\x29\x00\x2a\x00\x2b\x00\x2c\x00\x2d\x00\x2e\x00\x2f\x00\x30\x00\x31\x00\x32\x00\x33\x00\x34\x00\x35\x00\x36\x00\x37\x00\x38\x00\x39\x00\x3a\x00\x3b\x00\x3c\x00\x3d\x00\x3e\x00\x3f\x00\x40\x00\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x47\x00\x48\x00\x49\x00\x4a\x00\x4b\x00\x4c\x00\x4d\x00\x4e\x00\x4f\x00\x50\x00\x51\x00\x52\x00\x53\x00\x54\x00\x55\x00\x56\x00\x57\x00\x58\x00\x59\x00\x5a\x00\x5b\x00\x5c\x00\x5d\x00\x5e\x00\x5f\x00\x60\x00\x61\x00\x62\x00\x63\x00\x64\x00\x65\x00\x66\x00\x67\x00\x68\x00\x69\x00\x6a\x00\x6b\x00\x6c\x00\x6d\x00\x6e\x00\x6f\x00\x70\x00\x71\x00\x72\x00\x73\x00\x74\x00\x75\x00\x76\x00\x77\x00\x78\x00\x79\x00\x7a\x00\x7b\x00\x7c\x00\x7d\x00\x7e\x00\x7f\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x09\x00\x0a\x00\x0b\x00\x0c\x00\x0d\x00\x0e\x00\x0f\x00\x10\x00\x11\x00\x12\x00\x13\x00\x14\x00\x15\x00\x16\x00\x17\x00\x18\x00\x19\x00\x1a\x00\x1b\x00\x1c\x00\x1d\x00\x1e\x00\x1f\x00\x20\x00\x21\x00\x22\x00\x23\x00\x24\x00\x25\x00\x26\x00\x27\x00\x28\x00\x29\x00\x2a\x00\x2b\x00\x2c\x00\x2d\x00\x2e\x00\x2f\x00\x30\x00\x31\x00\x32\x00\x33\x00\x34\x00\x35\x00\x36\x00\x37\x00\x38\x00\x39\x00\x3a\x00\x3b\x00\x3c\x00\x3d\x00\x3e\x00\x3f\x00\x40\x00\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x47\x00\x48\x00\x49\x00\x4a\x00\x4b\x00\x4c\x00\x4d\x00\x4e\x00\x4f\x00\x50\x00\x51\x00\x52\x00\x53\x00\x54\x00\x55\x00\x56\x00\x57\x00\x58\x00\x59\x00\x5a\x00\x5b\x00\x5c\x00\x5d\x00\x5e\x00\x5f\x00\x60\x00\x61\x00\x62\x00\x63\x00\x64\x00\x65\x00\x66\x00\x67\x00\x68\x00\x69\x00\x6a\x00\x6b\x00\x6c\x00\x6d\x00\x6e\x00\x6f\x00\x70\x00\x71\x00\x72\x00\x73\x00\x74\x00\x75\x00\x76\x00\x77\x00\x78\x00\x79\x00\x7a\x00\x7b\x00\x7c\x00\x7d\x00\x7e\x00\x7f\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x09\x00\x0a\x00\x0b\x00\x0c\x00\x0d\x00\x0e\x00\x0f\x00\x10\x00\x11\x00\x12\x00\x13\x00\x14\x00\x15\x00\x16\x00\x17\x00\x18\x00\x19\x00\x1a\x00\x1b\x00\x1c\x00\x1d\x00\x1e\x00\x1f\x00\x20\x00\x21\x00\x22\x00\x23\x00\x24\x00\x25\x00\x26\x00\x27\x00\x28\x00\x29\x00\x2a\x00\x2b\x00\x2c\x00\x2d\x00\x2e\x00\x2f\x00\x30\x00\x31\x00\x32\x00\x33\x00\x34\x00\x35\x00\x36\x00\x37\x00\x38\x00\x39\x00\x3a\x00\x3b\x00\x3c\x00\x3d\x00\x3e\x00\x3f\x00\x40\x00\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x47\x00\x48\x00\x49\x00\x4a\x00\x4b\x00\x4c\x00\x4d\x00\x4e\x00\x4f\x00\x50\x00\x51\x00\x52\x00\x53\x00\x54\x00\x55\x00\x56\x00\x57\x00\x58\x00\x59\x00\x5a\x00\x5b\x00\x5c\x00\x5d\x00\x5e\x00\x5f\x00\x60\x00\x61\x00\x62\x00\x63\x00\x64\x00\x65\x00\x66\x00\x67\x00\x68\x00\x69\x00\x6a\x00\x6b\x00\x6c\x00\x6d\x00\x6e\x00\x6f\x00\x70\x00\x71\x00\x72\x00\x73\x00\x74\x00\x75\x00\x76\x00\x77\x00\x78\x00\x79\x00\x7a\x00\x7b\x00\x7c\x00\x7d\x00\x7e\x00\x7f\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x0a\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x3c\x00\xff\xff\x3e\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x09\x00\x0a\x00\x0b\x00\x0c\x00\x0d\x00\x0e\x00\x0f\x00\x10\x00\x11\x00\x12\x00\x13\x00\x14\x00\x15\x00\x16\x00\x17\x00\x18\x00\x19\x00\x1a\x00\x1b\x00\x1c\x00\x1d\x00\x1e\x00\x1f\x00\x20\x00\x21\x00\x22\x00\x23\x00\x24\x00\x25\x00\x26\x00\x27\x00\x28\x00\x29\x00\x2a\x00\x2b\x00\x2c\x00\x2d\x00\x2e\x00\x2f\x00\x30\x00\x31\x00\x32\x00\x33\x00\x34\x00\x35\x00\x36\x00\x37\x00\x38\x00\x39\x00\x3a\x00\x3b\x00\x3c\x00\x3d\x00\x3e\x00\x3f\x00\x40\x00\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x47\x00\x48\x00\x49\x00\x4a\x00\x4b\x00\x4c\x00\x4d\x00\x4e\x00\x4f\x00\x50\x00\x51\x00\x52\x00\x53\x00\x54\x00\x55\x00\x56\x00\x57\x00\x58\x00\x59\x00\x5a\x00\x5b\x00\x5c\x00\x5d\x00\x5e\x00\x5f\x00\x60\x00\x61\x00\x62\x00\x63\x00\x64\x00\x65\x00\x66\x00\x67\x00\x68\x00\x69\x00\x6a\x00\x6b\x00\x6c\x00\x6d\x00\x6e\x00\x6f\x00\x70\x00\x71\x00\x72\x00\x73\x00\x74\x00\x75\x00\x76\x00\x77\x00\x78\x00\x79\x00\x7a\x00\x7b\x00\x7c\x00\x7d\x00\x7e\x00\x7f\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x09\x00\x0a\x00\x0b\x00\x0c\x00\x0d\x00\x0e\x00\x0f\x00\x10\x00\x11\x00\x12\x00\x13\x00\x14\x00\x15\x00\x16\x00\x17\x00\x18\x00\x19\x00\x1a\x00\x1b\x00\x1c\x00\x1d\x00\x1e\x00\x1f\x00\x20\x00\x21\x00\x22\x00\x23\x00\x24\x00\x25\x00\x26\x00\x27\x00\x28\x00\x29\x00\x2a\x00\x2b\x00\x2c\x00\x2d\x00\x2e\x00\x2f\x00\x30\x00\x31\x00\x32\x00\x33\x00\x34\x00\x35\x00\x36\x00\x37\x00\x38\x00\x39\x00\x3a\x00\x3b\x00\x3c\x00\x3d\x00\x3e\x00\x3f\x00\x40\x00\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x47\x00\x48\x00\x49\x00\x4a\x00\x4b\x00\x4c\x00\x4d\x00\x4e\x00\x4f\x00\x50\x00\x51\x00\x52\x00\x53\x00\x54\x00\x55\x00\x56\x00\x57\x00\x58\x00\x59\x00\x5a\x00\x5b\x00\x5c\x00\x5d\x00\x5e\x00\x5f\x00\x60\x00\x61\x00\x62\x00\x63\x00\x64\x00\x65\x00\x66\x00\x67\x00\x68\x00\x69\x00\x6a\x00\x6b\x00\x6c\x00\x6d\x00\x6e\x00\x6f\x00\x70\x00\x71\x00\x72\x00\x73\x00\x74\x00\x75\x00\x76\x00\x77\x00\x78\x00\x79\x00\x7a\x00\x7b\x00\x7c\x00\x7d\x00\x7e\x00\x7f\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x0a\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x3c\x00\xff\xff\x3e\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\xff\xff\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x09\x00\x0a\x00\x0b\x00\x0c\x00\x0d\x00\x0e\x00\x0f\x00\x10\x00\x11\x00\x12\x00\x13\x00\x14\x00\x15\x00\x16\x00\x17\x00\x18\x00\x19\x00\x1a\x00\x1b\x00\x1c\x00\x1d\x00\x1e\x00\x1f\x00\x20\x00\x21\x00\x22\x00\x23\x00\x24\x00\x25\x00\x26\x00\x27\x00\x28\x00\x29\x00\x2a\x00\x2b\x00\x2c\x00\x2d\x00\x2e\x00\x2f\x00\x30\x00\x31\x00\x32\x00\x33\x00\x34\x00\x35\x00\x36\x00\x37\x00\x38\x00\x39\x00\x3a\x00\x3b\x00\x3c\x00\x3d\x00\x3e\x00\x3f\x00\x40\x00\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x47\x00\x48\x00\x49\x00\x4a\x00\x4b\x00\x4c\x00\x4d\x00\x4e\x00\x4f\x00\x50\x00\x51\x00\x52\x00\x53\x00\x54\x00\x55\x00\x56\x00\x57\x00\x58\x00\x59\x00\x5a\x00\x5b\x00\x5c\x00\x5d\x00\x5e\x00\x5f\x00\x60\x00\x61\x00\x62\x00\x63\x00\x64\x00\x65\x00\x66\x00\x67\x00\x68\x00\x69\x00\x6a\x00\x6b\x00\x6c\x00\x6d\x00\x6e\x00\x6f\x00\x70\x00\x71\x00\x72\x00\x73\x00\x74\x00\x75\x00\x76\x00\x77\x00\x78\x00\x79\x00\x7a\x00\x7b\x00\x7c\x00\x7d\x00\x7e\x00\x7f\x00\x21\x00\x0a\x00\x23\x00\x24\x00\x25\x00\x26\x00\x27\x00\xff\xff\xff\xff\x2a\x00\x2b\x00\xff\xff\x2d\x00\x2e\x00\x2f\x00\x30\x00\x31\x00\x32\x00\x33\x00\x34\x00\x35\x00\x36\x00\x37\x00\x38\x00\x39\x00\x3a\x00\x23\x00\x3c\x00\x3d\x00\x3e\x00\x3f\x00\x40\x00\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x47\x00\x48\x00\x49\x00\x4a\x00\x4b\x00\x4c\x00\x4d\x00\x4e\x00\x4f\x00\x50\x00\x51\x00\x52\x00\x53\x00\x54\x00\x55\x00\x56\x00\x57\x00\x58\x00\x59\x00\x5a\x00\xff\xff\x5c\x00\xff\xff\x5e\x00\x5f\x00\x60\x00\x61\x00\x62\x00\x63\x00\x64\x00\x65\x00\x66\x00\x67\x00\x68\x00\x69\x00\x6a\x00\x6b\x00\x6c\x00\x6d\x00\x6e\x00\x6f\x00\x70\x00\x71\x00\x72\x00\x73\x00\x74\x00\x75\x00\x76\x00\x77\x00\x78\x00\x79\x00\x7a\x00\xff\xff\x7c\x00\xff\xff\x7e\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x0a\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x21\x00\xff\xff\x23\x00\x24\x00\x25\x00\x26\x00\x27\x00\xff\xff\xff\xff\x2a\x00\x2b\x00\xff\xff\x2d\x00\x2e\x00\x2f\x00\x30\x00\x31\x00\x32\x00\x33\x00\x34\x00\x35\x00\x36\x00\x37\x00\x38\x00\x39\x00\x3a\x00\x2f\x00\x3c\x00\x3d\x00\x3e\x00\x3f\x00\x40\x00\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x47\x00\x48\x00\x49\x00\x4a\x00\x4b\x00\x4c\x00\x4d\x00\x4e\x00\x4f\x00\x50\x00\x51\x00\x52\x00\x53\x00\x54\x00\x55\x00\x56\x00\x57\x00\x58\x00\x59\x00\x5a\x00\xff\xff\x5c\x00\xff\xff\x5e\x00\x5f\x00\xff\xff\x61\x00\x62\x00\x63\x00\x64\x00\x65\x00\x66\x00\x67\x00\x68\x00\x69\x00\x6a\x00\x6b\x00\x6c\x00\x6d\x00\x6e\x00\x6f\x00\x70\x00\x71\x00\x72\x00\x73\x00\x74\x00\x75\x00\x76\x00\x77\x00\x78\x00\x79\x00\x7a\x00\xff\xff\x7c\x00\xff\xff\x7e\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x0a\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x0a\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x3c\x00\xff\xff\x3e\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00\x0a\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x22\x00\x23\x00\xff\xff\xff\xff\x26\x00\x27\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x2f\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x3c\x00\xff\xff\xff\xff\xff\xff\x40\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x5c\x00\x5d\x00\xff\xff\xff\xff\x60\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\x81\x00\x82\x00\x83\x00\x84\x00\x85\x00\x86\x00\x87\x00\x88\x00\x89\x00\x8a\x00\x8b\x00\x8c\x00\x8d\x00\x8e\x00\x8f\x00\x90\x00\x91\x00\x92\x00\x93\x00\x94\x00\x95\x00\x96\x00\x97\x00\x98\x00\x99\x00\x9a\x00\x9b\x00\x9c\x00\x9d\x00\x9e\x00\x9f\x00\xa0\x00\xa1\x00\xa2\x00\xa3\x00\xa4\x00\xa5\x00\xa6\x00\xa7\x00\xa8\x00\xa9\x00\xaa\x00\xab\x00\xac\x00\xad\x00\xae\x00\xaf\x00\xb0\x00\xb1\x00\xb2\x00\xb3\x00\xb4\x00\xb5\x00\xb6\x00\xb7\x00\xb8\x00\xb9\x00\xba\x00\xbb\x00\xbc\x00\xbd\x00\xbe\x00\xbf\x00\xc0\x00\xc1\x00\xc2\x00\xc3\x00\xc4\x00\xc5\x00\xc6\x00\xc7\x00\xc8\x00\xc9\x00\xca\x00\xcb\x00\xcc\x00\xcd\x00\xce\x00\xcf\x00\xd0\x00\xd1\x00\xd2\x00\xd3\x00\xd4\x00\xd5\x00\xd6\x00\xd7\x00\xd8\x00\xd9\x00\xda\x00\xdb\x00\xdc\x00\xdd\x00\xde\x00\xdf\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\xe6\x00\xe7\x00\xe8\x00\xe9\x00\xea\x00\xeb\x00\xec\x00\xed\x00\xee\x00\xef\x00\xf0\x00\xf1\x00\xf2\x00\xf3\x00\xf4\x00\xf5\x00\xf6\x00\xf7\x00\xf8\x00\xf9\x00\xfa\x00\xfb\x00\xfc\x00\xfd\x00\xfe\x00\xff\x00"#++alex_deflt :: AlexAddr+alex_deflt = AlexA# "\xff\xff\x01\x00\x6b\x00\xff\xff\xff\xff\x6b\x00\xff\xff\x07\x00\x08\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x22\x00\x22\x00\x28\x00\x28\x00\x2a\x00\x2a\x00\x2c\x00\x2c\x00\x30\x00\x30\x00\x33\x00\x33\x00\x37\x00\x37\x00\x3b\x00\x3b\x00\x3e\x00\x3e\x00\x41\x00\x41\x00\x42\x00\x42\x00\x42\x00\x43\x00\x43\x00\x48\x00\x48\x00\xff\xff\xff\xff\x4b\x00\x4b\x00\x08\x00\x08\x00\x08\x00\x07\x00\x07\x00\x07\x00\x4c\x00\x4c\x00\x4c\x00\x42\x00\x50\x00\x50\x00\xff\xff\x6b\x00\x6b\x00\x6b\x00\x61\x00\x61\x00\x61\x00\x4c\x00\xff\xff\x01\x00\x01\x00\x01\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x07\x00\x08\x00\xff\xff\xff\xff\x61\x00\x4c\x00\xff\xff\x6b\x00\xff\xff\xff\xff"#++alex_accept = listArray (0::Int,109) [AlexAcc (alex_action_5),AlexAcc (alex_action_9),AlexAccNone,AlexAcc (alex_action_8),AlexAcc (alex_action_5),AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccNone,AlexAccSkip,AlexAcc (alex_action_1),AlexAcc (alex_action_2),AlexAcc (alex_action_3),AlexAcc (alex_action_4),AlexAcc (alex_action_6),AlexAcc (alex_action_7),AlexAcc (alex_action_9),AlexAcc (alex_action_10),AlexAcc (alex_action_11),AlexAcc (alex_action_12),AlexAcc (alex_action_13),AlexAcc (alex_action_14),AlexAcc (alex_action_15),AlexAcc (alex_action_15),AlexAcc (alex_action_16),AlexAcc (alex_action_17),AlexAcc (alex_action_18),AlexAcc (alex_action_19),AlexAcc (alex_action_19),AlexAcc (alex_action_19),AlexAcc (alex_action_19),AlexAcc (alex_action_19),AlexAcc (alex_action_19),AlexAcc (alex_action_20),AlexAcc (alex_action_21),AlexAcc (alex_action_22),AlexAcc (alex_action_23)]+{-# LINE 89 "demo/HaddockLex.x" #-}++data Token+ = TokPara+ | TokNumber+ | TokBullet+ | TokDefStart+ | TokDefEnd+ | TokSpecial Char+ | TokIdent RdrName+ | TokString String+ | TokURL String+ | TokPic String+ | TokEmphasis String+ | TokAName String+ | TokBirdTrack String+ deriving Show++-- -----------------------------------------------------------------------------+-- Alex support stuff++type StartCode = Int+type Action = String -> StartCode -> (StartCode -> Maybe [Token]) -> Maybe [Token]++--TODO: we ought to switch to ByteString input.+type AlexInput = (Char, [Word8], String)++-- | For alex >= 3+--+-- See also alexGetChar+alexGetByte :: AlexInput -> Maybe (Word8,AlexInput)+alexGetByte (c,(b:bs),s) = Just (b,(c,bs,s))+alexGetByte (c,[],[]) = Nothing+alexGetByte (_,[],(c:s)) = case utf8Encode c of+ (b:bs) -> Just (b, (c, bs, s))++-- | Encode a Haskell String to a list of Word8 values, in UTF8 format.+utf8Encode :: Char -> [Word8]+utf8Encode = map fromIntegral . go . ord+ where+ go oc+ | oc <= 0x7f = [oc]++ | oc <= 0x7ff = [ 0xc0 + (oc `Data.Bits.shiftR` 6)+ , 0x80 + oc Data.Bits..&. 0x3f+ ]++ | oc <= 0xffff = [ 0xe0 + (oc `Data.Bits.shiftR` 12)+ , 0x80 + ((oc `Data.Bits.shiftR` 6) Data.Bits..&. 0x3f)+ , 0x80 + oc Data.Bits..&. 0x3f+ ]+ | otherwise = [ 0xf0 + (oc `Data.Bits.shiftR` 18)+ , 0x80 + ((oc `Data.Bits.shiftR` 12) Data.Bits..&. 0x3f)+ , 0x80 + ((oc `Data.Bits.shiftR` 6) Data.Bits..&. 0x3f)+ , 0x80 + oc Data.Bits..&. 0x3f+ ]++-- | For alex < 3+--+-- See also alexGetByte+alexGetChar :: AlexInput -> Maybe (Char, AlexInput)+alexGetChar (_, _, []) = Nothing+alexGetChar (_, _, c:cs) = Just (c, (c,[],cs))++alexInputPrevChar (c,_) = c++tokenise :: String -> Maybe [Token]+tokenise str =+ go ('\n', [], eofHack str) para+ where+ go inp@(_,_,str') sc =+ case alexScan inp sc of+ AlexEOF -> Just []+ AlexError _ -> Nothing+ AlexSkip inp' _ -> go inp' sc+ AlexToken inp' len act -> act (take len str') sc (\sc' -> go inp' sc')++-- NB. we add a final \n to the string, (see comment in the beginning of line+-- production above).+eofHack str = str++"\n"++andBegin :: Action -> StartCode -> Action+andBegin act new_sc = \str _ cont -> act str new_sc cont++token :: Token -> Action+token t = \_ sc cont -> liftM (t :) (cont sc)++strtoken, strtokenNL :: (String -> Token) -> Action+strtoken t = \str sc cont -> liftM (t str :) (cont sc)+strtokenNL t = \str sc cont -> liftM (t (filter (/= '\r') str) :) (cont sc)+-- ^ We only want LF line endings in our internal doc string format, so we+-- filter out all CRs.++begin :: StartCode -> Action+begin sc = \_ _ cont -> cont sc++-- -----------------------------------------------------------------------------+-- Lex a string as a Haskell identifier++ident :: Action+ident str sc cont = liftM (TokIdent str :) (cont sc)+++birdtrack,def,line,para,string :: Int+birdtrack = 1+def = 2+line = 3+para = 4+string = 5+alex_action_1 = begin birdtrack +alex_action_2 = token TokBullet `andBegin` string +alex_action_3 = token TokDefStart `andBegin` def +alex_action_4 = token TokNumber `andBegin` string +alex_action_5 = begin string +alex_action_6 = begin birdtrack +alex_action_7 = token TokPara `andBegin` para +alex_action_8 = begin string +alex_action_9 = strtokenNL TokBirdTrack `andBegin` line +alex_action_10 = strtoken $ \s -> TokSpecial (head s) +alex_action_11 = strtoken $ \s -> TokPic (init $ init $ tail $ tail s) +alex_action_12 = strtoken $ \s -> TokURL (init (tail s)) +alex_action_13 = strtoken $ \s -> TokAName (init (tail s)) +alex_action_14 = strtoken $ \s -> TokEmphasis (init (tail s)) +alex_action_15 = ident +alex_action_16 = strtoken (TokString . tail) +alex_action_17 = strtoken $ \s -> TokString [chr (read (init (drop 2 s)))] +alex_action_18 = strtoken $ \s -> case readHex (init (drop 3 s)) of [(n,_)] -> TokString [chr n]; _ -> error "hexParser: Can't happen" +alex_action_19 = strtoken TokString +alex_action_20 = strtokenNL TokString `andBegin` line +alex_action_21 = strtoken TokString +alex_action_22 = token TokDefEnd `andBegin` string +alex_action_23 = strtoken TokString +{-# LINE 1 "templates/GenericTemplate.hs" #-}+{-# LINE 1 "templates/GenericTemplate.hs" #-}+{-# LINE 1 "<command-line>" #-}+{-# LINE 1 "templates/GenericTemplate.hs" #-}+-- -----------------------------------------------------------------------------+-- ALEX TEMPLATE+--+-- This code is in the PUBLIC DOMAIN; you may copy it freely and use+-- it for any purpose whatsoever.++-- -----------------------------------------------------------------------------+-- INTERNALS and main scanner engine++{-# LINE 21 "templates/GenericTemplate.hs" #-}++++++-- Do not remove this comment. Required to fix CPP parsing when using GCC and a clang-compiled alex.+#if __GLASGOW_HASKELL__ > 706+#define GTE(n,m) (tagToEnum# (n >=# m))+#define EQ(n,m) (tagToEnum# (n ==# m))+#else+#define GTE(n,m) (n >=# m)+#define EQ(n,m) (n ==# m)+#endif+{-# LINE 51 "templates/GenericTemplate.hs" #-}+++data AlexAddr = AlexA# Addr#+-- Do not remove this comment. Required to fix CPP parsing when using GCC and a clang-compiled alex.+#if __GLASGOW_HASKELL__ < 503+uncheckedShiftL# = shiftL#+#endif++{-# INLINE alexIndexInt16OffAddr #-}+alexIndexInt16OffAddr (AlexA# arr) off =+#ifdef WORDS_BIGENDIAN+ narrow16Int# i+ where+ i = word2Int# ((high `uncheckedShiftL#` 8#) `or#` low)+ high = int2Word# (ord# (indexCharOffAddr# arr (off' +# 1#)))+ low = int2Word# (ord# (indexCharOffAddr# arr off'))+ off' = off *# 2#+#else+ indexInt16OffAddr# arr off+#endif++++++{-# INLINE alexIndexInt32OffAddr #-}+alexIndexInt32OffAddr (AlexA# arr) off = +#ifdef WORDS_BIGENDIAN+ narrow32Int# i+ where+ i = word2Int# ((b3 `uncheckedShiftL#` 24#) `or#`+ (b2 `uncheckedShiftL#` 16#) `or#`+ (b1 `uncheckedShiftL#` 8#) `or#` b0)+ b3 = int2Word# (ord# (indexCharOffAddr# arr (off' +# 3#)))+ b2 = int2Word# (ord# (indexCharOffAddr# arr (off' +# 2#)))+ b1 = int2Word# (ord# (indexCharOffAddr# arr (off' +# 1#)))+ b0 = int2Word# (ord# (indexCharOffAddr# arr off'))+ off' = off *# 4#+#else+ indexInt32OffAddr# arr off+#endif+++++++#if __GLASGOW_HASKELL__ < 503+quickIndex arr i = arr ! i+#else+-- GHC >= 503, unsafeAt is available from Data.Array.Base.+quickIndex = unsafeAt+#endif+++++-- -----------------------------------------------------------------------------+-- Main lexing routines++data AlexReturn a+ = AlexEOF+ | AlexError !AlexInput+ | AlexSkip !AlexInput !Int+ | AlexToken !AlexInput !Int a++-- alexScan :: AlexInput -> StartCode -> AlexReturn a+alexScan input (I# (sc))+ = alexScanUser undefined input (I# (sc))++alexScanUser user input (I# (sc))+ = case alex_scan_tkn user input 0# input sc AlexNone of+ (AlexNone, input') ->+ case alexGetByte input of+ Nothing -> ++++ AlexEOF+ Just _ ->++++ AlexError input'++ (AlexLastSkip input'' len, _) ->++++ AlexSkip input'' len++ (AlexLastAcc k input''' len, _) ->++++ AlexToken input''' len k+++-- Push the input through the DFA, remembering the most recent accepting+-- state it encountered.++alex_scan_tkn user orig_input len input s last_acc =+ input `seq` -- strict in the input+ let + new_acc = (check_accs (alex_accept `quickIndex` (I# (s))))+ in+ new_acc `seq`+ case alexGetByte input of+ Nothing -> (new_acc, input)+ Just (c, new_input) -> ++++ case fromIntegral c of { (I# (ord_c)) ->+ let+ base = alexIndexInt32OffAddr alex_base s+ offset = (base +# ord_c)+ check = alexIndexInt16OffAddr alex_check offset+ + new_s = if GTE(offset,0#) && EQ(check,ord_c)+ then alexIndexInt16OffAddr alex_table offset+ else alexIndexInt16OffAddr alex_deflt s+ in+ case new_s of+ -1# -> (new_acc, input)+ -- on an error, we want to keep the input *before* the+ -- character that failed, not after.+ _ -> alex_scan_tkn user orig_input (if c < 0x80 || c >= 0xC0 then (len +# 1#) else len)+ -- note that the length is increased ONLY if this is the 1st byte in a char encoding)+ new_input new_s new_acc+ }+ where+ check_accs (AlexAccNone) = last_acc+ check_accs (AlexAcc a ) = AlexLastAcc a input (I# (len))+ check_accs (AlexAccSkip) = AlexLastSkip input (I# (len))+{-# LINE 198 "templates/GenericTemplate.hs" #-}++data AlexLastAcc a+ = AlexNone+ | AlexLastAcc a !AlexInput !Int+ | AlexLastSkip !AlexInput !Int++instance Functor AlexLastAcc where+ fmap f AlexNone = AlexNone+ fmap f (AlexLastAcc x y z) = AlexLastAcc (f x) y z+ fmap f (AlexLastSkip x y) = AlexLastSkip x y++data AlexAcc a user+ = AlexAccNone+ | AlexAcc a+ | AlexAccSkip+{-# LINE 242 "templates/GenericTemplate.hs" #-}++-- used by wrappers+iUnbox (I# (i)) = i
+ dist/build/search-demo/search-demo-tmp/HaddockParse.hs view
@@ -0,0 +1,732 @@+{-# OPTIONS_GHC -w #-}+{-# OPTIONS -fglasgow-exts -cpp #-}+-- Disable warnings that the generated code causes+{-# OPTIONS_GHC -fno-warn-deprecated-flags+ -fno-warn-missing-signatures+ -fno-warn-unused-binds+ -fno-warn-unused-matches+ -fno-warn-lazy-unlifted-bindings+ -fno-warn-name-shadowing+ -fno-warn-incomplete-patterns+ -fno-warn-tabs #-}+module HaddockParse (parseHaddockParagraphs) where++import HaddockLex+import HaddockHtml+import HaddockTypes+import Data.Char (isSpace)+import qualified Data.Array as Happy_Data_Array+import qualified GHC.Exts as Happy_GHC_Exts++-- parser produced by Happy Version 1.19.2++newtype HappyAbsSyn = HappyAbsSyn HappyAny+#if __GLASGOW_HASKELL__ >= 607+type HappyAny = Happy_GHC_Exts.Any+#else+type HappyAny = forall a . a+#endif+happyIn5 :: (Doc RdrName) -> (HappyAbsSyn )+happyIn5 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyIn5 #-}+happyOut5 :: (HappyAbsSyn ) -> (Doc RdrName)+happyOut5 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyOut5 #-}+happyIn6 :: (Doc RdrName) -> (HappyAbsSyn )+happyIn6 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyIn6 #-}+happyOut6 :: (HappyAbsSyn ) -> (Doc RdrName)+happyOut6 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyOut6 #-}+happyIn7 :: (Doc RdrName) -> (HappyAbsSyn )+happyIn7 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyIn7 #-}+happyOut7 :: (HappyAbsSyn ) -> (Doc RdrName)+happyOut7 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyOut7 #-}+happyIn8 :: (Doc RdrName) -> (HappyAbsSyn )+happyIn8 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyIn8 #-}+happyOut8 :: (HappyAbsSyn ) -> (Doc RdrName)+happyOut8 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyOut8 #-}+happyIn9 :: ((Doc RdrName, Doc RdrName)) -> (HappyAbsSyn )+happyIn9 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyIn9 #-}+happyOut9 :: (HappyAbsSyn ) -> ((Doc RdrName, Doc RdrName))+happyOut9 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyOut9 #-}+happyIn10 :: (Doc RdrName) -> (HappyAbsSyn )+happyIn10 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyIn10 #-}+happyOut10 :: (HappyAbsSyn ) -> (Doc RdrName)+happyOut10 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyOut10 #-}+happyIn11 :: (Doc RdrName) -> (HappyAbsSyn )+happyIn11 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyIn11 #-}+happyOut11 :: (HappyAbsSyn ) -> (Doc RdrName)+happyOut11 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyOut11 #-}+happyIn12 :: (Doc RdrName) -> (HappyAbsSyn )+happyIn12 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyIn12 #-}+happyOut12 :: (HappyAbsSyn ) -> (Doc RdrName)+happyOut12 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyOut12 #-}+happyIn13 :: (Doc RdrName) -> (HappyAbsSyn )+happyIn13 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyIn13 #-}+happyOut13 :: (HappyAbsSyn ) -> (Doc RdrName)+happyOut13 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyOut13 #-}+happyIn14 :: (Doc RdrName) -> (HappyAbsSyn )+happyIn14 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyIn14 #-}+happyOut14 :: (HappyAbsSyn ) -> (Doc RdrName)+happyOut14 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyOut14 #-}+happyIn15 :: (Doc RdrName) -> (HappyAbsSyn )+happyIn15 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyIn15 #-}+happyOut15 :: (HappyAbsSyn ) -> (Doc RdrName)+happyOut15 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyOut15 #-}+happyIn16 :: (String) -> (HappyAbsSyn )+happyIn16 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyIn16 #-}+happyOut16 :: (HappyAbsSyn ) -> (String)+happyOut16 x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyOut16 #-}+happyInTok :: (Token) -> (HappyAbsSyn )+happyInTok x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyInTok #-}+happyOutTok :: (HappyAbsSyn ) -> (Token)+happyOutTok x = Happy_GHC_Exts.unsafeCoerce# x+{-# INLINE happyOutTok #-}+++happyActOffsets :: HappyAddr+happyActOffsets = HappyA# "\x01\x00\x27\x00\x0f\x00\x4f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x27\x00\x00\x00\x53\x00\x27\x00\x47\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1b\x00\x1b\x00\x3f\x00\x00\x00\x00\x00\x30\x00\x30\x00\x23\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x26\x00\x17\x00\x21\x00\x1d\x00\x53\x00\x53\x00\x00\x00\x00\x00\x00\x00\x00\x00\x27\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"#++happyGotoOffsets :: HappyAddr+happyGotoOffsets = HappyA# "\x4c\x00\x79\x00\x61\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x75\x00\x00\x00\x7d\x00\x71\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x6d\x00\x67\x00\xfe\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x41\x00\x00\x00\x36\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\xff\x00\x00\x00\x00\x7b\x00\x54\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"#++happyDefActions :: HappyAddr+happyDefActions = HappyA# "\xfa\xff\x00\x00\x00\x00\x00\x00\xf9\xff\xf8\xff\xf7\xff\xf6\xff\xf1\xff\xf2\xff\xed\xff\xec\xff\x00\x00\x00\x00\x00\x00\xe5\xff\xe4\xff\xe3\xff\xe6\xff\x00\x00\x00\x00\xef\xff\xe2\xff\xe7\xff\x00\x00\x00\x00\xfb\xff\xfa\xff\xfc\xff\xfa\xff\xf0\xff\xf4\xff\xf5\xff\x00\x00\xe0\xff\x00\x00\x00\x00\xe8\xff\x00\x00\xee\xff\xea\xff\xe9\xff\xeb\xff\x00\x00\xdf\xff\xe1\xff\xfd\xff\xf3\xff"#++happyCheck :: HappyAddr+happyCheck = HappyA# "\xff\xff\x0b\x00\x01\x00\x02\x00\x06\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x09\x00\x0a\x00\x0b\x00\x0c\x00\x0d\x00\x0e\x00\x01\x00\x02\x00\x0b\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x09\x00\x0a\x00\x0b\x00\x0c\x00\x01\x00\x0e\x00\x01\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x03\x00\x0e\x00\x0b\x00\x0c\x00\x01\x00\x0e\x00\x04\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x0d\x00\x07\x00\x08\x00\x0c\x00\x0a\x00\x0e\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x0f\x00\x0a\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x0b\x00\x0a\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x0e\x00\x0a\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x0d\x00\x09\x00\x0a\x00\x0c\x00\x0d\x00\x0e\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\xff\xff\x0a\x00\x05\x00\x06\x00\x07\x00\x08\x00\xff\xff\x0a\x00\x05\x00\x06\x00\x07\x00\x08\x00\xff\xff\x0a\x00\x07\x00\x08\x00\xff\xff\x0a\x00\x07\x00\x08\x00\xff\xff\x0a\x00\x07\x00\x08\x00\xff\xff\x0a\x00\x09\x00\x0a\x00\x09\x00\x0a\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"#++happyTable :: HappyAddr+happyTable = HappyA# "\x00\x00\x2c\x00\x0d\x00\x0e\x00\x1e\x00\x0f\x00\x10\x00\x11\x00\x12\x00\x13\x00\x14\x00\x15\x00\x16\x00\x17\x00\x1c\x00\x18\x00\x0d\x00\x0e\x00\x21\x00\x0f\x00\x10\x00\x11\x00\x12\x00\x13\x00\x14\x00\x15\x00\x16\x00\x17\x00\x0d\x00\x18\x00\x2b\x00\x0f\x00\x10\x00\x11\x00\x12\x00\x13\x00\x2c\x00\x23\x00\x16\x00\x17\x00\x0d\x00\x18\x00\x2e\x00\x0f\x00\x10\x00\x11\x00\x12\x00\x13\x00\x1e\x00\x2f\x00\x0a\x00\x17\x00\x0b\x00\x18\x00\x2e\x00\x1a\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x09\x00\x0a\x00\xff\xff\x0b\x00\x1c\x00\x1a\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x09\x00\x0a\x00\x16\x00\x0b\x00\x19\x00\x1a\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x09\x00\x0a\x00\x23\x00\x0b\x00\x0f\x00\x10\x00\x11\x00\x12\x00\x13\x00\x1e\x00\x28\x00\x25\x00\x17\x00\x27\x00\x18\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x09\x00\x0a\x00\x00\x00\x0b\x00\x1f\x00\x08\x00\x09\x00\x0a\x00\x00\x00\x0b\x00\x20\x00\x08\x00\x09\x00\x0a\x00\x00\x00\x0b\x00\x23\x00\x0a\x00\x00\x00\x0b\x00\x27\x00\x0a\x00\x00\x00\x0b\x00\x18\x00\x0a\x00\x00\x00\x0b\x00\x29\x00\x25\x00\x24\x00\x25\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"#++happyReduceArr = Happy_Data_Array.array (2, 32) [+ (2 , happyReduce_2),+ (3 , happyReduce_3),+ (4 , happyReduce_4),+ (5 , happyReduce_5),+ (6 , happyReduce_6),+ (7 , happyReduce_7),+ (8 , happyReduce_8),+ (9 , happyReduce_9),+ (10 , happyReduce_10),+ (11 , happyReduce_11),+ (12 , happyReduce_12),+ (13 , happyReduce_13),+ (14 , happyReduce_14),+ (15 , happyReduce_15),+ (16 , happyReduce_16),+ (17 , happyReduce_17),+ (18 , happyReduce_18),+ (19 , happyReduce_19),+ (20 , happyReduce_20),+ (21 , happyReduce_21),+ (22 , happyReduce_22),+ (23 , happyReduce_23),+ (24 , happyReduce_24),+ (25 , happyReduce_25),+ (26 , happyReduce_26),+ (27 , happyReduce_27),+ (28 , happyReduce_28),+ (29 , happyReduce_29),+ (30 , happyReduce_30),+ (31 , happyReduce_31),+ (32 , happyReduce_32)+ ]++happy_n_terms = 16 :: Int+happy_n_nonterms = 12 :: Int++happyReduce_2 = happySpecReduce_3 0# happyReduction_2+happyReduction_2 happy_x_3+ happy_x_2+ happy_x_1+ = case happyOut6 happy_x_1 of { happy_var_1 -> + case happyOut5 happy_x_3 of { happy_var_3 -> + happyIn5+ (docAppend happy_var_1 happy_var_3+ )}}++happyReduce_3 = happySpecReduce_2 0# happyReduction_3+happyReduction_3 happy_x_2+ happy_x_1+ = case happyOut5 happy_x_2 of { happy_var_2 -> + happyIn5+ (happy_var_2+ )}++happyReduce_4 = happySpecReduce_1 0# happyReduction_4+happyReduction_4 happy_x_1+ = case happyOut6 happy_x_1 of { happy_var_1 -> + happyIn5+ (happy_var_1+ )}++happyReduce_5 = happySpecReduce_0 0# happyReduction_5+happyReduction_5 = happyIn5+ (DocEmpty+ )++happyReduce_6 = happySpecReduce_1 1# happyReduction_6+happyReduction_6 happy_x_1+ = case happyOut7 happy_x_1 of { happy_var_1 -> + happyIn6+ (DocUnorderedList [happy_var_1]+ )}++happyReduce_7 = happySpecReduce_1 1# happyReduction_7+happyReduction_7 happy_x_1+ = case happyOut8 happy_x_1 of { happy_var_1 -> + happyIn6+ (DocOrderedList [happy_var_1]+ )}++happyReduce_8 = happySpecReduce_1 1# happyReduction_8+happyReduction_8 happy_x_1+ = case happyOut9 happy_x_1 of { happy_var_1 -> + happyIn6+ (DocDefList [happy_var_1]+ )}++happyReduce_9 = happySpecReduce_1 1# happyReduction_9+happyReduction_9 happy_x_1+ = case happyOut10 happy_x_1 of { happy_var_1 -> + happyIn6+ (happy_var_1+ )}++happyReduce_10 = happySpecReduce_2 2# happyReduction_10+happyReduction_10 happy_x_2+ happy_x_1+ = case happyOut10 happy_x_2 of { happy_var_2 -> + happyIn7+ (happy_var_2+ )}++happyReduce_11 = happySpecReduce_2 3# happyReduction_11+happyReduction_11 happy_x_2+ happy_x_1+ = case happyOut10 happy_x_2 of { happy_var_2 -> + happyIn8+ (happy_var_2+ )}++happyReduce_12 = happyReduce 4# 4# happyReduction_12+happyReduction_12 (happy_x_4 `HappyStk`+ happy_x_3 `HappyStk`+ happy_x_2 `HappyStk`+ happy_x_1 `HappyStk`+ happyRest)+ = case happyOut12 happy_x_2 of { happy_var_2 -> + case happyOut12 happy_x_4 of { happy_var_4 -> + happyIn9+ ((happy_var_2, happy_var_4)+ ) `HappyStk` happyRest}}++happyReduce_13 = happySpecReduce_1 5# happyReduction_13+happyReduction_13 happy_x_1+ = case happyOut12 happy_x_1 of { happy_var_1 -> + happyIn10+ (docParagraph happy_var_1+ )}++happyReduce_14 = happySpecReduce_1 5# happyReduction_14+happyReduction_14 happy_x_1+ = case happyOut11 happy_x_1 of { happy_var_1 -> + happyIn10+ (DocCodeBlock happy_var_1+ )}++happyReduce_15 = happySpecReduce_2 6# happyReduction_15+happyReduction_15 happy_x_2+ happy_x_1+ = case happyOutTok happy_x_1 of { (TokBirdTrack happy_var_1) -> + case happyOut11 happy_x_2 of { happy_var_2 -> + happyIn11+ (docAppend (DocString happy_var_1) happy_var_2+ )}}++happyReduce_16 = happySpecReduce_1 6# happyReduction_16+happyReduction_16 happy_x_1+ = case happyOutTok happy_x_1 of { (TokBirdTrack happy_var_1) -> + happyIn11+ (DocString happy_var_1+ )}++happyReduce_17 = happySpecReduce_2 7# happyReduction_17+happyReduction_17 happy_x_2+ happy_x_1+ = case happyOut13 happy_x_1 of { happy_var_1 -> + case happyOut12 happy_x_2 of { happy_var_2 -> + happyIn12+ (docAppend happy_var_1 happy_var_2+ )}}++happyReduce_18 = happySpecReduce_1 7# happyReduction_18+happyReduction_18 happy_x_1+ = case happyOut13 happy_x_1 of { happy_var_1 -> + happyIn12+ (happy_var_1+ )}++happyReduce_19 = happySpecReduce_1 8# happyReduction_19+happyReduction_19 happy_x_1+ = case happyOut15 happy_x_1 of { happy_var_1 -> + happyIn13+ (happy_var_1+ )}++happyReduce_20 = happySpecReduce_3 8# happyReduction_20+happyReduction_20 happy_x_3+ happy_x_2+ happy_x_1+ = case happyOut14 happy_x_2 of { happy_var_2 -> + happyIn13+ (DocMonospaced happy_var_2+ )}++happyReduce_21 = happySpecReduce_2 9# happyReduction_21+happyReduction_21 happy_x_2+ happy_x_1+ = case happyOut14 happy_x_2 of { happy_var_2 -> + happyIn14+ (docAppend (DocString "\n") happy_var_2+ )}++happyReduce_22 = happySpecReduce_2 9# happyReduction_22+happyReduction_22 happy_x_2+ happy_x_1+ = case happyOut15 happy_x_1 of { happy_var_1 -> + case happyOut14 happy_x_2 of { happy_var_2 -> + happyIn14+ (docAppend happy_var_1 happy_var_2+ )}}++happyReduce_23 = happySpecReduce_1 9# happyReduction_23+happyReduction_23 happy_x_1+ = case happyOut15 happy_x_1 of { happy_var_1 -> + happyIn14+ (happy_var_1+ )}++happyReduce_24 = happySpecReduce_1 10# happyReduction_24+happyReduction_24 happy_x_1+ = case happyOutTok happy_x_1 of { (TokString happy_var_1) -> + happyIn15+ (DocString happy_var_1+ )}++happyReduce_25 = happySpecReduce_1 10# happyReduction_25+happyReduction_25 happy_x_1+ = case happyOutTok happy_x_1 of { (TokEmphasis happy_var_1) -> + happyIn15+ (DocEmphasis (DocString happy_var_1)+ )}++happyReduce_26 = happySpecReduce_1 10# happyReduction_26+happyReduction_26 happy_x_1+ = case happyOutTok happy_x_1 of { (TokURL happy_var_1) -> + happyIn15+ (DocHyperlink (makeHyperlink happy_var_1)+ )}++happyReduce_27 = happySpecReduce_1 10# happyReduction_27+happyReduction_27 happy_x_1+ = case happyOutTok happy_x_1 of { (TokPic happy_var_1) -> + happyIn15+ (DocPic happy_var_1+ )}++happyReduce_28 = happySpecReduce_1 10# happyReduction_28+happyReduction_28 happy_x_1+ = case happyOutTok happy_x_1 of { (TokAName happy_var_1) -> + happyIn15+ (DocAName happy_var_1+ )}++happyReduce_29 = happySpecReduce_1 10# happyReduction_29+happyReduction_29 happy_x_1+ = case happyOutTok happy_x_1 of { (TokIdent happy_var_1) -> + happyIn15+ (DocIdentifier happy_var_1+ )}++happyReduce_30 = happySpecReduce_3 10# happyReduction_30+happyReduction_30 happy_x_3+ happy_x_2+ happy_x_1+ = case happyOut16 happy_x_2 of { happy_var_2 -> + happyIn15+ (DocModule happy_var_2+ )}++happyReduce_31 = happySpecReduce_1 11# happyReduction_31+happyReduction_31 happy_x_1+ = case happyOutTok happy_x_1 of { (TokString happy_var_1) -> + happyIn16+ (happy_var_1+ )}++happyReduce_32 = happySpecReduce_2 11# happyReduction_32+happyReduction_32 happy_x_2+ happy_x_1+ = case happyOutTok happy_x_1 of { (TokString happy_var_1) -> + case happyOut16 happy_x_2 of { happy_var_2 -> + happyIn16+ (happy_var_1 ++ happy_var_2+ )}}++happyNewToken action sts stk [] =+ happyDoAction 15# notHappyAtAll action sts stk []++happyNewToken action sts stk (tk:tks) =+ let cont i = happyDoAction i tk action sts stk tks in+ case tk of {+ TokSpecial '@' -> cont 1#;+ TokDefStart -> cont 2#;+ TokDefEnd -> cont 3#;+ TokSpecial '\"' -> cont 4#;+ TokURL happy_dollar_dollar -> cont 5#;+ TokPic happy_dollar_dollar -> cont 6#;+ TokAName happy_dollar_dollar -> cont 7#;+ TokEmphasis happy_dollar_dollar -> cont 8#;+ TokBullet -> cont 9#;+ TokNumber -> cont 10#;+ TokBirdTrack happy_dollar_dollar -> cont 11#;+ TokIdent happy_dollar_dollar -> cont 12#;+ TokPara -> cont 13#;+ TokString happy_dollar_dollar -> cont 14#;+ _ -> happyError' (tk:tks)+ }++happyError_ 15# tk tks = happyError' tks+happyError_ _ tk tks = happyError' (tk:tks)++happyThen :: () => Maybe a -> (a -> Maybe b) -> Maybe b+happyThen = (>>=)+happyReturn :: () => a -> Maybe a+happyReturn = (return)+happyThen1 m k tks = (>>=) m (\a -> k a tks)+happyReturn1 :: () => a -> b -> Maybe a+happyReturn1 = \a tks -> (return) a+happyError' :: () => [(Token)] -> Maybe a+happyError' = happyError++parseHaddockParagraphs tks = happySomeParser where+ happySomeParser = happyThen (happyParse 0# tks) (\x -> happyReturn (happyOut5 x))++parseHaddockString tks = happySomeParser where+ happySomeParser = happyThen (happyParse 1# tks) (\x -> happyReturn (happyOut12 x))++happySeq = happyDontSeq+++happyError :: [Token] -> Maybe a+happyError toks = Nothing++-- | Create a `Hyperlink` from given string.+--+-- A hyperlink consists of a URL and an optional label. The label is separated+-- from the url by one or more whitespace characters.+makeHyperlink :: String -> Hyperlink+makeHyperlink input = case break isSpace $ strip input of+ (url, "") -> Hyperlink url Nothing+ (url, label) -> Hyperlink url (Just . dropWhile isSpace $ label)++-- | Remove all leading and trailing whitespace+strip :: String -> String+strip = dropWhile isSpace . reverse . dropWhile isSpace . reverse+{-# LINE 1 "templates/GenericTemplate.hs" #-}+{-# LINE 1 "templates/GenericTemplate.hs" #-}+{-# LINE 1 "<command-line>" #-}+{-# LINE 1 "templates/GenericTemplate.hs" #-}+-- Id: GenericTemplate.hs,v 1.26 2005/01/14 14:47:22 simonmar Exp ++{-# LINE 13 "templates/GenericTemplate.hs" #-}++++++-- Do not remove this comment. Required to fix CPP parsing when using GCC and a clang-compiled alex.+#if __GLASGOW_HASKELL__ > 706+#define LT(n,m) ((Happy_GHC_Exts.tagToEnum# (n Happy_GHC_Exts.<# m)) :: Bool)+#define GTE(n,m) ((Happy_GHC_Exts.tagToEnum# (n Happy_GHC_Exts.>=# m)) :: Bool)+#define EQ(n,m) ((Happy_GHC_Exts.tagToEnum# (n Happy_GHC_Exts.==# m)) :: Bool)+#else+#define LT(n,m) (n Happy_GHC_Exts.<# m)+#define GTE(n,m) (n Happy_GHC_Exts.>=# m)+#define EQ(n,m) (n Happy_GHC_Exts.==# m)+#endif+{-# LINE 46 "templates/GenericTemplate.hs" #-}+++data Happy_IntList = HappyCons Happy_GHC_Exts.Int# Happy_IntList++++++{-# LINE 67 "templates/GenericTemplate.hs" #-}++{-# LINE 77 "templates/GenericTemplate.hs" #-}++{-# LINE 86 "templates/GenericTemplate.hs" #-}++infixr 9 `HappyStk`+data HappyStk a = HappyStk a (HappyStk a)++-----------------------------------------------------------------------------+-- starting the parse++happyParse start_state = happyNewToken start_state notHappyAtAll notHappyAtAll++-----------------------------------------------------------------------------+-- Accepting the parse++-- If the current token is 0#, it means we've just accepted a partial+-- parse (a %partial parser). We must ignore the saved token on the top of+-- the stack in this case.+happyAccept 0# tk st sts (_ `HappyStk` ans `HappyStk` _) =+ happyReturn1 ans+happyAccept j tk st sts (HappyStk ans _) = + (happyTcHack j (happyTcHack st)) (happyReturn1 ans)++-----------------------------------------------------------------------------+-- Arrays only: do the next action++++happyDoAction i tk st+ = {- nothing -}+++ case action of+ 0# -> {- nothing -}+ happyFail i tk st+ -1# -> {- nothing -}+ happyAccept i tk st+ n | LT(n,(0# :: Happy_GHC_Exts.Int#)) -> {- nothing -}++ (happyReduceArr Happy_Data_Array.! rule) i tk st+ where rule = (Happy_GHC_Exts.I# ((Happy_GHC_Exts.negateInt# ((n Happy_GHC_Exts.+# (1# :: Happy_GHC_Exts.Int#))))))+ n -> {- nothing -}+++ happyShift new_state i tk st+ where new_state = (n Happy_GHC_Exts.-# (1# :: Happy_GHC_Exts.Int#))+ where off = indexShortOffAddr happyActOffsets st+ off_i = (off Happy_GHC_Exts.+# i)+ check = if GTE(off_i,(0# :: Happy_GHC_Exts.Int#))+ then EQ(indexShortOffAddr happyCheck off_i, i)+ else False+ action+ | check = indexShortOffAddr happyTable off_i+ | otherwise = indexShortOffAddr happyDefActions st+++indexShortOffAddr (HappyA# arr) off =+ Happy_GHC_Exts.narrow16Int# i+ where+ i = Happy_GHC_Exts.word2Int# (Happy_GHC_Exts.or# (Happy_GHC_Exts.uncheckedShiftL# high 8#) low)+ high = Happy_GHC_Exts.int2Word# (Happy_GHC_Exts.ord# (Happy_GHC_Exts.indexCharOffAddr# arr (off' Happy_GHC_Exts.+# 1#)))+ low = Happy_GHC_Exts.int2Word# (Happy_GHC_Exts.ord# (Happy_GHC_Exts.indexCharOffAddr# arr off'))+ off' = off Happy_GHC_Exts.*# 2#++++++data HappyAddr = HappyA# Happy_GHC_Exts.Addr#+++++-----------------------------------------------------------------------------+-- HappyState data type (not arrays)++{-# LINE 170 "templates/GenericTemplate.hs" #-}++-----------------------------------------------------------------------------+-- Shifting a token++happyShift new_state 0# tk st sts stk@(x `HappyStk` _) =+ let i = (case Happy_GHC_Exts.unsafeCoerce# x of { (Happy_GHC_Exts.I# (i)) -> i }) in+-- trace "shifting the error token" $+ happyDoAction i tk new_state (HappyCons (st) (sts)) (stk)++happyShift new_state i tk st sts stk =+ happyNewToken new_state (HappyCons (st) (sts)) ((happyInTok (tk))`HappyStk`stk)++-- happyReduce is specialised for the common cases.++happySpecReduce_0 i fn 0# tk st sts stk+ = happyFail 0# tk st sts stk+happySpecReduce_0 nt fn j tk st@((action)) sts stk+ = happyGoto nt j tk st (HappyCons (st) (sts)) (fn `HappyStk` stk)++happySpecReduce_1 i fn 0# tk st sts stk+ = happyFail 0# tk st sts stk+happySpecReduce_1 nt fn j tk _ sts@((HappyCons (st@(action)) (_))) (v1`HappyStk`stk')+ = let r = fn v1 in+ happySeq r (happyGoto nt j tk st sts (r `HappyStk` stk'))++happySpecReduce_2 i fn 0# tk st sts stk+ = happyFail 0# tk st sts stk+happySpecReduce_2 nt fn j tk _ (HappyCons (_) (sts@((HappyCons (st@(action)) (_))))) (v1`HappyStk`v2`HappyStk`stk')+ = let r = fn v1 v2 in+ happySeq r (happyGoto nt j tk st sts (r `HappyStk` stk'))++happySpecReduce_3 i fn 0# tk st sts stk+ = happyFail 0# tk st sts stk+happySpecReduce_3 nt fn j tk _ (HappyCons (_) ((HappyCons (_) (sts@((HappyCons (st@(action)) (_))))))) (v1`HappyStk`v2`HappyStk`v3`HappyStk`stk')+ = let r = fn v1 v2 v3 in+ happySeq r (happyGoto nt j tk st sts (r `HappyStk` stk'))++happyReduce k i fn 0# tk st sts stk+ = happyFail 0# tk st sts stk+happyReduce k nt fn j tk st sts stk+ = case happyDrop (k Happy_GHC_Exts.-# (1# :: Happy_GHC_Exts.Int#)) sts of+ sts1@((HappyCons (st1@(action)) (_))) ->+ let r = fn stk in -- it doesn't hurt to always seq here...+ happyDoSeq r (happyGoto nt j tk st1 sts1 r)++happyMonadReduce k nt fn 0# tk st sts stk+ = happyFail 0# tk st sts stk+happyMonadReduce k nt fn j tk st sts stk =+ case happyDrop k (HappyCons (st) (sts)) of+ sts1@((HappyCons (st1@(action)) (_))) ->+ let drop_stk = happyDropStk k stk in+ happyThen1 (fn stk tk) (\r -> happyGoto nt j tk st1 sts1 (r `HappyStk` drop_stk))++happyMonad2Reduce k nt fn 0# tk st sts stk+ = happyFail 0# tk st sts stk+happyMonad2Reduce k nt fn j tk st sts stk =+ case happyDrop k (HappyCons (st) (sts)) of+ sts1@((HappyCons (st1@(action)) (_))) ->+ let drop_stk = happyDropStk k stk++ off = indexShortOffAddr happyGotoOffsets st1+ off_i = (off Happy_GHC_Exts.+# nt)+ new_state = indexShortOffAddr happyTable off_i++++ in+ happyThen1 (fn stk tk) (\r -> happyNewToken new_state sts1 (r `HappyStk` drop_stk))++happyDrop 0# l = l+happyDrop n (HappyCons (_) (t)) = happyDrop (n Happy_GHC_Exts.-# (1# :: Happy_GHC_Exts.Int#)) t++happyDropStk 0# l = l+happyDropStk n (x `HappyStk` xs) = happyDropStk (n Happy_GHC_Exts.-# (1#::Happy_GHC_Exts.Int#)) xs++-----------------------------------------------------------------------------+-- Moving to a new state after a reduction+++happyGoto nt j tk st = + {- nothing -}+ happyDoAction j tk new_state+ where off = indexShortOffAddr happyGotoOffsets st+ off_i = (off Happy_GHC_Exts.+# nt)+ new_state = indexShortOffAddr happyTable off_i+++++-----------------------------------------------------------------------------+-- Error recovery (0# is the error token)++-- parse error if we are in recovery and we fail again+happyFail 0# tk old_st _ stk@(x `HappyStk` _) =+ let i = (case Happy_GHC_Exts.unsafeCoerce# x of { (Happy_GHC_Exts.I# (i)) -> i }) in+-- trace "failing" $ + happyError_ i tk++{- We don't need state discarding for our restricted implementation of+ "error". In fact, it can cause some bogus parses, so I've disabled it+ for now --SDM++-- discard a state+happyFail 0# tk old_st (HappyCons ((action)) (sts)) + (saved_tok `HappyStk` _ `HappyStk` stk) =+-- trace ("discarding state, depth " ++ show (length stk)) $+ happyDoAction 0# tk action sts ((saved_tok`HappyStk`stk))+-}++-- Enter error recovery: generate an error token,+-- save the old token and carry on.+happyFail i tk (action) sts stk =+-- trace "entering error recovery" $+ happyDoAction 0# tk action sts ( (Happy_GHC_Exts.unsafeCoerce# (Happy_GHC_Exts.I# (i))) `HappyStk` stk)++-- Internal happy errors:++notHappyAtAll :: a+notHappyAtAll = error "Internal Happy error\n"++-----------------------------------------------------------------------------+-- Hack to get the typechecker to accept our action functions+++happyTcHack :: Happy_GHC_Exts.Int# -> a -> a+happyTcHack x y = y+{-# INLINE happyTcHack #-}+++-----------------------------------------------------------------------------+-- Seq-ing. If the --strict flag is given, then Happy emits +-- happySeq = happyDoSeq+-- otherwise it emits+-- happySeq = happyDontSeq++happyDoSeq, happyDontSeq :: a -> b -> b+happyDoSeq a b = a `seq` b+happyDontSeq a b = b++-----------------------------------------------------------------------------+-- Don't inline any functions from the template. GHC has a nasty habit+-- of deciding to inline happyGoto everywhere, which increases the size of+-- the generated parser quite a bit.+++{-# NOINLINE happyDoAction #-}+{-# NOINLINE happyTable #-}+{-# NOINLINE happyCheck #-}+{-# NOINLINE happyActOffsets #-}+{-# NOINLINE happyGotoOffsets #-}+{-# NOINLINE happyDefActions #-}++{-# NOINLINE happyShift #-}+{-# NOINLINE happySpecReduce_0 #-}+{-# NOINLINE happySpecReduce_1 #-}+{-# NOINLINE happySpecReduce_2 #-}+{-# NOINLINE happySpecReduce_3 #-}+{-# NOINLINE happyReduce #-}+{-# NOINLINE happyMonadReduce #-}+{-# NOINLINE happyGoto #-}+{-# NOINLINE happyFail #-}++-- end of Happy Template.
full-text-search.cabal view
@@ -1,11 +1,13 @@ name: full-text-search-version: 0.2.0.0+version: 0.2.1.0 synopsis: In-memory full text search engine description: An in-memory full text search engine library. It lets you run full-text queries on a collection of your documents. . Features: .+ * Keyword queries and auto-complete\/auto-suggest queries.+ . * Can search over any type of \"document\". (You explain how to extract search terms from them.) .@@ -23,46 +25,111 @@ * In-memory but quite compact. It does not keep a copy of your original documents. .+ * Quick incremental index updates, making it possible to+ keep your text search in-sync with your data.+ . It is independent of the document type, so you have to write the document-specific parts: extracting search terms- and any case-normalisation or stemming. This is quite- easy using libraries such as+ and any stop words, case-normalisation or stemming. This+ is quite easy using libraries such as <http://hackage.haskell.org/package/tokenize tokenize> and <http://hackage.haskell.org/package/snowball snowball>. .- For an example, see the code for the+ The source package includes a demo to illustrate how to+ use the library. The demo is a simplified version of how+ the library is used in the <http://hackage.haskell.org/package/hackage-server hackage-server>- where it is used for the package search feature.+ where it provides the backend for the package search feature. license: BSD3 license-file: LICENSE author: Duncan Coutts maintainer: Duncan Coutts <duncan@well-typed.com>-copyright: 2013-2014 Duncan Coutts, 2014 Well-Typed LLP-category: Data, Text, NLP+copyright: 2013-2014 Duncan Coutts, 2014 Well-Typed LLP,+ 2014 IRIS Connect Ltd.+category: Data, Text, Search build-type: Simple cabal-version: >=1.10+extra-source-files: changelog source-repository head type: darcs location: http://code.haskell.org/full-text-search/ +flag build-search-demo+ default: False+ description: Build a little program illustrating the use of the library+ library exposed-modules: Data.SearchEngine, Data.SearchEngine.BM25F- other-modules: Data.SearchEngine.DocFeatVals,+ other-modules: Data.SearchEngine.Types,+ Data.SearchEngine.Update,+ Data.SearchEngine.Query,+ Data.SearchEngine.Autosuggest,+ Data.SearchEngine.SearchIndex,+ Data.SearchEngine.DocFeatVals, Data.SearchEngine.TermBag, Data.SearchEngine.DocTermIds,- Data.SearchEngine.SearchIndex, Data.SearchEngine.DocIdSet other-extensions: BangPatterns, NamedFieldPuns, RecordWildCards,- GeneralizedNewtypeDeriving+ GeneralizedNewtypeDeriving,+ ScopedTypeVariables build-depends: base >=4.5 && <4.7, array >=0.4 && <0.5, vector >=0.10 && <0.11, containers >=0.4 && <0.6, text >=0.11 && <1.2 default-language: Haskell2010+ ghc-options: -Wall -funbox-strict-fields+++executable search-demo+ main-is: Main.hs+ other-modules: PackageSearch+ ExtractNameTerms+ ExtractDescriptionTerms+ PackageIndexUtils+ -- support code for package descriptions:+ HaddockHtml+ HaddockLex+ HaddockParse+ HaddockTypes+ hs-source-dirs: demo+ if !flag(build-search-demo)+ buildable: False+ else+ build-depends: full-text-search,+ base, text, containers, array,+ tokenize == 0.1.*,+ snowball == 1.0.*,+ transformers,+ split == 0.2.*,+ Cabal >= 1.14 && < 1.20,+ bytestring, filepath, directory, tar, time, mtl+ build-tools: alex, happy+ default-language: Haskell2010+ other-extensions: GeneralizedNewtypeDeriving ghc-options: -Wall++test-suite qc-props+ type: exitcode-stdio-1.0+ main-is: Main.hs+ hs-source-dirs: . tests+ build-depends: base,+ array,+ vector,+ containers,+ text,+ QuickCheck ==2.*,+ tasty ==0.8.*,+ tasty-quickcheck ==0.8.*+ other-modules: Test.Data.SearchEngine.TermBag,+ Test.Data.SearchEngine.DocIdSet,+ Data.SearchEngine.DocTermIds,+ Data.SearchEngine.DocIdSet+ default-language: Haskell2010+ ghc-options: -Wall+
+ tests/Main.hs view
@@ -0,0 +1,38 @@++import qualified Test.Data.SearchEngine.DocIdSet as DocIdSet+import qualified Test.Data.SearchEngine.TermBag as TermBag++import Test.Tasty+import Test.Tasty.QuickCheck+++main :: IO ()+main = defaultMain $+ testGroup ""+ [ docIdSetTests+ , termBagTests+ ]++docIdSetTests :: TestTree+docIdSetTests =+ testGroup "TermIdSet"+ [ testProperty "prop_insert" DocIdSet.prop_insert+ , testProperty "prop_delete" DocIdSet.prop_delete+ , testProperty "prop_delete'" DocIdSet.prop_delete'+ , testProperty "prop_union" DocIdSet.prop_union+ , testProperty "prop_union'" DocIdSet.prop_union'+ ]++termBagTests :: TestTree+termBagTests =+ testGroup "TermBag"+ [ testProperty "prop_invariant" TermBag.prop_invariant+ , testProperty "prop_elems" TermBag.prop_elems+ , testProperty "prop_fromList" TermBag.prop_fromList+ , testProperty "prop_size" TermBag.prop_size+ , testProperty "prop_termCount" TermBag.prop_termCount+ , testProperty "prop_denseTable1" TermBag.prop_denseTable1+ , testProperty "prop_denseTable2" TermBag.prop_denseTable2+ ]++
+ tests/Test/Data/SearchEngine/DocIdSet.hs view
@@ -0,0 +1,55 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+module Test.Data.SearchEngine.DocIdSet where++import Data.SearchEngine.DocIdSet++import qualified Data.Vector.Unboxed as Vec+import qualified Data.List as List+import Test.QuickCheck+++instance Arbitrary DocIdSet where+ arbitrary = fromList `fmap` (listOf arbitrary)++instance Arbitrary DocId where+ arbitrary = DocId `fmap` choose (0,15)+++prop_insert :: DocIdSet -> DocId -> Bool+prop_insert dset x =+ let dset' = insert x dset+ in invariant dset && invariant dset'+ && all (`member` dset') (x : toList dset)++prop_delete :: DocIdSet -> DocId -> Bool+prop_delete dset x =+ let dset' = delete x dset+ in invariant dset && invariant dset'+ && all (`member` dset') (List.delete x (toList dset))+ && not (x `member` dset')++prop_delete' :: DocIdSet -> Bool+prop_delete' dset =+ all (prop_delete dset) (toList dset)++prop_union :: DocIdSet -> DocIdSet -> Bool+prop_union dset1 dset2 =+ let dset = union dset1 dset2+ dset' = fromList (List.union (toList dset1) (toList dset2))++ in invariant dset && invariant dset'+ && dset == dset'++prop_union' :: DocIdSet -> DocIdSet -> Bool+prop_union' dset1 dset2 =+ let dset = union dset1 dset2+ dset' = List.foldl' (\s i -> insert i s) dset1 (toList dset2)+ dset'' = List.foldl' (\s i -> insert i s) dset2 (toList dset1)+ in invariant dset && invariant dset' && invariant dset''+ && dset == dset'+ && dset' == dset''++member :: DocId -> DocIdSet -> Bool+member x (DocIdSet vec) =+ x `List.elem` Vec.toList vec+
+ tests/Test/Data/SearchEngine/TermBag.hs view
@@ -0,0 +1,55 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+module Test.Data.SearchEngine.TermBag where++import Data.SearchEngine.TermBag++import qualified Data.Vector.Unboxed as Vec+import qualified Data.List as List+import Test.QuickCheck+++instance Arbitrary TermBag where+ arbitrary = fromList `fmap` (listOf arbitrary)++instance Arbitrary TermId where+ arbitrary = TermId `fmap` choose (0,5)++prop_invariant :: TermBag -> Bool+prop_invariant = invariant++prop_elems :: [TermId] -> Bool+prop_elems tids =+ (map head . List.group . List.sort) tids+ == (elems . fromList) tids++prop_fromList :: [TermId] -> Bool+prop_fromList tids =+ (map (\g -> (head g, fromIntegral (length g `min` 255)))+ . List.group . List.sort) tids+ == (toList . fromList) tids++prop_size :: [TermId] -> Bool+prop_size tids =+ (size . fromList) tids == length tids++prop_termCount :: [TermId] -> Bool+prop_termCount tids =+ and [ termCount bag tid == count + | let bag = fromList tids + , (tid, count) <- toList bag+ ]++prop_denseTable1 :: [TermBag] -> Bool+prop_denseTable1 bags =+ Vec.toList terms == (List.sort . foldr List.union [] . map elems) bags+ where+ (terms, _) = denseTable bags++prop_denseTable2 :: [TermBag] -> Bool+prop_denseTable2 bags =+ and [ termCount bag (terms Vec.! t) == counts Vec.! (b * numTerms + t)+ | let (terms, counts) = denseTable bags+ numTerms = Vec.length terms+ , (b, bag) <- zip [0..] bags+ , t <- [0..Vec.length terms - 1]+ ]