@@ -1,52 +1,171 @@
1module FillInTheGaps where
1module FillInTheGaps where
3import Prelude
3import Prelude
4−
4+import Data.Array (foldMap, intercalate)
5import Data.Array as A
5import Data.Array as A
6+import Data.Either (Either(..))
7+import Data.Foldable (foldl, for_)
6−import Data.Maybe (fromMaybe)
8+import Data.Maybe (Maybe(..), fromMaybe, fromMaybe')
9+import Data.Monoid (guard)
7import Data.String (Pattern(..), split)
10import Data.String (Pattern(..), split)
11+import Data.String as S
12+import Data.String.Regex (Regex)
13+import Data.String.Regex as Regex
14+import Data.String.Regex.Flags as RegexFlags
15+import Data.String.Regex.Unsafe (unsafeRegex)
8import Data.Tuple.Nested ((/\))
16import Data.Tuple.Nested ((/\))
9import Effect (Effect)
17import Effect (Effect)
18+import Effect.Aff (launchAff_)
19+import Effect.Class (liftEffect)
10import Justifill (justifill)
20import Justifill (justifill)
22+import Milkis.Impl (FetchImpl)
23+import Milkis.Impl.Window (windowFetch)
11import Partial.Unsafe (unsafeCrashWith)
24import Partial.Unsafe (unsafeCrashWith)
12−import React.Basic (JSX, ReactComponent, element)
25+import React.Basic (JSX, ReactComponent, element, fragment)
26+import React.Basic.DOM (form_)
13import React.Basic.DOM as R
27import React.Basic.DOM as R
28+import React.Basic.Events (handler_)
29+import React.Basic.Helpers (jsx)
14−import React.Basic.Hooks (component, useState)
30+import React.Basic.Hooks (component, useEffect, useState)
15import React.Basic.Hooks as React
31import React.Basic.Hooks as React
32+import React.Basic.Hooks.Aff (useAff)
33+import Yoga.Button.Component (ButtonType(..), mkButton)
34+import Yoga.CompileEditor.Component (compileAndRun)
16−import Yoga.Helpers (intersperse)
35+import Yoga.Helpers ((?||))
17import Yoga.InlineCode.Component as InlineCode
36import Yoga.InlineCode.Component as InlineCode
37+import Yoga.Modal.Component as Modal
40+ = ExpectedResult String
46+derive instance eqSegment ∷ Eq Segment
47+getResult ∷ Segment -> Maybe String
49+ ExpectedResult r -> Just r
19−data Segment = Filler String | Hole String
52+findResult ∷ Array Segment -> String
53+findResult = fromMaybe' (\_ -> unsafeCrashWith "Even teachers make mistakes") <<< A.findMap getResult
55+toCode ∷ (Array (Array Segment)) -> String
56+toCode lines = intercalate "\n" mapped
58+ mapped = lines <#> (intercalate "" <<< map segmentToCode)
59+ segmentToCode = case _ of
21−renderSegments :: ReactComponent InlineCode.Props -> ((Array (Array Segment) -> Array (Array Segment)) -> Effect Unit) -> Array (Array Segment) -> JSX
64+complete ∷ (Array (Array Segment)) -> Boolean
22−renderSegments ic update arrs = R.div_ (arrs `flip A.mapWithIndex` renderLine)
65+complete arr = foldl f true (join arr)
73+renderSegments ∷ ReactComponent InlineCode.Props -> ((Array (Array Segment) -> Array (Array Segment)) -> Effect Unit) -> Array (Array Segment) -> JSX
74+renderSegments ic update arrs = R.div_ (A.mapWithIndex renderLine (onlyVisible arrs))
76+ onlyVisible arr = A.slice start end arr
78+ start = A.findIndex (_ == [ Start ]) arr ?|| 0
79+ end = A.findIndex (_ == [ End ]) arr ?|| A.length arr
24− renderLine i l = R.div_ (l `flip A.mapWithIndex` renderSegment i)
80+ renderLine i l = R.div_ (A.mapWithIndex (renderSegment i) l)
25 renderSegment i j = case _ of
81 renderSegment i j = case _ of
26− Filler s -> R.span_ [R.text s]
82+ Filler s -> R.code_ [ R.text s ]
27− Hole _ -> element ic $ justifill { onSubmit: \s -> update (updateSegments i j s) }
83+ Hole width _ -> element ic $ justifill { width, onSubmit: \v -> update (updateSegments i j (S.trim v)) }
29−updateSegments :: Int -> Int -> String -> Array (Array Segment) -> Array (Array Segment)
86+updateSegments ∷ Int -> Int -> String -> Array (Array Segment) -> Array (Array Segment)
30updateSegments i j v arrs = fromMaybe [] (A.modifyAt i (updateLine j v) arrs)
87updateSegments i j v arrs = fromMaybe [] (A.modifyAt i (updateLine j v) arrs)
32−updateLine :: Int -> String -> Array Segment -> Array Segment
89+updateLine ∷ Int -> String -> Array Segment -> Array Segment
33updateLine idx v arr = fromMaybe [] (A.modifyAt idx f arr)
90updateLine idx v arr = fromMaybe [] (A.modifyAt idx f arr)
35 f = case _ of
92 f = case _ of
36− Filler s -> unsafeCrashWith "Updated a filler"
94+ _ -> unsafeCrashWith "Updated a non-hole"
97+holeRegex = unsafeRegex "({-.*?-})" RegexFlags.global
100+resultRegex = unsafeRegex "--result" RegexFlags.global
103+startRegex = unsafeRegex "--start here" RegexFlags.global
106+endRegex = unsafeRegex "--end here" RegexFlags.global
108+rawSegments ∷ String -> Array String
109+rawSegments = Regex.split holeRegex
111+toSegment ∷ String -> Segment
112+toSegment = case _ of
114+ | Regex.test holeRegex x -> Hole (S.length x - 4) ""
116+ | S.indexOf (Pattern "--result ") x == Just 0 -> ExpectedResult ((S.stripPrefix (Pattern "--result ") x) ?|| "")
118+ | Regex.test startRegex x -> Start
120+ | Regex.test endRegex x -> End
121+ other -> Filler other
39−mkFillInTheGaps :: Effect (ReactComponent { code :: String })
123+mkFillInTheGaps ∷ FetchImpl -> Effect (ReactComponent { code ∷ String })
40−mkFillInTheGaps = do
124+mkFillInTheGaps fetch = do
41 ic <- InlineCode.makeComponent
125 ic <- InlineCode.makeComponent
126+ modal <- Modal.makeComponent
42− component "FillInTheGaps" \{ code } -> React.do
128+ component "FillInTheGaps" \{ code } -> React.do
45 lines = split (Pattern "\n") code
130 lines = split (Pattern "\n") code
46− rawSegments = split (Pattern separator) >>> intersperse separator
131+
47− toSegment = case _ of
48− x | x == separator -> Hole ""
49− other -> Filler other
50 initialSegments = lines <#> \line -> rawSegments line <#> toSegment
132 initialSegments = lines <#> \line -> rawSegments line <#> toSegment
51 segments /\ modifySegments <- useState initialSegments
133 segments /\ modifySegments <- useState initialSegments
134+ result /\ modifyResult <- useState Nothing
136+ expectedResult = findResult (join segments)
141+ res <- compileAndRun (M.fetch fetch) { code: toCode segments }
142+ modifyResult (const (Just $ res)) # liftEffect
52− pure $ renderSegments ic (modifySegments) segments
145+ [ renderSegments ic (modifySegments) segments
147+ { onClick: handler_ onClick
149+ -- if complete segments
152+ -- else DisabledButton
161+ | stdout == expectedResult -> "Hooray!"
162+ Left l -> "Does not compile"
163+ Right _ -> "Not " <> (findResult (join segments))
165+ [ R.text $ compileResultToString result ]
168+compileResultToString = case _ of
170+ Just (Left cr) -> cr.result <#> _.message # intercalate "/n"
171+ Just (Right r) -> r.stdout