@@ -12,11 +12,13 @@ import Foreword
12
12
13
13
import Clay qualified
14
14
import Control.Monad.Except (liftEither )
15
- import Control.Monad.Log (Severity (Notice ), WithSeverity , msgSeverity )
15
+ import Control.Monad.Log (WithSeverity )
16
+ import Control.Monad.Trans.Maybe (runMaybeT )
16
17
import Data.Aeson (FromJSON , ToJSON )
17
18
import Data.Data (Data (.. ))
18
19
import Data.Default qualified as Default
19
20
import Data.Generics.Uniplate.Data (children )
21
+ import Data.List.Extra (compareLength )
20
22
import Data.Map ((!?) )
21
23
import Data.Map qualified as Map
22
24
import Data.Tree (Tree )
@@ -129,9 +131,9 @@ import Primer.Core (
129
131
import Primer.Core qualified as Primer
130
132
import Primer.Core.Utils (forgetTypeMetadata )
131
133
import Primer.Def (defAST )
132
- import Primer.Eval (Dir (.. ), NormalOrderOptions (.. ))
134
+ import Primer.Eval (Dir (.. ), EvalLog , NormalOrderOptions (.. ), findRedex , redexes , step )
133
135
import Primer.Eval.Redex (RunRedexOptions (.. ), ViewRedexOptions (.. ))
134
- import Primer.EvalFullStep (EvalFullError (TimedOut ), EvalLog , evalFull )
136
+ import Primer.EvalFullStep (EvalFullError (TimedOut ), evalFull )
135
137
import Primer.JSON (CustomJSON (.. ), PrimerJSON )
136
138
import Primer.Log (runPureLogT )
137
139
import Primer.Miso.Layout (
@@ -154,21 +156,24 @@ import Primer.Miso.Util (
154
156
bindingsInExpr ,
155
157
bindingsInType ,
156
158
clayToMiso ,
159
+ exitFullscreen ,
157
160
findASTDef ,
158
161
kindsInType ,
162
+ logAllToConsole ,
159
163
nodeSelectionType ,
160
164
optToName ,
161
165
readMs ,
162
166
realToClay ,
167
+ requestFullscreen ,
168
+ runEval ,
163
169
runMutationWithNullDb ,
164
- runTC ,
165
170
selectedDefName ,
166
171
showMs ,
167
172
startAppWithSavedState ,
168
173
stringToOpt ,
169
174
typeBindingsInExpr ,
170
175
)
171
- import Primer.Name (Name , unName )
176
+ import Primer.Name (Name , NameCounter , unName )
172
177
import Primer.Typecheck (SmartHoles (SmartHoles ), buildTypingContext , exprTtoExpr , typeTtoType )
173
178
174
179
start :: JSM ()
@@ -187,11 +192,15 @@ start =
187
192
}
188
193
, eval =
189
194
EvalModel
190
- { expr = Nothing
191
- , error = Nothing
192
- , opts =
195
+ { opts =
193
196
EvalOpts
194
- { normalOrder = UnderBinders
197
+ { -- TODO we're setting these to `False` so that the initial states match visuals
198
+ -- would maybe be better to use our defaults from tests and and old frontend instead
199
+ -- (see `evalFullTest` - grouped lets, push and elide, aggressive)
200
+ -- requires Miso stuff - not sure how to set initial state properly
201
+ -- OTOH, maybe just reduce steps to 0? esp. now that we have interactive eval
202
+ -- since the initial state isn't really displayed there anyway
203
+ normalOrder = UnderBinders
195
204
, viewRedex =
196
205
ViewRedexOptions
197
206
{ groupedLets = False
@@ -202,10 +211,11 @@ start =
202
211
RunRedexOptions
203
212
{ pushAndElide = False
204
213
}
205
- , stepLimit = 10
214
+ , stepLimit = 0
206
215
, dir = Chk
207
216
}
208
217
, fullscreen = False
218
+ , history = []
209
219
}
210
220
}
211
221
}
@@ -245,10 +255,17 @@ data ActionPanelModel = ActionPanelModel
245
255
deriving (ToJSON , FromJSON ) via PrimerJSON ActionPanelModel
246
256
247
257
data EvalModel = EvalModel
248
- { expr :: Maybe Expr
249
- , error :: Maybe MisoString
250
- , opts :: EvalOpts
258
+ { opts :: EvalOpts
251
259
, fullscreen :: Bool
260
+ , history :: [(Expr , (ID , NameCounter ), ([ID ], Maybe ID ))]
261
+ -- ^ current first, original last
262
+ -- (where original is the expr we get to from non-interactive eval)
263
+ -- includes eval counter states after producing the expr
264
+ -- empty means no def selection
265
+ -- TODO set default steps down to 0 on account of this? be nice to be less arbitrary anyway, but maybe orthogonal
266
+ -- also, we now cache redexes here - not strictly necessary but a simple optimisation that also makes undo code simpler
267
+ -- well, now it's the only place we put them, but still we don't _have_ to remember old ones
268
+ -- TODO use record? `data EvalHistoryEntry`?
252
269
}
253
270
deriving stock (Eq , Show , Generic )
254
271
deriving (ToJSON , FromJSON ) via PrimerJSON EvalModel
@@ -276,6 +293,8 @@ data Action
276
293
| SetApp Primer.App. App
277
294
| SetEvalOpts (EvalOpts -> EvalOpts )
278
295
| ToggleFullscreenEval
296
+ | ChooseRedex ID
297
+ | StepBackEval
279
298
280
299
updateModel :: Action -> Model -> Effect Action Model
281
300
updateModel =
@@ -333,7 +352,55 @@ updateModel =
333
352
SetEvalOpts f -> do
334
353
# components % # eval % # opts %= f
335
354
setEval
336
- ToggleFullscreenEval -> # components % # eval % # fullscreen %= not
355
+ ToggleFullscreenEval -> do
356
+ # components % # eval % # fullscreen %= not
357
+ -- TODO this is nice on mobile...
358
+ -- orthogonal to this commit
359
+ -- we should really separate the notions of "eval takes up whole app" and "app is actually fullscreen"
360
+ -- JSaddle code needs clean up - error handling, not using `eval` etc.
361
+ -- ideally, mobile browsers would just have a generic option for this, like pressing f11 on desktop...
362
+ -- Opera Mini has this but it's clunky
363
+ fs' <- use $ # components % # eval % # fullscreen
364
+ scheduleIO_ $
365
+ void
366
+ if fs'
367
+ then requestFullscreen
368
+ else exitFullscreen
369
+ ChooseRedex id -> do
370
+ app <- use # app
371
+ history <- use $ # components % # eval % # history
372
+ opts <- use $ # components % # eval % # opts
373
+ let
374
+ -- TODO DRY with view
375
+ defs = progAllDefs (appProg app)
376
+ tydefs = progAllTypeDefs (appProg app)
377
+ defs' = snd <$> defs
378
+ tydefs' = snd <$> tydefs
379
+ let exprToEvalMaybe = case history of
380
+ (e0', s0', _) : _ -> Just (e0', s0')
381
+ [] -> Nothing
382
+ case exprToEvalMaybe of
383
+ -- TODO `Nothing` means no def selected...
384
+ -- this should only actually happen on race conditions,
385
+ -- given that `ChooseRedex` will only be triggered by clicking on an eval expr
386
+ Nothing -> pure ()
387
+ Just (currentEvalExpr, s0) -> do
388
+ let ((res, evalStepLogs), s1) =
389
+ runEval s0 $ step opts. viewRedex. avoidShadowing' tydefs' defs' currentEvalExpr opts. dir id
390
+ scheduleIO_ $ logAllToConsole evalStepLogs
391
+ case res of
392
+ -- TODO set the error field instead?
393
+ Left err -> scheduleIO_ $ consoleLog $ " eval error: " <> show err
394
+ -- TODO do something with detail? at least note that it's unused?
395
+ Right (expr, _detail) -> do
396
+ scheduleIO_ $ logAllToConsole redexesLogs
397
+ # components % # eval % # history %= ((expr, s1, rxs) : )
398
+ where
399
+ (rxs, redexesLogs) = getRedexes opts tydefs' defs' expr
400
+ StepBackEval -> do
401
+ (# components % # eval % # history) %= \ case
402
+ [] -> [] -- TODO this shouldn't really happen - we should actually grey out the button in this state
403
+ _ : es -> es
337
404
where
338
405
-- TODO the only part of this that should really require `IO` is writing to a database
339
406
-- (currently we use `NullDb` anyway but this will change)
@@ -353,28 +420,30 @@ updateModel =
353
420
opts <- use $ # components % # eval % # opts
354
421
fullscreen <- use $ # components % # eval % # fullscreen
355
422
let (tydefs, defs, maybeDef) = getDefs app
423
+ -- TODO clean this whole section up a bit in separate commit
424
+ -- maybe re-inline `runEval`? now only used twice (`runPureLogT @_ @(WithSeverity EvalLog)`)
356
425
evalModel <- case maybeDef of
357
- Nothing -> pure EvalModel {expr = Nothing , error = Just " No selection for eval " , opts, fullscreen }
426
+ Nothing -> pure EvalModel {opts, fullscreen, history = [] }
358
427
Just def -> do
359
428
let nextId = succ $ appIdCounter app
360
429
nextName = succ $ appNameCounter app
361
430
-- TODO put this in to a background thread rather than blocking whole program for expensive evaluations
362
- (evalResult, logs) =
363
- either absurd identity
364
- . runTC (succ nextId, nextName)
365
- . runPureLogT
431
+ ((evalResult, evalFullLogs), s') =
432
+ runEval (succ nextId, nextName)
366
433
. evalFull opts. normalOrder opts. viewRedex opts. runRedex tydefs defs opts. stepLimit opts. dir
367
434
$ Ann (Meta nextId Nothing Nothing ) (exprTtoExpr def. expr) (typeTtoType def. sig)
368
- scheduleIO_ $ logAllToConsole @ EvalLog logs
369
- pure case evalResult of
370
- Left (TimedOut expr) -> EvalModel {expr = Just expr, error = Just " Eval timed out:" , opts, fullscreen}
371
- Right expr -> EvalModel {expr = Just expr, error = Nothing , opts, fullscreen}
435
+ expr = case evalResult of
436
+ Left (TimedOut e) -> e
437
+ Right e -> e
438
+ (rxs, redexesLogs) = getRedexes opts tydefs defs expr
439
+ scheduleIO_ $ logAllToConsole evalFullLogs
440
+ scheduleIO_ $ logAllToConsole redexesLogs
441
+ pure EvalModel {opts, fullscreen, history = [(expr, s', rxs)]}
372
442
# components % # eval .= evalModel
373
- -- TODO better logging, including handling different severities appropriately
374
- logAllToConsole :: Show a => Seq (WithSeverity a ) -> JSM ()
375
- logAllToConsole logs =
376
- let issues = filter ((<= Notice ) . msgSeverity) $ toList logs
377
- in unless (null issues) $ consoleLog $ ms $ unlines $ map show issues
443
+ getRedexes (opts :: EvalOpts ) tydefs defs expr = runIdentity $ runPureLogT @ _ @ (WithSeverity EvalLog ) do
444
+ normalOrderRedex <- runMaybeT $ getID <$> findRedex opts. normalOrder opts. viewRedex tydefs defs opts. dir expr
445
+ allRedexes <- redexes opts. viewRedex. avoidShadowing' tydefs defs opts. dir expr
446
+ pure (allRedexes, normalOrderRedex)
378
447
-- TODO DRY this with `viewModel`
379
448
-- when we use Miso components it might be easier to compute this in one place then send messages around
380
449
getDefs app =
@@ -512,6 +581,10 @@ viewModel Model{..} =
512
581
]
513
582
in div_
514
583
[id_ " options" ]
584
+ -- TODO some of these should in principle be able to be set on the fly for interactive eval
585
+ -- i.e. without resetting history
586
+ -- possibly crucial to debugging eval issues I've been seeing
587
+ -- of course it's hard to square this with wanting the non-interactive eval output to reflect opts
515
588
[ checkBox " Stop at binders" \ b -> # normalOrder .~ if b then StopAtBinders else UnderBinders
516
589
, checkBox " Grouped lets" \ b -> # viewRedex % # groupedLets .~ b
517
590
, checkBox " Aggressive elision" \ b -> # viewRedex % # aggressiveElision .~ b
@@ -530,17 +603,28 @@ viewModel Model{..} =
530
603
]
531
604
, text " Steps"
532
605
]
533
- , button_ [onClick ToggleFullscreenEval ] [" ⛶" ]
534
606
]
607
+ , div_
608
+ -- TODO add styling here, and make this a separate commit
609
+ -- maybe even just squash with introduction of the fullscreen button
610
+ [id_ " " ]
611
+ $ munless (compareLength components. eval. history 2 == LT ) [button_ [onClick StepBackEval ] [" ↩" ]]
612
+ <> [ button_ [onClick ToggleFullscreenEval ] [" ⛶" ]
613
+ ]
535
614
]
536
- <> case components. eval. expr of
537
- Nothing -> [text " No definition selected for evaluation" ]
538
- Just expr ->
539
- ( case components. eval. error of
540
- Nothing -> []
541
- Just s -> [text s]
542
- )
543
- <> [fst . viewTree $ viewTreeExpr mkMeta expr]
615
+ <> case components. eval. history of
616
+ [] -> [text " No definition selected for evaluation" ]
617
+ (expr, _, rxs) : _ ->
618
+ [fst . viewTree $ viewTreeExpr (mkMeta' . getID) expr]
619
+ where
620
+ mkMeta' id =
621
+ if not $ id `elem` fst rxs
622
+ then (Just id , Nothing , NoHighlight )
623
+ else
624
+ ( Just id
625
+ , Just $ ChooseRedex id
626
+ , if snd rxs == Just id then AnimatedHighlight else SimpleHighlight
627
+ )
544
628
]
545
629
where
546
630
mkMeta = const (Nothing , Nothing , NoHighlight )
0 commit comments