├── .gitignore ├── Test.hs ├── Test1.hs ├── app └── Main.hs ├── chameleon ├── chameleon.cabal ├── hie.yaml ├── package.yaml ├── readme.md ├── src ├── Binding.hs ├── Builtin.hs ├── Constraint.hs ├── FieldOrdering.hs ├── Graph.hs ├── Instance.hs ├── JsonInstance.hs ├── Kanren.hs ├── Nameable.hs ├── Reasoning.hs ├── Run.hs ├── Scope.hs └── Typing.hs ├── stack.yaml ├── stack.yaml.lock ├── static ├── Debugger.jsx ├── Editor.jsx ├── MenuBar.jsx ├── TabReport.jsx ├── Toggle.jsx ├── TypeSig.jsx ├── build │ ├── challenge.html │ ├── consent.html │ ├── explanatory.html │ ├── favicon.ico │ ├── feedback.html │ ├── index.html │ ├── intro.html │ ├── introduction.html │ ├── logo.png │ ├── main.css │ ├── monash-logo.svg │ ├── out.js │ ├── out.js.map │ ├── playground.html │ ├── playground.js │ ├── playground.js.map │ ├── study.html │ ├── tutorial.html │ ├── tutorial.svg │ └── tutorial │ │ └── pngs │ │ ├── Card Active.png │ │ ├── Card Box.png │ │ ├── Card Inactive.png │ │ ├── Card X.png │ │ ├── Card Y.png │ │ ├── Debugging Message.png │ │ ├── Deduction Example Step 1.png │ │ ├── Deduction Example Step 2.png │ │ ├── Deduction Example Step 3.png │ │ ├── Deduction Example Step 4.png │ │ ├── Deduction Step Active.png │ │ ├── Deduction Step Annotation.png │ │ ├── Deduction Step Buttons.png │ │ ├── Deduction Step Highlight.png │ │ ├── Deduction Step Inactive.png │ │ ├── Deduction Step Menubar Button.png │ │ ├── Deduction Step UI.png │ │ ├── Expand Level 1.png │ │ ├── Expand Level 2.png │ │ ├── Highlight Left.png │ │ ├── Highlight Right.png │ │ ├── Highlights All.png │ │ ├── Highlights Blue.png │ │ ├── Highlights Orange.png │ │ ├── Menubar.png │ │ ├── Original.png │ │ ├── Possible Types 1.png │ │ ├── Possible Types 2.png │ │ └── Possible Types.png ├── challenge.jsx ├── cm.js ├── code.js ├── debuggerSlice.js ├── index.jsx ├── package-lock.json ├── package.json ├── playground.jsx ├── report.js ├── store.js └── util.js └── test ├── Intro.hs ├── NewTest1.hs ├── NewTest2.hs ├── NewTest3.hs ├── Test2.hs ├── Test3.hs ├── Test4.hs ├── Test5.hs ├── Test6.hs ├── Test7.hs ├── Test8.hs └── TypeClassBasic.hs /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .stack-work 3 | node_modules 4 | .env 5 | .envrc -------------------------------------------------------------------------------- /Test.hs: -------------------------------------------------------------------------------- 1 | divides x y = y `mod` x == 0 2 | 3 | 4 | dropEvery [] _ = [] 5 | dropEvery (x:xs) n = dropEvery' (x:xs) n 1 6 | 7 | dropEvery' :: [Int] -> Int -> Int -> [Int] 8 | dropEvery' [] _ _ = [] 9 | dropEvery' (x:xs) n i = 10 | let current = 11 | if n `divides` i 12 | then [] 13 | else [x] 14 | in current : dropEvery' xs n (i+1) -------------------------------------------------------------------------------- /Test1.hs: -------------------------------------------------------------------------------- 1 | toWeekday n = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] !! n 2 | 3 | seperateByComma :: [String] -> String 4 | seperateByComma [] = "" 5 | seperateByComma [x] = x 6 | seperateByComma (x : xs) = x ++ "," ++ seperateByComma xs 7 | 8 | range xs 9 | | length xs < 3 = seperateByComma xs 10 | | otherwise = head xs ++ "-" ++ last xs 11 | 12 | dayRange :: [Int] -> [String] 13 | dayRange days = 14 | let grouped = groupBy' (\a b -> a + 1 == b) days 15 | in map (range . toWeekday) grouped 16 | 17 | -- unlike groupBy which compares any element 18 | -- to the first, 19 | -- groupBy' compares any element to the last 20 | -- element 21 | groupBy' :: (a -> a -> Bool) -> [a] -> [[a]] 22 | groupBy' f (x : xs) = 23 | let go f (x : xs) ((a : as) : bs) = 24 | if f a x 25 | then go f xs ((x : a : as) : bs) 26 | else go f xs ([x] : (a : as) : bs) 27 | go _ [] as = reverse (map reverse as) 28 | in go f xs [[x]] -------------------------------------------------------------------------------- /app/Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module Main where 4 | 5 | import qualified Data.ByteString.Lazy.Char8 as BS 6 | import Data.Monoid (mconcat) 7 | import Control.Monad.IO.Class 8 | import qualified Data.Text.Lazy as T 9 | import JsonInstance 10 | import Run hiding (main) 11 | import Web.Scotty 12 | ( ScottyM, 13 | body, 14 | file, 15 | get, 16 | json, 17 | param, 18 | post, 19 | regex, 20 | scotty, 21 | setHeader, 22 | ) 23 | 24 | main = scotty 5000 ( 25 | typecheck 26 | >> home 27 | >> sourceMap 28 | >> js 29 | >> css 30 | >> svg 31 | >> jpg 32 | >> png 33 | >> favicon 34 | >> page 35 | ) 36 | 37 | typecheck :: ScottyM () 38 | typecheck = post "/typecheck" $ do 39 | content <- body 40 | let result = processFile (BS.unpack content) 41 | json result 42 | 43 | 44 | 45 | 46 | home :: ScottyM () 47 | home = get "/" $ do 48 | file "static/build/index.html" 49 | 50 | js :: ScottyM () 51 | js = get (regex "^.*\\.js$") $ do 52 | path <- param "0" 53 | let filename = "static/build" `T.append` path 54 | setHeader "Content-Type" "application/javascript" 55 | file (T.unpack filename) 56 | 57 | css :: ScottyM () 58 | css = get (regex "^.*\\.css") $ do 59 | path <- param "0" 60 | let filename = "static/build" `T.append` path 61 | setHeader "Content-Type" "text/css" 62 | file (T.unpack filename) 63 | 64 | 65 | svg :: ScottyM () 66 | svg = get (regex "^.*\\.svg") $ do 67 | path <- param "0" 68 | let filename = "static/build" `T.append` path 69 | setHeader "Content-Type" "image/svg+xml" 70 | file (T.unpack filename) 71 | 72 | jpg :: ScottyM () 73 | jpg = get (regex "^.*\\.jpg") $ do 74 | path <- param "0" 75 | let filename = "static/build" `T.append` path 76 | setHeader "Content-Type" "image/jpeg" 77 | file (T.unpack filename) 78 | 79 | png :: ScottyM () 80 | png = get (regex "^.*\\.png") $ do 81 | path <- param "0" 82 | let filename = "static/build" `T.append` path 83 | setHeader "Content-Type" "image/png" 84 | file (T.unpack filename) 85 | 86 | favicon :: ScottyM () 87 | favicon = get "/favicon.ico" $ do 88 | setHeader "Content-Type" "image/vnd.microsoft.icon" 89 | file "static/build/favicon.ico" 90 | 91 | sourceMap :: ScottyM () 92 | sourceMap = get (regex "^.*\\.js\\.map") $ do 93 | path <- param "0" 94 | let filename = "static/build" `T.append` path 95 | setHeader "Content-Type" "application/json" 96 | file (T.unpack filename) 97 | 98 | page :: ScottyM () 99 | page = get "/:page" $ do 100 | pageName <- param "page" 101 | let filename = "static/build/" `T.append` pageName `T.append` ".html" 102 | file (T.unpack filename) -------------------------------------------------------------------------------- /chameleon: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/chameleon -------------------------------------------------------------------------------- /chameleon.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 1.12 2 | 3 | -- This file has been generated from package.yaml by hpack version 0.34.4. 4 | -- 5 | -- see: https://github.com/sol/hpack 6 | 7 | name: chameleon 8 | version: 0.1.0.0 9 | description: Please see the README on GitHub 10 | homepage: https://github.com/maybetonyfu/chameleon#readme 11 | bug-reports: https://github.com/maybetonyfu/chameleon/issues 12 | author: Tony Fu 13 | maintainer: tonyfu@fastmail.com 14 | copyright: 2021 Tony Fu 15 | license: BSD3 16 | build-type: Simple 17 | 18 | source-repository head 19 | type: git 20 | location: https://github.com/maybetonyfu/chameleon 21 | 22 | library 23 | exposed-modules: 24 | Binding 25 | Builtin 26 | Constraint 27 | FieldOrdering 28 | Graph 29 | Instance 30 | JsonInstance 31 | Kanren 32 | Nameable 33 | Reasoning 34 | Run 35 | Scope 36 | Typing 37 | other-modules: 38 | Paths_chameleon 39 | hs-source-dirs: 40 | src 41 | build-depends: 42 | Agda 43 | , aeson 44 | , base 45 | , bytestring 46 | , containers 47 | , haskell-src-exts 48 | , lens 49 | , scotty 50 | , string-qq 51 | , text 52 | , transformers 53 | default-language: Haskell2010 54 | 55 | executable chameleon 56 | main-is: Main.hs 57 | other-modules: 58 | Paths_chameleon 59 | hs-source-dirs: 60 | app 61 | ghc-options: -threaded -rtsopts -with-rtsopts=-N 62 | build-depends: 63 | Agda 64 | , aeson 65 | , base 66 | , bytestring 67 | , chameleon 68 | , containers 69 | , haskell-src-exts 70 | , lens 71 | , scotty 72 | , string-qq 73 | , text 74 | , transformers 75 | default-language: Haskell2010 76 | -------------------------------------------------------------------------------- /hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | stack: -------------------------------------------------------------------------------- /package.yaml: -------------------------------------------------------------------------------- 1 | name: chameleon 2 | version: 0.1.0.0 3 | github: "maybetonyfu/chameleon" 4 | license: BSD3 5 | author: "Tony Fu" 6 | maintainer: "tonyfu@fastmail.com" 7 | copyright: "2021 Tony Fu" 8 | 9 | # Metadata used when publishing your package 10 | # synopsis: Short description of your package 11 | # category: Web 12 | 13 | # To avoid duplicated efforts in documentation and dealing with the 14 | # complications of embedding Haddock markup inside cabal files, it is 15 | # common to point users to the README.md file. 16 | description: Please see the README on GitHub 17 | 18 | dependencies: 19 | - aeson 20 | - base 21 | - bytestring 22 | - containers 23 | - haskell-src-exts 24 | - transformers 25 | - Agda 26 | - scotty 27 | - string-qq 28 | - lens 29 | - text 30 | 31 | library: 32 | source-dirs: src 33 | 34 | executables: 35 | chameleon: 36 | main: Main.hs 37 | source-dirs: app 38 | ghc-options: 39 | - -threaded 40 | - -rtsopts 41 | - -with-rtsopts=-N 42 | dependencies: 43 | - chameleon 44 | 45 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Chameleon 2 | 3 | Chameleon Logo 4 | A tool to make solving type errors in Haskell simple and fun 5 | 6 | ## Why Chameleon 7 | 8 | - Human-Centered Methods 9 | 10 | We improve each feature in Chameleon based on the feedback from the Haskell community. Debugging idioms in Chameleon has been tested individually and in combinations. 11 | 12 | - Multi-location Type Errors 13 | 14 | While many type systems try to pinpoint one exact error location in the code, the accuracy is often hit or miss. Chameleon tries to narrow down to a few suspects and asks the programmer to identify the real culprit. While both approaches have pros and cons, we believe Chameleon is more flexible and catches bugs faster. 15 | 16 | - Unbiased Type Errors 17 | 18 | Instead of assuming one type is "Expected" and one type is "Actual", Chameleon will report two equally possible alternatives that type errors can happen. Many techniques have been proposed to solve this problem (Known as left-right bias) on type solver level. Chameleon combines the type solver capable of eliminating this bias and smart visual cues to distinguish the evidence for one type and the other. 19 | 20 | - Step-by-step Debugging 21 | 22 | The deduction step is a tool to peek inside the type checking engine. It shows step-by-step reasoning that explains why one type cannot reconcile with another in simple language. Chameleon's interactive interface allows users to make incremental assumptions and see how that affects the typing of the whole program. 23 | 24 | 25 | - More are coming! 26 | 27 | Hang tight! We are working on more features to make Chameleon even better! 28 | 29 | ## Playground 30 | 31 | The chameleon playground is an online editor for you to test out some of the chameleon features. 32 | 33 | [Go to playground](https://chameleon.typecheck.me/playground) 34 | -------------------------------------------------------------------------------- /src/Builtin.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE QuasiQuotes #-} 2 | 3 | module Builtin where 4 | 5 | import Data.String.QQ 6 | 7 | builtin :: String 8 | builtin = 9 | [s| 10 | 11 | mod :: Int -> Int -> Int 12 | mod = undefined 13 | 14 | div :: Int -> Int -> Int 15 | div = undefined 16 | 17 | (/=) :: a -> a -> Bool 18 | (/=) = undefined 19 | 20 | data Ordering = LT | EQ | GT 21 | 22 | data IO a = IO a 23 | 24 | data Maybe a = Nothing | Just a 25 | 26 | compare :: a -> a -> Ordering 27 | compare = undefined 28 | 29 | (<) :: a -> a -> Bool 30 | (<) = undefined 31 | 32 | (<=) :: a -> a -> Bool 33 | (<=) = undefined 34 | 35 | (>) :: a -> a -> Bool 36 | (>) = undefined 37 | 38 | (>=) :: a -> a -> Bool 39 | (>=) = undefined 40 | 41 | max :: a -> a -> a 42 | max = undefined 43 | 44 | min :: a -> a -> a 45 | min = undefined 46 | 47 | (==) :: a -> a -> Bool 48 | (==) = undefined 49 | 50 | (+) :: Int -> Int -> Int 51 | (+) = undefined 52 | 53 | (-) :: Int -> Int -> Int 54 | (-) = undefined 55 | 56 | (*) :: Int -> Int -> Int 57 | (*) = undefined 58 | 59 | negate :: a -> a 60 | negate = undefined 61 | 62 | abs :: a -> a 63 | abs = undefined 64 | 65 | signum :: a -> a 66 | signum = undefined 67 | 68 | fromInteger :: Integer -> a 69 | fromInteger = undefined 70 | 71 | id :: a -> a 72 | id = undefined 73 | 74 | map :: (a -> b) -> [a] -> [b] 75 | map = undefined 76 | 77 | not :: Bool -> Bool 78 | not = undefined 79 | 80 | (&&) :: Bool -> Bool -> Bool 81 | (&&) = undefined 82 | 83 | (||) :: Bool -> Bool -> Bool 84 | (||) = undefined 85 | 86 | otherwise :: Bool 87 | otherwise = undefined 88 | 89 | error :: [Char] -> a 90 | error = undefined 91 | 92 | length :: [a] -> Int 93 | length = undefined 94 | 95 | toUpper :: Char -> Char 96 | toUpper = undefined 97 | 98 | toLower :: Char -> Char 99 | toLower = undefined 100 | 101 | (++) :: [a] -> [a] -> [a] 102 | (++) = undefined 103 | 104 | filter :: (a -> Bool) -> [a] -> [a] 105 | filter = undefined 106 | 107 | lines :: [Char] -> [[Char]] 108 | lines = undefined 109 | 110 | unlines :: [[Char]] -> [Char] 111 | unlines = undefined 112 | 113 | (<>) :: a -> a -> a 114 | (<>) = undefined 115 | 116 | mempty :: a 117 | mempty = undefined 118 | 119 | mappend :: a -> a -> a 120 | mappend = undefined 121 | 122 | mconcat :: [a] -> a 123 | mconcat = undefined 124 | 125 | fmap :: (a -> b) -> f a -> f b 126 | fmap = undefined 127 | 128 | pure :: a -> f a 129 | pure = undefined 130 | 131 | (<*>) :: f (a -> b) -> f a -> f b 132 | (<*>) = undefined 133 | 134 | (>>=) :: m a -> (a -> m b) -> m b 135 | (>>=) = undefined 136 | 137 | (>>) :: m a -> m b -> m b 138 | (>>) = undefined 139 | 140 | return :: a -> m a 141 | return = undefined 142 | 143 | putStrLn :: [Char] -> IO () 144 | putStrLn = undefined 145 | 146 | zip :: [a] -> [b] -> [(a, b)] 147 | zip = undefined 148 | 149 | readFile :: [Char] -> IO [Char] 150 | readFile = undefined 151 | 152 | toEnum :: Int -> a 153 | toEnum = undefined 154 | 155 | fromEnum :: a -> Int 156 | fromEnum = undefined 157 | 158 | fst :: (a, b) -> a 159 | fst = undefined 160 | 161 | snd :: (a, b) -> b 162 | snd = undefined 163 | 164 | (^) :: Int -> Int -> Int 165 | (^) = undefined 166 | 167 | rem :: Int -> Int -> Int 168 | rem = undefined 169 | 170 | getContents :: IO [Char] 171 | getContents = undefined 172 | 173 | ($) :: (a -> b) -> a -> b 174 | ($) = undefined 175 | 176 | reverse :: [a] -> [a] 177 | reverse = undefined 178 | 179 | foldr :: (a -> b -> b) -> b -> [a] -> b 180 | foldr = undefined 181 | 182 | foldl' :: (b -> a -> b) -> b -> [a] -> b 183 | foldl' = undefined 184 | 185 | foldl :: (b -> a -> b) -> b -> [a] -> b 186 | foldl = undefined 187 | 188 | head :: [a] -> a 189 | head = undefined 190 | 191 | last :: [a] -> a 192 | last = undefined 193 | 194 | init :: [a] -> [a] 195 | init = undefined 196 | 197 | tail :: [a] -> [a] 198 | tail = undefined 199 | 200 | elem :: a -> [a] -> Bool 201 | elem = undefined 202 | 203 | show :: a -> [Char] 204 | show = undefined 205 | 206 | type String = [Char] 207 | 208 | permutations :: [a] -> [[a]] 209 | permutations = undefined 210 | 211 | (.) :: (b -> c) -> (a -> b) -> a -> c 212 | (.) = undefined 213 | 214 | take :: n -> [a] -> [a] 215 | take = undefined 216 | 217 | drop :: n -> [a] -> [a] 218 | drop = undefined 219 | 220 | 221 | and :: [Bool] -> Bool 222 | and = undefined 223 | 224 | or :: [Bool] -> Bool 225 | or = undefined 226 | 227 | (!!) :: [a] -> Int -> a 228 | (!!) = undefined 229 | |] 230 | -------------------------------------------------------------------------------- /src/Constraint.hs: -------------------------------------------------------------------------------- 1 | module Constraint where 2 | 3 | import qualified Data.Map as Map 4 | import Data.Maybe 5 | import Debug.Trace 6 | import Kanren 7 | 8 | walkUpdateTags :: Term -> Subst -> Subst 9 | walkUpdateTags Atom {} subs = subs 10 | walkUpdateTags Unit subs = subs 11 | walkUpdateTags (Pair x y _) subs = walkUpdateTags y (walkUpdateTags x subs) 12 | walkUpdateTags (Var x tags) subs = 13 | case Map.lookup x subs of 14 | Nothing -> subs 15 | Just t -> trace ("\nFor " ++ x ++ " -> " ++ show t ++ " with tags " ++ show tags) $ 16 | walkUpdateTags t (Map.insert x (appendTags tags t) subs) 17 | 18 | walkSyncTermTags :: Subst -> Subst 19 | walkSyncTermTags subs = 20 | let addTerm (Var v tags) mapping = Map.insertWith (++) (var v) tags mapping 21 | addTerm (Atom a tags) mapping = Map.insertWith (++) (atom a) tags mapping 22 | addTerm (Pair x y tags) mapping = Map.insertWith (++) (pair (setTags [] x) (setTags [] y)) tags (addTerm y . addTerm x $ mapping) 23 | addTerm _ mapping = mapping 24 | lookUpAndSetTag (Var v _) mapping = Var v (fromMaybe [] (Map.lookup (var v) mapping)) 25 | lookUpAndSetTag (Atom a _) mapping = Atom a (fromMaybe [] (Map.lookup (atom a) mapping)) 26 | lookUpAndSetTag (Pair x y _) mapping = 27 | Pair 28 | (lookUpAndSetTag x mapping) 29 | (lookUpAndSetTag y mapping) 30 | (fromMaybe [] (Map.lookup (pair (setTags [] x) (setTags [] y)) mapping)) 31 | lookUpAndSetTag Unit mapping = Unit 32 | tagmap = foldr addTerm Map.empty (Map.elems subs ++ Map.elems subs) 33 | reinsertTags (key, term) sub = Map.insert key (lookUpAndSetTag term tagmap) sub 34 | in trace ("\nTagMap:\n" ++ unlines (map show (Map.toList tagmap)) ++ "\n") $ 35 | foldr reinsertTags subs (Map.toList subs) 36 | 37 | deriveRequirement :: Subst -> Subst 38 | deriveRequirement subs = 39 | let allKeys = Map.keys subs 40 | unifiedTags = trace ("\nAll keys:\n" ++ show allKeys) foldr (walkUpdateTags . var) (walkSyncTermTags subs) allKeys 41 | in trace ("\nUnified Tags:\n" ++ unlines (map show (Map.toList unifiedTags)) ++ "\n") $ walkSyncTermTags unifiedTags -------------------------------------------------------------------------------- /src/FieldOrdering.hs: -------------------------------------------------------------------------------- 1 | module FieldOrdering where 2 | 3 | import Debug.Trace 4 | import Language.Haskell.Exts.Parser 5 | import Language.Haskell.Exts.Pretty 6 | import Language.Haskell.Exts.Syntax 7 | import Nameable 8 | import System.Environment 9 | 10 | processFile :: String -> IO () 11 | processFile filepath = do 12 | contents <- readFile filepath 13 | let pResult = parseModuleWithMode parseMode contents 14 | parseMode = defaultParseMode {parseFilename = filepath} 15 | case pResult of 16 | ParseOk hModule -> do 17 | print "OK" 18 | let order = getFieldOrdering hModule 19 | mapM_ print order 20 | ParseFailed srcLoc message -> 21 | putStrLn $ 22 | unlines 23 | [ prettyPrint srcLoc, 24 | message 25 | ] 26 | 27 | type FieldOrdering = (String, Int) 28 | 29 | class HasFieldOrdering f where 30 | getFieldOrdering :: f a -> [FieldOrdering] 31 | 32 | instance HasFieldOrdering Module where 33 | getFieldOrdering (Module _ _ _ _ decls) = concatMap getFieldOrdering decls 34 | getFieldOrdering _ = error "Not a module" 35 | 36 | instance HasFieldOrdering Decl where 37 | getFieldOrdering (DataDecl _ _ _ _ conDecls _) = concatMap getFieldOrdering conDecls 38 | getFieldOrdering _ = [] 39 | 40 | instance HasFieldOrdering QualConDecl where 41 | getFieldOrdering (QualConDecl _ _ _ conDecl) = getFieldOrdering conDecl 42 | 43 | instance HasFieldOrdering ConDecl where 44 | getFieldOrdering (RecDecl _ name fields) = 45 | let filedsNames = concatMap (\(FieldDecl _ names _) -> map getName names) fields 46 | in zip filedsNames (take (length filedsNames) [0 ..]) 47 | getFieldOrdering _ = [] 48 | 49 | main :: IO () 50 | main = do 51 | args <- getArgs 52 | let path = head args 53 | processFile path 54 | -------------------------------------------------------------------------------- /src/Graph.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell #-} 2 | 3 | module Graph where 4 | 5 | import qualified Data.Set as Set 6 | import Data.Maybe 7 | import Control.Lens 8 | 9 | data Graph a = Graph { 10 | _nodes :: Set.Set a, 11 | _edges :: Set.Set (a, a) 12 | } deriving Show 13 | makeLenses ''Graph 14 | 15 | emptyGraph :: Graph a 16 | emptyGraph = Graph { 17 | _nodes = Set.empty, 18 | _edges = Set.empty 19 | } 20 | 21 | addNode :: Ord a => a -> Graph a -> Graph a 22 | addNode a = over nodes (Set.insert a) 23 | 24 | addEdge :: Ord a => a -> a -> Graph a -> Graph a 25 | addEdge a1 a2 graph = over edges (Set.insert (min a1 a2, max a1 a2)) (addNode a2 (addNode a1 graph)) 26 | 27 | reachable :: Ord a => a -> Graph a -> [a] 28 | reachable a graph = concat . Set.toList . Set.insert [a] . Set.map (\(x,y) -> if x == a then [y] else [x | y == a]) $ view edges graph -------------------------------------------------------------------------------- /src/Instance.hs: -------------------------------------------------------------------------------- 1 | module Instance where 2 | 3 | import Binding hiding (main, processFile) 4 | import Debug.Trace 5 | import Kanren 6 | import Language.Haskell.Exts.Parser 7 | import Language.Haskell.Exts.Pretty 8 | import Language.Haskell.Exts.SrcLoc 9 | import Language.Haskell.Exts.Syntax 10 | import Nameable 11 | import System.Environment 12 | 13 | processFile :: String -> IO () 14 | processFile filepath = do 15 | contents <- readFile filepath 16 | let pResult = parseModuleWithMode parseMode contents 17 | parseMode = defaultParseMode {parseFilename = filepath} 18 | case pResult of 19 | ParseOk hModule -> do 20 | print "OK" 21 | let bindings = moduleBindings hModule 22 | cons = getInstances bindings hModule 23 | res = run1 [] (hasInstance cons "X" (Pair (atom "Maybe") (var "abcd") [])) 24 | print res 25 | ParseFailed srcLoc message -> 26 | putStrLn $ 27 | unlines 28 | [ prettyPrint srcLoc, 29 | message 30 | ] 31 | 32 | data Instance = Instance {tcName :: String, tcVar :: Term, tcAdditional :: [Instance] -> Term -> Goal} 33 | 34 | class HasInstance f where 35 | getInstances :: [(Binding, Int)] -> f SrcSpanInfo -> [Instance] 36 | 37 | instance HasInstance Module where 38 | getInstances bindings (Module _ _ _ _ decls) = concatMap (getInstances bindings) decls 39 | getInstances bindings _ = error "Not a module" 40 | 41 | instance HasInstance Decl where 42 | getInstances bindings (InstDecl _ _ instanceRule _) = getInstances bindings instanceRule 43 | getInstances bindings _ = [] 44 | 45 | instance HasInstance InstRule where 46 | getInstances bindings (IParen _ insRule) = getInstances bindings insRule 47 | getInstances bindings (IRule _ _ maybeContext (IHApp _ (IHCon _ qname) typeDef)) = 48 | let term = typeToTerm bindings typeDef 49 | in case maybeContext of 50 | Nothing -> [Instance (getName qname) term (\_ _ -> succeeds)] 51 | Just ctx -> 52 | case ctx of 53 | (CxSingle _ (TypeA _ t)) -> 54 | let (Pair x y _) = typeToTerm bindings t 55 | tVar = y 56 | className = atomToString x 57 | fun = \ins t -> 58 | callFresh 59 | ( \t' -> 60 | let newTerm = replaceTerm tVar t' term 61 | in conj2 (t === newTerm) (hasInstance ins className t') 62 | ) 63 | in [Instance (getName qname) term fun] 64 | _ -> [] 65 | getInstances bindings _ = error "Multiple parameter type class is not supported" 66 | 67 | typeToTerm :: [(Binding, Int)] -> Type SrcSpanInfo -> Term 68 | typeToTerm bindings (TyVar (SrcSpanInfo sp _) name) = var $ uniqueNameFromBinding bindings Nothing (getName name) sp 69 | typeToTerm bindings (TyCon _ qname) = atom (getName qname) 70 | typeToTerm bindings (TyList _ t) = lstOf (typeToTerm bindings t) 71 | typeToTerm bindings (TyFun _ t1 t2) = funOf [typeToTerm bindings t1, typeToTerm bindings t2] 72 | typeToTerm bindings (TyTuple _ _ ts) = tupOf . map (typeToTerm bindings) $ ts 73 | typeToTerm bindings (TyUnboxedSum _ ts) = tupOf . map (typeToTerm bindings) $ ts 74 | typeToTerm bindings (TyApp _ t1 t2) = Pair (typeToTerm bindings t1) (typeToTerm bindings t2) [] 75 | typeToTerm bindings (TyParen _ t) = typeToTerm bindings t 76 | typeToTerm _ _ = error "Unsupported type" 77 | 78 | varsFromType :: [(Binding, Int)] -> Type SrcSpanInfo -> [Term] 79 | varsFromType bindings (TyVar (SrcSpanInfo sp _) name) = [var $ uniqueNameFromBinding bindings Nothing (getName name) sp] 80 | varsFromType bindings (TyCon _ qname) = [] 81 | varsFromType bindings (TyList _ t) = varsFromType bindings t 82 | varsFromType bindings (TyFun _ t1 t2) = varsFromType bindings t1 ++ varsFromType bindings t2 83 | varsFromType bindings (TyTuple _ _ ts) = concatMap (varsFromType bindings) ts 84 | varsFromType bindings (TyUnboxedSum _ ts) = concatMap (varsFromType bindings) ts 85 | varsFromType bindings (TyApp _ t1 t2) = varsFromType bindings t1 ++ varsFromType bindings t2 86 | varsFromType bindings (TyParen _ t) = varsFromType bindings t 87 | varsFromType _ _ = error "Unsupported type" 88 | 89 | hasInstance :: [Instance] -> String -> Term -> Goal 90 | hasInstance ins typeclass t = 91 | let instances = filter (\(Instance cl _ _) -> cl == typeclass) ins 92 | generateCondaLine fromClass fromInstance additional = 93 | [ callFresh 94 | ( \newVar1 -> 95 | callFresh 96 | ( \newVar2 -> 97 | conjN 98 | [ newVar1 ==< fromClass, 99 | newVar2 ==< fromInstance, 100 | newVar2 === newVar1, 101 | additional ins newVar2 102 | ] 103 | ) 104 | ), 105 | succeeds 106 | ] 107 | condaLines = map (\i -> generateCondaLine t (tcVar i) (tcAdditional i)) instances 108 | in conda $ condaLines ++ [[succeeds, fails]] 109 | 110 | main :: IO () 111 | main = do 112 | args <- getArgs 113 | let path = head args 114 | processFile path 115 | -------------------------------------------------------------------------------- /src/JsonInstance.hs: -------------------------------------------------------------------------------- 1 | module JsonInstance where 2 | 3 | import Data.Aeson 4 | import Kanren 5 | import Language.Haskell.Exts.SrcLoc 6 | import Reasoning 7 | import Run 8 | 9 | instance ToJSON TypeForm 10 | 11 | instance ToJSON SrcLoc 12 | 13 | instance ToJSON Order 14 | 15 | instance ToJSON ChStep 16 | 17 | instance ToJSON SrcSpan 18 | 19 | instance ToJSON ChContext 20 | 21 | instance ToJSON ChResult 22 | 23 | instance ToJSON Affinity 24 | -------------------------------------------------------------------------------- /src/Nameable.hs: -------------------------------------------------------------------------------- 1 | module Nameable where 2 | 3 | import Language.Haskell.Exts.Syntax 4 | 5 | newtype StringLike a = StringLike {getString :: String} deriving (Show, Eq) 6 | 7 | class Nameable n where 8 | getName :: n a -> String 9 | 10 | instance Nameable Name where 11 | getName (Ident _ name) = name 12 | getName (Symbol _ name) = name 13 | 14 | instance Nameable QName where 15 | getName (Qual _ _ name) = getName name 16 | getName (UnQual _ name) = getName name 17 | getName (Special _ special) = getName special 18 | 19 | instance Nameable SpecialCon where 20 | getName (UnitCon l) = "()" 21 | getName (ListCon l) = "List" 22 | getName (FunCon l) = "Function" 23 | getName (TupleCon l _ n) = "(" ++ take (n - 1) (repeat ',') ++ ")" 24 | getName (Cons l) = ":" 25 | getName (UnboxedSingleCon l) = "(# #)" 26 | getName (ExprHole l) = "_" 27 | 28 | instance Nameable Match where 29 | getName (Match _ name _ _ _) = getName name 30 | getName (InfixMatch _ _ name _ _ _) = getName name 31 | 32 | instance Nameable TyVarBind where 33 | getName (KindedVar _ name _) = getName name 34 | getName (UnkindedVar _ name) = getName name 35 | 36 | instance Nameable StringLike where 37 | getName (StringLike x) = x 38 | 39 | instance Nameable QOp where 40 | getName (QVarOp _ qname) = getName qname 41 | getName (QConOp _ qname) = getName qname -------------------------------------------------------------------------------- /src/Reasoning.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveGeneric #-} 2 | 3 | module Reasoning where 4 | 5 | import Debug.Trace 6 | import GHC.Generics 7 | import Kanren 8 | import Language.Haskell.Exts.SrcLoc (SrcSpan) 9 | import Scope 10 | import Typing 11 | 12 | data ChStep = ChStep 13 | { explanation :: String, 14 | order :: Order, 15 | stepA :: SrcSpan, 16 | stepB :: SrcSpan, 17 | stepId :: (Int, Int) 18 | } 19 | deriving (Show, Eq, Generic) 20 | 21 | data Order = LR | RL deriving (Show, Eq, Generic) 22 | 23 | compareConstraints :: LabeledGoal -> LabeledGoal -> [ChStep] 24 | compareConstraints (Label n1 (term1, term1') _ reason1 loc1 _) (Label n2 (term2, term2') _ reason2 loc2 _) 25 | | loc1 == loc2 = [] 26 | | loc1 `within` loc2 27 | && reason1 == "Annotated" 28 | && reason2 == "Annotated" = 29 | [ChStep "is in the context of" LR loc1 loc2 (n1, n2)] 30 | | loc2 `within` loc1 31 | && reason1 == "Annotated" 32 | && reason2 == "Annotated" = 33 | [ChStep "is in the context of" RL loc1 loc2 (n1, n2)] 34 | 35 | | loc1 `within` loc2 36 | && reason2 == "Applied" 37 | = 38 | -- application expression: x = y z 39 | case (isFunction term2, isFunction term2') of 40 | (True, False) -> if term1 == term2' || term1' == term2' 41 | then [ChStep "is applied at" LR loc1 loc2 (n1, n2)] 42 | else [ChStep "is an argument in" LR loc1 loc2 (n1, n2)] 43 | (False, True) -> if term1 == term2 || term1' == term2 44 | then [ChStep "is applied at" LR loc1 loc2 (n1, n2)] 45 | else [ChStep "is an argument in" LR loc1 loc2 (n1, n2)] 46 | (_ , _) -> [ChStep "is part of" LR loc1 loc2 (n1, n2)] 47 | 48 | | loc2 `within` loc1 49 | && reason1 == "Applied" 50 | = 51 | 52 | case (isFunction term1, isFunction term1') of 53 | (True, False) -> if term2 == term1' || term2' == term1' 54 | then [ChStep "is applied at" RL loc1 loc2 (n1, n2)] 55 | else [ChStep "is an argument in" RL loc1 loc2 (n1, n2)] 56 | (False, True) -> if term2 == term1 || term2' == term1 57 | then [ChStep "is applied at" RL loc1 loc2 (n1, n2)] 58 | else [ChStep "is an argument in" RL loc1 loc2 (n1, n2)] 59 | (_ , _) -> [ChStep "is part of" RL loc1 loc2 (n1, n2)] 60 | | loc1 `within` loc2 61 | && reason1 == "Matched" 62 | && reason2 == "Matched" 63 | = 64 | -- application expression in pattern matching:x (Y z) = u 65 | case (isFunction term2, isFunction term2') of 66 | (True, False) -> if term1 == term2' || term1' == term2' 67 | then [ChStep "is applied at" LR loc1 loc2 (n1, n2)] 68 | else [ChStep "is an argument in" LR loc1 loc2 (n1, n2)] 69 | (False, True) -> if term1 == term2 || term1' == term2 70 | then [ChStep "is applied at" LR loc1 loc2 (n1, n2)] 71 | else [ChStep "is an argument in" LR loc1 loc2 (n1, n2)] 72 | (_ , _) -> [ChStep "is part of" LR loc1 loc2 (n1, n2)] 73 | 74 | | loc2 `within` loc1 75 | && reason1 == "Matched" 76 | && reason2 == "Matched" 77 | = 78 | -- application expression in pattern matching:x (Y z) = u 79 | case (isFunction term1, isFunction term1') of 80 | (True, False) -> if term2 == term1' || term2' == term1' 81 | then [ChStep "is applied at" RL loc1 loc2 (n1, n2)] 82 | else [ChStep "is an argument in" RL loc1 loc2 (n1, n2)] 83 | (False, True) -> if term2 == term1 || term2' == term1 84 | then [ChStep "is applied at" RL loc1 loc2 (n1, n2)] 85 | else [ChStep "is an argument in" RL loc1 loc2 (n1, n2)] 86 | (_ , _) -> [ChStep "is part of" RL loc1 loc2 (n1, n2)] 87 | 88 | | loc2 `within` loc1 && reason1 == "Literal" = [ChStep "is defined in" RL loc1 loc2 (n1, n2)] 89 | | loc1 `within` loc2 && reason2 == "Literal" = [ChStep "is defined in" LR loc1 loc2 (n1, n2)] 90 | | reason1 == "Annotated" && reason2 == "Annotated" = [] 91 | | reason1 == "Annotated" && reason2 /= "Annotated" = [ChStep "is annotated at" RL loc1 loc2 (n1, n2)] 92 | | reason1 /= "Annotated" && reason2 == "Annotated" = [ChStep "is annotated at" LR loc1 loc2 (n1, n2)] 93 | | term1 == term2 && not (isFresh term1) && not (isFresh term2) = [ChStep "is identical to" LR loc1 loc2 (n1, n2)] 94 | | term1 == term2' && not (isFresh term1) && not (isFresh term2') = [ChStep "is identical to" LR loc1 loc2 (n1, n2)] 95 | | term1' == term2 && not (isFresh term1') && not (isFresh term2) = [ChStep "is identical to" LR loc1 loc2 (n1, n2)] 96 | | term1' == term2' && not (isFresh term1') && not (isFresh term2') = [ChStep "is identical to" LR loc1 loc2 (n1, n2)] 97 | | reason1 == "Defined" && reason2 == "Matched" = [ChStep "takes argument" LR loc1 loc2 (n1, n2)] 98 | | reason1 == "Matched" && reason2 == "Defined" = [ChStep "takes argument" RL loc1 loc2 (n1, n2)] 99 | | 100 | -- trace 101 | -- ( "\nreason1: " 102 | -- ++ reason1 103 | -- ++ " (" 104 | -- ++ show term1 105 | -- ++ " === " 106 | -- ++ show term1' 107 | -- ++ ")\nreason2: " 108 | -- ++ reason2 109 | -- ++ " (" 110 | -- ++ show term2 111 | -- ++ " === " 112 | -- ++ show term2' 113 | -- ++ ")\n" 114 | -- ) $ 115 | reason1 /= "Defined" && reason2 == "Defined" = [ChStep "is defined as" RL loc1 loc2 (n1, n2)] 116 | | 117 | -- trace 118 | -- ( "\nreason1: " 119 | -- ++ reason1 120 | -- ++ " (" 121 | -- ++ show term1 122 | -- ++ " === " 123 | -- ++ show term1' 124 | -- ++ ")\nreason2: " 125 | -- ++ reason2 126 | -- ++ " (" 127 | -- ++ show term2 128 | -- ++ " === " 129 | -- ++ show term2' 130 | -- ++ ")\n" 131 | -- ) $ 132 | reason1 == "Defined" && reason2 /= "Defined" = [ChStep "is defined as" LR loc1 loc2 (n1, n2)] 133 | | 134 | 135 | reason1 /= "Defined" && reason2 == "Defined" = [ChStep "is defined as" RL loc1 loc2 (n1, n2)] 136 | | reason1 == "Applied" && reason2 == "Applied" && loc1 <= loc2 = [ChStep "is applied at" LR loc1 loc2 (n1, n2)] 137 | | reason1 == "Applied" && reason2 == "Applied" && loc1 > loc2 = [ChStep "is applied at" RL loc1 loc2 (n1, n2)] 138 | | reason1 == "Applied" && reason2 /= "Applied" = [ChStep "is applied at" RL loc1 loc2 (n1, n2)] 139 | | reason1 /= "Applied" && reason2 == "Applied" = [ChStep "is applied at" LR loc1 loc2 (n1, n2)] 140 | | reason2 == "Instanciated" 141 | || reason1 == "Instanciated" = [ChStep "has same type as" LR loc1 loc2 (n1, n2)] 142 | | otherwise = 143 | -- trace 144 | -- ( "\nreason1: " 145 | -- ++ reason1 146 | -- ++ " (" 147 | -- ++ show term1 148 | -- ++ " === " 149 | -- ++ show term1' 150 | -- ++ ")\nreason2: " 151 | -- ++ reason2 152 | -- ++ " (" 153 | -- ++ show term2 154 | -- ++ " === " 155 | -- ++ show term2' 156 | -- ++ ")\n" 157 | -- ) $ 158 | [ChStep "has same type as" LR loc1 loc2 (n1, n2)] 159 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by 'stack init' 2 | # 3 | # Some commonly used options have been documented as comments in this file. 4 | # For advanced use and comprehensive documentation of the format, please see: 5 | # https://docs.haskellstack.org/en/stable/yaml_configuration/ 6 | 7 | # Resolver to choose a 'specific' stackage snapshot or a compiler version. 8 | # A snapshot resolver dictates the compiler version and the set of packages 9 | # to be used for project dependencies. For example: 10 | # 11 | # resolver: lts-3.5 12 | # resolver: nightly-2015-09-21 13 | # resolver: ghc-7.10.2 14 | # 15 | # The location of a snapshot can be provided as a file or url. Stack assumes 16 | # a snapshot provided as a file might change, whereas a url resource does not. 17 | # 18 | # resolver: ./custom-snapshot.yaml 19 | # resolver: https://example.com/snapshots/2018-01-01.yaml 20 | resolver: lts-18.18 21 | 22 | # User packages to be built. 23 | # Various formats can be used as shown in the example below. 24 | # 25 | # packages: 26 | # - some-directory 27 | # - https://example.com/foo/bar/baz-0.0.2.tar.gz 28 | # subdirs: 29 | # - auto-update 30 | # - wai 31 | packages: 32 | - . 33 | # Dependency packages to be pulled from upstream that are not in the resolver. 34 | # These entries can reference officially published versions as well as 35 | # forks / in-progress versions pinned to a git hash. For example: 36 | # 37 | # extra-deps: 38 | # - acme-missiles-0.3 39 | # - git: https://github.com/commercialhaskell/stack.git 40 | # commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a 41 | # 42 | extra-deps: 43 | 44 | # Override default flag values for local packages and extra-deps 45 | # flags: {} 46 | 47 | # Extra package databases containing global packages 48 | # extra-package-dbs: [] 49 | 50 | # Control whether we use the GHC we find on the path 51 | # system-ghc: true 52 | # 53 | # Require a specific version of stack, using version ranges 54 | # require-stack-version: -any # Default 55 | # require-stack-version: ">=2.5" 56 | # 57 | # Override the architecture used by stack, especially useful on Windows 58 | # arch: i386 59 | # arch: x86_64 60 | # 61 | # Extra directories used by stack for building 62 | # extra-include-dirs: [/path/to/dir] 63 | # extra-lib-dirs: [/path/to/dir] 64 | # 65 | # Allow a newer minor version of GHC than the snapshot specifies 66 | # compiler-check: newer-minor 67 | -------------------------------------------------------------------------------- /stack.yaml.lock: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by Stack. 2 | # You should not edit this file by hand. 3 | # For more information, please see the documentation at: 4 | # https://docs.haskellstack.org/en/stable/lock_files 5 | 6 | packages: [] 7 | snapshots: 8 | - completed: 9 | size: 586296 10 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/18.yaml 11 | sha256: 63539429076b7ebbab6daa7656cfb079393bf644971156dc349d7c0453694ac2 12 | original: lts-18.18 13 | -------------------------------------------------------------------------------- /static/Debugger.jsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { useSelector, useDispatch } from 'react-redux'; 3 | import { unAlias } from './util'; 4 | import * as R from "ramda" 5 | import { 6 | prevStep, 7 | nextStep, 8 | setStep, 9 | switchTaskThunk, 10 | } from './debuggerSlice'; 11 | import { ArrowCircleUpIcon, ArrowCircleDownIcon } from "@heroicons/react/solid" 12 | import TabReport from "./TabReport" 13 | import TypeSig from './TypeSig' 14 | 15 | const Debugger = () => { 16 | let wellTyped = useSelector(state => state.debugger.wellTyped); 17 | let loadError = useSelector(state => state.debugger.loadError); 18 | let parseError = useSelector(state => state.debugger.parseError); 19 | return ( 20 |
21 | {(() => { 22 | if (wellTyped) { 23 | return
24 | 25 | Congratulations! Your code is well typed. 26 |
27 | } else if (parseError !== null) { 28 | return ; 29 | } else if (loadError !== null) { 30 | return ; 31 | } else if (!wellTyped) { 32 | return ; 33 | } 34 | })()} 35 |
36 | ); 37 | }; 38 | 39 | const ParseErrorReport = () => { 40 | let parseError = useSelector(state => state.debugger.parseError); 41 | return ( 42 |
43 |

A syntax error was found in the code

44 |
45 |

{parseError.message}

46 |

47 | Location: {parseError.loc.srcLine}:{parseError.loc.srcColumn} 48 |

49 |
50 |
51 | ); 52 | }; 53 | 54 | const LoadErrorReport = () => { 55 | let loadError = useSelector(state => state.debugger.loadError); 56 | return ( 57 |
58 |

A variable is used without being declared.

59 | {loadError.map(([v, loc]) => { 60 | return ( 61 |
62 |

Variable: {v}

63 |

64 | Location: {' ' + loc.srcSpanStartLine}: 65 | {loc.srcSpanStartColumn} -{' ' + loc.srcSpanEndLine}: 66 | {loc.srcSpanEndColumn} 67 |

68 |
69 | ); 70 | })} 71 |
72 | ); 73 | }; 74 | 75 | const TypeErrorReport = () => { 76 | return ( 77 |
81 | 82 |
83 | Below are all the expressions (in the middle column) 84 | that can cause the type error. 85 |
86 |
(Use the up and down buttons to verify each fact)
87 | 88 | 89 | 90 |
91 | ); 92 | }; 93 | 94 | const ImportedTypes = () => { 95 | let contextItem = useSelector(state => state.debugger.currentContextItem); 96 | let hasProperty = R.has('contextGlobals') 97 | let hasMoreThanOneGlobal = R.pipe(R.prop('contextGlobals'), R.length, R.equals(0), R.not) 98 | return
99 |
Imported functions types:
100 | { 101 | (() => { 102 | if (R.allPass([hasProperty, hasMoreThanOneGlobal])(contextItem)) { 103 | return contextItem.contextGlobals.map((mapping, k) => ) 104 | } else { 105 | return
No globals
106 | } 107 | })() 108 | } 109 |
110 | } 111 | 112 | const Imported = ({ mapping }) => { 113 | let [name, sig] = mapping 114 | return
{name} :: {sig}
115 | } 116 | 117 | const Message = () => { 118 | let contextItem = useSelector(state => state.debugger.currentContextItem); 119 | return contextItem === null ? null : ( 120 |
121 |
122 |
It is possible to infer two conflicting types for the expression 123 | 124 | {contextItem['contextExp']} 125 | : 126 |
127 |
128 | 129 |
130 | Possible type 1: 131 | 132 | 136 | 137 |
138 |
Possible type 1 can be infered from the orange highlights on the left side
139 | 140 |
141 | Possible type 2: 142 | 143 | 147 | 148 |
149 |
Possible type 2 can be infered from the blue highlights on the left side
150 | 151 |
152 | ); 153 | }; 154 | 155 | const TypingTable = () => { 156 | let dispatch = useDispatch(); 157 | let context = useSelector(state => state.debugger.context); 158 | return ( 159 |
163 |
164 | { 166 | 167 | dispatch(prevStep()); 168 | }} 169 | className="w-5 cursor-pointer" 170 | > 171 |
172 |
TYPE 1
173 |
EXPRESSION
174 |
TYPE 2
175 | 176 | {(() => { 177 | if (context.length === 0) { 178 | return ; 179 | } else { 180 | return context.map((row, i) => ( 181 | 182 | )); 183 | } 184 | })()} 185 |
186 | { 188 | dispatch(nextStep()); 189 | }} 190 | className="w-5 cursor-pointer" 191 | > 192 |
193 |
194 |
195 |
196 |
197 | ); 198 | }; 199 | 200 | const EmptyContextTable = () => { 201 | let steps = useSelector(state => state.debugger.steps); 202 | let currentTraverseId = useSelector(state => state.debugger.currentTraverseId); 203 | let dispatch = useDispatch(); 204 | return ( 205 | <> 206 |
207 | {steps.map(({ stepId }, i) => { 208 | return ( 209 |
{ 212 | dispatch(setStep(i)); 213 | }} 214 | className={ 215 | 'rounded-lg w-4 h-4 my-0.5 p-0.5 cursor-pointer text-xs leading-3 text-center ' + 216 | (R.equals(stepId, currentTraverseId) 217 | ? 'bg-green-400' 218 | : 'bg-gray-400') 219 | } 220 | > 221 | {i + 1} 222 |
223 | ); 224 | })} 225 |
226 |
227 |
228 |
229 | 230 | ); 231 | }; 232 | 233 | const ContextRow = ({ row }) => { 234 | let currentTraverseId = useSelector(state => state.debugger.currentTraverseId); 235 | let steps = useSelector(state => state.debugger.steps); 236 | let dispatch = useDispatch(); 237 | let { 238 | contextExp, 239 | contextType1String, 240 | contextType1SimpleString, 241 | contextType2String, 242 | contextType2SimpleString, 243 | contextSteps, 244 | } = row; 245 | let affinity = 246 | R.pipe( 247 | R.find(R.pipe(R.nth(0), R.equals(currentTraverseId))), 248 | R.nth(1) 249 | )(contextSteps) 250 | 251 | let affinityClass = 252 | affinity === 'R' ? 'sideA' : affinity === 'L' ? 'sideB' : 'sideAB'; 253 | let firstReleventStepTId = R.nth(0)(contextSteps.find(R.nth(2))); 254 | let lastReleventStepTId = 255 | R.pipe( 256 | R.reverse, 257 | R.find(R.nth(2)), 258 | R.nth(0) 259 | )(contextSteps) 260 | 261 | let firstReleventStep = steps.findIndex(step => 262 | R.equals(step['stepId'], firstReleventStepTId), 263 | ); 264 | let lastReleventStep = steps.findIndex(step => 265 | R.equals(step['stepId'], lastReleventStepTId), 266 | ); 267 | return ( 268 | <> 269 | 270 |
{ 272 | dispatch(setStep(firstReleventStep)); 273 | }} 274 | className='rounded-sm p-1 groupMarkerB flex justify-center items-center cursor-pointer' 275 | > 276 | {' '} 280 |
281 |
286 | {contextExp} 287 |
288 |
{ 290 | dispatch(setStep(lastReleventStep)); 291 | }} 292 | className='rounded-sm p-1 groupMarkerA flex justify-center items-center cursor-pointer' 293 | > 294 | {' '} 298 |
299 | 300 | ); 301 | }; 302 | 303 | const Stepper = ({ rowInfo }) => { 304 | let steps = useSelector(state => state.debugger.steps); 305 | let currentTraverseId = useSelector(state => state.debugger.currentTraverseId); 306 | let stepsInRow = rowInfo.filter(ri => ri[2]); 307 | let dispatch = useDispatch(); 308 | return ( 309 | <> 310 |
311 | {stepsInRow.map(([traverseId, _z1, _z2]) => { 312 | let stepId = steps.findIndex( 313 | R.pipe( 314 | R.prop('stepId'), 315 | R.equals(traverseId) 316 | ) 317 | ); 318 | return ( 319 |
{ 322 | dispatch(setStep(stepId)); 323 | }} 324 | className={ 325 | 'rounded-lg w-4 h-4 my-0.5 p-0.5 cursor-pointer text-xs leading-3 text-center ' + 326 | (R.equals(traverseId, currentTraverseId) 327 | ? 'bg-green-400' 328 | : 'bg-gray-400') 329 | } 330 | > 331 | {stepId + 1} 332 |
333 | ); 334 | })} 335 |
336 | 337 | ); 338 | }; 339 | 340 | export default Debugger -------------------------------------------------------------------------------- /static/Editor.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from 'react'; 2 | import { useSelector, useDispatch } from 'react-redux'; 3 | import { editorModes, setText, toEditMode, setCursorPosition } from './debuggerSlice'; 4 | import { within } from './util'; 5 | import * as R from 'ramda'; 6 | 7 | const App = () => { 8 | return ( 9 |
13 | 14 |
15 | ); 16 | }; 17 | 18 | const Editor = () => { 19 | const mode = useSelector(R.path(['debugger', 'mode'])); 20 | return ( 21 |
22 | {(() => { 23 | if (mode === editorModes.edit) { 24 | return ; 25 | } else if (mode === editorModes.normal) { 26 | return ; 27 | } 28 | })()} 29 |
30 | ); 31 | }; 32 | 33 | const EditorEditMode = () => { 34 | const text = useSelector(R.path(['debugger', 'text'])); 35 | const cursorPosition = useSelector(R.path(['debugger', 'cursorPosition'])) 36 | const dispatch = useDispatch(); 37 | const inputEl = useRef(null); 38 | useEffect(() => { 39 | inputEl.current.focus(); 40 | inputEl.current.selectionStart = cursorPosition 41 | 42 | inputEl.current.selectionEnd = cursorPosition 43 | }, []); 44 | return ( 45 | 53 | ); 54 | }; 55 | 56 | const EditorNormerMode = () => { 57 | const text = useSelector(R.path(['debugger', 'text'])); 58 | const dispatch = useDispatch(); 59 | return ( 60 |
dispatch(toEditMode())}> 61 | {text.split('\n').map((t, line) => ( 62 | 63 | ))} 64 |
65 | ); 66 | }; 67 | 68 | const Line = ({ text, line }) => { 69 | const fulltext = useSelector(R.path(['debugger', 'text'])); 70 | const dispatch = useDispatch() 71 | 72 | return ( 73 |
74 | {text.split('').map((t, ch) => ( 75 | 76 | ))} 77 |
{ 78 | let offset = fulltext.split('\n').filter((l, ln) => ln <= line).map(l => l.length + 1).reduce((x, y) => x + y, 0) - 1 79 | dispatch(setCursorPosition(offset)) 80 | 81 | }} className='inline-block h-6 flex-grow cursor-text' >
82 |
83 | ); 84 | }; 85 | 86 | const Cell = ({ text, line, ch }) => { 87 | const point = { line, ch }; 88 | const fulltext = useSelector(R.path(['debugger', 'text'])); 89 | const highlights = useSelector(R.path(['debugger', 'highlights'])); 90 | const widgets = useSelector(R.path(['debugger', 'widgets'])); 91 | const deductionStpe = useSelector(R.path(['debugger', 'debuggingSteps'])); 92 | const dispatch = useDispatch() 93 | const highlighters = highlights 94 | .filter(R.curry(within)(point)) 95 | .map((hl, k) => ( 96 | 102 | )); 103 | const appliedWidgets = R.pipe( 104 | R.filter(R.pipe(R.prop('relativeTo'), R.equals(point))), 105 | R.map(wgt => ( 106 | 112 | )), 113 | )(widgets); 114 | 115 | return ( 116 |
{ 117 | let offset = fulltext.split('\n').filter((l, ln) => ln < line).map(l => l.length + 1).reduce((x, y) => x + y, 0) + ch 118 | dispatch(setCursorPosition(offset)) 119 | }} className='inline-block h-6 relative' style={{ width: '0.6rem' }}> 120 | {highlighters} 121 | {deductionStpe ? appliedWidgets : null} 122 |
{text}
123 |
124 | ); 125 | }; 126 | 127 | const Widget = ({ styles, classes, content }) => { 128 | const highlightFilter = useSelector(R.path(['debugger', 'highlightFilter'])); 129 | const numOfSteps = useSelector(R.path(['debugger', 'numOfSteps'])); 130 | const pinnedStep = useSelector(R.path(['debugger', 'pinnedStep'])); 131 | 132 | const stepLabelFace = 133 | pinnedStep === content.step 134 | ? 'bg-green-400 text-black border-green-400 border bg-green-400' 135 | : 'bg-gray-200 border border-dashed border-black'; 136 | if ( 137 | highlightFilter.includes('marker1') && 138 | !highlightFilter.includes('marker2') 139 | ) 140 | return null; 141 | if ( 142 | highlightFilter.includes('marker2') && 143 | !highlightFilter.includes('marker1') 144 | ) 145 | return null; 146 | if ( 147 | highlightFilter.includes('marker2') && 148 | highlightFilter.includes('marker1') && 149 | !highlightFilter.includes('markerDefinition') 150 | ) 151 | return null; 152 | 153 | if (content.type === 'annotation') { 154 | // this is very cluncky 155 | if (content.direction === 'LR') { 156 | return ( 157 |
164 | 165 | {content.reason} 166 | 167 | (step 168 | 174 | {numOfSteps - content.step} 175 | 176 | ) 177 |
178 | ); 179 | } else { 180 | return ( 181 |
188 | 189 | {content.reason} 190 | 191 | (step 192 | 198 | {numOfSteps - content.step} 199 | 200 | ) 201 |
202 | ); 203 | } 204 | } else { 205 | return
; 206 | } 207 | }; 208 | 209 | const Highlighter = ({ highlight, line, ch }) => { 210 | const deductionSteps = useSelector(R.path(['debugger', 'debuggingSteps'])); 211 | const highlightFilter = useSelector(R.path(['debugger', 'highlightFilter'])); 212 | 213 | const borderResetter = 214 | deductionSteps && R.equals(highlightFilter, ['markerDefination']) 215 | ? {} 216 | : { borderWidth: 0 }; 217 | 218 | let classes = highlight.marker.shared; 219 | if (R.equals(highlight.from, { line, ch })) { 220 | classes = [...classes, ...highlight.marker.start]; 221 | } 222 | if (R.equals(highlight.to, { line, ch: ch + 1 })) { 223 | classes = [...classes, ...highlight.marker.end]; 224 | } 225 | 226 | classes = R.any(f => R.includes(f, classes))(highlightFilter) ? [] : classes; 227 | return ( 228 |
232 | ); 233 | }; 234 | 235 | export default App; 236 | -------------------------------------------------------------------------------- /static/MenuBar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useDispatch, useSelector } from 'react-redux'; 3 | import * as R from 'ramda'; 4 | import { 5 | toEditMode, 6 | toNormalMode, 7 | switchTaskThunk, 8 | typeCheckThunk, 9 | setTask, 10 | nextStep, 11 | prevStep, 12 | editorModes, 13 | } from './debuggerSlice'; 14 | import { EyeIcon, BookOpenIcon } from '@heroicons/react/solid'; 15 | import { Event, Source, track } from './report'; 16 | import { getMode } from './util'; 17 | 18 | const MenuBar = () => { 19 | const dispatch = useDispatch(); 20 | const mode = useSelector(R.path(['debugger', 'mode'])); 21 | const multipleExps = useSelector(R.path(['debugger', 'multipleExps'])); 22 | const deductionSteps = useSelector(R.path(['debugger', 'debuggingSteps'])); 23 | const currentTaskNum = useSelector(R.path(['debugger', 'currentTaskNum'])); 24 | const debuggingMode = getMode(multipleExps, deductionSteps); 25 | const attempts = useSelector(R.path(['debugger', 'attempts'])); 26 | const currentTaskAttemps = attempts[currentTaskNum]; 27 | 28 | return ( 29 |
30 |
31 | 32 | 33 | 37 | 44 | 48 | 52 | 56 | 60 | 64 | 68 | 72 | 76 | 83 | 87 | 88 | 89 | {OUTPUT_TARGET === 'playground' ? ( 90 | <> 91 | {' '} 92 |

Load examples:

93 | {' '} 108 | 109 | ) : Task {currentTaskNum + 1}/9 } 110 | 126 | {currentTaskAttemps > 5 && OUTPUT_TARGET === 'userstudy' ? ( 127 | 150 | ) : null} 151 | {mode === editorModes.normal ? null : ( 152 | 168 | )} 169 | 170 | 176 | 177 | Tutorial 178 | 179 |
180 | 181 |
182 |
{debuggingMode}
183 |
184 |
185 | ); 186 | }; 187 | 188 | export default MenuBar; 189 | -------------------------------------------------------------------------------- /static/Toggle.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Toggle = ({ active, onClick }) => { 4 | return
5 | 19 | 22 | 23 |
24 | } 25 | 26 | export default Toggle -------------------------------------------------------------------------------- /static/TypeSig.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { unAlias } from './util' 3 | 4 | 5 | const StringTypeSig = ({ simple, full }) => { 6 | let unlaliasedFull = unAlias(full); 7 | if (unlaliasedFull.length > 50) { 8 | return {unAlias(simple)}; 9 | } else { 10 | return {unlaliasedFull}; 11 | } 12 | }; 13 | 14 | 15 | export default StringTypeSig -------------------------------------------------------------------------------- /static/build/challenge.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Chameleon Type Debugger 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 |
Type check
26 | 27 | 28 | 36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | 46 |
47 | 49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /static/build/consent.html: -------------------------------------------------------------------------------- 1 | 44 |

Our study is finished. Feel free to check out chameleon at the playground -------------------------------------------------------------------------------- /static/build/explanatory.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | Chameleon Type Debugger | Explanatory Statement 16 | 17 | 18 | 19 |

20 |

21 | 22 |

23 |

24 | Explanatory Statement 25 |

26 |

Project ID: 27885

27 |

28 | Project Title: Interactive Haskell Type Inference Exploration 29 |

30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 55 | 60 | 64 | 65 | 66 |
Prof. Tim DwyerProf. Peter StuckeyShuai Fu (Ph.D. student)
Department of Human Centred ComputingDepartment of Data Science and Artificial IntelligenceDepartment of Human Centred Computing
Phone: 9905 0234Phone: 9903 2405Phone: 0449 127 003
52 | Email: 53 | tim.dwyer@monash.edu 54 | 56 | Email: 57 | peter.stuckey@monash.edu 59 | 61 | Email: 62 | shuai.fu@monash.edu 63 |
67 |
68 |

69 | You are invited to take part in this study. Please read this Explanatory 70 | Statement in full before deciding whether or not to participate in this 71 | research. If you would like further information regarding any aspect of 72 | this project, you are encouraged to contact the researchers via the phone 73 | numbers or email addresses listed above. 74 |

75 |

76 | What does the research involve? 77 |

78 |

79 | This study is designed to study the effectiveness of different 80 | types of error messages in the Chameleon type checking tool. 81 | Chameleon is a tool we have developed to help programmers 82 | identify and solve Haskell type errors. 83 |

84 |

85 | Once you have navigated to the user study website, you will be 86 | prompted with 9 different code challenges. Each code challenge is an 87 | editable code fragment in a web page, presented with debugging 88 | information on the right side of the webpage to help you investigate 89 | the type errors (if there are any). 90 |

91 |

92 | Chameleon can be configured in three modes: basic mode, balanced 93 | mode, and advanced mode. In basic mode, debugging information is 94 | minimal, and you have little control over the display of the error 95 | message. In balanced mode, you have moderate control to narrow down 96 | the possible cause of a type error. In advanced mode, you have 97 | access to a maximal amount of debugging information and granular 98 | control in tracing the root cause of type errors. At the start of 99 | each code challenge, the Chameleon debugging tool is set to one of 100 | the three modes. However, you are free to switch between modes by 101 | pressing the tab key or clicking the dedicated mode switching 102 | buttons. 103 |

104 |

105 | After the study, you will be asked to leave overall feedback of your 106 | experience in working with Chameleon debugger. 107 |

108 |

109 | How do we collect the data? 110 |

111 |

We will collect the data in two methods:

112 | 122 |

123 | We will use first party cookie (under the same domain name) to store 124 | a temporary unique identifier in your browser. We will not share 125 | this cookie to any other parties. Nor will we access any other 126 | cookie you already have installed from other websites. All data 127 | transmission from your browser to our server are encrypted. 128 |

129 |

130 | Why were you chosen for this research? 131 |

132 |

133 | You opt in to participate this research by click the link we posted 134 | on internet forum and interest group websites. 135 |

136 |

138 | Consenting to participate in the project and withdrawing from the 139 | research 140 |

141 |

The consent process includes the following steps:

142 | 153 |

154 | You can choose to withdraw from further participation at any stage. 155 | To withdraw from this study, you simply close the user study website 156 | tab in the web browser. We will erase all the data associate with 157 | your session if you did not complete the study. Additionally, we 158 | will provide you a withdraw ID at the start of this study. You can 159 | email the student investigator ( 161 | shuai.fu@monash.edu 162 | ) with your withdraw ID at anytime to withdraw your data, even 163 | if you completed the study. 164 |

165 |

166 | Possible benefits and risks to participants 167 |

168 |

169 | There's no risks involved in participating this research. You 170 | are not required to be physically present. The study does not 171 | require you to perform physical activities or interact with physical 172 | objects. We will keep the study within a minimal time frame of 20 173 | minutes to avoid strain. The information you will provide in the 174 | study are limited to the area of programming language knowledge and 175 | skill. 176 |

177 |

178 | The user study gives no immediate benefit to the participants, 179 | However in the long-term, you may benefit from the tools that we 180 | develop as part of the research. Broadly, this user study will help 181 | researchers to better understand how to design, and improve the 182 | compiler interface and interactive IDE. 183 |

184 |

Confidentiality

185 |

186 | We will not collect any data that can be linked to your identity, 187 | your device, or your location. We will use a random identifier (such 188 | as GUID) to group the collected data by each participant. 189 |

190 |

191 | All published data will remain anonymous. The collected data and 192 | questionnaire answers will be used in the publication. 193 |

194 |

195 | The members of the this project may include the user study results 196 | in journal articles, conference papers, or book chapters. Student 197 | co-investigator may include the user study result in his Ph.D. 198 | thesis. 199 |

200 |

Storage of data

201 |

202 | The data will be stored offline-only, in an encrypted hard drive 203 | located in Australia. The members of the Interactive Haskell Type 204 | Inference Exploration project will be able to access the data. The 205 | data will be erased from the hard drive 12 months after it is 206 | collected. 207 |

208 |

Results

209 |

210 | The members of the Interactive Haskell Type Inference Exploration 211 | project may include the user study results in journal articles, 212 | conference papers, or book chapters. Student co-investigator (Shuai 213 | Fu) may include the user study result in his Ph.D. thesis. We will 214 | only publish summary-formed results. No personal data will be 215 | published. Participants may gain access to a pre-print electronic 216 | version of a journal article or conference paper after it will have 217 | been published. Participants may gain access to the electronic 218 | version of the student investigator’s Ph.D. thesis after it has been 219 | published. 220 |

221 |

Complaints

222 |

223 | Should you have any concerns or complaints about the conduct of the 224 | project, you are welcome to contact the Executive Officer, Monash 225 | University Human Research Ethics Committee (MUHREC): 226 |

227 |

228 | Executive Officer Monash University Human Research Ethics Committee 229 | (MUHREC) Room 111, Chancellery Building D, 26 Sports Walk, Clayton 230 | Campus Research Office Monash University VIC 3800 231 |

232 |

Tel: +61 3 9905 2052

233 | 234 |

235 | Email: 236 | muhrec@monash.edu 237 |

238 | 239 |

Fax: +61 3 9905 3831

240 |

Thank you,

241 |

Prof. Tim Dwyer

242 |
243 | 244 | 245 | -------------------------------------------------------------------------------- /static/build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/favicon.ico -------------------------------------------------------------------------------- /static/build/feedback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Chameleon Type Debugger 9 | 10 | 11 | 12 | 40 | 41 | 42 | 43 | 50 | 56 |
57 | 64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /static/build/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Chameleon Type Debugger 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 21 | 22 | 23 |
24 | 25 |
26 |

Chameleon

27 |

28 | A tool to make solving type errors in Haskell simple and fun. 29 |

30 | Go to playground 31 |
32 |
33 | 34 | 35 | 36 | 37 | 39 | 40 | 43 | 45 | 47 | 49 | 51 | 52 | 54 | 55 |
56 |
57 |
58 |

Why Chameleon?

59 |
60 |
61 |

Human-Centered Methods

62 |

63 | We improve each feature in Chameleon based on the 64 | feedback from the Haskell community. Debugging idioms 65 | in Chameleon has been tested individually and in combinations. 66 |

67 |
68 |
69 |

Multi-location type errors

70 |

71 | 72 | While many type systems try to pinpoint one exact error location in the code, the accuracy is often 73 | hit or 74 | miss. Chameleon tries to narrow down to a few suspects and asks the programmer to identify the real 75 | culprit. 76 | While both approaches have pros and cons, we believe Chameleon is more flexible and catches bugs 77 | faster. 78 |

79 |
80 |
81 |

Unbiased type errors

82 |

83 | Instead of assuming one type is "Expected" and one type is "Actual", Chameleon will report two 84 | equally 85 | possible alternatives that type errors can happen. Many techniques have been proposed to solve this 86 | problem 87 | (Known as left-right bias) on type solver level. Chameleon combines the type solver capable of 88 | eliminating 89 | this bias and smart visual cues to distinguish the evidence for one type and the other. 90 |

91 |
92 |
93 |

Deduction step

94 |

95 | The deduction step is a tool to peek inside the type checking engine. It shows step-by-step 96 | reasoning that 97 | explains why one type cannot reconcile with another in simple language. Chameleon's interactive 98 | interface 99 | allows users to make incremental assumptions and see how that affects the typing of the whole 100 | program. 101 |

102 |
103 |
104 |

More are coming!

105 |

106 | Hang tight! We are working on more features to make Chameleon even better! 107 |

108 |
109 |
110 | 111 |
112 |
113 |

Playground

114 |

The chameleon playground is an online editor for you to test out some of the chameleon 115 | features. 116 |

117 | Go to playground 118 |
119 |
120 |

Resources

121 | 135 |
136 |
137 |

Contact us

138 |
139 | Don't hesitate to send us a message. It could be you are teaching Haskell and 140 | interested in running Chameleon in your class, 141 | or you have new ideas for debugging type errors, or you want to get involved 142 | in the Chameleon project, either by reporting bugs, writing code, 143 | or participating in user studies. 144 | 145 |
146 |
147 |
148 | 149 | 151 |
152 |
153 | 154 | 155 |
156 | 157 | 160 |
161 |
162 | 172 | 203 | 204 | 205 | -------------------------------------------------------------------------------- /static/build/introduction.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Chameleon Type Debugger | Introduction 10 | 11 | 12 |
13 |

Start

25 | 26 |
27 |
28 | 29 |
30 | 31 | -------------------------------------------------------------------------------- /static/build/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/logo.png -------------------------------------------------------------------------------- /static/build/main.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --colorA: #67A9CF; 3 | --colorB: #ef8a62; 4 | --colorA-lighter: #ADD9F2; 5 | --colorB-lighter: #FFD4C3; 6 | font-variant-ligatures: none; 7 | } 8 | 9 | body { 10 | font-family: 'IBM Plex Sans', sans-serif; 11 | } 12 | 13 | .markerA { 14 | /* background-color: var(--colorA); */ 15 | /* border: solid 2px var(--colorA); */ 16 | } 17 | 18 | 19 | .markerA::before { 20 | content: " "; 21 | position: relative; 22 | width: 2px; 23 | display: inline-block; 24 | background: var(--colorA); 25 | margin-left: -3px; 26 | } 27 | 28 | 29 | .markerA::after { 30 | content: " "; 31 | position: relative; 32 | width: 2px; 33 | display: inline-block; 34 | background: var(--colorA); 35 | margin-right: -3px; 36 | } 37 | 38 | .markerB::before { 39 | content: " "; 40 | position: relative; 41 | width: 2px; 42 | display: inline-block; 43 | background: var(--colorB); 44 | margin-left: -3px; 45 | } 46 | 47 | .markerB::after { 48 | content: " "; 49 | position: relative; 50 | width: 2px; 51 | display: inline-block; 52 | background: var(--colorB); 53 | margin-right: -3px; 54 | } 55 | 56 | .markerA.markerB::before { 57 | content: " "; 58 | position: relative; 59 | width: 3px; 60 | display: inline-block; 61 | background: linear-gradient(90deg, var(--colorB), var(--colorB) 49%, var(--colorA) 50%); 62 | ; 63 | margin-left: -3px; 64 | } 65 | 66 | .markerA.markerB::after { 67 | content: " "; 68 | position: relative; 69 | width: 3px; 70 | display: inline-block; 71 | background: linear-gradient(90deg, var(--colorB), var(--colorB) 49%, var(--colorA) 50%); 72 | ; 73 | margin-left: -3px; 74 | } 75 | 76 | 77 | .markerB { 78 | /* background-color: var(--colorB); */ 79 | /* border: solid 2px var(--colorB); */ 80 | } 81 | 82 | 83 | .markerASmall.markerA.markerB { 84 | /* background-color: var(--colorA); */ 85 | /* border: solid 2px var(--colorB); */ 86 | } 87 | 88 | 89 | .markerBSmall.markerA.markerB { 90 | /* background-color: var(--colorB); */ 91 | /* border: solid 2px var(--colorA); */ 92 | } 93 | 94 | .groupMarkerA { 95 | background-color: var(--colorA-lighter); 96 | border: solid 2px var(--colorA-lighter); 97 | 98 | 99 | } 100 | 101 | .groupMarkerB { 102 | background-color: var(--colorB-lighter); 103 | border: solid 2px var(--colorB-lighter); 104 | } 105 | 106 | .code { 107 | font-family: 'JetBrains Mono', monospace; 108 | font-variant-ligatures: none; 109 | 110 | } 111 | 112 | .arrow-right { 113 | width: 0; 114 | height: 0; 115 | border-top: 5px solid transparent; 116 | border-bottom: 5px solid transparent; 117 | border-left: 5px solid white; 118 | } 119 | 120 | .outline-start { 121 | border-left-style: solid; 122 | border-left-width: 3px; 123 | border-top-left-radius: 3px; 124 | border-bottom-left-radius: 3px; 125 | } 126 | 127 | 128 | .outline-end { 129 | border-right-style: solid; 130 | border-right-width: 3px; 131 | border-top-right-radius: 3px; 132 | border-bottom-right-radius: 3px; 133 | } 134 | 135 | .outline-start.markerA { 136 | border-left-color: var(--colorA); 137 | opacity: 0.8; 138 | 139 | } 140 | 141 | .outline-start.markerB { 142 | border-left-color: var(--colorB); 143 | opacity: 0.8; 144 | 145 | } 146 | 147 | .outline-start.groupMarkerA { 148 | border-left-color: var(--colorA-lighter); 149 | 150 | } 151 | 152 | .outline-start.groupMarkerB { 153 | border-left-color: var(--colorB-lighter); 154 | } 155 | 156 | 157 | .outline-end.markerA { 158 | border-right-color: var(--colorA); 159 | 160 | } 161 | 162 | .outline-end.markerB { 163 | border-right-color: var(--colorB); 164 | 165 | } 166 | 167 | .outline-end.groupMarkerA { 168 | border-right-color: var(--colorA-lighter); 169 | 170 | } 171 | 172 | .outline-end.groupMarkerB { 173 | border-right-color: var(--colorB-lighter); 174 | } 175 | 176 | 177 | .sideA { 178 | background: var(--colorA); 179 | } 180 | 181 | .sideB { 182 | background: var(--colorB); 183 | } 184 | 185 | .sideAB { 186 | background: linear-gradient(90deg, var(--colorB), var(--colorB) 49%, var(--colorA) 50%); 187 | } 188 | 189 | 190 | .context-grid { 191 | grid-template-columns: auto 1fr auto 1fr; 192 | } 193 | 194 | .split-container { 195 | display: grid; 196 | grid-template-columns: 900px 10px 1fr; 197 | } 198 | 199 | .split-gutter-col { 200 | grid-row: 1/-1; 201 | cursor: col-resize; 202 | } 203 | 204 | .split-gutter-col-1 { 205 | grid-column: 2; 206 | } 207 | 208 | /* New Editor */ 209 | 210 | 211 | :root { 212 | 213 | /* gray */ 214 | --color-gray-1: #f6f6f6; 215 | --color-gray-2: #e2e2e2; 216 | --color-gray-3: #8b8b8b; 217 | --color-gray-4: #6f6f6f; 218 | --color-gray-5: #3e3e3e; 219 | --color-gray-6: #222222; 220 | 221 | /* rose */ 222 | /* hue 1 */ 223 | --color-rose-1: #fff7f9; 224 | --color-rose-2: #ffdce5; 225 | --color-rose-3: #ff3b8d; 226 | --color-rose-4: #db0072; 227 | --color-rose-5: #800040; 228 | --color-rose-6: #4c0023; 229 | 230 | /* raspberry */ 231 | /* hue 2 */ 232 | --color-raspberry-1: #fff8f8; 233 | --color-raspberry-2: #ffdddf; 234 | --color-raspberry-3: #ff426c; 235 | --color-raspberry-4: #de0051; 236 | --color-raspberry-5: #82002c; 237 | --color-raspberry-6: #510018; 238 | 239 | /* red */ 240 | /* hue 3 */ 241 | --color-red-1: #fff8f6; 242 | --color-red-2: #ffddd8; 243 | --color-red-3: #ff4647; 244 | --color-red-4: #e0002b; 245 | --color-red-5: #830014; 246 | --color-red-6: #530003; 247 | 248 | /* orange */ 249 | /* hue 4 */ 250 | --color-orange-1: #fff8f5; 251 | --color-orange-2: #ffded1; 252 | --color-orange-3: #fd4d00; 253 | --color-orange-4: #cd3c00; 254 | --color-orange-5: #752100; 255 | --color-orange-6: #401600; 256 | 257 | /* cinnamon */ 258 | /* hue 5 */ 259 | --color-cinnamon-1: #fff8f3; 260 | --color-cinnamon-2: #ffdfc6; 261 | --color-cinnamon-3: #d57300; 262 | --color-cinnamon-4: #ac5c00; 263 | --color-cinnamon-5: #633300; 264 | --color-cinnamon-6: #371d00; 265 | 266 | /* amber */ 267 | /* hue 6 */ 268 | --color-amber-1: #fff8ef; 269 | --color-amber-2: #ffe0b2; 270 | --color-amber-3: #b98300; 271 | --color-amber-4: #926700; 272 | --color-amber-5: #523800; 273 | --color-amber-6: #302100; 274 | 275 | /* yellow */ 276 | /* hue 7 */ 277 | --color-yellow-1: #fff9e5; 278 | --color-yellow-2: #ffe53e; 279 | --color-yellow-3: #9c8b00; 280 | --color-yellow-4: #7d6f00; 281 | --color-yellow-5: #463d00; 282 | --color-yellow-6: #292300; 283 | 284 | /* lime */ 285 | /* hue 8 */ 286 | --color-lime-1: #f7ffac; 287 | --color-lime-2: #d5f200; 288 | --color-lime-3: #819300; 289 | --color-lime-4: #677600; 290 | --color-lime-5: #394100; 291 | --color-lime-6: #222600; 292 | 293 | /* chartreuse */ 294 | /* hue 9 */ 295 | --color-chartreuse-1: #e5ffc3; 296 | --color-chartreuse-2: #98fb00; 297 | --color-chartreuse-3: #5c9b00; 298 | --color-chartreuse-4: #497c00; 299 | --color-chartreuse-5: #264500; 300 | --color-chartreuse-6: #182600; 301 | 302 | /* green */ 303 | /* hue 10 */ 304 | --color-green-1: #e0ffd9; 305 | --color-green-2: #72ff6c; 306 | --color-green-3: #00a21f; 307 | --color-green-4: #008217; 308 | --color-green-5: #004908; 309 | --color-green-6: #062800; 310 | 311 | /* emerald */ 312 | /* hue 11 */ 313 | --color-emerald-1: #dcffe6; 314 | --color-emerald-2: #5dffa2; 315 | --color-emerald-3: #00a05a; 316 | --color-emerald-4: #008147; 317 | --color-emerald-5: #004825; 318 | --color-emerald-6: #002812; 319 | 320 | /* aquamarine */ 321 | /* hue 12 */ 322 | --color-aquamarine-1: #daffef; 323 | --color-aquamarine-2: #42ffc6; 324 | --color-aquamarine-3: #009f78; 325 | --color-aquamarine-4: #007f5f; 326 | --color-aquamarine-5: #004734; 327 | --color-aquamarine-6: #00281b; 328 | 329 | /* teal */ 330 | /* hue 13 */ 331 | --color-teal-1: #d7fff7; 332 | --color-teal-2: #00ffe4; 333 | --color-teal-3: #009e8c; 334 | --color-teal-4: #007c6e; 335 | --color-teal-5: #00443c; 336 | --color-teal-6: #002722; 337 | 338 | /* cyan */ 339 | /* hue 14 */ 340 | --color-cyan-1: #c4fffe; 341 | --color-cyan-2: #00fafb; 342 | --color-cyan-3: #00999a; 343 | --color-cyan-4: #007a7b; 344 | --color-cyan-5: #004344; 345 | --color-cyan-6: #002525; 346 | 347 | /* powder */ 348 | /* hue 15 */ 349 | --color-powder-1: #dafaff; 350 | --color-powder-2: #8df0ff; 351 | --color-powder-3: #0098a9; 352 | --color-powder-4: #007987; 353 | --color-powder-5: #004048; 354 | --color-powder-6: #002227; 355 | 356 | /* sky */ 357 | /* hue 16 */ 358 | --color-sky-1: #e3f7ff; 359 | --color-sky-2: #aee9ff; 360 | --color-sky-3: #0094b4; 361 | --color-sky-4: #007590; 362 | --color-sky-5: #00404f; 363 | --color-sky-6: #001f28; 364 | 365 | /* cerulean */ 366 | /* hue 17 */ 367 | --color-cerulean-1: #e8f6ff; 368 | --color-cerulean-2: #b9e3ff; 369 | --color-cerulean-3: #0092c5; 370 | --color-cerulean-4: #00749d; 371 | --color-cerulean-5: #003c54; 372 | --color-cerulean-6: #001d2a; 373 | 374 | /* azure */ 375 | /* hue 18 */ 376 | --color-azure-1: #e8f2ff; 377 | --color-azure-2: #c6e0ff; 378 | --color-azure-3: #008fdb; 379 | --color-azure-4: #0071af; 380 | --color-azure-5: #003b5e; 381 | --color-azure-6: #001c30; 382 | 383 | /* blue */ 384 | /* hue 19 */ 385 | --color-blue-1: #f0f4ff; 386 | --color-blue-2: #d4e0ff; 387 | --color-blue-3: #0089fc; 388 | --color-blue-4: #006dca; 389 | --color-blue-5: #00386d; 390 | --color-blue-6: #001a39; 391 | 392 | /* indigo */ 393 | /* hue 20 */ 394 | --color-indigo-1: #f3f3ff; 395 | --color-indigo-2: #deddff; 396 | --color-indigo-3: #657eff; 397 | --color-indigo-4: #0061fc; 398 | --color-indigo-5: #00328a; 399 | --color-indigo-6: #001649; 400 | 401 | /* violet */ 402 | /* hue 21 */ 403 | --color-violet-1: #f7f1ff; 404 | --color-violet-2: #e8daff; 405 | --color-violet-3: #9b70ff; 406 | --color-violet-4: #794aff; 407 | --color-violet-5: #2d0fbf; 408 | --color-violet-6: #0b0074; 409 | 410 | /* purple */ 411 | /* hue 22 */ 412 | --color-purple-1: #fdf4ff; 413 | --color-purple-2: #f7d9ff; 414 | --color-purple-3: #d150ff; 415 | --color-purple-4: #b01fe3; 416 | --color-purple-5: #660087; 417 | --color-purple-6: #3a004f; 418 | 419 | /* magenta */ 420 | /* hue 23 */ 421 | --color-magenta-1: #fff3fc; 422 | --color-magenta-2: #ffd7f6; 423 | --color-magenta-3: #f911e0; 424 | --color-magenta-4: #ca00b6; 425 | --color-magenta-5: #740068; 426 | --color-magenta-6: #44003c; 427 | 428 | /* pink */ 429 | /* hue 24 */ 430 | --color-pink-1: #fff7fb; 431 | --color-pink-2: #ffdcec; 432 | --color-pink-3: #ff2fb2; 433 | --color-pink-4: #d2008f; 434 | --color-pink-5: #790051; 435 | --color-pink-6: #4b0030; 436 | } 437 | 438 | .marker1 { 439 | /* background-color: var(--color-cerulean-2); 440 | */ 441 | background-color: var(--colorA-lighter); 442 | } 443 | 444 | .marker2 { 445 | background-color: var(--colorB-lighter); 446 | } 447 | 448 | .markerDefination { 449 | /* background-color: var(--color-cerulean-2); 450 | */ 451 | background-color: rgb(204, 204, 204); 452 | } 453 | 454 | 455 | /* ---------------------------------------------- 456 | * Generated by Animista on 2022-6-14 11:55:53 457 | * Licensed under FreeBSD License. 458 | * See http://animista.net/license for more info. 459 | * w: http://animista.net, t: @cssanimista 460 | * ---------------------------------------------- */ 461 | 462 | /** 463 | * ---------------------------------------- 464 | * animation color-change-2x 465 | * ---------------------------------------- 466 | */ 467 | .color-change { 468 | -webkit-animation: color-change 1s linear infinite alternate both; 469 | animation: color-change 1s linear infinite alternate both; 470 | } 471 | 472 | @-webkit-keyframes color-change { 473 | 0% { 474 | background: rgb(209, 213, 219); 475 | } 476 | 477 | 100% { 478 | background: #47d557; 479 | } 480 | } 481 | 482 | @keyframes color-change { 483 | 0% { 484 | background: rgb(209, 213, 219); 485 | } 486 | 487 | 100% { 488 | background: #47d557; 489 | } 490 | } -------------------------------------------------------------------------------- /static/build/monash-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/build/playground.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Chameleon Type Debugger 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /static/build/study.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Chameleon Type Debugger 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /static/build/tutorial.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Tutorial | Chameleon Type Debugger 14 | 27 | 32 | 33 | 34 | 35 |
36 |
37 |
38 |

Chameleon Window Layout

39 | Chameleon consists of an 40 | 42 | and a 43 | 45 |
46 |
47 | 48 | 49 | 50 |
51 |
52 | 53 |
54 |
55 |

Uncertain expressions and cards

56 | One important feature of Chameleon is to show all the 'uncertain' expressions 57 | as a list of 58 | . Clicking on a card 60 | activates it. The 61 | is black, 64 | are white. This feature can be enabled by clicking the 67 | or pressing tab key. 71 |
72 |
73 | 74 | 75 | 76 | 77 | 78 |
79 |
80 | 81 | 82 |
83 |
84 |

Possible types

85 | 86 | An uncertain expression is an expression can be typed in 87 | . We refer to them as 91 | and 94 | . 97 |
98 |
99 | 100 | 101 | 102 | 103 |
104 |
105 | 106 | 107 |
108 |
109 |

Highlights

110 | 111 | Highlights are fragments of code with 112 | 116 | 117 | . Highlights can be either 118 | or 121 | . 124 | They are the parts of the source code from which Chameleon deduces the 125 | possible types. 126 | 127 |
128 |
129 | 130 | 131 | 132 | 133 |
134 |
135 | 136 | 137 |
138 |
139 |

Highlights (color-switching)

140 | Highlights switch color (blue to orange or vice versa) when the 141 | active card (or 142 | deduction step 143 | ) changes. 144 | Notice the highlight of y 145 | on the second line changes color when activating 146 | . 149 |
150 |
151 | 152 | 153 |
154 |
155 | 156 | 157 |
158 |
159 |

Deduction step (UI)

160 | Deduction step is another important feature of Chameleon. A deduction step is displayed as an 161 | 165 | in the editor window. It comprises 166 | 170 | (one blue and one orange) outlined in black, and 171 | 175 | showing how the two are related to one another. Deduction step can be enabled by clicking the second 176 | or pressing the tab key. 178 |
179 |
180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 |
188 | 189 |
190 |
191 |

Deduction step (control)

192 | Each card contains one or more 193 | 196 | . Clicking on 197 | a step button activates the step. Each card can associate with 0 or 1 198 | and any number of 202 | . 205 |
206 |
207 | 208 | 209 | 210 | 211 |
212 |
213 | 214 | 215 |
216 |
217 |

Deduction step (example)

218 | When activating a step, some highlights switch color. 220 | The message in the text box changes as well. 221 | For example, activating 222 | , 225 | , and 228 | 231 | result in the editor window showing different deduction steps. 232 |
233 |
234 | 235 | 236 | 237 | 238 |
239 |
240 |
241 | 242 | 243 | -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Card Active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Card Active.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Card Box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Card Box.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Card Inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Card Inactive.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Card X.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Card X.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Card Y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Card Y.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Debugging Message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Debugging Message.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Deduction Example Step 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Deduction Example Step 1.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Deduction Example Step 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Deduction Example Step 2.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Deduction Example Step 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Deduction Example Step 3.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Deduction Example Step 4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Deduction Example Step 4.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Deduction Step Active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Deduction Step Active.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Deduction Step Annotation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Deduction Step Annotation.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Deduction Step Buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Deduction Step Buttons.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Deduction Step Highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Deduction Step Highlight.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Deduction Step Inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Deduction Step Inactive.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Deduction Step Menubar Button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Deduction Step Menubar Button.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Deduction Step UI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Deduction Step UI.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Expand Level 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Expand Level 1.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Expand Level 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Expand Level 2.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Highlight Left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Highlight Left.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Highlight Right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Highlight Right.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Highlights All.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Highlights All.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Highlights Blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Highlights Blue.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Highlights Orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Highlights Orange.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Menubar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Menubar.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Original.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Possible Types 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Possible Types 1.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Possible Types 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Possible Types 2.png -------------------------------------------------------------------------------- /static/build/tutorial/pngs/Possible Types.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maybetonyfu/chameleon/d7664303a8b4bf180b9dea4a64226ba0f9aa877b/static/build/tutorial/pngs/Possible Types.png -------------------------------------------------------------------------------- /static/cm.js: -------------------------------------------------------------------------------- 1 | export function initializeEditor(code) { 2 | let editor = CodeMirror(document.getElementById('editor'), { 3 | lineNumbers: true, 4 | mode: null, 5 | value: code, 6 | }); 7 | editor.setSize('100%', 'calc(100vh - 3rem)'); 8 | return editor; 9 | } 10 | 11 | export function clearDecorations(editor) { 12 | if (editor.getAllMarks().length) { 13 | editor.getAllMarks().forEach(m => m.clear()); 14 | } 15 | ['widgetTop', 'widgeBottom', 'widgetInbetween', 'widgetAnnotation'].forEach( 16 | elemId => { 17 | let elem = document.getElementById(elemId); 18 | if (elem) elem.parentNode.removeChild(elem); 19 | }, 20 | ); 21 | } 22 | 23 | export function highlight(locA, locB, groupA, groupB, editor) { 24 | groupA.forEach(hl => { 25 | if (surroundOrIntersect(hl, locA) || surroundOrIntersect(hl, locB)) return; 26 | 27 | editor.markText(hl.from, hl.to, { className: 'groupMarkerA' }); 28 | }); 29 | 30 | groupB.forEach(hl => { 31 | if (surroundOrIntersect(hl, locA) || surroundOrIntersect(hl, locB)) return; 32 | 33 | editor.markText(hl.from, hl.to, { className: 'groupMarkerB' }); 34 | }); 35 | editor.markText(locA.from, locA.to, { className: 'markerA' }); 36 | editor.markText(locB.from, locB.to, { className: 'markerB' }); 37 | if (doesLocSurround(locA, locB)) { 38 | editor.markText(locB.from, locB.to, { className: 'markerBSmall' }); 39 | } 40 | if (doesLocSurround(locB, locA)) { 41 | editor.markText(locB.from, locB.to, { className: 'markerASmall' }); 42 | } 43 | } 44 | 45 | export function drawAnnotations(locA, locB, text, editor) { 46 | if ( 47 | document.getElementsByClassName('markerB').length === 0 || 48 | document.getElementsByClassName('markerA').length === 0 49 | ) 50 | return; 51 | if (locB.from.line < locA.from.line) { 52 | // B 53 | // A 54 | const topElem = document 55 | .getElementsByClassName('markerB')[0] 56 | .getBoundingClientRect(); 57 | const bottomElem = document 58 | .getElementsByClassName('markerA')[0] 59 | .getBoundingClientRect(); 60 | const [topBox, bottomBox, inbetweenBox, annotationBox] = boxStyles( 61 | topElem, 62 | bottomElem, 63 | text, 64 | ); 65 | editor.addWidget(locA.from, bottomBox); 66 | editor.addWidget(locA.from, annotationBox); 67 | editor.addWidget(locB.from, inbetweenBox); 68 | editor.addWidget(locB.from, topBox); 69 | } else { 70 | // A 71 | // B 72 | const topElem = document 73 | .getElementsByClassName('markerA')[0] 74 | .getBoundingClientRect(); 75 | const bottomElem = document 76 | .getElementsByClassName('markerB')[0] 77 | .getBoundingClientRect(); 78 | const [topBox, bottomBox, inbetweenBox, annotationBox] = boxStyles( 79 | topElem, 80 | bottomElem, 81 | text, 82 | false, 83 | ); 84 | editor.addWidget(locA.from, topBox); 85 | editor.addWidget(locA.from, inbetweenBox); 86 | editor.addWidget(locB.from, bottomBox); 87 | editor.addWidget(locB.from, annotationBox); 88 | } 89 | } 90 | 91 | function boxStyles(topElem, bottomElem, text, color = false) { 92 | const downwardBarHeight = 5; 93 | const annotationWidth = 300; 94 | const annotationHeight = 20; 95 | const stepAsideDistance = 600; 96 | const styleTop = [ 97 | color ? `background: aquamarine;` : 'background:transparent;', 98 | `height: ${downwardBarHeight}px;`, 99 | `margin-left: ${topElem.width / 2}px;`, 100 | `margin-top: -${topElem.height + downwardBarHeight}px;`, 101 | `width: ${stepAsideDistance - topElem.left - topElem.width / 2}px;`, 102 | `border-left: thin solid #4ade80;`, 103 | `border-top: thin solid #4ade80;`, 104 | `border-right: thin solid #4ade80;`, 105 | 106 | `z-index:2;`, 107 | ].join(''); 108 | const styleBottom = [ 109 | color ? `background: lightpink;` : 'background:transparent;', 110 | `height: ${downwardBarHeight}px;`, 111 | `margin-left: ${bottomElem.width / 2}px;`, 112 | `width:${stepAsideDistance - bottomElem.left - bottomElem.width / 2}px;`, 113 | `border-left: thin solid #4ade80;`, 114 | `border-bottom: thin solid #4ade80;`, 115 | `border-right: thin solid #4ade80;`, 116 | `z-index:2;`, 117 | ].join(''); 118 | const styleInbetween = [ 119 | color ? `background: burlywood;` : 'background:transparent;', 120 | `width:${stepAsideDistance - topElem.left - topElem.width / 2}px;`, 121 | `height: ${bottomElem.top - topElem.top}px;`, 122 | `margin-top: -${topElem.height}px;`, 123 | `margin-left: ${topElem.width / 2}px;`, 124 | `border-right: thin solid #4ade80;`, 125 | `z-index:2;`, 126 | ].join(''); 127 | const styleAnnotation = [ 128 | color ? `background: lightgreen;` : 'background:transparent;', 129 | // `background: beige;`, 130 | `width:${annotationWidth}px;`, 131 | `height: ${annotationHeight}px;`, 132 | `font-size: 14px;`, 133 | `text-align: center;`, 134 | `margin-top: -${annotationHeight}px;`, 135 | `margin-left: ${stepAsideDistance - 136 | bottomElem.left - 137 | annotationWidth / 2}px;`, 138 | `z-index:2;`, 139 | // `border: thin solid red;` 140 | ].join(''); 141 | const topBox = html(`
`); 142 | const bottomBox = html(`
`); 143 | const inbetweenBox = html( 144 | `
`, 145 | ); 146 | const annotationBox = html( 147 | `
${text}
`, 148 | ); 149 | return [topBox, bottomBox, inbetweenBox, annotationBox]; 150 | } 151 | 152 | function html(str) { 153 | const placeholder = document.createElement('div'); 154 | placeholder.innerHTML = str; 155 | return placeholder.firstElementChild; 156 | } 157 | 158 | function isPointBefore(point1, point2) { 159 | if (point1.line < point2.line) return true; 160 | else if (point1.line === point2.line) return point1.ch <= point2.ch; 161 | else return false; 162 | } 163 | 164 | function isPointAfter(point1, point2) { 165 | if (point1.line > point2.line) return true; 166 | else if (point1.line === point2.line) return point1.ch >= point2.ch; 167 | else return false; 168 | } 169 | 170 | function doesLocSurround(locA, locB) { 171 | return isPointBefore(locA.from, locB.from) && isPointAfter(locA.to, locB.to); 172 | } 173 | 174 | function surroundOrIntersect(a, b) { 175 | return ( 176 | doesLocSurround(a, b) || doesLocSurround(b, a) || doesLocIntersect(a, b) 177 | ); 178 | } 179 | 180 | function doesLocIntersect(locA, locB) { 181 | return ( 182 | (isPointBefore(locA.from, locB.from) && 183 | isPointBefore(locB.from, locA.to) && 184 | isPointBefore(locA.to, locB.to) && 185 | isPointBefore(locB.from, locA.to)) || 186 | (isPointBefore(locB.from, locA.from) && 187 | isPointBefore(locA.from, locB.to) && 188 | isPointBefore(locB.to, locA.to) && 189 | isPointBefore(locA.from, locB.to)) 190 | ); 191 | } 192 | 193 | function getIntersection(locA, locB) { 194 | if (isPointBefore(locA.from, locB.from)) { 195 | return { from: locB.from, to: locA.to }; 196 | } else { 197 | return { from: locA.from, to: locB.to }; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /static/code.js: -------------------------------------------------------------------------------- 1 | const intro = n => `module Task${n} where 2 | -- Objective: fix the type error in this file 3 | x y = 4 | case y of 5 | 3 -> '3' 6 | False -> '4' 7 | ` 8 | 9 | const dropEvery = n => `module Task${n} where 10 | -- Objective: fix the type error in this file 11 | 12 | divides x y = y \`mod\` x == 0 13 | 14 | 15 | dropEvery [] _ = [] 16 | dropEvery (x:xs) n = dropEvery' (x:xs) n 1 17 | 18 | dropEvery' :: [Int] -> Int -> Int -> [Int] 19 | dropEvery' [] _ _ = [] 20 | dropEvery' (x:xs) n i = 21 | let current = 22 | if n \`divides\` i 23 | then [] 24 | else [x] 25 | in current : dropEvery' xs n (i+1) 26 | ` 27 | 28 | const rotate = n => `module Task${n} where 29 | -- Objective: fix the type error in this file 30 | 31 | -- Rotate a list N places to the left. 32 | 33 | rotate1 :: [a] -> [a] 34 | rotate1 x = tail x ++ [head x] 35 | 36 | rotate1Back :: [a] -> [a] 37 | rotate1Back x = last x : init x 38 | 39 | 40 | rotate :: [a] -> Int -> [a] 41 | rotate [] _ = [] 42 | rotate x 0 = x 43 | rotate x y 44 | | y > 0 = rotate rotate1 (y-1) 45 | | otherwise = rotate rotate1Back x (y+1) 46 | ` 47 | 48 | const exampeExtend = n => `module Task${n} where 49 | 50 | data Expr = C Int | 51 | Comb Expr Expr| 52 | V String | 53 | Let String Expr Expr 54 | 55 | data Env = Env [(String, Int)] 56 | 57 | eval :: Expr -> Env -> (Env, Int) 58 | eval (C x) env = (env, x) 59 | eval (Let v e1 e2) env = let (env1, v1) = eval e1 env 60 | env2 = extend v v1 61 | ans = eval e2 env2 62 | in ans 63 | eval (V v) env = (env, find v env) 64 | 65 | extend :: String -> Int -> Env -> Env 66 | extend v e (Env env) = Env ([(v,e)] ++ env) 67 | 68 | find v (Env []) = error "Unbound variable" 69 | find v1 (Env ((v2,e):es)) = if v1 == v2 then e else find v1 (Env es) 70 | 71 | `; 72 | 73 | const weekdayRange = n => `module Task${n} where 74 | 75 | toWeekday n = 76 | ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] !! n 77 | 78 | seperateByComma [] = "" 79 | seperateByComma [x] = x 80 | seperateByComma (x : xs) = x ++ "," ++ seperateByComma xs 81 | 82 | range xs 83 | | length xs < 3 = seperateByComma xs 84 | | otherwise = head xs ++ "-" ++ last xs 85 | 86 | -- dayRange :: [Int] -> [String] 87 | dayRange days = 88 | let grouped = groupBy' (\\a b -> a + 1 == b) days 89 | in map (\\x -> range (toWeekday x)) grouped 90 | 91 | -- unlike groupBy which compares any element 92 | -- to the first, 93 | -- groupBy' compares any element to the last 94 | -- element 95 | groupBy' :: (a -> a -> Bool) -> [a] -> [[a]] 96 | groupBy' f (x : xs) = 97 | let go f (x : xs) ((a : as) : bs) = 98 | if f a x 99 | then go f xs ((x : a : as) : bs) 100 | else go f xs ([x] : (a : as) : bs) 101 | go _ [] as = reverse (map reverse as) 102 | in go f xs [[x]] 103 | 104 | ` 105 | 106 | const exampleJValue = n => `module Task${n} where 107 | 108 | data JValue = JString String 109 | | JNumber Double 110 | | JBool Bool 111 | | JNull 112 | | JObject [(String, JValue)] 113 | | JArray [JValue] 114 | 115 | renderJValue :: JValue -> String 116 | renderJValue (JString s) = show s 117 | renderJValue (JNumber n) = show n 118 | renderJValue (JBool True) = "true" 119 | renderJValue (JBool False) = "false" 120 | renderJValue JNull = "null" 121 | 122 | renderJValue (JObject o) = "{" ++ renderPairs o ++ "}" 123 | renderJValue (JArray a) = "[" ++ renderPairs a ++ "]" 124 | 125 | renderPair :: (String, JValue) -> String 126 | renderPair (k,v) = show k ++ ": " ++ renderJValue v 127 | 128 | renderPairs :: [(String,JValue)] -> String 129 | renderPairs [] = "" 130 | renderPairs [p] = renderPair p 131 | renderPairs (p:ps) = renderPair p ++ "," ++ renderPairs ps 132 | 133 | -- renderArrayValues is not used anywhere, I wonder why 134 | renderArrayValues [] = "" 135 | renderArrayValues [v] = renderJValue v 136 | renderArrayValues (v:vs) = renderJValue v ++ "," ++ renderArrayValues vs 137 | `; 138 | 139 | const exampleNQueens = n => `module Task${n} where 140 | 141 | nqueens size = 142 | filter evaluateBoard (board_permutations size) 143 | 144 | --N sized Chess boards are represented as a one-dimension array. 145 | board_permutations size = permutations [0..size - 1] 146 | 147 | --Count the number of valid boards for a specified Chess board size. 148 | count_boards size = length . nqueens 149 | 150 | init' [] = error "init' applied at empty list" 151 | init' [a, b] = [a] 152 | init' (a:as) = a : init' as 153 | 154 | --Recursively check that prior rows are valid. 155 | evaluateBoard [] = True 156 | evaluateBoard rows = 157 | evaluateBoard (init' rows) && 158 | validate 159 | (init' rows) 160 | (last (rows - 1)) 161 | (last rows + 1) 162 | (last rows) 163 | 164 | 165 | --Validate that a Queen on a row doesn't have conflicts with earlier rows. 166 | validate [] _ _ _ = True 167 | validate rows left right position = 168 | if last rows == left || last rows == right || last rows == position 169 | then False 170 | else validate (init' rows) (left - 1) (right + 1) position 171 | `; 172 | 173 | const exampleRockPaperScissors = n => `module Task${n} where 174 | 175 | data Hand = Rock | Paper | Scissors 176 | type Score = (Int, Int) 177 | 178 | winsOver :: Hand -> Hand -> Bool 179 | Rock \`winsOver\` Scissors = True 180 | Paper \`winsOver\` Rock = True 181 | Scissors \`winsOver\` Paper = True 182 | _ \`winsOver\` _ = False 183 | 184 | computeScore :: Hand -> Hand -> Score 185 | computeScore h1 h2 186 | | h1 \`winsOver\` h2 = (1, 0) 187 | | h2 \`winsOver\` h1 = (0, 1) 188 | | otherwise = (0, 0) 189 | 190 | combine a b = (fst a + fst b, snd a + snd b) 191 | 192 | zip' (a:as) (b:bs) = (a,b) : zip' as bs 193 | 194 | foldl1 :: (b -> a -> b) -> b -> [a] -> b 195 | foldl1 _ b [] = b 196 | foldl1 f b (a:as) = foldl1 f (f b a) as 197 | 198 | pairScore (h1, h2) = computeScore h1 h2 199 | 200 | score :: [Hand] -> [Hand] -> Score 201 | score h1 h2 = 202 | foldl1 combine (0, 0) (pairScore (zip' h1 h2)) 203 | 204 | `; 205 | 206 | const exampleDateSpan = n => `module Task${n} where 207 | data Period 208 | = DayPeriod Day 209 | | WeekPeriod Day 210 | | MonthPeriod Year Month 211 | | YearPeriod Year 212 | 213 | type Year = Int 214 | type Month = Int -- 1-12 215 | data Day = Day Year Month Int 216 | 217 | data DataSpan = DateSpan (Maybe Day) (Maybe Day) 218 | 219 | addDays :: Int -> Day -> Day 220 | addDays n day = day 221 | 222 | fromGregorian :: Year -> Month -> Int -> Maybe Day 223 | fromGregorian y m d = Just (Day y m d) 224 | 225 | periodAsDateSpan :: Period -> DataSpan 226 | periodAsDateSpan (WeekPeriod b) = 227 | DateSpan (Just b) (Just (addDays 7 b)) 228 | periodAsDateSpan (MonthPeriod y m) = 229 | let 230 | (y', m') 231 | | m == 12 = (y + 1, 1) 232 | | otherwise = (y, m + 1) 233 | dayStart = Just (fromGregorian y m 1) 234 | dayEnd = Just (fromGregorian y' m' 1) 235 | in DateSpan dayStart dayEnd 236 | 237 | 238 | 239 | `; 240 | 241 | const exampleBookTrans = n => `module Task${n} where 242 | 243 | standardTrans z = 244 | case z of 245 | "shorttitle" -> ["short"] 246 | "sorttitle" -> ["sorted"] 247 | "indextitle" -> ["index"] 248 | "indexsorttitle" -> ["index", "sorted"] 249 | _ -> z 250 | 251 | 252 | -- bookTrans :: String -> [String] 253 | bookTrans z = 254 | case z of 255 | "title" -> ["booktitle"] 256 | "subtitle" -> ["booksubtitle"] 257 | "titleaddon" -> ["booktitleaddon"] 258 | "shorttitle" -> [] 259 | "sorttitle" -> [] 260 | "indextitle" -> [] 261 | "indexsorttitle" -> [] 262 | _ -> [z] 263 | 264 | transformKey x y "author" 265 | | x \`elem\` ["mvbook", "book"] = 266 | ["bookauthor", "author"] 267 | -- note: this next clause is not in the biblatex manual, but it makes 268 | -- sense in the context of CSL conversion: 269 | transformKey x y "author" 270 | | x == "mvbook" = ["bookauthor", "author"] 271 | transformKey "mvbook" y z 272 | | y \`elem\` ["book", "inbook", "bookinbook", "suppbook"] = 273 | standardTrans z 274 | transformKey _ _ x = [x] 275 | 276 | `; 277 | 278 | const exampleTake = n => `module Task${n} where 279 | 280 | -- Takes the first n elements from a list 281 | take' :: Int -> [Int] -> [Int] 282 | take' n [] = [] 283 | take' n (x:xs) = x ++ take' (n - 1) xs 284 | `; 285 | 286 | const examplePassword = n => `module Task${n} where 287 | 288 | -- A data type to represent password 289 | data Password = P String 290 | 291 | -- Validate how good a password is 292 | validate :: Password -> String 293 | validate password = 294 | if length password > 10 295 | then "Great password" 296 | else "Password too short" 297 | `; 298 | 299 | 300 | const insertAt = n => `module Task${n} where 301 | -- Objective: fix the type error in this file 302 | 303 | -- Insert an element at a given position into a list. 304 | 305 | insertAt el lst n = 306 | let accu (i, acc) x = 307 | if i == n 308 | then (acc ++ [el,x],i+1) 309 | else (acc ++ [x],i+1) 310 | in fst $ foldl accu ([],1) lst 311 | 312 | 313 | ` 314 | 315 | const balanceTree = n => `module Task${n} where 316 | -- Objective: fix the type error in this file 317 | 318 | data Tree a = Empty | Branch a (Tree a) (Tree a) 319 | leaf x = Branch x Empty Empty 320 | 321 | isBalancedTree Empty = True 322 | isBalancedTree (Branch _ l r) = 323 | (countBranches l - countBranches r) == 1 324 | || (countBranches r - countBranches l) == 1 325 | && isBalancedTree l && isBalancedTree r 326 | 327 | 328 | countBranches Empty = 0 329 | countBranches (Branch _ l r) = 1 + l + r 330 | ` 331 | 332 | const mostBasic = n => `module Task${n} where 333 | 334 | x y = 335 | case y of 336 | Nothing -> Just 0 337 | Just n -> n * 2 338 | ` 339 | 340 | const ifelse = n => `module Task${n} where 341 | u = 0 342 | v = 0.1 343 | z = True 344 | y = if z then u else v 345 | ` 346 | [[1,2,3,4], [4,5,6,7]] [[1,2,3,4], [5,6,7,8]] 347 | const compress = n => `module Task${n} where 348 | -- Objective: fix the type error in this file 349 | 350 | -- Eliminate consecutive duplicates of list elements. 351 | 352 | compress = foldr skipDups 353 | 354 | skipDups x [] = [x] 355 | skipDups x acc 356 | | x == head acc = acc 357 | | otherwise = x : acc 358 | 359 | expect = [3,4,5,6] 360 | 361 | actual = compress [3,3,4,5,6,6] 362 | 363 | test :: Bool 364 | test = expect == actual 365 | ` 366 | 367 | 368 | const uconandvcon = n => `module Task${n} where 369 | -- Objective: fix the type error in this file 370 | 371 | data U = UCon Bool Int (Int, Int) 372 | 373 | u (UCon x y z) = 374 | if x 375 | then z 376 | else fst y + snd y 377 | 378 | actual = u (UCon False 0 (15, 10)) 379 | expect = 25 380 | 381 | test :: Bool 382 | test = actual == expect 383 | 384 | ` 385 | 386 | const quicksort = n => `module Task${n} where 387 | -- Objective: fix the type error in this file 388 | 389 | quick :: [Int] -> [Int] 390 | quick [] = [] 391 | quick (x:xs)= 392 | let littlebigs = split xs 393 | in 394 | quick (fst littlebigs) 395 | ++ [x] 396 | ++ quick (snd littlebigs) 397 | 398 | split [] _ result = result 399 | split (x:xs) n (littles, bigs) = 400 | if x < n 401 | then split xs n (x:littles, bigs) 402 | else split xs n (littles, x:bigs) 403 | ` 404 | 405 | 406 | const printXML = n => `module Task${n} where 407 | -- Objective: fix the type error in this file 408 | 409 | data XML = XML Position Part 410 | data Position = Top | Bottom | Left | Right 411 | 412 | type Name = String 413 | 414 | data Part = 415 | Element Name [Attribute] [XML] 416 | | Comment String 417 | | Text String 418 | 419 | getPart :: XML -> Part 420 | getPart (XML pos part) = part 421 | 422 | 423 | printXML (Element name [attributs] xmls) = 424 | "<" ++ name ++ ">" 425 | ++ mconcat (map printXML xmls) 426 | ++ "" 427 | printXML (Text text) = text 428 | 429 | 430 | ` 431 | 432 | const euler1 = n => `module Task${n} where 433 | -- Objective: fix the type error in this file 434 | 435 | -- Add all the natural numbers below 1000 436 | -- that are multiples of 3 or 5. 437 | sum [] = 0 438 | sum [x] = x 439 | sum (x:xs) = x + sum xs 440 | 441 | check (x:xs) 442 | | x \`mod\` 3 == 0 || x \`mod\` 5 == 0 = x + check xs 443 | | otherwise = check xs 444 | 445 | problem_1 = sum (check [1..999]) 446 | ` 447 | const examples = [ 448 | euler1, 449 | dropEvery, 450 | rotate, 451 | insertAt, 452 | balanceTree, 453 | compress, 454 | uconandvcon, 455 | quicksort, 456 | printXML, 457 | // weekdayRange, 458 | ].map((ex, n) => ex(n + 1)); 459 | 460 | export default examples; 461 | -------------------------------------------------------------------------------- /static/debuggerSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; 2 | import tasks from './code'; 3 | import * as R from 'ramda'; 4 | import { 5 | drawAnnotations, 6 | makeParentHighlightB, 7 | makeHighlightB, 8 | makeHighlight, 9 | doesRangeSurround, 10 | convertLocation, 11 | getRandomInt 12 | } from './util'; 13 | 14 | 15 | 16 | let debuggerOrders = [R.flatten(R.repeat(['level1', 'level2', 'level3'], 3)) 17 | , R.flatten(R.repeat(['level1', 'level3', 'level2'], 3)) 18 | , R.flatten(R.repeat(['level2', 'level1', 'level3'], 3)) 19 | , R.flatten(R.repeat(['level2', 'level3', 'level1'], 3)) 20 | , R.flatten(R.repeat(['level3', 'level1', 'level2'], 3)) 21 | , R.flatten(R.repeat(['level3', 'level2', 'level1'], 3))] 22 | 23 | let debuggerOrder = debuggerOrders[getRandomInt(6)] 24 | 25 | let setLevelTo = x => { 26 | if (x === 'level1') return debuggingLevel1 27 | if (x === 'level2') return debuggingLevel2 28 | if (x === 'level3') return debuggingLevel3 29 | } 30 | 31 | export const editorModes = { 32 | edit: 0, 33 | normal: 1, 34 | }; 35 | 36 | export let typeCheckThunk = createAsyncThunk( 37 | 'typeCheck', 38 | async (_, { dispatch, getState }) => { 39 | dispatch(resetHighlights()); 40 | let state = getState(); 41 | let text = state.debugger.text; 42 | let currentTaskNum = state.debugger.currentTaskNum 43 | dispatch(incrementAttemps(currentTaskNum)) 44 | // console.log(text); 45 | let response = await fetch('/typecheck', { 46 | method: 'POST', 47 | body: text, 48 | }); 49 | let data = response.json(); 50 | return data; 51 | }, 52 | ); 53 | 54 | export let toggleMultileExpThunk = createAsyncThunk( 55 | 'multipleExpThunk', 56 | async (_, { dispatch, getState }) => { 57 | let state = getState(); 58 | let newStep = Math.floor(state.debugger.steps.length / 2); 59 | if (state.debugger.multipleExps) { 60 | dispatch(setStep(newStep)); 61 | } 62 | dispatch(toggleMultipleExps()); 63 | return null; 64 | }, 65 | ); 66 | 67 | export let switchTaskThunk = createAsyncThunk( 68 | 'switchTask', 69 | async (n, { dispatch }) => { 70 | let setLevelAction = setLevelTo(debuggerOrder[n]) 71 | dispatch(setTask(n)); 72 | dispatch(typeCheckThunk(null)); 73 | dispatch(setLevelAction()) 74 | }, 75 | ); 76 | 77 | const initialState = { 78 | currentStepNum: null, 79 | text: '', 80 | longestLine: 0, 81 | currentTraverseId: null, 82 | currentContextItem: null, 83 | pinnedStep: 0, 84 | highlightFilter: ['markerDefination'], 85 | steps: [], 86 | context: [], 87 | numOfSteps: 0, 88 | numOfContextRows: 0, 89 | prevLocs: [], 90 | nextLocs: [], 91 | currentTaskNum: null, 92 | cursorPosition: 0, 93 | wellTyped: false, 94 | loadError: null, 95 | parseError: null, 96 | mode: editorModes.normal, 97 | widgets: [], 98 | highlights: [], 99 | debuggingSteps: false, 100 | multipleExps: false, 101 | attempts: [0, 0, 0, 0, 0, 0, 0, 0, 0] 102 | }; 103 | 104 | const { actions, reducer } = createSlice({ 105 | name: 'editor', 106 | initialState, 107 | reducers: { 108 | toEditMode: R.assoc('mode', editorModes.edit), 109 | toNormalMode: R.assoc('mode', editorModes.normal), 110 | toggleDebuggerStpes: R.modify('debuggingSteps', R.not), 111 | toggleMultipleExps: R.modify('multipleExps', R.not), 112 | incrementAttemps(state, action) { 113 | state.attempts = state.attempts.map((v, i) => i === action.payload ? v + 1 : v) 114 | }, 115 | setCursorPosition (state, action) { 116 | state.cursorPosition = action.payload 117 | }, 118 | debuggingLevel1(state) { 119 | state.debuggingSteps = false 120 | state.multipleExps = false 121 | }, 122 | 123 | debuggingLevel2(state) { 124 | state.debuggingSteps = false 125 | state.multipleExps = true 126 | }, 127 | 128 | debuggingLevel3(state) { 129 | state.debuggingSteps = true 130 | state.multipleExps = true 131 | }, 132 | 133 | setText(state, action) { 134 | state.text = action.payload; 135 | }, 136 | showOnlyMark1(state) { 137 | state.highlightFilter = ['marker1', 'markerDefination']; 138 | }, 139 | showOnlyMark2(state) { 140 | state.highlightFilter = ['marker2', 'markerDefination']; 141 | }, 142 | showBoth(state) { 143 | state.highlightFilter = ['markerDefination']; 144 | }, 145 | showDefination(state) { 146 | state.highlightFilter = ['marker1', 'marker2']; 147 | }, 148 | setTask(state, action) { 149 | if (action.payload < 0 || action.payload > tasks.length) return state; 150 | state.currentTaskNum = action.payload; 151 | state.text = tasks[action.payload]; 152 | state.longestLine = R.pipe( 153 | R.split('\n'), 154 | R.map(R.split('')), 155 | R.map(R.length), 156 | R.sort(R.subtract), 157 | R.reverse, 158 | R.head, 159 | )(tasks[action.payload]); 160 | }, 161 | setStep(state, action) { 162 | if (state.currentStepNum === null) return state; 163 | if (action.payload > state.numOfSteps - 1 || action.payload < 0) 164 | return state; 165 | 166 | let currentStepNum = action.payload; 167 | let { highlights, widgets } = convertStep( 168 | state.steps[currentStepNum], 169 | currentStepNum, 170 | state.longestLine, 171 | ); 172 | 173 | let currentTraverseId = state.steps[currentStepNum].stepId; 174 | let currentContextItem = getCurrentActiveContext( 175 | state.context, 176 | currentTraverseId, 177 | ); 178 | 179 | state.currentStepNum = currentStepNum; 180 | state.highlights = [ 181 | ...highlights, 182 | ...getPrevLocs(state.steps, currentStepNum), 183 | ...getNextLocs(state.steps, currentStepNum), 184 | getDefinitionHighlight(currentContextItem), 185 | ]; 186 | state.widgets = widgets; 187 | 188 | state.currentContextItem = currentContextItem; 189 | state.currentTraverseId = currentTraverseId; 190 | }, 191 | lockStep(state, action) { 192 | if (state.currentStepNum === null) return state; 193 | if (action.payload > state.numOfSteps - 1 || action.payload < 0) 194 | return state; 195 | let currentStepNum = action.payload; 196 | let { highlights, widgets } = convertStep( 197 | state.steps[currentStepNum], 198 | currentStepNum, 199 | state.longestLine, 200 | ); 201 | let currentTraverseId = state.steps[currentStepNum].stepId; 202 | let currentContextItem = getCurrentActiveContext( 203 | state.context, 204 | currentTraverseId, 205 | ); 206 | state.currentStepNum = currentStepNum; 207 | state.highlights = [ 208 | ...highlights, 209 | ...getPrevLocs(state.steps, currentStepNum), 210 | ...getNextLocs(state.steps, currentStepNum), 211 | getDefinitionHighlight(currentContextItem), 212 | ]; 213 | state.widgets = widgets; 214 | state.currentContextItem = currentContextItem; 215 | state.currentTraverseId = currentTraverseId; 216 | state.pinnedStep = action.payload; 217 | }, 218 | resetHighlights(state) { 219 | state.highlights = []; 220 | state.widgets = []; 221 | }, 222 | prevStep(state) { 223 | if (state.currentStepNum === null) return state; 224 | if (state.currentStepNum <= 0) return state; 225 | let currentStepNum = state.currentStepNum - 1; 226 | let { highlights, widgets } = convertStep( 227 | state.steps[currentStepNum], 228 | currentStepNum, 229 | state.longestLine, 230 | ); 231 | let currentTraverseId = state.steps[currentStepNum].stepId; 232 | let currentContextItem = getCurrentActiveContext( 233 | state.context, 234 | currentTraverseId, 235 | ); 236 | state.pinnedStep = currentStepNum; 237 | state.currentStepNum = currentStepNum; 238 | state.highlights = [ 239 | ...highlights, 240 | ...getPrevLocs(state.steps, currentStepNum), 241 | ...getNextLocs(state.steps, currentStepNum), 242 | getDefinitionHighlight(currentContextItem), 243 | ]; 244 | state.widgets = widgets; 245 | 246 | state.currentContextItem = currentContextItem; 247 | state.currentTraverseId = currentTraverseId; 248 | }, 249 | nextStep(state) { 250 | if (state.currentStepNum === null) return state; 251 | if (state.currentStepNum >= state.numOfSteps - 1) return state; 252 | 253 | let currentStepNum = state.currentStepNum + 1; 254 | let { highlights, widgets } = convertStep( 255 | state.steps[currentStepNum], 256 | currentStepNum, 257 | state.longestLine, 258 | ); 259 | 260 | state.pinnedStep = currentStepNum; 261 | let currentTraverseId = state.steps[currentStepNum].stepId; 262 | let currentContextItem = getCurrentActiveContext( 263 | state.context, 264 | currentTraverseId, 265 | ); 266 | 267 | state.currentStepNum = currentStepNum; 268 | state.highlights = [ 269 | ...highlights, 270 | ...getPrevLocs(state.steps, currentStepNum), 271 | ...getNextLocs(state.steps, currentStepNum), 272 | getDefinitionHighlight(currentContextItem), 273 | ]; 274 | state.widgets = widgets; 275 | 276 | state.currentContextItem = currentContextItem; 277 | state.currentTraverseId = currentTraverseId; 278 | }, 279 | }, 280 | extraReducers: builder => { 281 | builder.addCase(typeCheckThunk.fulfilled, (state, action) => { 282 | if (action.payload.tag === 'ChTypeError') { 283 | let steps = action.payload.steps; 284 | let context = action.payload.contextTable; 285 | let currentStepNum = Math.floor(steps.length / 2); 286 | let longestLine = R.pipe( 287 | R.split('\n'), 288 | R.map(R.split('')), 289 | R.map(R.length), 290 | R.sort(R.subtract), 291 | R.reverse, 292 | R.head, 293 | )(state.text); 294 | 295 | let { highlights, widgets } = convertStep( 296 | steps[currentStepNum], 297 | currentStepNum, 298 | longestLine, 299 | ); 300 | let currentTraverseId = steps[currentStepNum].stepId; 301 | state.context = context; 302 | state.steps = steps; 303 | state.currentContextItem = getCurrentActiveContext( 304 | context, 305 | currentTraverseId, 306 | ); 307 | state.highlights = [ 308 | ...highlights, 309 | ...getPrevLocs(steps, currentStepNum), 310 | ...getNextLocs(steps, currentStepNum), 311 | getDefinitionHighlight(state.currentContextItem), 312 | ]; 313 | state.widgets = widgets; 314 | state.numOfSteps = steps.length; 315 | state.numOfContextRows = context.length; 316 | state.currentStepNum = currentStepNum; 317 | state.pinnedStep = currentStepNum; 318 | state.currentTraverseId = currentTraverseId; 319 | 320 | state.parseError = null; 321 | state.loadError = null; 322 | state.wellTyped = false; 323 | state.longestLine = longestLine; 324 | } else if (action.payload.tag === 'ChSuccess') { 325 | return Object.assign( 326 | {}, 327 | { 328 | ...state, 329 | wellTyped: true, 330 | parseError: null, 331 | loadError: null, 332 | }, 333 | ); 334 | } else if (action.payload.tag === 'ChLoadError') { 335 | let loadError = action.payload.missing; 336 | return Object.assign( 337 | {}, 338 | { 339 | ...state, 340 | loadError, 341 | parseError: null, 342 | }, 343 | ); 344 | } else if (action.payload.tag === 'ChParseError') { 345 | let parseError = { 346 | message: action.payload.message, 347 | loc: action.payload.loc, 348 | }; 349 | return Object.assign( 350 | {}, 351 | { 352 | ...state, 353 | parseError, 354 | loadError: null, 355 | }, 356 | ); 357 | } 358 | }); 359 | }, 360 | }); 361 | 362 | export const { 363 | setStep, 364 | prevStep, 365 | nextStep, 366 | lockStep, 367 | setTask, 368 | setText, 369 | toEditMode, 370 | toNormalMode, 371 | resetHighlights, 372 | toggleDebuggerStpes, 373 | toggleMultipleExps, 374 | showOnlyMark1, 375 | showOnlyMark2, 376 | showBoth, 377 | showDefination, 378 | debuggingLevel1, 379 | debuggingLevel2, 380 | debuggingLevel3, 381 | incrementAttemps, 382 | setCursorPosition 383 | } = actions; 384 | export default reducer; 385 | 386 | // Step and context related convenient functions 387 | 388 | function getCurrentActiveContext(contexts, currentTraverseId) { 389 | let item = contexts.find(c => { 390 | return R.nth(2)( 391 | c.contextSteps.find(x => R.equals(R.nth(0, x), currentTraverseId)), 392 | ); 393 | }); 394 | return item === undefined ? null : item; 395 | } 396 | 397 | function getDefinitionHighlight(ctxItm) { 398 | let definitionBlock = convertLocation(ctxItm.contextDefinedIn); 399 | return makeHighlight(definitionBlock, 'markerDefination'); 400 | } 401 | 402 | function getPrevLocs(steps, currentNum) { 403 | if (steps.length === 0) return []; 404 | let { rangeA, rangeB } = convertStep(steps[currentNum], currentNum, 0); 405 | return steps 406 | .filter((_, i) => i < currentNum) 407 | .map(step => convertStep(step, 0, 0)) 408 | .flatMap(step => [step.rangeA, step.rangeB]) 409 | .filter( 410 | l => !(doesRangeSurround(l, rangeA) || doesRangeSurround(l, rangeB)), 411 | ) 412 | .flatMap(l => makeHighlight(l, 'marker1')); 413 | } 414 | 415 | function getNextLocs(steps, currentNum) { 416 | if (steps.length === 0) return []; 417 | let { rangeA, rangeB } = convertStep(steps[currentNum], currentNum, 0); 418 | return steps 419 | .filter((_, i) => i > currentNum) 420 | .map(step => convertStep(step, 0, 0)) 421 | .flatMap(step => [step.rangeA, step.rangeB]) 422 | .filter( 423 | l => !(doesRangeSurround(l, rangeA) || doesRangeSurround(l, rangeB)), 424 | ) 425 | .flatMap(l => makeHighlight(l, 'marker2')); 426 | } 427 | 428 | function convertStep(step, stepNum, offset) { 429 | let reason = step['explanation']; 430 | let direction = step['order']; 431 | let rangeA = convertLocation(step['stepA']); 432 | let rangeB = convertLocation(step['stepB']); 433 | let highlights; 434 | if (doesRangeSurround(rangeA, rangeB)) { 435 | highlights = [ 436 | makeParentHighlightB(rangeA, 'marker1'), 437 | makeHighlightB(rangeB, 'marker2'), 438 | ]; 439 | } else if (doesRangeSurround(rangeB, rangeA)) { 440 | highlights = [ 441 | makeParentHighlightB(rangeB, 'marker2'), 442 | makeHighlightB(rangeA, 'marker1'), 443 | ]; 444 | } else { 445 | highlights = [ 446 | makeHighlightB(rangeB, 'marker2'), 447 | makeHighlightB(rangeA, 'marker1'), 448 | ]; 449 | } 450 | 451 | let widgets = drawAnnotations( 452 | rangeA, 453 | rangeB, 454 | reason, 455 | stepNum, 456 | direction, 457 | offset, 458 | ); 459 | return { highlights, widgets, rangeA, rangeB }; 460 | } 461 | -------------------------------------------------------------------------------- /static/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider, useSelector, useDispatch } from 'react-redux'; 4 | import store from './store'; 5 | import { 6 | editorModes, 7 | nextStep, 8 | prevStep, 9 | toggleMultipleExps, 10 | toggleDebuggerStpes, 11 | switchTaskThunk, 12 | showOnlyMark1, 13 | showOnlyMark2, 14 | showBoth, 15 | toNormalMode, 16 | typeCheckThunk, 17 | } from './debuggerSlice'; 18 | import Splitter from '@devbookhq/splitter'; 19 | import Editor from './Editor'; 20 | import Debugger from './Debugger'; 21 | import MenuBar from './MenuBar'; 22 | import Modal from 'react-modal'; 23 | import { nanoid } from 'nanoid'; 24 | import mixpanel from 'mixpanel-browser'; 25 | import { Event, Source, track } from './report'; 26 | import { getMode } from './util'; 27 | import * as R from 'ramda'; 28 | import Tracker from '@openreplay/tracker'; 29 | 30 | const tracker = new Tracker({ 31 | projectKey: "VzGISOLFpFFv1yHRdHHJ", 32 | ingestPoint: "https://data.ercu.be/ingest", 33 | }); 34 | 35 | Modal.setAppElement('#react-root'); 36 | 37 | let userId, userProgress; 38 | if (localStorage.getItem('userId') === null) { 39 | userId = nanoid(); 40 | userProgress = -1; 41 | localStorage.setItem('userId', userId); 42 | localStorage.setItem('userProgress', -1); 43 | } else { 44 | userId = localStorage.getItem('userId'); 45 | userProgress = parseInt(localStorage.getItem('userProgress'), 10) || -1; 46 | if (userProgress === 8) { 47 | window.location = '/playground' 48 | } 49 | } 50 | 51 | mixpanel.init('6be6077e1d5b8de6978c65490e1666ea', { 52 | debug: false, 53 | ignore_dnt: true, 54 | api_host: 'https://data.chameleon.typecheck.me', 55 | }); 56 | 57 | mixpanel.identify(userId); 58 | tracker.start(); 59 | tracker.setUserID(userId) 60 | 61 | store.dispatch(switchTaskThunk(userProgress + 1)); 62 | 63 | window.addEventListener('keyup', event => { 64 | const keyName = event.key; 65 | let state = store.getState(); 66 | if ( 67 | state.debugger.mode === editorModes.normal && 68 | (keyName === '1' || keyName === '2') 69 | ) { 70 | store.dispatch(showBoth()); 71 | } 72 | }); 73 | 74 | window.addEventListener('keydown', event => { 75 | let state = store.getState(); 76 | const keyName = event.key; 77 | if (keyName === 'Tab') { 78 | event.preventDefault(); 79 | if (!state.debugger.multipleExps) { 80 | store.dispatch(toggleMultipleExps()); 81 | track({ 82 | event: Event.balancedMode, 83 | task: state.debugger.currentTaskNum, 84 | mode: 'Basic Mode', 85 | source: Source.keyboard, 86 | }); 87 | } else if (state.debugger.multipleExps && !state.debugger.debuggingSteps) { 88 | track({ 89 | event: Event.advancedMode, 90 | task: state.debugger.currentTaskNum, 91 | mode: 'Balanced Mode', 92 | source: Source.keyboard, 93 | }); 94 | store.dispatch(toggleDebuggerStpes()); 95 | } else if (state.debugger.multipleExps && state.debugger.debuggingSteps) { 96 | track({ 97 | event: Event.basicMode, 98 | task: state.debugger.currentTaskNum, 99 | mode: 'Advanced Mode', 100 | source: Source.keyboard, 101 | }); 102 | store.dispatch(toggleMultipleExps()); 103 | store.dispatch(toggleDebuggerStpes()); 104 | } 105 | } 106 | 107 | if (state.debugger.mode === editorModes.edit && keyName === 'Escape') { 108 | track({ 109 | event: Event.typeCheck, 110 | task: state.debugger.currentTaskNum, 111 | mode: getMode(state.debugger.multipleExps, state.debugger.debuggingSteps), 112 | source: Source.keyboard, 113 | }); 114 | store.dispatch(toNormalMode()); 115 | store.dispatch(typeCheckThunk()); 116 | } 117 | if (state.debugger.mode === editorModes.normal) { 118 | if (keyName === '1') { 119 | track({ 120 | event: Event.narrowType1, 121 | task: state.debugger.currentTaskNum, 122 | mode: getMode( 123 | state.debugger.multipleExps, 124 | state.debugger.debuggingSteps, 125 | ), 126 | source: Source.keyboard, 127 | }); 128 | store.dispatch(showOnlyMark1()); 129 | } 130 | 131 | if (keyName === '2') { 132 | track({ 133 | event: Event.narrowType2, 134 | task: state.debugger.currentTaskNum, 135 | mode: getMode( 136 | state.debugger.multipleExps, 137 | state.debugger.debuggingSteps, 138 | ), 139 | source: Source.keyboard, 140 | }); 141 | store.dispatch(showOnlyMark2()); 142 | } 143 | 144 | if (state.debugger.debuggingSteps) { 145 | if ( 146 | keyName === 'ArrowDown' || 147 | keyName === 'ArrowRight' || 148 | keyName === 'j' || 149 | keyName === 'l' 150 | ) { 151 | track({ 152 | event: Event.next, 153 | task: state.debugger.currentTaskNum, 154 | mode: getMode( 155 | state.debugger.multipleExps, 156 | state.debugger.debuggingSteps, 157 | ), 158 | source: Source.keyboard, 159 | }); 160 | store.dispatch(prevStep()); 161 | } 162 | if ( 163 | keyName === 'ArrowUp' || 164 | keyName === 'ArrowLeft' || 165 | keyName === 'k' || 166 | keyName === 'h' 167 | ) { 168 | track({ 169 | event: Event.prev, 170 | task: state.debugger.currentTaskNum, 171 | mode: getMode( 172 | state.debugger.multipleExps, 173 | state.debugger.debuggingSteps, 174 | ), 175 | source: Source.keyboard, 176 | }); 177 | store.dispatch(nextStep()); 178 | } 179 | } 180 | } 181 | }); 182 | 183 | const App = () => { 184 | let wellTyped = useSelector(state => state.debugger.wellTyped); 185 | return ( 186 | <> 187 | 191 | 192 | {/* this is for study only */} 193 | 194 |
195 | 196 |
197 | 198 | 199 | 200 | 201 |
202 |
203 | 204 | ); 205 | }; 206 | 207 | const ModelContent = () => { 208 | let dispatch = useDispatch(); 209 | let currentTaskNum = useSelector(state => state.debugger.currentTaskNum); 210 | const multipleExps = useSelector(R.path(['debugger', 'multipleExps'])); 211 | const deductionSteps = useSelector(R.path(['debugger', 'debuggingSteps'])); 212 | const mode = getMode(multipleExps, deductionSteps); 213 | useEffect(() => { 214 | track({ 215 | event: Event.succeed, 216 | source: Source.remote, 217 | mode, 218 | task: currentTaskNum, 219 | }); 220 | localStorage.setItem('userProgress', currentTaskNum); 221 | }, []); 222 | return ( 223 |
224 |
225 |

226 | Congratulations. You fixed the type error! 227 |

228 | {currentTaskNum === 8 ? ( 229 |

Click next to leave us some feedback.

230 | ) : ( 231 |

232 | Click next to head over to the next challenge. 233 |

234 | )} 235 |
236 | 253 |
254 | ); 255 | }; 256 | 257 | ReactDOM.render( 258 | 259 | 260 | 261 | 262 | , 263 | document.getElementById('react-root'), 264 | ); 265 | -------------------------------------------------------------------------------- /static/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chameleon-designs", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "esbuild index.jsx --define:OUTPUT_TARGET=\\\"userstudy\\\" --define:process.env.NODE_ENV=\\\"production\\\" --bundle --minify --sourcemap --outfile=build/out.js", 8 | "dev": "esbuild index.jsx --define:OUTPUT_TARGET=\\\"userstudy\\\" --define:process.env.NODE_ENV=\\\"development\\\" --bundle --sourcemap --outfile=build/out.js --watch", 9 | "build:playground": "esbuild playground.jsx --define:OUTPUT_TARGET=\\\"playground\\\" --define:process.env.NODE_ENV=\\\"production\\\" --bundle --minify --sourcemap --bundle --outfile=build/playground.js", 10 | "dev:playground": "esbuild playground.jsx --define:OUTPUT_TARGET=\\\"playground\\\" --define:process.env.NODE_ENV=\\\"development\\\" --bundle --outfile=build/playground.js --watch" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@analytics/mixpanel": "^0.4.0", 17 | "@devbookhq/splitter": "^1.3.2", 18 | "@floating-ui/react-dom": "^0.7.1", 19 | "@floating-ui/react-dom-interactions": "^0.6.1", 20 | "@heroicons/react": "^1.0.6", 21 | "@openreplay/tracker": "3.4.7", 22 | "@reduxjs/toolkit": "^1.8.0", 23 | "analytics": "^0.8.1", 24 | "esbuild": "^0.14.29", 25 | "mixpanel-browser": "^2.45.0", 26 | "nanoid": "^3.3.4", 27 | "ramda": "^0.28.0", 28 | "react": "^17.0.2", 29 | "react-dom": "^17.0.2", 30 | "react-modal": "^3.15.1", 31 | "react-redux": "7.2.5" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /static/playground.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider, useSelector, useDispatch } from 'react-redux'; 4 | import store from './store'; 5 | import { 6 | editorModes, 7 | nextStep, 8 | prevStep, 9 | toggleMultipleExps, 10 | toggleDebuggerStpes, 11 | switchTaskThunk, 12 | showOnlyMark1, 13 | showOnlyMark2, 14 | showBoth, 15 | toNormalMode, 16 | typeCheckThunk, 17 | } from './debuggerSlice'; 18 | import Splitter from '@devbookhq/splitter'; 19 | import Editor from './Editor'; 20 | import Debugger from './Debugger'; 21 | import MenuBar from './MenuBar'; 22 | import { Event, Source, track } from './report'; 23 | import { getMode } from './util'; 24 | 25 | 26 | store.dispatch(switchTaskThunk(0)); 27 | 28 | window.addEventListener('keyup', event => { 29 | const keyName = event.key; 30 | let state = store.getState(); 31 | if ( 32 | state.debugger.mode === editorModes.normal && 33 | (keyName === '1' || keyName === '2') 34 | ) { 35 | store.dispatch(showBoth()); 36 | } 37 | }); 38 | 39 | window.addEventListener('keydown', event => { 40 | let state = store.getState(); 41 | const keyName = event.key; 42 | if (keyName === 'Tab') { 43 | event.preventDefault(); 44 | if (!state.debugger.multipleExps) { 45 | store.dispatch(toggleMultipleExps()); 46 | track({ 47 | event: Event.balancedMode, 48 | task: state.debugger.currentTaskNum, 49 | mode: 'Basic Mode', 50 | source: Source.keyboard, 51 | }); 52 | } else if (state.debugger.multipleExps && !state.debugger.debuggingSteps) { 53 | track({ 54 | event: Event.advancedMode, 55 | task: state.debugger.currentTaskNum, 56 | mode: 'Balanced Mode', 57 | source: Source.keyboard, 58 | }); 59 | store.dispatch(toggleDebuggerStpes()); 60 | } else if (state.debugger.multipleExps && state.debugger.debuggingSteps) { 61 | track({ 62 | event: Event.basicMode, 63 | task: state.debugger.currentTaskNum, 64 | mode: 'Advanced Mode', 65 | source: Source.keyboard, 66 | }); 67 | store.dispatch(toggleMultipleExps()); 68 | store.dispatch(toggleDebuggerStpes()); 69 | } 70 | } 71 | 72 | if (state.debugger.mode === editorModes.edit && keyName === 'Escape') { 73 | track({ 74 | event: Event.typeCheck, 75 | task: state.debugger.currentTaskNum, 76 | mode: getMode(state.debugger.multipleExps, state.debugger.debuggingSteps), 77 | source: Source.keyboard, 78 | }); 79 | store.dispatch(toNormalMode()); 80 | store.dispatch(typeCheckThunk()); 81 | } 82 | if (state.debugger.mode === editorModes.normal) { 83 | if (keyName === '1') { 84 | track({ 85 | event: Event.narrowType1, 86 | task: state.debugger.currentTaskNum, 87 | mode: getMode( 88 | state.debugger.multipleExps, 89 | state.debugger.debuggingSteps, 90 | ), 91 | source: Source.keyboard, 92 | }); 93 | store.dispatch(showOnlyMark1()); 94 | } 95 | 96 | if (keyName === '2') { 97 | track({ 98 | event: Event.narrowType2, 99 | task: state.debugger.currentTaskNum, 100 | mode: getMode( 101 | state.debugger.multipleExps, 102 | state.debugger.debuggingSteps, 103 | ), 104 | source: Source.keyboard, 105 | }); 106 | store.dispatch(showOnlyMark2()); 107 | } 108 | 109 | if (state.debugger.debuggingSteps) { 110 | if ( 111 | keyName === 'ArrowDown' || 112 | keyName === 'ArrowRight' || 113 | keyName === 'j' || 114 | keyName === 'l' 115 | ) { 116 | track({ 117 | event: Event.next, 118 | task: state.debugger.currentTaskNum, 119 | mode: getMode( 120 | state.debugger.multipleExps, 121 | state.debugger.debuggingSteps, 122 | ), 123 | source: Source.keyboard, 124 | }); 125 | store.dispatch(prevStep()); 126 | } 127 | if ( 128 | keyName === 'ArrowUp' || 129 | keyName === 'ArrowLeft' || 130 | keyName === 'k' || 131 | keyName === 'h' 132 | ) { 133 | track({ 134 | event: Event.prev, 135 | task: state.debugger.currentTaskNum, 136 | mode: getMode( 137 | state.debugger.multipleExps, 138 | state.debugger.debuggingSteps, 139 | ), 140 | source: Source.keyboard, 141 | }); 142 | store.dispatch(nextStep()); 143 | } 144 | } 145 | } 146 | }); 147 | 148 | const App = () => { 149 | return ( 150 | <> 151 |
152 | 153 |
154 | 155 | 156 | 157 | 158 |
159 |
160 | 161 | ); 162 | }; 163 | 164 | ReactDOM.render( 165 | 166 | 167 | 168 | 169 | , 170 | document.getElementById('react-root'), 171 | ); 172 | -------------------------------------------------------------------------------- /static/report.js: -------------------------------------------------------------------------------- 1 | import mixpanel from 'mixpanel-browser'; 2 | 3 | export let Source = { 4 | mouse: 'Mouse', 5 | keyboard: 'Keyboard', 6 | remote: 'Remote', 7 | }; 8 | 9 | export let Event = { 10 | basicMode: 'Set Basic Mode', 11 | balancedMode: 'Set Balanced Mode', 12 | advancedMode: 'Set Advanced Mode', 13 | next: 'Next Step', 14 | prev: 'Previous Step', 15 | typeCheck: 'Type Check', 16 | succeed: 'Succeed', 17 | loaderr: 'Load Error', 18 | syntaxerr: 'Syntax Error', 19 | typeerr: 'Type Error', 20 | peekStep: 'Peek Step', 21 | peekExp: 'Peek Expression', 22 | peekDef: 'Peek Definition', 23 | gotoStep: 'Go To Step', 24 | gotoExp: 'Go To Expression', 25 | narrowType1: 'Narrow To Possible Type 1', 26 | narrowType2: 'Narrow To Possible Type 2', 27 | narrowRelevent: 'Narrow Relevant Type', 28 | inspect: 'Inspect Relevant Type', 29 | reset: 'Reset Task', 30 | abandon: 'Abandon Task', 31 | }; 32 | 33 | export let track; 34 | if (OUTPUT_TARGET === 'playground') { 35 | track = () => {} 36 | } else { 37 | if (process.env.NODE_ENV === 'production') { 38 | track = ({ event, task, mode, source }) => { 39 | mixpanel.track(event, { 40 | Stage: 'live', 41 | Task: task, 42 | Mode: mode, 43 | 'Input Source': source, 44 | }); 45 | }; 46 | } else { 47 | track = ({ event, task, mode, source }) => { 48 | console.log(`[Task ${task} - ${mode}] ${event} (From ${source})`); 49 | }; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /static/store.js: -------------------------------------------------------------------------------- 1 | import { 2 | configureStore, 3 | } from '@reduxjs/toolkit'; 4 | import debuggerReducer from "./debuggerSlice.js" 5 | // import editorReducer from "./editorSlice" 6 | 7 | 8 | const store = configureStore({ 9 | reducer: { 10 | 'debugger': debuggerReducer, 11 | // 'editor': editorReducer 12 | }, 13 | // devTools: true, 14 | }); 15 | 16 | export default store; 17 | -------------------------------------------------------------------------------- /static/util.js: -------------------------------------------------------------------------------- 1 | export const unAlias = str => { 2 | return str.replaceAll('[Char]', 'String'); 3 | }; 4 | 5 | export const within = (point, { from, to }) => 6 | pointAfterInclusive(point, from) && pointBeforeExclusive(point, to); 7 | 8 | export function convertLocation({ 9 | srcSpanEndLine, 10 | srcSpanEndColumn, 11 | srcSpanStartColumn, 12 | srcSpanStartLine, 13 | }) { 14 | return { 15 | from: { line: srcSpanStartLine - 1, ch: srcSpanStartColumn - 1 }, 16 | to: { line: srcSpanEndLine - 1, ch: srcSpanEndColumn - 1 }, 17 | }; 18 | } 19 | 20 | export const pointBeforeInclusive = (point1, point2) => { 21 | if (point1.line < point2.line) { 22 | return true; 23 | } else if (point1.line === point2.line) { 24 | return point1.ch <= point2.ch; 25 | } else { 26 | return false; 27 | } 28 | }; 29 | 30 | export const pointBeforeExclusive = (point1, point2) => { 31 | if (point1.line < point2.line) { 32 | return true; 33 | } else if (point1.line === point2.line) { 34 | return point1.ch < point2.ch; 35 | } else { 36 | return false; 37 | } 38 | }; 39 | 40 | export const pointAfterInclusive = (point1, point2) => { 41 | if (point1.line > point2.line) { 42 | return true; 43 | } else if (point1.line === point2.line) { 44 | return point1.ch >= point2.ch; 45 | } else { 46 | return false; 47 | } 48 | }; 49 | 50 | export const pointAfterExclusive = (point1, point2) => { 51 | if (point1.line > point2.line) { 52 | return true; 53 | } else if (point1.line === point2.line) { 54 | return point1.ch > point2.ch; 55 | } else { 56 | return false; 57 | } 58 | }; 59 | 60 | export const doesRangeSurround = (rangeA, rangeB) => { 61 | return ( 62 | pointBeforeInclusive(rangeA.from, rangeB.from) && 63 | pointAfterInclusive(rangeA.to, rangeB.to) 64 | ); 65 | }; 66 | 67 | export const doesRangeIntersect = (rangeA, rangeB) => { 68 | return ( 69 | (pointBeforeInclusive(rangeA.from, rangB.from) && 70 | pointBeforeInclusive(rangeB.from, rangeA.to) && 71 | pointBeforeInclusive(rangeA.to, rangeB.to) && 72 | pointBeforeInclusive(rangeB.from, rangeA.to)) || 73 | (pointBeforeInclusive(rangeB.from, rangeA.from) && 74 | pointBeforeInclusive(rangeA.from, rangeB.to) && 75 | pointBeforeInclusive(rangeB.to, rangeA.to) && 76 | pointBeforeInclusive(rangeA.from, rangeB.to)) 77 | ); 78 | }; 79 | 80 | export function drawAnnotations(rangeA, rangeB, reason, step, direction, offset) { 81 | let color = false; 82 | if (rangeB.from.line < rangeA.from.line) { 83 | // B 84 | // A 85 | const [topBox, bottomBox, inbetweenBox, annotationBox] = boxStyles( 86 | { 87 | left: rangeB.from.ch, 88 | top: rangeB.from.line, 89 | width: rangeB.to.ch - rangeB.from.ch, 90 | height: 1, 91 | }, 92 | { 93 | left: rangeA.from.ch, 94 | top: rangeA.from.line, 95 | width: rangeA.to.ch - rangeA.from.ch, 96 | height: 1, 97 | }, 98 | color, 99 | offset, 100 | ); 101 | return [ 102 | { 103 | relativeTo: rangeB.from, 104 | key: 'top-line', 105 | styles: topBox, 106 | classes: ['absolute', 'z-40', 'rounded-t-sm', 'border-l', 'border-t', 'border-r'], 107 | content: { type: 'empty' }, 108 | }, 109 | { 110 | relativeTo: rangeB.from, 111 | key: 'inbetween-line', 112 | styles: inbetweenBox, 113 | classes: ['absolute', 'z-20', 'border-r'], 114 | content: { type: 'empty' }, 115 | }, 116 | { 117 | relativeTo: rangeA.from, 118 | key: 'bottom-line', 119 | styles: bottomBox, 120 | classes: ['absolute', 'z-20', 'rounded-b-sm', 'border-l', 'border-b', 'border-r'], 121 | content: { type: 'empty' }, 122 | }, 123 | { 124 | relativeTo: rangeA.from, 125 | key: 'annotation-box', 126 | styles: annotationBox, 127 | classes: ['absolute', 'text-center', 'text-sm', 'z-20'], 128 | content: { type: 'annotation', direction, reason, step }, 129 | }, 130 | ]; 131 | } else { 132 | // A 133 | // B 134 | const [topBox, bottomBox, inbetweenBox, annotationBox] = boxStyles( 135 | { 136 | left: rangeA.from.ch, 137 | top: rangeA.from.line, 138 | width: rangeA.to.ch - rangeA.from.ch, 139 | height: 1, 140 | }, 141 | { 142 | left: rangeB.from.ch, 143 | top: rangeB.from.line, 144 | width: rangeB.to.ch - rangeB.from.ch, 145 | height: 1, 146 | }, 147 | color, 148 | offset 149 | ); 150 | return [ 151 | { 152 | relativeTo: rangeA.from, 153 | key: 'top-line', 154 | styles: topBox, 155 | classes: ['absolute', 'z-40', 'rounded-t-sm', 'border-l', 'border-t', 'border-r'], 156 | content: { type: 'empty' }, 157 | }, 158 | { 159 | relativeTo: rangeA.from, 160 | key: 'inbetween-line', 161 | styles: inbetweenBox, 162 | classes: ['absolute', 'z-20', 'border-r'], 163 | content: { type: 'empty' }, 164 | }, 165 | { 166 | relativeTo: rangeB.from, 167 | key: 'bottom-line', 168 | styles: bottomBox, 169 | classes: ['absolute', 'z-20', 'rounded-b-sm', 'border-l', 'border-b', 'border-r'], 170 | content: { type: 'empty' }, 171 | }, 172 | { 173 | relativeTo: rangeB.from, 174 | key: 'annotation-box', 175 | styles: annotationBox, 176 | classes: ['absolute', 'text-center', 'text-sm', 'z-20'], 177 | content: { type: 'annotation', direction, reason, step }, 178 | }, 179 | ]; 180 | } 181 | } 182 | 183 | function boxStyles(topElem, bottomElem, color, offset) { 184 | const downwardBarHeight = 0.28; 185 | const annotationWidth = 18; 186 | const annotationHeight = 1.25; 187 | const chWidth = 0.625; 188 | const chHeight = 1.5; 189 | const lineColor = '#666666'; 190 | const stepAsideDistance = offset * chWidth + 10 191 | const styleTop = { 192 | background: color ? 'var(--color-azure-3)' : 'transparent', 193 | opacity: color ? 0.5 : 1, 194 | height: `${downwardBarHeight}rem`, 195 | left: `${(topElem.width * chWidth) / 2}rem`, 196 | top: `${-downwardBarHeight}rem`, 197 | width: 198 | `${stepAsideDistance - 199 | topElem.left * chWidth - 200 | (topElem.width * chWidth) / 2}rem`, 201 | borderColor: lineColor 202 | }; 203 | 204 | const styleBottom = { 205 | background: color ? 'var(--color-sky-3)' : 'transparent', 206 | opacity: color ? 0.5 : 1, 207 | height: `${downwardBarHeight}rem`, 208 | left: `${(bottomElem.width * chWidth) / 2}rem`, 209 | top: `${bottomElem.height * chHeight}rem`, 210 | width: 211 | `${stepAsideDistance - 212 | bottomElem.left * chWidth - 213 | (bottomElem.width * chWidth) / 2}rem`, 214 | borderColor: lineColor 215 | }; 216 | 217 | const styleInbetween = { 218 | background: color ? 'var(--color-violet-3)' : 'transparent', 219 | opacity: color ? 0.5 : 1, 220 | width: 221 | `${stepAsideDistance - 222 | topElem.left * chWidth - 223 | (topElem.width * chWidth) / 2}rem`, 224 | height: `${(bottomElem.top - topElem.top + 1) * chHeight - annotationHeight}rem`, 225 | top: `${0}rem`, 226 | left: `${(topElem.width * chWidth) / 2}rem`, 227 | borderColor: lineColor 228 | }; 229 | 230 | const styleAnnotation = { 231 | background: color ? 'var(--color-orange-3)' : 'transparent', 232 | opacity: color ? 0.5 : 1, 233 | width: `${annotationWidth}rem`, 234 | height: `${annotationHeight}rem`, 235 | top: `${bottomElem.height * chHeight - annotationHeight}rem`, 236 | left: 237 | `${stepAsideDistance - bottomElem.left * chWidth - annotationWidth / 2}rem`, 238 | }; 239 | return [styleTop, styleBottom, styleInbetween, styleAnnotation]; 240 | } 241 | 242 | export const partitionBy = (array, fun) => { 243 | let res = {} 244 | for (let elem of array) { 245 | let key = fun(elem) 246 | if (res.hasOwnProperty(key)) { 247 | res[key] = [...res[key], elem] 248 | } else { 249 | res[key] = [elem] 250 | } 251 | } 252 | return Object.values(res) 253 | } 254 | 255 | // z-index order: text > bottom bar >= BorderBoxChild > BorderBoxParent > topBar > normalBoxParent > normalBoxChild 256 | // 50 40 40 30 20 10 0 257 | export const makeParentHighlightB = (range, m) => { 258 | return { 259 | ...range, 260 | marker: { 261 | shared: [ 262 | m, 263 | '-inset-y-0.5', 264 | 'inset-x-0', 265 | 'border-t', 266 | 'border-b', 267 | 'border-black', 268 | 'z-30' 269 | ], 270 | start: ['-left-0.5', 'border-l', 'rounded-l-sm'], 271 | end: ['-right-0.5', 'border-r', 'rounded-r-sm'], 272 | }, 273 | }; 274 | }; 275 | 276 | export const makeParentHighlight = (range, m) => { 277 | return { 278 | ...range, 279 | marker: { 280 | shared: [m, '-inset-y-0.5', 'inset-x-0', 'z-10'], 281 | start: ['-left-0.5', 'rounded-l-sm'], 282 | end: ['-right-0.5', 'rounded-r-sm'], 283 | }, 284 | }; 285 | }; 286 | 287 | export const makeHighlightB = (range, marker) => { 288 | return { 289 | ...range, 290 | marker: { 291 | shared: [marker, 'inset-0', 'border-t', 'border-b', 'border-black', 'z-40'], 292 | start: ['border-l', 'rounded-l-sm'], 293 | end: ['border-r', 'rounded-r-sm'], 294 | }, 295 | }; 296 | }; 297 | 298 | export const makeHighlight = (range, marker) => { 299 | return { 300 | ...range, 301 | marker: { 302 | shared: [marker, 'inset-0', 'z-0'], 303 | start: ['rounded-l-sm'], 304 | end: ['rounded-r-sm'], 305 | }, 306 | }; 307 | }; 308 | 309 | export function getRandomInt(max) { 310 | return Math.floor(Math.random() * max); 311 | } 312 | 313 | export function getMode (mulExp, deduction) { 314 | return !mulExp ? 'Basic Mode': deduction ? 'Advanced Mode' : 'Balanced Mode' 315 | 316 | } -------------------------------------------------------------------------------- /test/Intro.hs: -------------------------------------------------------------------------------- 1 | module ExampleBasicMode where 2 | 3 | ident a = (a, a) 4 | 5 | fun a c = ident c 6 | 7 | fun a True = ('C', 'D') 8 | 9 | -------------------------------------------------------------------------------- /test/NewTest1.hs: -------------------------------------------------------------------------------- 1 | module Example1 where 2 | 3 | nqueens size = 4 | filter evaluateBoard (board_permutations size) 5 | 6 | --N sized Chess boards are represented as a one-dimension array. 7 | board_permutations size = permutations [0..size - 1] 8 | 9 | --Count the number of valid boards for a specified Chess board size. 10 | count_boards size = length . nqueens 11 | 12 | --Recursively check that prior rows are valid. 13 | evaluateBoard [] = True 14 | evaluateBoard rows = 15 | evaluateBoard (init rows) && 16 | validate (init rows) (last (rows - 1)) (last rows + 1) (init rows) (last rows) 17 | 18 | 19 | --Validate that a Queen on a row doesn't have conflicts with earlier rows. 20 | validate [] _ _ _ = True 21 | validate rows left right position = 22 | if last rows == left || last rows == right || last rows == position 23 | then False 24 | else validate (init rows) (left - 1) (right + 1) position 25 | -------------------------------------------------------------------------------- /test/NewTest2.hs: -------------------------------------------------------------------------------- 1 | data Hand = Rock | Paper | Scissors 2 | type Score = (Int, Int) 3 | 4 | winsOver :: Hand -> Hand -> Bool 5 | Rock `winsOver` Scissors = True 6 | Paper `winsOver` Rock = True 7 | Scissors `winsOver` Paper = True 8 | _ `winsOver` _ = False 9 | 10 | computeScore :: Hand -> Hand -> Score 11 | computeScore h1 h2 12 | | h1 `winsOver` h2 = (1, 0) 13 | | h2 `winsOver` h1 = (0, 1) 14 | | otherwise = (0, 0) 15 | 16 | combine a b = (fst a + fst b, snd a + snd b) 17 | 18 | zip' (a:as) (b:bs) = (a,b) : zip' as bs 19 | 20 | 21 | ;; pairScore :: (Hand, Hand) -> Score 22 | pairScore (h1, h2) = computeScore h1 h2 23 | 24 | score :: [Hand] -> [Hand] -> Score 25 | score h1 h2 = 26 | foldl combine (0, 0) (pairScore (zip' h1 h2)) 27 | -------------------------------------------------------------------------------- /test/NewTest3.hs: -------------------------------------------------------------------------------- 1 | module Task where 2 | 3 | standardTrans z = 4 | case z of 5 | "shorttitle" -> [] 6 | "sorttitle" -> [] 7 | "indextitle" -> [] 8 | "indexsorttitle" -> [] 9 | _ -> z 10 | 11 | 12 | -- bookTrans :: String -> [String] 13 | bookTrans z = 14 | case z of 15 | "title" -> ["booktitle"] 16 | "subtitle" -> ["booksubtitle"] 17 | "titleaddon" -> ["booktitleaddon"] 18 | "shorttitle" -> [] 19 | "sorttitle" -> [] 20 | "indextitle" -> [] 21 | "indexsorttitle" -> [] 22 | _ -> [z] 23 | 24 | -- transformKey source target key 25 | -- derived from Appendix C of bibtex manual 26 | -- transformKey :: String -> String -> String -> [String] 27 | transformKey _ _ "ids" = [] 28 | transformKey _ _ "crossref" = [] 29 | transformKey _ _ "xref" = [] 30 | transformKey _ _ "entryset" = [] 31 | transformKey _ _ "entrysubtype" = [] 32 | transformKey _ _ "execute" = [] 33 | transformKey _ _ "label" = [] 34 | transformKey _ _ "options" = [] 35 | transformKey _ _ "presort" = [] 36 | transformKey _ _ "related" = [] 37 | transformKey _ _ "relatedoptions" = [] 38 | transformKey _ _ "relatedstring" = [] 39 | transformKey _ _ "relatedtype" = [] 40 | transformKey _ _ "shorthand" = [] 41 | transformKey _ _ "shorthandintro" = [] 42 | transformKey _ _ "sortkey" = [] 43 | transformKey x y "author" 44 | | x `elem` ["mvbook", "book"] = 45 | ["bookauthor", "author"] 46 | -- note: this next clause is not in the biblatex manual, but it makes 47 | -- sense in the context of CSL conversion: 48 | transformKey x y "author" 49 | | x == "mvbook" = ["bookauthor", "author"] 50 | transformKey "mvbook" y z 51 | | y `elem` ["book", "inbook", "bookinbook", "suppbook"] = standardTrans z 52 | transformKey x y z 53 | | x `elem` ["mvcollection", "mvreference"] = 54 | bookTrans z 55 | transformKey "mvproceedings" y z 56 | | y `elem` ["proceedings", "inproceedings"] = standardTrans z 57 | transformKey "book" y z 58 | | y `elem` ["inbook", "bookinbook", "suppbook"] = bookTrans z 59 | transformKey x y z 60 | | x `elem` ["collection", "reference"] = bookTrans z 61 | transformKey "proceedings" "inproceedings" z = bookTrans z 62 | transformKey "periodical" y z 63 | | y `elem` ["article", "suppperiodical"] = 64 | case z of 65 | "title" -> ["journaltitle"] 66 | "subtitle" -> ["journalsubtitle"] 67 | "shorttitle" -> [] 68 | "sorttitle" -> [] 69 | "indextitle" -> [] 70 | "indexsorttitle" -> [] 71 | _ -> [z] 72 | transformKey _ _ x = [x] -------------------------------------------------------------------------------- /test/Test2.hs: -------------------------------------------------------------------------------- 1 | module Task where 2 | type Dimension = Integer 3 | 4 | type D = (Dimension, Dimension) 5 | 6 | type SizeHints = ((Maybe D), (Maybe D), (Maybe (D, D))) 7 | 8 | sh_max_size :: SizeHints -> Maybe D 9 | sh_max_size (a, b, c) = a 10 | 11 | sh_resize_inc :: SizeHints -> Maybe D 12 | sh_resize_inc (a, b, c) = b 13 | 14 | sh_aspect :: SizeHints -> Maybe (D, D) 15 | sh_aspect (a, b, c) = c 16 | 17 | applyAspectHint ((minx, miny), (maxx, maxy)) (w, h) 18 | | or [minx < 1, miny < 1, maxx < 1, maxy < 1] = (w, h) 19 | | w * maxy > h * maxx = (h * maxx `div` maxy, h) 20 | | w * miny < h * minx = (w, w * miny `div` minx) 21 | | otherwise = (w, h) 22 | 23 | applyResizeIncHint (iw, ih) (w, h) = 24 | if iw > 0 && ih > 0 then (w - w `mod` iw, h - h `mod` ih) else (w, h) 25 | 26 | -- | Reduce the dimensions if they exceed the given maximum dimensions. 27 | -- applyMaxSizeHint :: D -> D -> D 28 | applyMaxSizeHint (mw, mh) (w, h) = 29 | if mw > 0 && mh > 0 then (min w mw, min h mh) else (w, h) 30 | 31 | applySizeHints' sh d = 32 | case sh_aspect sh of 33 | Nothing -> d 34 | Just asp -> applyAspectHint asp d -------------------------------------------------------------------------------- /test/Test3.hs: -------------------------------------------------------------------------------- 1 | module Task where 2 | 3 | data Expr = C Int | 4 | Comb Expr Expr| 5 | V [Char] | 6 | Let [Char] Expr Expr 7 | 8 | data Env = Env [([Char], Int)] 9 | 10 | eval :: Expr -> Env -> (Env, Int) 11 | eval (Let v e1 e2) env = let (env1, v1) = eval e1 env 12 | env2 = extend v v1 13 | ans = eval e2 env2 14 | in ans 15 | 16 | extend :: [Char] -> Int -> Env -> Env 17 | extend v e (Env env) = Env ([(v,e)] ++ env) -------------------------------------------------------------------------------- /test/Test4.hs: -------------------------------------------------------------------------------- 1 | last' :: [a] -> a 2 | last' [x] = x 3 | last' (x:xs) = xs 4 | -------------------------------------------------------------------------------- /test/Test5.hs: -------------------------------------------------------------------------------- 1 | module Task where 2 | data List a = Nil | Cons a (List a) 3 | 4 | instance Semigroup (List a) where 5 | Nil <> l = l 6 | l <> Nil = l 7 | (Cons x xs) <> y = Cons x (xs <> y) 8 | 9 | instance Monoid (List a) where 10 | mempty = Nil 11 | mappend = (<>) 12 | 13 | 14 | instance (Show a) => Show (List a) where 15 | show Nil = "" 16 | show (Cons x xs) = show x ++ ", " ++ show xs 17 | 18 | instance Functor List where 19 | fmap f Nil = Nil 20 | fmap f (Cons x xs) = Cons (f x) (fmap f xs) 21 | 22 | instance Applicative List where 23 | pure = return 24 | Nil <*> m = Nil 25 | (Cons f fs) <*> m = Cons (fmap f m) (fs <*> m) 26 | 27 | instance Monad List where 28 | return x = Cons x Nil 29 | xs >>= k = join $ fmap k xs 30 | 31 | join :: List (List a) -> List a 32 | join Nil = Nil 33 | join (Cons xs xss) = cat xs (join xss) 34 | 35 | cat :: List a -> List a -> List a 36 | cat Nil ys = ys 37 | cat (Cons x xs) ys = Cons x (cat xs ys) 38 | 39 | neighbors :: (Num a) => a -> a -> List a 40 | neighbors x dx = Cons (x - dx) (Cons x (Cons (x + dx) Nil)) 41 | -------------------------------------------------------------------------------- /test/Test6.hs: -------------------------------------------------------------------------------- 1 | take' :: Int -> [Int] -> [Int] 2 | take' n [] = [] 3 | take' n (x:xs) = x ++ (take' (n - 1) xs) -------------------------------------------------------------------------------- /test/Test7.hs: -------------------------------------------------------------------------------- 1 | module Task where 2 | 3 | data Pet = Cat String | Dog String | Snake 4 | 5 | petIsDog :: Pet -> Bool 6 | petIsDog Dog = True 7 | petIsDog _ = False -------------------------------------------------------------------------------- /test/Test8.hs: -------------------------------------------------------------------------------- 1 | module Task where 2 | 3 | data Expr = C Int | 4 | Comb Expr Expr| 5 | V [Char] | 6 | Let [Char] Expr Expr 7 | 8 | 9 | eval :: Expr -> [([Char], Int)] -> ([([Char], Int)], Int) 10 | eval (Let v e1 e2) env = let (env1, v1) = eval e1 env 11 | env2 = extend v v1 12 | ans = eval e2 env2 13 | in ans 14 | 15 | extend :: [Char] -> Int -> [([Char], Int)] -> [([Char], Int)] 16 | extend v e env = [(v,e)] ++ env 17 | 18 | answer :: Expr -> ([([Char], Int)], Int) 19 | answer e = eval e [] 20 | -------------------------------------------------------------------------------- /test/TypeClassBasic.hs: -------------------------------------------------------------------------------- 1 | class X a where 2 | funX :: a -> b -> cz -- funX is global -- b is local to the line -- a is local to the block 3 | funY :: d -> a -> Int 4 | --------------------------------------------------------------------------------