CloogleServer.icl 14.4 KB
Newer Older
1 2
module CloogleServer

3 4 5 6 7 8 9 10
import StdArray
import StdBool
import StdFile
import StdList
import StdOrdList
import StdOverloaded
import StdString
import StdTuple
Camil Staps's avatar
Camil Staps committed
11
from StdFunc import o, flip, const
12
from StdMisc import abort
13

14
from TCPIP import :: IPAddress, :: Port, instance toString IPAddress
15 16

from Data.Func import $
Camil Staps's avatar
Camil Staps committed
17
import Data.List
18
import Data.Tuple
19 20 21 22
import Data.Maybe
import System.CommandLine
import Data.Functor
import Control.Applicative
23
import Control.Monad
Camil Staps's avatar
Camil Staps committed
24
from Text import class Text(concat,trim,indexOf,toLowerCase),
25
	instance Text String, instance + String
26
import Text.JSON
Camil Staps's avatar
Camil Staps committed
27 28

import System.Time
29

30 31
from SimpleTCPServer import :: LogMessage{..}, serve, :: Logger
import qualified SimpleTCPServer
32 33
import TypeDB
import Type
34
import Cache
35
import Cloogle
Camil Staps's avatar
Camil Staps committed
36

37
MAX_RESULTS    :== 15
Camil Staps's avatar
Camil Staps committed
38
CACHE_PREFETCH :== 5
39

40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
:: RequestCacheKey
	= { c_unify     :: Maybe Type
	  , c_name      :: Maybe String
	  , c_className :: Maybe String
	  , c_typeName  :: Maybe String
	  , c_modules   :: Maybe [String]
	  , c_libraries :: Maybe ([String], Bool)
	  , c_page      :: Maybe Int
	  }

derive JSONEncode Kind, ClassOrGeneric, Type, RequestCacheKey
instance toString RequestCacheKey
where toString rck = toString $ toJSON rck

toRequestCacheKey :: Request -> RequestCacheKey
toRequestCacheKey r =
	{ c_unify     = r.unify >>= parseType o fromString
	, c_name      = r.name
	, c_className = r.className
	, c_typeName  = r.typeName
	, c_modules   = sort <$> r.modules
	, c_libraries = appFst sort <$> r.libraries
	, c_page      = r.page
	}

65 66 67 68 69 70 71 72 73
Start w
# (io, w) = stdio w
# (cmdline, w) = getCommandLine w
| length cmdline <> 2 = help io w
# [_,port:_] = cmdline
# port = toInt port
# (db, io) = openDb io
# (_, w) = fclose io w
| isNothing db = abort "stdin does not have a TypeDB\n"
74
#! db = fromJust db
Camil Staps's avatar
Camil Staps committed
75
= serve (handle db) (Just log) port w
76
where
Camil Staps's avatar
Camil Staps committed
77 78 79 80 81
	help :: *File *World -> *World
	help io w
	# io = io <<< "Usage: ./CloogleServer <port>\n"
	= snd $ fclose io w

Camil Staps's avatar
Camil Staps committed
82
	handle :: !TypeDB !(Maybe Request) !*World -> *(!Response, CacheKey, !*World)
83
	handle _ Nothing w = (err CLOOGLE_E_INVALIDINPUT "Couldn't parse input", "", w)
Camil Staps's avatar
Camil Staps committed
84
	handle db (Just request=:{unify,name,page}) w
85
		//Check cache
Camil Staps's avatar
Camil Staps committed
86
		# (mbResponse, w) = readCache key w
87 88
		| isJust mbResponse
			# r = fromJust mbResponse
Camil Staps's avatar
Camil Staps committed
89
			= ({r & return = if (r.return == 0) 1 r.return}, cacheKey request, w)
90
		| isJust name && size (fromJust name) > 40
91
			= respond (err CLOOGLE_E_INVALIDNAME "Function name too long") w
Camil Staps's avatar
Camil Staps committed
92
		| isJust name && any isSpace (fromString $ fromJust name)
93
			= respond (err CLOOGLE_E_INVALIDNAME "Name cannot contain spaces") w
Camil Staps's avatar
Camil Staps committed
94
		| isJust unify && isNothing (parseType $ fromString $ fromJust unify)
95
			= respond (err CLOOGLE_E_INVALIDTYPE "Couldn't parse type") w
Camil Staps's avatar
Camil Staps committed
96 97
		// Results
		# drop_n = fromJust (page <|> pure 0) * MAX_RESULTS
Camil Staps's avatar
oops  
Camil Staps committed
98
		# results = drop drop_n $ sort $ search request db
Camil Staps's avatar
Camil Staps committed
99 100
		# more = max 0 (length results - MAX_RESULTS)
		// Suggestions
101
		# mbType = unify >>= parseType o fromString
Camil Staps's avatar
Camil Staps committed
102
		# suggestions
103 104
			= sortBy (\a b -> snd a > snd b) <$>
			  filter ((<)(length results) o snd) <$>
105
			  (mbType >>= \t -> suggs name t db)
Camil Staps's avatar
Camil Staps committed
106
		# (results,nextpages) = splitAt MAX_RESULTS results
Camil Staps's avatar
Camil Staps committed
107
		// Response
108
		# response = if (isEmpty results)
109
			(err CLOOGLE_E_NORESULTS "No results")
110
			{ return = 0
Camil Staps's avatar
Camil Staps committed
111 112 113 114
		    , msg = "Success"
		    , data           = results
		    , more_available = Just more
		    , suggestions    = suggestions
115
		    }
Camil Staps's avatar
Camil Staps committed
116 117
		// Save page prefetches
		# w = cachePages CACHE_PREFETCH 1 response nextpages w
118
		// Save cache file
119 120
		= respond response w
	where
121 122
		key = toRequestCacheKey request

Camil Staps's avatar
Camil Staps committed
123
		respond :: Response *World -> *(Response, CacheKey, *World)
124
		respond r w = (r, cacheKey key, writeCache LongTerm key r w)
Camil Staps's avatar
Camil Staps committed
125 126 127 128 129 130 131 132

		cachePages :: Int Int Response [Result] *World -> *World
		cachePages _ _  _ [] w = w
		cachePages 0 _  _ _  w = w
		cachePages npages i response results w
		# w = writeCache Brief req` resp` w
		= cachePages (npages - 1) (i + 1) response keep w
		where
133
			req` = { key & c_page = ((+) i) <$> (key.c_page <|> pure 0) }
Camil Staps's avatar
Camil Staps committed
134 135 136 137 138 139
			resp` =
				{ response
				& more_available = Just $ max 0 (length results - MAX_RESULTS)
				, data = give
				}
			(give,keep) = splitAt MAX_RESULTS results
Mart Lubbers's avatar
Mart Lubbers committed
140

141
	suggs :: !(Maybe String) !Type !TypeDB -> Maybe [(Request, Int)]
142
	suggs n (Func is r cc) db
143 144
		| length is < 3
			= Just [let t` = concat $ print False $ Func is` r cc in
145 146
			        let request = {zero & name=n, unify=Just t`} in
			        (request, length $ search request db)
147
			        \\ is` <- permutations is | is` <> is]
148
	suggs _ _ _ = Nothing
Camil Staps's avatar
Camil Staps committed
149

150
	search :: !Request !TypeDB -> [Result]
Camil Staps's avatar
Camil Staps committed
151 152 153 154
	search {unify,name,className,typeName,modules,libraries,page} db
		# db = case libraries of
			(Just ls) = filterLocations (isLibMatch ls) db
			Nothing   = db
155 156 157
		# db = case modules of
			(Just ms) = filterLocations (isModMatch ms) db
			Nothing   = db
Camil Staps's avatar
Camil Staps committed
158 159 160 161
		| isJust className
			# className = fromJust className
			# classes = findClass className db
			= map (flip makeClassResult db) classes
Camil Staps's avatar
Camil Staps committed
162 163 164
		| isJust typeName
			# typeName = fromJust typeName
			# types = findType typeName db
165
			= [makeTypeResult (Just typeName) l td db \\ (l,td) <- types]
166
		# mbType = prepare_unification True <$> (unify >>= parseType o fromString)
Camil Staps's avatar
Camil Staps committed
167
		// Search normal functions
168
		# filts = catMaybes [ (\t _ -> isUnifiable t) <$> mbType
169
		                    , (\n loc _ -> isNameMatch (size n*2/3) n loc) <$> name
170
		                    ]
171
		# funs = map (\f -> makeFunctionResult name mbType Nothing f db) $ findFunction`` filts db
Camil Staps's avatar
Camil Staps committed
172
		// Search macros
173
		# macros = case (isNothing mbType,name) of
174
			(True,Just n) = findMacro` (\loc _ -> isNameMatch (size n*2/3) n loc) db
175
			_             = []
Camil Staps's avatar
Camil Staps committed
176
		# macros = map (\(lhs,rhs) -> makeMacroResult name lhs rhs) macros
Camil Staps's avatar
Camil Staps committed
177
		// Search class members
Camil Staps's avatar
Camil Staps committed
178
		# filts = catMaybes [ (\t _ _ _ _->isUnifiable t) <$> mbType
179 180
		                    , (\n (Location lib mod _ _ _) _ _ f _ -> isNameMatch
		                      (size n*2/3) n (Location lib mod Nothing Nothing f)) <$> name
181
		                    ]
Camil Staps's avatar
Camil Staps committed
182
		# members = findClassMembers`` filts db
183 184
		# members = map (\(Location lib mod line iclline cls,vs,_,f,et) -> makeFunctionResult name mbType
			(Just {cls_name=cls,cls_vars=vs}) (Location lib mod line iclline f,et) db) members
Camil Staps's avatar
Camil Staps committed
185
		// Search types
186
		# lcName = if (isJust mbType && isType (fromJust mbType))
Camil Staps's avatar
Camil Staps committed
187 188
			(let (Type name _) = fromJust mbType in Just $ toLowerCase name)
			(toLowerCase <$> name)
189 190 191
		# types = case (isNothing mbType,lcName) of
			(True,Just n) = findType` (\loc _ -> toLowerCase (getName loc) == n) db
			_             = []
192
		# types = map (\(tl,td) -> makeTypeResult name tl td db) types
193 194
		// Search classes
		# classes = case (isNothing mbType, toLowerCase <$> name) of
Camil Staps's avatar
Camil Staps committed
195
			(True, Just c) = findClass` (\loc _ _ _ -> toLowerCase (getName loc) == c) db
Camil Staps's avatar
Camil Staps committed
196 197
			_              = []
		# classes = map (flip makeClassResult db) classes
Camil Staps's avatar
Camil Staps committed
198
		// Merge results
Camil Staps's avatar
Camil Staps committed
199
		= sort $ funs ++ members ++ types ++ classes ++ macros
Camil Staps's avatar
Camil Staps committed
200

201
	makeClassResult :: (Location, [TypeVar], ClassContext, [(Name,ExtendedType)])
Camil Staps's avatar
Camil Staps committed
202
		TypeDB -> Result
Camil Staps's avatar
Camil Staps committed
203 204 205 206 207
	makeClassResult rec=:(Builtin _, _, _, _) db
		= ClassResult
		  ( { library  = ""
		    , filename = ""
		    , dcl_line = Nothing
208
		    , icl_line = Nothing
Camil Staps's avatar
Camil Staps committed
209 210 211 212 213 214
		    , modul    = ""
		    , distance = -100
		    , builtin  = Just True
		    }
		  , makeClassResultExtras rec db
		  )
215
	makeClassResult rec=:(Location lib mod line iclline cls, vars, cc, funs) db
Camil Staps's avatar
Camil Staps committed
216 217 218
		= ClassResult
		  ( { library  = lib
		    , filename = modToFilename mod
Camil Staps's avatar
Camil Staps committed
219
		    , dcl_line = line
220
		    , icl_line = iclline
Camil Staps's avatar
Camil Staps committed
221
		    , modul    = mod
222
		    , distance = -100
Camil Staps's avatar
Camil Staps committed
223
		    , builtin  = Nothing
Camil Staps's avatar
Camil Staps committed
224
		    }
Camil Staps's avatar
Camil Staps committed
225
		  , makeClassResultExtras rec db
Camil Staps's avatar
Camil Staps committed
226
		  )
Camil Staps's avatar
Camil Staps committed
227 228 229 230 231
	makeClassResultExtras :: (Location, [TypeVar], ClassContext, [(Name,ExtendedType)])
		TypeDB -> ClassResultExtras
	makeClassResultExtras (l, vars, cc, funs) db
		= { class_name = cls
		  , class_heading = foldl ((+) o (flip (+) " ")) cls vars +
232
		      if (isEmpty cc) "" " | " + concat (print False cc)
Camil Staps's avatar
Camil Staps committed
233 234 235 236 237 238
		  , class_funs = [print_fun fun \\ fun <- funs]
		  , class_instances
		      = sortBy (\(a,_) (b,_) -> a < b)
		          [([concat (print False t) \\ t <- ts], map loc ls)
		          \\ (ts,ls) <- getInstances cls db]
		  }
239
	where
Camil Staps's avatar
Camil Staps committed
240 241
		cls = case l of
			Builtin c = c
242
			Location _ _ _ _ c = c
Camil Staps's avatar
Camil Staps committed
243

244 245 246
		print_fun :: (Name,ExtendedType) -> String
		print_fun f=:(_,ET _ et) = fromJust $
			et.te_representation <|> (pure $ concat $ print False f)
Camil Staps's avatar
Camil Staps committed
247

248
	makeTypeResult :: (Maybe String) Location TypeDef TypeDB -> Result
249
	makeTypeResult mbName (Location lib mod line iclline t) td db
Camil Staps's avatar
Camil Staps committed
250 251 252
		= TypeResult
		  ( { library  = lib
		    , filename = modToFilename mod
Camil Staps's avatar
Camil Staps committed
253
		    , dcl_line = line
254
		    , icl_line = iclline
Camil Staps's avatar
Camil Staps committed
255
		    , modul    = mod
Camil Staps's avatar
Camil Staps committed
256
		    , distance
257
		        = if (isNothing mbName) -100 (levenshtein` t (fromJust mbName))
Camil Staps's avatar
Camil Staps committed
258 259
		    , builtin  = Nothing
		    }
260
		  , { type             = concat $ print False td
261 262
		    , type_instances   = map (appSnd3 (map (concat o (print False)))) $
		        map (appThd3 (map loc)) $ getTypeInstances t db
263 264
		    , type_derivations = map (appSnd (map loc)) $ getTypeDerivations t db
		    }
Camil Staps's avatar
Camil Staps committed
265
		  )
266
	makeTypeResult mbName (Builtin t) td db
Camil Staps's avatar
Camil Staps committed
267 268 269
		= TypeResult
		  ( { library  = ""
		    , filename = ""
Camil Staps's avatar
Camil Staps committed
270
		    , dcl_line = Nothing
271
		    , icl_line = Nothing
Camil Staps's avatar
Camil Staps committed
272 273 274 275
		    , modul    = ""
		    , distance
		        = if (isNothing mbName) -100 (levenshtein` t (fromJust mbName))
		    , builtin  = Just True
Camil Staps's avatar
Camil Staps committed
276
		    }
277
		  , { type             = concat $ print False td
278 279
		    , type_instances   = map (appSnd3 (map (concat o (print False)))) $
		        map (appThd3 (map loc)) $ getTypeInstances t db
280 281
		    , type_derivations = map (appSnd (map loc)) $ getTypeDerivations t db
		    }
Camil Staps's avatar
Camil Staps committed
282 283
		  )

284
	makeMacroResult :: (Maybe String) Location Macro -> Result
285
	makeMacroResult mbName (Location lib mod line iclline m) mac
Camil Staps's avatar
Camil Staps committed
286 287 288
		= MacroResult
		  ( { library  = lib
		    , filename = modToFilename mod
Camil Staps's avatar
Camil Staps committed
289
		    , dcl_line = line
290
		    , icl_line = iclline
Camil Staps's avatar
Camil Staps committed
291 292
		    , modul    = mod
		    , distance
Camil Staps's avatar
Camil Staps committed
293
		        = if (isNothing mbName) -100 (levenshtein` (fromJust mbName) m)
Camil Staps's avatar
Camil Staps committed
294
		    , builtin  = Nothing
Camil Staps's avatar
Camil Staps committed
295 296 297 298 299 300
		    }
		  , { macro_name = m
		    , macro_representation = mac.macro_as_string
		    }
		  )

Camil Staps's avatar
Camil Staps committed
301
	makeFunctionResult :: (Maybe String) (Maybe Type) (Maybe ShortClassResult)
302
		(Location, ExtendedType) TypeDB -> Result
Camil Staps's avatar
Camil Staps committed
303
	makeFunctionResult
Camil Staps's avatar
Camil Staps committed
304
		orgsearch orgsearchtype mbCls (fl, et=:(ET type tes)) db
Camil Staps's avatar
Camil Staps committed
305 306
		= FunctionResult
		  ( { library  = lib
Camil Staps's avatar
Camil Staps committed
307
		    , filename = modToFilename mod
Camil Staps's avatar
Camil Staps committed
308
		    , dcl_line = line
309
		    , icl_line = iclline
Camil Staps's avatar
Camil Staps committed
310 311
		    , modul    = mod
		    , distance = distance
Camil Staps's avatar
Camil Staps committed
312
		    , builtin  = builtin
Camil Staps's avatar
Camil Staps committed
313
		    }
314 315
		  , { func     = fromJust (tes.te_representation <|>
		                           (pure $ concat $ print False (fname,et)))
316 317
		    , unifier  = toStrUnifier <$> finish_unification <$>
		        (orgsearchtype >>= unify [] (prepare_unification False type))
Camil Staps's avatar
Camil Staps committed
318
		    , cls      = mbCls
319
		    , constructor_of = if tes.te_isconstructor
Camil Staps's avatar
Camil Staps committed
320 321
		        (let (Func _ r _) = type in Just $ concat $ print False r)
		        Nothing
322
		    , recordfield_of = if tes.te_isrecordfield
Camil Staps's avatar
Camil Staps committed
323 324
		        (let (Func [t:_] _ _) = type in Just $ concat $ print False t)
		        Nothing
325 326
		    , generic_derivations
		        = let derivs = getDerivations fname db in
Camil Staps's avatar
Camil Staps committed
327 328
		          const (sortBy (\(a,_) (b,_) -> a < b)
				    [(concat $ print False d, map loc ls) \\ (d,ls) <- derivs]) <$>
Camil Staps's avatar
Camil Staps committed
329
		          tes.te_generic_vars
Camil Staps's avatar
Camil Staps committed
330 331
		    }
		  )
Camil Staps's avatar
Camil Staps committed
332
	where
333 334 335
		(lib,mod,fname,line,iclline,builtin) = case fl of
			(Location l m ln iln f) = (l,  m,  f, ln,      iln,     Nothing)
			(Builtin f)             = ("", "", f, Nothing, Nothing, Just True)
Camil Staps's avatar
Camil Staps committed
336

337 338
		toStrUnifier :: Unifier -> StrUnifier
		toStrUnifier (tvas1, tvas2) = (map toStr tvas1, map toStr tvas2)
339
		where toStr (var, type) = (var, concat $ print False type)
340

Camil Staps's avatar
Camil Staps committed
341
		toStrPriority :: (Maybe Priority) -> String
342
		toStrPriority p = case print False p of [] = ""; ss = concat [" ":ss]
Camil Staps's avatar
Camil Staps committed
343

Camil Staps's avatar
Camil Staps committed
344
		distance
Camil Staps's avatar
Camil Staps committed
345
			| isNothing orgsearch || fromJust orgsearch == ""
Camil Staps's avatar
Camil Staps committed
346 347
				| isNothing orgsearchtype = 0
				# orgsearchtype = fromJust orgsearchtype
348 349
				# (Just (ass1, ass2)) = finish_unification <$>
					unify [] orgsearchtype (prepare_unification False type)
350
				= penalty + toInt (sum [typeComplexity t \\ (_,t)<-ass1 ++ ass2 | not (isVar t)])
351
			# orgsearch = fromJust orgsearch
352
			= penalty + levenshtein` orgsearch fname
353
		where
354 355 356 357 358
			penalty
			| tes.te_isrecordfield = 2
			| tes.te_isconstructor = 1
			| otherwise            = 0

359 360 361 362 363 364
			typeComplexity :: Type -> Real
			typeComplexity (Type _ ts) = 1.2 * foldr ((+) o typeComplexity) 1.0 ts
			typeComplexity (Func is r _) = 2.0 * foldr ((+) o typeComplexity) 1.0 [r:is]
			typeComplexity (Var _) = 1.0
			typeComplexity (Cons _ ts) = 1.2 * foldr ((+) o typeComplexity) 1.0 ts
			typeComplexity (Uniq t) = 3.0 + typeComplexity t
Camil Staps's avatar
Camil Staps committed
365

Camil Staps's avatar
Camil Staps committed
366
	levenshtein` :: String String -> Int
Camil Staps's avatar
Camil Staps committed
367
	levenshtein` a b = if (indexOf a b == -1) 0 -100 + levenshtein [c \\ c <-: a] [c \\ c <-: b]
Camil Staps's avatar
Camil Staps committed
368

Camil Staps's avatar
Camil Staps committed
369 370
	modToFilename :: String -> String
	modToFilename mod = (toString $ reverse $ takeWhile ((<>)'.')
371
	                              $ reverse $ fromString mod) + ".dcl"
Camil Staps's avatar
Camil Staps committed
372

Camil Staps's avatar
Camil Staps committed
373
	isUnifiable :: Type ExtendedType -> Bool
374
	isUnifiable t1 (ET t2 _) = isJust (unify [] t1 (prepare_unification False t2))
Camil Staps's avatar
Camil Staps committed
375

376 377 378
	isNameMatch :: !Int !String Location -> Bool
	isNameMatch maxdist n1 loc
		# (n1, n2) = ({toLower c \\ c <-: n1}, {toLower c \\ c <-: getName loc})
Camil Staps's avatar
Camil Staps committed
379
		= n1 == "" || indexOf n1 n2 <> -1 || levenshtein [c \\ c <-: n1] [c \\ c <-: n2] <= maxdist
Camil Staps's avatar
Camil Staps committed
380

381
	isModMatch :: ![String] Location -> Bool
382 383
	isModMatch mods (Location _ mod _ _ _) = isMember mod mods
	isModMatch _    (Builtin _)            = False
Camil Staps's avatar
Camil Staps committed
384

385
	isLibMatch :: (![String], !Bool) Location -> Bool
386 387
	isLibMatch (libs,_) (Location lib _ _ _ _) = any (\l -> indexOf l lib == 0) libs
	isLibMatch (_,blti) (Builtin _)            = blti
Camil Staps's avatar
Camil Staps committed
388

389
	loc :: Location -> LocationResult
390
	loc (Location lib mod ln iln _) = (lib, mod, ln, iln)
Camil Staps's avatar
Camil Staps committed
391

Camil Staps's avatar
Camil Staps committed
392
	log :: (LogMessage (Maybe Request) Response CacheKey) IPAddress *World
393
		-> *(IPAddress, *World)
Camil Staps's avatar
Camil Staps committed
394
	log msg s w
Camil Staps's avatar
Camil Staps committed
395 396
	| not needslog = (newS msg s, w)
	# (tm,w) = localTime w
Camil Staps's avatar
Camil Staps committed
397
	# (io,w) = stdio w
Camil Staps's avatar
Camil Staps committed
398
	# io = io <<< trim (toString tm) <<< " " <<< msgToString msg s
Camil Staps's avatar
Camil Staps committed
399
	= (newS msg s, snd (fclose io w))
Camil Staps's avatar
Camil Staps committed
400
	where
Camil Staps's avatar
Camil Staps committed
401
		needslog = case msg of (Received _) = True; (Sent _ _) = True; _ = False
Camil Staps's avatar
Camil Staps committed
402

Camil Staps's avatar
Camil Staps committed
403
	newS :: (LogMessage (Maybe Request) Response CacheKey) IPAddress -> IPAddress
Camil Staps's avatar
Camil Staps committed
404 405
	newS m s = case m of (Connected ip) = ip; _ = s

Camil Staps's avatar
Camil Staps committed
406
	msgToString :: (LogMessage (Maybe Request) Response CacheKey) IPAddress -> String
Camil Staps's avatar
Camil Staps committed
407
	msgToString (Received Nothing) ip
408
		= toString ip + " <-- Nothing\n"
Camil Staps's avatar
Camil Staps committed
409
	msgToString (Received (Just a)) ip
410
		= toString ip + " <-- " + toString a + "\n"
Camil Staps's avatar
Camil Staps committed
411
	msgToString (Sent {return,data,msg,more_available} ck) ip
412
		= toString ip + " --> " + toString (length data)
Camil Staps's avatar
Camil Staps committed
413 414 415
			+ " results (" + toString return + "; " + msg
			+ if (isJust more_available) ("; " + toString (fromJust more_available) + " more") ""
			+ "; cache: " + ck + ")\n"
Camil Staps's avatar
Camil Staps committed
416
	msgToString _ _ = ""