-
Notifications
You must be signed in to change notification settings - Fork 65
/
Copy pathExpando.elm
executable file
·645 lines (459 loc) · 15.1 KB
/
Expando.elm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
module Debugger.Expando exposing
( Expando
, Msg
, init
, merge
, update
, view
)
import Dict exposing (Dict)
import Elm.Kernel.Debugger
import Html exposing (Html, div, span, text)
import Html.Attributes exposing (class, style)
import Html.Events exposing (onClick)
import Json.Decode as Json
-- MODEL
type Expando
= S String
| Primitive String
| Sequence SeqType Bool (List Expando)
| Dictionary Bool (List (Expando, Expando))
| Record Bool (Dict String Expando)
| Constructor (Maybe String) Bool (List Expando)
type SeqType
= ListSeq
| SetSeq
| ArraySeq
seqTypeToString : Int -> SeqType -> String
seqTypeToString n seqType =
case seqType of
ListSeq ->
"List(" ++ String.fromInt n ++ ")"
SetSeq ->
"Set(" ++ String.fromInt n ++ ")"
ArraySeq ->
"Array(" ++ String.fromInt n ++ ")"
-- INITIALIZE
init : a -> Expando
init value =
initHelp True (Elm.Kernel.Debugger.init value)
initHelp : Bool -> Expando -> Expando
initHelp isOuter expando =
case expando of
S _ ->
expando
Primitive _ ->
expando
Sequence seqType isClosed items ->
if isOuter then
Sequence seqType False (List.map (initHelp False) items)
else if List.length items <= 8 then
Sequence seqType False items
else
expando
Dictionary isClosed keyValuePairs ->
if isOuter then
Dictionary False (List.map (\( k, v ) -> ( k, initHelp False v )) keyValuePairs)
else if List.length keyValuePairs <= 8 then
Dictionary False keyValuePairs
else
expando
Record isClosed entries ->
if isOuter then
Record False (Dict.map (\_ v -> initHelp False v) entries)
else if Dict.size entries <= 4 then
Record False entries
else
expando
Constructor maybeName isClosed args ->
if isOuter then
Constructor maybeName False (List.map (initHelp False) args)
else if List.length args <= 4 then
Constructor maybeName False args
else
expando
-- PRESERVE OLD EXPANDO STATE (open/closed)
merge : a -> Expando -> Expando
merge value expando =
mergeHelp expando (Elm.Kernel.Debugger.init value)
mergeHelp : Expando -> Expando -> Expando
mergeHelp old new =
case (old, new) of
(_, S _) ->
new
(_, Primitive _) ->
new
(Sequence _ isClosed oldValues, Sequence seqType _ newValues) ->
Sequence seqType isClosed (mergeListHelp [] oldValues newValues)
(Dictionary isClosed _, Dictionary _ keyValuePairs) ->
Dictionary isClosed keyValuePairs
(Record isClosed oldDict, Record _ newDict) ->
Record isClosed <| Dict.map (mergeDictHelp oldDict) newDict
(Constructor _ isClosed oldValues, Constructor maybeName _ newValues) ->
Constructor maybeName isClosed (mergeListHelp [] oldValues newValues)
_ ->
new
mergeListHelp : List Expando -> List Expando -> List Expando -> List Expando
mergeListHelp acc olds news =
case (olds, news) of
([], _) ->
List.reverse acc ++ news
(_, []) ->
List.reverse acc
(x :: xs, y :: ys) ->
mergeListHelp (mergeHelp x y :: acc) xs ys
mergeDictHelp : Dict String Expando -> String -> Expando -> Expando
mergeDictHelp oldDict key value =
case Dict.get key oldDict of
Nothing ->
value
Just oldValue ->
mergeHelp oldValue value
-- UPDATE
type Msg
= Toggle
| Index Redirect Int Msg
| Field String Msg
type Redirect
= None
| Key
| Value
update : Msg -> Expando -> Expando
update msg value =
case value of
S _ ->
-- Debug.crash "nothing changes a primitive"
value
Primitive _ ->
-- Debug.crash "nothing changes a primitive"
value
Sequence seqType isClosed valueList ->
case msg of
Toggle ->
Sequence seqType (not isClosed) valueList
Index None index subMsg ->
Sequence seqType isClosed <| updateIndex index (update subMsg) valueList
Index _ _ _ ->
-- Debug.crash "no redirected indexes on sequences"
value
Field _ _ ->
-- Debug.crash "no field on sequences"
value
Dictionary isClosed keyValuePairs ->
case msg of
Toggle ->
Dictionary (not isClosed) keyValuePairs
Index redirect index subMsg ->
case redirect of
None ->
-- Debug.crash "must have redirect for dictionaries"
value
Key ->
Dictionary isClosed <|
updateIndex index (\( k, v ) -> ( update subMsg k, v )) keyValuePairs
Value ->
Dictionary isClosed <|
updateIndex index (\( k, v ) -> ( k, update subMsg v )) keyValuePairs
Field _ _ ->
-- Debug.crash "no field for dictionaries"
value
Record isClosed valueDict ->
case msg of
Toggle ->
Record (not isClosed) valueDict
Index _ _ _ ->
-- Debug.crash "no index for records"
value
Field field subMsg ->
Record isClosed (Dict.update field (updateField subMsg) valueDict)
Constructor maybeName isClosed valueList ->
case msg of
Toggle ->
Constructor maybeName (not isClosed) valueList
Index None index subMsg ->
Constructor maybeName isClosed <|
updateIndex index (update subMsg) valueList
Index _ _ _ ->
-- Debug.crash "no redirected indexes on sequences"
value
Field _ _ ->
-- Debug.crash "no field for constructors"
value
updateIndex : Int -> (a -> a) -> List a -> List a
updateIndex n func list =
case list of
[] ->
[]
x :: xs ->
if n <= 0
then func x :: xs
else x :: updateIndex (n - 1) func xs
updateField : Msg -> Maybe Expando -> Maybe Expando
updateField msg maybeExpando =
case maybeExpando of
Nothing ->
-- Debug.crash "key does not exist"
maybeExpando
Just expando ->
Just (update msg expando)
-- VIEW
view : Maybe String -> Expando -> Html Msg
view maybeKey expando =
case expando of
S stringRep ->
div (leftPad maybeKey) (lineStarter maybeKey Nothing [ span [ red ] [ text stringRep ] ])
Primitive stringRep ->
div (leftPad maybeKey) (lineStarter maybeKey Nothing [ span [ blue ] [ text stringRep ] ])
Sequence seqType isClosed valueList ->
viewSequence maybeKey seqType isClosed valueList
Dictionary isClosed keyValuePairs ->
viewDictionary maybeKey isClosed keyValuePairs
Record isClosed valueDict ->
viewRecord maybeKey isClosed valueDict
Constructor maybeName isClosed valueList ->
viewConstructor maybeKey maybeName isClosed valueList
-- VIEW SEQUENCE
viewSequence : Maybe String -> SeqType -> Bool -> List Expando -> Html Msg
viewSequence maybeKey seqType isClosed valueList =
let
starter = seqTypeToString (List.length valueList) seqType
in
div (leftPad maybeKey)
[ div [ onClick Toggle ] (lineStarter maybeKey (Just isClosed) [ text starter ])
, if isClosed then text "" else viewSequenceOpen valueList
]
viewSequenceOpen : List Expando -> Html Msg
viewSequenceOpen values =
div [] (List.indexedMap viewConstructorEntry values)
-- VIEW DICTIONARY
viewDictionary : Maybe String -> Bool -> List (Expando, Expando) -> Html Msg
viewDictionary maybeKey isClosed keyValuePairs =
let
starter = "Dict(" ++ String.fromInt (List.length keyValuePairs) ++ ")"
in
div (leftPad maybeKey)
[ div [ onClick Toggle ] (lineStarter maybeKey (Just isClosed) [ text starter ])
, if isClosed then text "" else viewDictionaryOpen keyValuePairs
]
viewDictionaryOpen : List (Expando, Expando) -> Html Msg
viewDictionaryOpen keyValuePairs =
div [] (List.indexedMap viewDictionaryEntry keyValuePairs)
viewDictionaryEntry : Int -> (Expando, Expando) -> Html Msg
viewDictionaryEntry index ( key, value ) =
case key of
S stringRep ->
Html.map (Index Value index) (view (Just stringRep) value)
Primitive stringRep ->
Html.map (Index Value index) (view (Just stringRep) value)
_ ->
div []
[ Html.map (Index Key index) (view (Just "key") key)
, Html.map (Index Value index) (view (Just "value") value)
]
-- VIEW RECORD
viewRecord : Maybe String -> Bool -> Dict String Expando -> Html Msg
viewRecord maybeKey isClosed record =
let
(start, middle, end) =
if isClosed then
(Tuple.second (viewTinyRecord record), text "", text "")
else
([ text "{" ], viewRecordOpen record, div (leftPad (Just ())) [ text "}" ])
in
div (leftPad maybeKey)
[ div [ onClick Toggle ] (lineStarter maybeKey (Just isClosed) start)
, middle
, end
]
viewRecordOpen : Dict String Expando -> Html Msg
viewRecordOpen record =
div [] (List.map viewRecordEntry (Dict.toList record))
viewRecordEntry : ( String, Expando ) -> Html Msg
viewRecordEntry ( field, value ) =
Html.map (Field field) (view (Just field) value)
-- VIEW CONSTRUCTOR
viewConstructor : Maybe String -> Maybe String -> Bool -> List Expando -> Html Msg
viewConstructor maybeKey maybeName isClosed valueList =
let
tinyArgs = List.map (Tuple.second << viewExtraTiny) valueList
description =
case (maybeName, tinyArgs) of
(Nothing , [] ) -> [ text "()" ]
(Nothing , x :: xs) -> text "( " :: span [] x :: List.foldr (\args rest -> text ", " :: span [] args :: rest) [ text " )" ] xs
(Just name, [] ) -> [ text name ]
(Just name, x :: xs) -> text (name ++ " ") :: span [] x :: List.foldr (\args rest -> text " " :: span [] args :: rest) [] xs
(maybeIsClosed, openHtml) =
case valueList of
[] ->
(Nothing, div [] [])
[ entry ] ->
case entry of
S _ ->
(Nothing, div [] [])
Primitive _ ->
(Nothing, div [] [])
Sequence _ _ subValueList ->
( Just isClosed
, if isClosed then div [] [] else
Html.map (Index None 0) (viewSequenceOpen subValueList)
)
Dictionary _ keyValuePairs ->
( Just isClosed
, if isClosed then div [] [] else
Html.map (Index None 0) (viewDictionaryOpen keyValuePairs)
)
Record _ record ->
( Just isClosed
, if isClosed then div [] [] else
Html.map (Index None 0) (viewRecordOpen record)
)
Constructor _ _ subValueList ->
( Just isClosed
, if isClosed then div [] [] else
Html.map (Index None 0) (viewConstructorOpen subValueList)
)
_ ->
( Just isClosed
, if isClosed then div [] [] else viewConstructorOpen valueList
)
in
div (leftPad maybeKey)
[ div [ onClick Toggle ] (lineStarter maybeKey maybeIsClosed description)
, openHtml
]
viewConstructorOpen : List Expando -> Html Msg
viewConstructorOpen valueList =
div [] (List.indexedMap viewConstructorEntry valueList)
viewConstructorEntry : Int -> Expando -> Html Msg
viewConstructorEntry index value =
Html.map (Index None index) (view (Just (String.fromInt index)) value)
-- VIEW TINY
viewTiny : Expando -> ( Int, List (Html msg) )
viewTiny value =
case value of
S stringRep ->
let
str = elideMiddle stringRep
in
( String.length str
, [ span [ red ] [ text str ] ]
)
Primitive stringRep ->
( String.length stringRep
, [ span [ blue ] [ text stringRep ] ]
)
Sequence seqType _ valueList ->
viewTinyHelp <| seqTypeToString (List.length valueList) seqType
Dictionary _ keyValuePairs ->
viewTinyHelp <| "Dict(" ++ String.fromInt (List.length keyValuePairs) ++ ")"
Record _ record ->
viewTinyRecord record
Constructor maybeName _ [] ->
viewTinyHelp <| Maybe.withDefault "Unit" maybeName
Constructor maybeName _ valueList ->
viewTinyHelp <|
case maybeName of
Nothing -> "Tuple(" ++ String.fromInt (List.length valueList) ++ ")"
Just name -> name ++ " …"
viewTinyHelp : String -> ( Int, List (Html msg) )
viewTinyHelp str =
(String.length str, [ text str ])
elideMiddle : String -> String
elideMiddle str =
if String.length str <= 18
then str
else String.left 8 str ++ "..." ++ String.right 8 str
-- VIEW TINY RECORDS
viewTinyRecord : Dict String Expando -> ( Int, List (Html msg) )
viewTinyRecord record =
if Dict.isEmpty record then
(2, [ text "{}" ])
else
viewTinyRecordHelp 0 "{ " (Dict.toList record)
viewTinyRecordHelp : Int -> String -> List ( String, Expando ) -> ( Int, List (Html msg) )
viewTinyRecordHelp length starter entries =
case entries of
[] ->
(length + 2, [ text " }" ])
(field, value) :: rest ->
let
fieldLen = String.length field
(valueLen, valueHtmls) = viewExtraTiny value
newLength = length + fieldLen + valueLen + 5
in
if newLength > 60 then
(length + 4, [ text ", … }" ])
else
let
(finalLength, otherHtmls) = viewTinyRecordHelp newLength ", " rest
in
( finalLength
, text starter
:: span [ purple ] [ text field ]
:: text " = "
:: span [] valueHtmls
:: otherHtmls
)
viewExtraTiny : Expando -> ( Int, List (Html msg) )
viewExtraTiny value =
case value of
Record _ record ->
viewExtraTinyRecord 0 "{" (Dict.keys record)
_ ->
viewTiny value
viewExtraTinyRecord : Int -> String -> List String -> ( Int, List (Html msg) )
viewExtraTinyRecord length starter entries =
case entries of
[] ->
(length + 1, [ text "}" ])
field :: rest ->
let
nextLength = length + String.length field + 1
in
if nextLength > 18 then
(length + 2, [ text "…}" ])
else
let
(finalLength, otherHtmls) = viewExtraTinyRecord nextLength "," rest
in
( finalLength
, text starter :: span [ purple ] [ text field ] :: otherHtmls
)
-- VIEW HELPERS
lineStarter : Maybe String -> Maybe Bool -> List (Html msg) -> List (Html msg)
lineStarter maybeKey maybeIsClosed description =
let
arrow =
case maybeIsClosed of
Nothing -> makeArrow ""
Just True -> makeArrow "▸"
Just False -> makeArrow "▾"
in
case maybeKey of
Nothing ->
arrow :: description
Just key ->
arrow :: span [ purple ] [ text key ] :: text " = " :: description
makeArrow : String -> Html msg
makeArrow arrow =
span
[ style "color" "#777"
, style "padding-left" "2ch"
, style "width" "2ch"
, style "display" "inline-block"
]
[ text arrow ]
leftPad : Maybe a -> List (Html.Attribute msg)
leftPad maybeKey =
case maybeKey of
Nothing -> []
Just _ -> [ style "padding-left" "4ch" ]
red : Html.Attribute msg
red =
style "color" "rgb(196, 26, 22)"
blue : Html.Attribute msg
blue =
style "color" "rgb(28, 0, 207)"
purple : Html.Attribute msg
purple =
style "color" "rgb(136, 19, 145)"