TT types and values, some text manipulation, basics of Core
Unified · Split
| @@ -0,0 +1,26 @@ | |||
| 1 | ||| Utilities functions for contitionally delaying values. | ||
| 2 | module Control.Delayed | ||
| 3 | |||
| 4 | %default total | ||
| 5 | |||
| 6 | ||| Type-level function for a conditionally delayed type (`Lazy` or `Inf`). | ||
| 7 | public export | ||
| 8 | delayed : DelayReason -> Bool -> Type -> Type | ||
| 9 | delayed r False a = a | ||
| 10 | delayed r True a = Delayed r a | ||
| 11 | |||
| 12 | ||| Type-level function for a conditionally infinite type. | ||
| 13 | public export | ||
| 14 | inf : Bool -> Type -> Type | ||
| 15 | inf = delayed Infinite | ||
| 16 | |||
| 17 | ||| Type-level function for a conditionally lazy type. | ||
| 18 | public export | ||
| 19 | lazy : Bool -> Type -> Type | ||
| 20 | lazy = delayed LazyValue | ||
| 21 | |||
| 22 | ||| Conditionally delay a value. | ||
| 23 | export | ||
| 24 | delay : (d : Bool) -> a -> delayed r d a | ||
| 25 | delay False x = x | ||
| 26 | delay True x = Delay x | ||
| @@ -0,0 +1,433 @@ | |||
| 1 | module Core.Core | ||
| 2 | |||
| 3 | import Core.Env | ||
| 4 | import Core.TT | ||
| 5 | import Parser.Support | ||
| 6 | |||
| 7 | import public Control.Catchable | ||
| 8 | import public Data.IORef | ||
| 9 | |||
| 10 | %default covering | ||
| 11 | |||
| 12 | public export | ||
| 13 | data TTCErrorMsg | ||
| 14 | = FormatOlder | ||
| 15 | | FormatNewer | ||
| 16 | | EndOfBuffer String | ||
| 17 | | Corrupt String | ||
| 18 | |||
| 19 | public export | ||
| 20 | data CaseError = DifferingArgNumbers | ||
| 21 | | DifferingTypes | ||
| 22 | | MatchErased (vars ** (Env Term vars, Term vars)) | ||
| 23 | | UnknownType | ||
| 24 | |||
| 25 | -- All possible errors, carrying a location | ||
| 26 | public export | ||
| 27 | data Error | ||
| 28 | = Fatal Error -- flag as unrecoverable (so don't postpone awaiting further info) | ||
| 29 | | CantConvert FC (Env Term vars) (Term vars) (Term vars) | ||
| 30 | | CantSolveEq FC (Env Term vars) (Term vars) (Term vars) | ||
| 31 | | Cycle FC (Env Term vars) (Term vars) (Term vars) | ||
| 32 | | WhenUnifying FC (Env Term vars) (Term vars) (Term vars) Error | ||
| 33 | | ValidCase FC (Env Term vars) (Either (Term vars) Error) | ||
| 34 | | UndefinedName FC Name | ||
| 35 | | InvisibleName FC Name | ||
| 36 | | BadTypeConType FC Name | ||
| 37 | | BadDataConType FC Name Name | ||
| 38 | -- | NotCovering FC Name Covering | ||
| 39 | -- | NotTotal FC Name PartialReason | ||
| 40 | | LinearUsed FC Nat Name | ||
| 41 | | LinearMisuse FC Name RigCount RigCount | ||
| 42 | | BorrowPartial FC (Env Term vars) (Term vars) (Term vars) | ||
| 43 | | BorrowPartialType FC (Env Term vars) (Term vars) | ||
| 44 | | AmbiguousName FC (List Name) | ||
| 45 | | AmbiguousElab FC (Env Term vars) (List (Term vars)) | ||
| 46 | | AmbiguousSearch FC (Env Term vars) (List (Term vars)) | ||
| 47 | | AllFailed (List (Maybe Name, Error)) | ||
| 48 | | RecordTypeNeeded FC (Env Term vars) | ||
| 49 | | NotRecordField FC String (Maybe Name) | ||
| 50 | | NotRecordType FC Name | ||
| 51 | | IncompatibleFieldUpdate FC (List String) | ||
| 52 | | InvalidImplicit FC (Env Term vars) Name (Term vars) | ||
| 53 | | TryWithImplicits FC (Env Term vars) (List (Name, Term vars)) | ||
| 54 | | CantSolveGoal FC (Term []) | ||
| 55 | | DeterminingArg FC Name (Env Term vars) (Term vars) | ||
| 56 | | UnsolvedHoles (List (FC, Name)) | ||
| 57 | | CantInferArgType FC (Env Term vars) Name Name (Term vars) | ||
| 58 | | SolvedNamedHole FC (Env Term vars) Name (Term vars) | ||
| 59 | | VisibilityError FC Visibility Name Visibility Name | ||
| 60 | | NonLinearPattern FC Name | ||
| 61 | | BadPattern FC Name | ||
| 62 | | NoDeclaration FC Name | ||
| 63 | | AlreadyDefined FC Name | ||
| 64 | | NotFunctionType FC (Env Term vars) (Term vars) | ||
| 65 | | RewriteNoChange FC (Env Term vars) (Term vars) (Term vars) | ||
| 66 | | NotRewriteRule FC (Env Term vars) (Term vars) | ||
| 67 | | CaseCompile FC Name CaseError | ||
| 68 | | BadDotPattern FC (Env Term vars) String (Term vars) (Term vars) | ||
| 69 | | BadImplicit FC String | ||
| 70 | | BadRunElab FC (Env Term vars) (Term vars) | ||
| 71 | | GenericMsg FC String | ||
| 72 | | TTCError TTCErrorMsg | ||
| 73 | | FileErr String FileError | ||
| 74 | | ParseFail FC ParseError | ||
| 75 | | ModuleNotFound FC (List String) | ||
| 76 | | CyclicImports (List (List String)) | ||
| 77 | | ForceNeeded | ||
| 78 | | InternalError String | ||
| 79 | |||
| 80 | | InType FC Name Error | ||
| 81 | | InCon FC Name Error | ||
| 82 | | InLHS FC Name Error | ||
| 83 | | InRHS FC Name Error | ||
| 84 | |||
| 85 | export | ||
| 86 | Show TTCErrorMsg where | ||
| 87 | show FormatOlder = "TTC data is in an older format" | ||
| 88 | show FormatNewer = "TTC data is in a newer format" | ||
| 89 | show (EndOfBuffer when) = "End of buffer when reading " ++ when | ||
| 90 | show (Corrupt ty) = "Corrupt TTC data for " ++ ty | ||
| 91 | |||
| 92 | -- Simplest possible display - higher level languages should unelaborate names | ||
| 93 | -- and display annotations appropriately | ||
| 94 | export | ||
| 95 | Show Error where | ||
| 96 | show (Fatal err) = show err | ||
| 97 | show (CantConvert fc env x y) | ||
| 98 | = show fc ++ ":Type mismatch: " ++ show x ++ " and " ++ show y | ||
| 99 | show (CantSolveEq fc env x y) | ||
| 100 | = show fc ++ ":" ++ show x ++ " and " ++ show y ++ " are not equal" | ||
| 101 | show (Cycle fc env x y) | ||
| 102 | = show fc ++ ":Occurs check failed: " ++ show x ++ " and " ++ show y | ||
| 103 | show (WhenUnifying fc _ x y err) | ||
| 104 | = show fc ++ ":When unifying: " ++ show x ++ " and " ++ show y ++ "\n\t" ++ show err | ||
| 105 | show (ValidCase fc _ prob) | ||
| 106 | = show fc ++ ":" ++ | ||
| 107 | case prob of | ||
| 108 | Left tm => assert_total (show tm) ++ " is not a valid impossible pattern because it typechecks" | ||
| 109 | Right err => "Not a valid impossible pattern:\n\t" ++ assert_total (show err) | ||
| 110 | show (UndefinedName fc x) = show fc ++ ":Undefined name " ++ show x | ||
| 111 | show (InvisibleName fc (NS ns x)) | ||
| 112 | = show fc ++ ":Name " ++ show x ++ " is inaccessible since " ++ | ||
| 113 | showSep "." (reverse ns) ++ " is not explicitly imported" | ||
| 114 | show (InvisibleName fc x) = show fc ++ ":Name " ++ show x ++ " is inaccessible" | ||
| 115 | show (BadTypeConType fc n) | ||
| 116 | = show fc ++ ":Return type of " ++ show n ++ " must be Type" | ||
| 117 | show (BadDataConType fc n fam) | ||
| 118 | = show fc ++ ":Return type of " ++ show n ++ " must be in " ++ show fam | ||
| 119 | -- show (NotCovering fc n cov) | ||
| 120 | -- = show fc ++ ":" ++ show n ++ " is not covering:\n\t" ++ | ||
| 121 | -- case cov of | ||
| 122 | -- IsCovering => "Oh yes it is (Internal error!)" | ||
| 123 | -- MissingCases cs => "Missing cases:\n\t" ++ | ||
| 124 | -- showSep "\n\t" (map show cs) | ||
| 125 | -- NonCoveringCall ns => "Calls non covering function" | ||
| 126 | -- ++ (case ns of | ||
| 127 | -- [fn] => " " ++ show fn | ||
| 128 | -- _ => "s: " ++ showSep ", " (map show ns)) | ||
| 129 | -- | ||
| 130 | -- show (NotTotal fc n r) | ||
| 131 | -- = show fc ++ ":" ++ show n ++ " is not total" | ||
| 132 | show (LinearUsed fc count n) | ||
| 133 | = show fc ++ ":There are " ++ show count ++ " uses of linear name " ++ show n | ||
| 134 | show (LinearMisuse fc n exp ctx) | ||
| 135 | = show fc ++ ":Trying to use " ++ showRig exp ++ " name " ++ show n ++ | ||
| 136 | " in " ++ showRel ctx ++ " context" | ||
| 137 | where | ||
| 138 | showRig : RigCount -> String | ||
| 139 | showRig Rig0 = "irrelevant" | ||
| 140 | showRig Rig1 = "linear" | ||
| 141 | showRig RigW = "unrestricted" | ||
| 142 | |||
| 143 | showRel : RigCount -> String | ||
| 144 | showRel Rig0 = "irrelevant" | ||
| 145 | showRel Rig1 = "relevant" | ||
| 146 | showRel RigW = "non-linear" | ||
| 147 | show (BorrowPartial fc env t arg) | ||
| 148 | = show fc ++ ":" ++ show t ++ " borrows argument " ++ show arg ++ | ||
| 149 | " so must be fully applied" | ||
| 150 | show (BorrowPartialType fc env t) | ||
| 151 | = show fc ++ ":" ++ show t ++ " borrows, so must return a concrete type" | ||
| 152 | |||
| 153 | show (AmbiguousName fc ns) = show fc ++ ":Ambiguous name " ++ show ns | ||
| 154 | show (AmbiguousElab fc env ts) = show fc ++ ":Ambiguous elaboration " ++ show ts | ||
| 155 | show (AmbiguousSearch fc env ts) = show fc ++ ":Ambiguous search " ++ show ts | ||
| 156 | show (AllFailed ts) = "No successful elaboration: " ++ assert_total (show ts) | ||
| 157 | show (RecordTypeNeeded fc env) | ||
| 158 | = show fc ++ ":Can't infer type of record to update" | ||
| 159 | show (NotRecordField fc fld Nothing) | ||
| 160 | = show fc ++ ":" ++ fld ++ " is not part of a record type" | ||
| 161 | show (NotRecordField fc fld (Just ty)) | ||
| 162 | = show fc ++ ":Record type " ++ show ty ++ " has no field " ++ fld | ||
| 163 | show (NotRecordType fc ty) | ||
| 164 | = show fc ++ ":" ++ show ty ++ " is not a record type" | ||
| 165 | show (IncompatibleFieldUpdate fc flds) | ||
| 166 | = show fc ++ ":Field update " ++ showSep "->" flds ++ " not compatible with other updates" | ||
| 167 | show (InvalidImplicit fc env n tm) = show fc ++ ":" ++ show n ++ " is not a valid implicit argument in " ++ show tm | ||
| 168 | show (TryWithImplicits fc env imps) | ||
| 169 | = show fc ++ ":Need to bind implicits " | ||
| 170 | ++ showSep "," (map (\x => show (fst x) ++ " : " ++ show (snd x)) imps) | ||
| 171 | ++ "\n(The front end should probably have done this for you. Please report!)" | ||
| 172 | show (CantSolveGoal fc g) = show fc ++ ":Can't solve goal " ++ assert_total (show g) | ||
| 173 | show (DeterminingArg fc n env g) | ||
| 174 | = show fc ++ ":Can't solve goal " ++ assert_total (show g) ++ | ||
| 175 | " since argument " ++ show n ++ " can't be inferred" | ||
| 176 | show (UnsolvedHoles hs) = "Unsolved holes " ++ show hs | ||
| 177 | show (CantInferArgType fc env n h ty) | ||
| 178 | = show fc ++ ":Can't infer type for " ++ show n ++ | ||
| 179 | " (got " ++ show ty ++ " with hole " ++ show h ++ ")" | ||
| 180 | show (SolvedNamedHole fc _ h _) = show fc ++ ":Named hole " ++ show h ++ " is solved by unification" | ||
| 181 | show (VisibilityError fc vx x vy y) | ||
| 182 | = show fc ++ ":" ++ show vx ++ " " ++ show x ++ " cannot refer to " | ||
| 183 | ++ show vy ++ " " ++ show y | ||
| 184 | show (NonLinearPattern fc n) = show fc ++ ":Non linear pattern variable " ++ show n | ||
| 185 | show (BadPattern fc n) = show fc ++ ":Pattern not allowed here: " ++ show n | ||
| 186 | show (NoDeclaration fc x) = show fc ++ ":No type declaration for " ++ show x | ||
| 187 | show (AlreadyDefined fc x) = show fc ++ ":" ++ show x ++ " is already defined" | ||
| 188 | show (NotFunctionType fc env tm) = show fc ++ ":Not a function type: " ++ show tm | ||
| 189 | show (RewriteNoChange fc env rule ty) | ||
| 190 | = show fc ++ ":Rewriting by " ++ show rule ++ " did not change type " ++ show ty | ||
| 191 | show (NotRewriteRule fc env rule) | ||
| 192 | = show fc ++ ":" ++ show rule ++ " is not a rewrite rule type" | ||
| 193 | show (CaseCompile fc n DifferingArgNumbers) | ||
| 194 | = show fc ++ ":Patterns for " ++ show n ++ " have different numbers of arguments" | ||
| 195 | show (CaseCompile fc n DifferingTypes) | ||
| 196 | = show fc ++ ":Patterns for " ++ show n ++ " require matching on different types" | ||
| 197 | show (CaseCompile fc n UnknownType) | ||
| 198 | = show fc ++ ":Can't infer type to match in " ++ show n | ||
| 199 | show (CaseCompile fc n (MatchErased (_ ** (env, tm)))) | ||
| 200 | = show fc ++ ":Attempt to match on erased argument " ++ show tm ++ | ||
| 201 | " in " ++ show n | ||
| 202 | show (BadDotPattern fc env reason x y) | ||
| 203 | = show fc ++ ":Can't match on " ++ show x ++ | ||
| 204 | (if reason /= "" then " (" ++ reason ++ ")" else "") ++ | ||
| 205 | " - it elaborates to " ++ show y | ||
| 206 | show (BadImplicit fc str) = show fc ++ ":" ++ str ++ " can't be bound here" | ||
| 207 | show (BadRunElab fc env script) = show fc ++ ":Bad elaborator script " ++ show script | ||
| 208 | show (GenericMsg fc str) = show fc ++ ":" ++ str | ||
| 209 | show (TTCError msg) = "Error in TTC file: " ++ show msg | ||
| 210 | show (FileErr fname err) = "File error (" ++ fname ++ "): " ++ show err | ||
| 211 | show (ParseFail fc err) = "Parse error (" ++ show err ++ ")" | ||
| 212 | show (ModuleNotFound fc ns) | ||
| 213 | = show fc ++ ":" ++ showSep "." (reverse ns) ++ " not found" | ||
| 214 | show (CyclicImports ns) | ||
| 215 | = "Module imports form a cycle: " ++ showSep " -> " (map showMod ns) | ||
| 216 | where | ||
| 217 | showMod : List String -> String | ||
| 218 | showMod ns = showSep "." (reverse ns) | ||
| 219 | show ForceNeeded = "Internal error when resolving implicit laziness" | ||
| 220 | show (InternalError str) = "INTERNAL ERROR: " ++ str | ||
| 221 | |||
| 222 | show (InType fc n err) | ||
| 223 | = show fc ++ ":When elaborating type of " ++ show n ++ ":\n" ++ | ||
| 224 | show err | ||
| 225 | show (InCon fc n err) | ||
| 226 | = show fc ++ ":When elaborating type of constructor " ++ show n ++ ":\n" ++ | ||
| 227 | show err | ||
| 228 | show (InLHS fc n err) | ||
| 229 | = show fc ++ ":When elaborating left hand side of " ++ show n ++ ":\n" ++ | ||
| 230 | show err | ||
| 231 | show (InRHS fc n err) | ||
| 232 | = show fc ++ ":When elaborating right hand side of " ++ show n ++ ":\n" ++ | ||
| 233 | show err | ||
| 234 | |||
| 235 | export | ||
| 236 | getErrorLoc : Error -> Maybe FC | ||
| 237 | getErrorLoc (Fatal err) = getErrorLoc err | ||
| 238 | getErrorLoc (CantConvert loc env tm y) = Just loc | ||
| 239 | getErrorLoc (CantSolveEq loc env tm y) = Just loc | ||
| 240 | getErrorLoc (Cycle loc env tm y) = Just loc | ||
| 241 | getErrorLoc (WhenUnifying loc env tm y z) = Just loc | ||
| 242 | getErrorLoc (ValidCase loc env y) = Just loc | ||
| 243 | getErrorLoc (UndefinedName loc y) = Just loc | ||
| 244 | getErrorLoc (InvisibleName loc y) = Just loc | ||
| 245 | getErrorLoc (BadTypeConType loc y) = Just loc | ||
| 246 | getErrorLoc (BadDataConType loc y z) = Just loc | ||
| 247 | -- getErrorLoc (NotCovering loc _ _) = Just loc | ||
| 248 | -- getErrorLoc (NotTotal loc _ _) = Just loc | ||
| 249 | getErrorLoc (LinearUsed loc k y) = Just loc | ||
| 250 | getErrorLoc (LinearMisuse loc y z w) = Just loc | ||
| 251 | getErrorLoc (BorrowPartial loc _ _ _) = Just loc | ||
| 252 | getErrorLoc (BorrowPartialType loc _ _) = Just loc | ||
| 253 | getErrorLoc (AmbiguousName loc xs) = Just loc | ||
| 254 | getErrorLoc (AmbiguousElab loc _ xs) = Just loc | ||
| 255 | getErrorLoc (AmbiguousSearch loc _ xs) = Just loc | ||
| 256 | getErrorLoc (AllFailed ((_, x) :: xs)) = getErrorLoc x | ||
| 257 | getErrorLoc (AllFailed []) = Nothing | ||
| 258 | getErrorLoc (RecordTypeNeeded loc _) = Just loc | ||
| 259 | getErrorLoc (NotRecordField loc _ _) = Just loc | ||
| 260 | getErrorLoc (NotRecordType loc _) = Just loc | ||
| 261 | getErrorLoc (IncompatibleFieldUpdate loc _) = Just loc | ||
| 262 | getErrorLoc (InvalidImplicit loc _ y tm) = Just loc | ||
| 263 | getErrorLoc (TryWithImplicits loc _ _) = Just loc | ||
| 264 | getErrorLoc (CantSolveGoal loc tm) = Just loc | ||
| 265 | getErrorLoc (DeterminingArg loc y env tm) = Just loc | ||
| 266 | getErrorLoc (UnsolvedHoles ((loc, _) :: xs)) = Just loc | ||
| 267 | getErrorLoc (UnsolvedHoles []) = Nothing | ||
| 268 | getErrorLoc (CantInferArgType loc _ y z tm) = Just loc | ||
| 269 | getErrorLoc (SolvedNamedHole loc _ _ y) = Just loc | ||
| 270 | getErrorLoc (VisibilityError loc y z w s) = Just loc | ||
| 271 | getErrorLoc (NonLinearPattern loc y) = Just loc | ||
| 272 | getErrorLoc (BadPattern loc y) = Just loc | ||
| 273 | getErrorLoc (NoDeclaration loc y) = Just loc | ||
| 274 | getErrorLoc (AlreadyDefined loc y) = Just loc | ||
| 275 | getErrorLoc (NotFunctionType loc _ tm) = Just loc | ||
| 276 | getErrorLoc (RewriteNoChange loc _ tm ty) = Just loc | ||
| 277 | getErrorLoc (NotRewriteRule loc _ ty) = Just loc | ||
| 278 | getErrorLoc (CaseCompile loc y z) = Just loc | ||
| 279 | getErrorLoc (BadDotPattern loc _ y tm z) = Just loc | ||
| 280 | getErrorLoc (BadImplicit loc y) = Just loc | ||
| 281 | getErrorLoc (BadRunElab loc _ tm) = Just loc | ||
| 282 | getErrorLoc (GenericMsg loc y) = Just loc | ||
| 283 | getErrorLoc (TTCError x) = Nothing | ||
| 284 | getErrorLoc (FileErr x y) = Nothing | ||
| 285 | getErrorLoc (ParseFail loc x) = Just loc | ||
| 286 | getErrorLoc (ModuleNotFound loc xs) = Just loc | ||
| 287 | getErrorLoc (CyclicImports xs) = Nothing | ||
| 288 | getErrorLoc ForceNeeded = Nothing | ||
| 289 | getErrorLoc (InternalError x) = Nothing | ||
| 290 | getErrorLoc (InType x y err) = getErrorLoc err | ||
| 291 | getErrorLoc (InCon x y err) = getErrorLoc err | ||
| 292 | getErrorLoc (InLHS x y err) = getErrorLoc err | ||
| 293 | getErrorLoc (InRHS x y err) = getErrorLoc err | ||
| 294 | |||
| 295 | public export | ||
| 296 | record GlobalOpts where | ||
| 297 | constructor MkGlobalOpts | ||
| 298 | logLevel : Nat | ||
| 299 | |||
| 300 | export | ||
| 301 | defaultOpts : GlobalOpts | ||
| 302 | defaultOpts = MkGlobalOpts 0 | ||
| 303 | |||
| 304 | -- Core is a wrapper around IO that is specialised for efficiency. | ||
| 305 | export | ||
| 306 | record Core t where | ||
| 307 | constructor MkCore | ||
| 308 | runCore : IORef GlobalOpts -> IO (Either Error t) | ||
| 309 | |||
| 310 | export | ||
| 311 | coreRun : GlobalOpts -> | ||
| 312 | Core a -> | ||
| 313 | (Error -> IO b) -> (a -> IO b) -> IO b | ||
| 314 | coreRun opts (MkCore act) err ok | ||
| 315 | = do oref <- newIORef opts | ||
| 316 | either err ok !(act oref) | ||
| 317 | |||
| 318 | export | ||
| 319 | coreFail : Error -> Core a | ||
| 320 | coreFail e = MkCore (\oref => pure (Left e)) | ||
| 321 | |||
| 322 | export | ||
| 323 | wrapError : (Error -> Error) -> Core a -> Core a | ||
| 324 | wrapError fe (MkCore prog) | ||
| 325 | = MkCore (\oref => | ||
| 326 | prog oref >>= | ||
| 327 | (\x => case x of | ||
| 328 | Left err => pure (Left (fe err)) | ||
| 329 | Right val => pure (Right val))) | ||
| 330 | |||
| 331 | -- This would be better if we restrict it to a limited set of IO operations | ||
| 332 | export | ||
| 333 | %inline | ||
| 334 | coreLift : IO a -> Core a | ||
| 335 | coreLift op = MkCore (\oref => map Right op) | ||
| 336 | |||
| 337 | {- Monad, Applicative, Traversable are specialised by hand for Core. | ||
| 338 | In theory, this shouldn't be necessary, but it turns out that Idris 1 doesn't | ||
| 339 | specialise interfaces under 'case' expressions, and this has a significant | ||
| 340 | impact on both compile time and run time. | ||
| 341 | |||
| 342 | Of course it would be a good idea to fix this in Idris, but it's not an urgent | ||
| 343 | thing on the road to self hosting, and we can make sure this isn't a problem | ||
| 344 | in the next version (i.e., in this project...)! -} | ||
| 345 | |||
| 346 | -- Monad (specialised) | ||
| 347 | export %inline | ||
| 348 | (>>=) : Core a -> (a -> Core b) -> Core b | ||
| 349 | (>>=) (MkCore act) f | ||
| 350 | = MkCore (\oref => | ||
| 351 | act oref >>= | ||
| 352 | (\x => case x of | ||
| 353 | Left err => pure (Left err) | ||
| 354 | Right val => runCore (f val) oref)) | ||
| 355 | |||
| 356 | -- Applicative (specialised) | ||
| 357 | export %inline | ||
| 358 | pure : a -> Core a | ||
| 359 | pure x = MkCore (\oref => pure (pure x)) | ||
| 360 | |||
| 361 | export | ||
| 362 | (<*>) : Core (a -> b) -> Core a -> Core b | ||
| 363 | (<*>) (MkCore f) (MkCore a) = MkCore (\oref => [| f oref <*> a oref |]) | ||
| 364 | |||
| 365 | export %inline | ||
| 366 | when : Bool -> Lazy (Core ()) -> Core () | ||
| 367 | when True f = f | ||
| 368 | when False f = pure () | ||
| 369 | |||
| 370 | export | ||
| 371 | Catchable Core Error where | ||
| 372 | catch (MkCore prog) h | ||
| 373 | = MkCore (\oref => | ||
| 374 | do p' <- prog oref | ||
| 375 | case p' of | ||
| 376 | Left e => let MkCore he = h e in he oref | ||
| 377 | Right val => pure (Right val)) | ||
| 378 | throw = coreFail | ||
| 379 | |||
| 380 | -- Traversable (specialised) | ||
| 381 | traverse' : (a -> Core b) -> List a -> List b -> Core (List b) | ||
| 382 | traverse' f [] acc = pure (reverse acc) | ||
| 383 | traverse' f (x :: xs) acc | ||
| 384 | = traverse' f xs (!(f x) :: acc) | ||
| 385 | |||
| 386 | export | ||
| 387 | traverse : (a -> Core b) -> List a -> Core (List b) | ||
| 388 | traverse f xs = traverse' f xs [] | ||
| 389 | |||
| 390 | export | ||
| 391 | data Ref : label -> Type -> Type where | ||
| 392 | MkRef : IORef a -> Ref x a | ||
| 393 | |||
| 394 | export | ||
| 395 | newRef : (x : label) -> t -> Core (Ref x t) | ||
| 396 | newRef x val | ||
| 397 | = do ref <- coreLift (newIORef val) | ||
| 398 | pure (MkRef ref) | ||
| 399 | |||
| 400 | export %inline | ||
| 401 | get : (x : label) -> {auto ref : Ref x a} -> Core a | ||
| 402 | get x {ref = MkRef io} = coreLift (readIORef io) | ||
| 403 | |||
| 404 | export %inline | ||
| 405 | put : (x : label) -> {auto ref : Ref x a} -> a -> Core () | ||
| 406 | put x {ref = MkRef io} val = coreLift (writeIORef io val) | ||
| 407 | |||
| 408 | export %inline | ||
| 409 | getOpts : Core GlobalOpts | ||
| 410 | getOpts = MkCore (\oref => pure (Right !(readIORef oref))) | ||
| 411 | |||
| 412 | export %inline | ||
| 413 | putOpts : GlobalOpts -> Core () | ||
| 414 | putOpts opts = MkCore (\oref => pure (Right !(writeIORef oref opts))) | ||
| 415 | |||
| 416 | export | ||
| 417 | cond : List (Lazy Bool, Lazy a) -> a -> a | ||
| 418 | cond [] def = def | ||
| 419 | cond ((x, y) :: xs) def = if x then y else cond xs def | ||
| 420 | |||
| 421 | export | ||
| 422 | log : Nat -> String -> Core () | ||
| 423 | log lvl msg | ||
| 424 | = do opts <- getOpts | ||
| 425 | if logLevel opts >= lvl | ||
| 426 | then coreLift $ putStrLn $ "LOG " ++ show lvl ++ ": " ++ msg | ||
| 427 | else pure () | ||
| 428 | export | ||
| 429 | setLogLevel : Nat -> Core () | ||
| 430 | setLogLevel n | ||
| 431 | = do opts <- getOpts | ||
| 432 | putOpts (record { logLevel = n } opts) | ||
| 433 | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | module Core.Env | ||
| 2 | |||
| 3 | import Core.TT | ||
| 4 | |||
| 5 | -- Environment containing types and values of local variables | ||
| 6 | namespace Env | ||
| 7 | public export | ||
| 8 | data Env : (tm : List Name -> Type) -> List Name -> Type where | ||
| 9 | Nil : Env tm [] | ||
| 10 | (::) : Binder (tm vars) -> Env tm vars -> Env tm (x :: vars) | ||
| 11 | |||
| 12 | export | ||
| 13 | extend : (x : Name) -> Binder (tm vars) -> Env tm vars -> Env tm (x :: vars) | ||
| 14 | extend x = (::) {x} | ||
| 15 | |||
| 16 | export | ||
| 17 | length : Env tm xs -> Nat | ||
| 18 | length [] = 0 | ||
| 19 | length (x :: xs) = S (length xs) | ||
| 20 | |||
| 21 | export | ||
| 22 | bindEnv : FC -> Env Term vars -> (tm : Term vars) -> ClosedTerm | ||
| 23 | bindEnv loc [] tm = tm | ||
| 24 | bindEnv loc (b :: env) tm | ||
| 25 | = bindEnv loc env (Bind loc _ b tm) | ||
| @@ -0,0 +1,37 @@ | |||
| 1 | module Core.FC | ||
| 2 | |||
| 3 | public export | ||
| 4 | FilePos : Type | ||
| 5 | FilePos = (Int, Int) | ||
| 6 | |||
| 7 | showPos : FilePos -> String | ||
| 8 | showPos (l, c) = show (l + 1) ++ ":" ++ show (c + 1) | ||
| 9 | |||
| 10 | public export | ||
| 11 | FileName : Type | ||
| 12 | FileName = String | ||
| 13 | |||
| 14 | public export | ||
| 15 | record FC where | ||
| 16 | constructor MkFC | ||
| 17 | file : FileName | ||
| 18 | startPos : FilePos | ||
| 19 | endPos : FilePos | ||
| 20 | |||
| 21 | export | ||
| 22 | emptyFC : FC | ||
| 23 | emptyFC = MkFC "(no file)" (0, 0) (0, 0) | ||
| 24 | |||
| 25 | export | ||
| 26 | toplevelFC : FC | ||
| 27 | toplevelFC = MkFC "(toplevel)" (0, 0) (0, 0) | ||
| 28 | |||
| 29 | %name FC fc | ||
| 30 | |||
| 31 | export | ||
| 32 | Show FC where | ||
| 33 | show loc = file loc ++ ":" ++ | ||
| 34 | showPos (startPos loc) ++ "--" ++ | ||
| 35 | showPos (endPos loc) | ||
| 36 | |||
| 37 | |||
| @@ -0,0 +1,23 @@ | |||
| 1 | module Core.Name | ||
| 2 | |||
| 3 | %default total | ||
| 4 | |||
| 5 | public export | ||
| 6 | data Name : Type where | ||
| 7 | NS : List String -> Name -> Name -- in a namespace | ||
| 8 | UN : String -> Name -- user defined name | ||
| 9 | MN : String -> Int -> Name -- machine generated name | ||
| 10 | PV : Name -> Name -> Name -- pattern variable name | ||
| 11 | MV : Int -> Name -- metavariable reference | ||
| 12 | Nested : Name -> Name -> Name -- nested function name | ||
| 13 | Resolved : Int -> Name -- resolved, index into context | ||
| 14 | |||
| 15 | export Show Name where | ||
| 16 | show n = "[not done yet]" | ||
| 17 | |||
| 18 | export | ||
| 19 | showSep : String -> List String -> String | ||
| 20 | showSep sep [] = "" | ||
| 21 | showSep sep [x] = x | ||
| 22 | showSep sep (x :: xs) = x ++ sep ++ showSep sep xs | ||
| 23 | |||
| @@ -0,0 +1,381 @@ | |||
| 1 | module Core.TT | ||
| 2 | |||
| 3 | import public Core.FC | ||
| 4 | import public Core.Name | ||
| 5 | |||
| 6 | %default covering | ||
| 7 | |||
| 8 | %hide Raw -- from Reflection in the Prelude | ||
| 9 | %hide Binder | ||
| 10 | %hide NameType | ||
| 11 | %hide Case | ||
| 12 | |||
| 13 | public export | ||
| 14 | data NameType : Type where | ||
| 15 | Bound : NameType | ||
| 16 | Func : NameType | ||
| 17 | DataCon : (tag : Int) -> (arity : Nat) -> NameType | ||
| 18 | TyCon : (tag : Int) -> (arity : Nat) -> NameType | ||
| 19 | |||
| 20 | public export | ||
| 21 | data Constant | ||
| 22 | = I Int | ||
| 23 | | BI Integer | ||
| 24 | | Str String | ||
| 25 | | Ch Char | ||
| 26 | | Db Double | ||
| 27 | | WorldVal | ||
| 28 | |||
| 29 | | IntType | ||
| 30 | | IntegerType | ||
| 31 | | StringType | ||
| 32 | | CharType | ||
| 33 | | DoubleType | ||
| 34 | | WorldType | ||
| 35 | |||
| 36 | export | ||
| 37 | constantEq : (x, y : Constant) -> Maybe (x = y) | ||
| 38 | constantEq (I x) (I y) = case decEq x y of | ||
| 39 | Yes Refl => Just Refl | ||
| 40 | No contra => Nothing | ||
| 41 | constantEq (BI x) (BI y) = case decEq x y of | ||
| 42 | Yes Refl => Just Refl | ||
| 43 | No contra => Nothing | ||
| 44 | constantEq (Str x) (Str y) = case decEq x y of | ||
| 45 | Yes Refl => Just Refl | ||
| 46 | No contra => Nothing | ||
| 47 | constantEq (Ch x) (Ch y) = case decEq x y of | ||
| 48 | Yes Refl => Just Refl | ||
| 49 | No contra => Nothing | ||
| 50 | constantEq (Db x) (Db y) = Nothing -- no DecEq for Doubles! | ||
| 51 | constantEq WorldVal WorldVal = Just Refl | ||
| 52 | constantEq IntType IntType = Just Refl | ||
| 53 | constantEq IntegerType IntegerType = Just Refl | ||
| 54 | constantEq StringType StringType = Just Refl | ||
| 55 | constantEq CharType CharType = Just Refl | ||
| 56 | constantEq DoubleType DoubleType = Just Refl | ||
| 57 | constantEq WorldType WorldType = Just Refl | ||
| 58 | constantEq _ _ = Nothing | ||
| 59 | |||
| 60 | export | ||
| 61 | Show Constant where | ||
| 62 | show (I x) = show x | ||
| 63 | show (BI x) = show x | ||
| 64 | show (Str x) = show x | ||
| 65 | show (Ch x) = show x | ||
| 66 | show (Db x) = show x | ||
| 67 | show WorldVal = "%MkWorld" | ||
| 68 | show IntType = "Int" | ||
| 69 | show IntegerType = "Integer" | ||
| 70 | show StringType = "String" | ||
| 71 | show CharType = "Char" | ||
| 72 | show DoubleType = "Double" | ||
| 73 | show WorldType = "%World" | ||
| 74 | |||
| 75 | export | ||
| 76 | Eq Constant where | ||
| 77 | (I x) == (I y) = x == y | ||
| 78 | (BI x) == (BI y) = x == y | ||
| 79 | (Str x) == (Str y) = x == y | ||
| 80 | (Ch x) == (Ch y) = x == y | ||
| 81 | (Db x) == (Db y) = x == y | ||
| 82 | WorldVal == WorldVal = True | ||
| 83 | IntType == IntType = True | ||
| 84 | IntegerType == IntegerType = True | ||
| 85 | StringType == StringType = True | ||
| 86 | CharType == CharType = True | ||
| 87 | DoubleType == DoubleType = True | ||
| 88 | WorldType == WorldType = True | ||
| 89 | _ == _ = False | ||
| 90 | |||
| 91 | public export | ||
| 92 | data PiInfo = Implicit | Explicit | AutoImplicit | ||
| 93 | |||
| 94 | public export | ||
| 95 | data RigCount = Rig0 | Rig1 | RigW | ||
| 96 | |||
| 97 | export | ||
| 98 | isLinear : RigCount -> Bool | ||
| 99 | isLinear Rig1 = True | ||
| 100 | isLinear _ = False | ||
| 101 | |||
| 102 | export | ||
| 103 | Eq RigCount where | ||
| 104 | (==) Rig0 Rig0 = True | ||
| 105 | (==) Rig1 Rig1 = True | ||
| 106 | (==) RigW RigW = True | ||
| 107 | (==) _ _ = False | ||
| 108 | |||
| 109 | export | ||
| 110 | Ord RigCount where | ||
| 111 | compare Rig0 Rig0 = EQ | ||
| 112 | compare Rig0 Rig1 = LT | ||
| 113 | compare Rig0 RigW = LT | ||
| 114 | |||
| 115 | compare Rig1 Rig0 = GT | ||
| 116 | compare Rig1 Rig1 = EQ | ||
| 117 | compare Rig1 RigW = LT | ||
| 118 | |||
| 119 | compare RigW Rig0 = GT | ||
| 120 | compare RigW Rig1 = GT | ||
| 121 | compare RigW RigW = EQ | ||
| 122 | |||
| 123 | export | ||
| 124 | Show RigCount where | ||
| 125 | show Rig0 = "Rig0" | ||
| 126 | show Rig1 = "Rig1" | ||
| 127 | show RigW = "RigW" | ||
| 128 | |||
| 129 | export | ||
| 130 | rigPlus : RigCount -> RigCount -> RigCount | ||
| 131 | rigPlus Rig0 Rig0 = Rig0 | ||
| 132 | rigPlus Rig0 Rig1 = Rig1 | ||
| 133 | rigPlus Rig0 RigW = RigW | ||
| 134 | rigPlus Rig1 Rig0 = Rig1 | ||
| 135 | rigPlus Rig1 Rig1 = RigW | ||
| 136 | rigPlus Rig1 RigW = RigW | ||
| 137 | rigPlus RigW Rig0 = RigW | ||
| 138 | rigPlus RigW Rig1 = RigW | ||
| 139 | rigPlus RigW RigW = RigW | ||
| 140 | |||
| 141 | export | ||
| 142 | rigMult : RigCount -> RigCount -> RigCount | ||
| 143 | rigMult Rig0 Rig0 = Rig0 | ||
| 144 | rigMult Rig0 Rig1 = Rig0 | ||
| 145 | rigMult Rig0 RigW = Rig0 | ||
| 146 | rigMult Rig1 Rig0 = Rig0 | ||
| 147 | rigMult Rig1 Rig1 = Rig1 | ||
| 148 | rigMult Rig1 RigW = RigW | ||
| 149 | rigMult RigW Rig0 = Rig0 | ||
| 150 | rigMult RigW Rig1 = RigW | ||
| 151 | rigMult RigW RigW = RigW | ||
| 152 | |||
| 153 | public export | ||
| 154 | data Binder : Type -> Type where | ||
| 155 | -- Lambda bound variables with their implicitness | ||
| 156 | Lam : RigCount -> PiInfo -> (ty : type) -> Binder type | ||
| 157 | -- Let bound variables with their value | ||
| 158 | Let : RigCount -> (val : type) -> (ty : type) -> Binder type | ||
| 159 | -- Forall/pi bound variables with their implicitness | ||
| 160 | Pi : RigCount -> PiInfo -> (ty : type) -> Binder type | ||
| 161 | -- pattern bound variables | ||
| 162 | PVar : RigCount -> (ty : type) -> Binder type | ||
| 163 | -- the type of pattern bound variables | ||
| 164 | PVTy : RigCount -> (ty : type) -> Binder type | ||
| 165 | |||
| 166 | export | ||
| 167 | binderType : Binder tm -> tm | ||
| 168 | binderType (Lam _ x ty) = ty | ||
| 169 | binderType (Let _ val ty) = ty | ||
| 170 | binderType (Pi _ x ty) = ty | ||
| 171 | binderType (PVar _ ty) = ty | ||
| 172 | binderType (PVTy _ ty) = ty | ||
| 173 | |||
| 174 | export | ||
| 175 | multiplicity : Binder tm -> RigCount | ||
| 176 | multiplicity (Lam c x ty) = c | ||
| 177 | multiplicity (Let c val ty) = c | ||
| 178 | multiplicity (Pi c x ty) = c | ||
| 179 | multiplicity (PVar c ty) = c | ||
| 180 | multiplicity (PVTy c ty) = c | ||
| 181 | |||
| 182 | export | ||
| 183 | setMultiplicity : Binder tm -> RigCount -> Binder tm | ||
| 184 | setMultiplicity (Lam c x ty) c' = Lam c' x ty | ||
| 185 | setMultiplicity (Let c val ty) c' = Let c' val ty | ||
| 186 | setMultiplicity (Pi c x ty) c' = Pi c' x ty | ||
| 187 | setMultiplicity (PVar c ty) c' = PVar c' ty | ||
| 188 | setMultiplicity (PVTy c ty) c' = PVTy c' ty | ||
| 189 | |||
| 190 | showCount : RigCount -> String | ||
| 191 | showCount Rig0 = "0 " | ||
| 192 | showCount Rig1 = "1 " | ||
| 193 | showCount RigW = "" | ||
| 194 | |||
| 195 | Show ty => Show (Binder ty) where | ||
| 196 | show (Lam c _ t) = "\\" ++ showCount c ++ show t | ||
| 197 | show (Pi c _ t) = "Pi " ++ showCount c ++ show t | ||
| 198 | show (Let c v t) = "let " ++ showCount c ++ show v ++ " : " ++ show t | ||
| 199 | show (PVar c t) = "pat " ++ showCount c ++ show t | ||
| 200 | show (PVTy c t) = "pty " ++ showCount c ++ show t | ||
| 201 | |||
| 202 | export | ||
| 203 | setType : Binder tm -> tm -> Binder tm | ||
| 204 | setType (Lam c x _) ty = Lam c x ty | ||
| 205 | setType (Let c val _) ty = Let c val ty | ||
| 206 | setType (Pi c x _) ty = Pi c x ty | ||
| 207 | setType (PVar c _) ty = PVar c ty | ||
| 208 | setType (PVTy c _) ty = PVTy c ty | ||
| 209 | |||
| 210 | export | ||
| 211 | Functor Binder where | ||
| 212 | map func (Lam c x ty) = Lam c x (func ty) | ||
| 213 | map func (Let c val ty) = Let c (func val) (func ty) | ||
| 214 | map func (Pi c x ty) = Pi c x (func ty) | ||
| 215 | map func (PVar c ty) = PVar c (func ty) | ||
| 216 | map func (PVTy c ty) = PVTy c (func ty) | ||
| 217 | |||
| 218 | public export | ||
| 219 | data IsVar : Name -> Nat -> List Name -> Type where | ||
| 220 | First : IsVar n Z (n :: ns) | ||
| 221 | Later : IsVar n i ns -> IsVar n (S i) (m :: ns) | ||
| 222 | |||
| 223 | public export | ||
| 224 | data Var : List Name -> Type where | ||
| 225 | MkVar : {i : Nat} -> IsVar n i vars -> Var vars | ||
| 226 | |||
| 227 | export | ||
| 228 | sameVar : {i, j : _} -> IsVar n i xs -> IsVar m j xs -> Bool | ||
| 229 | sameVar {i} {j} _ _ = i == j | ||
| 230 | |||
| 231 | public export | ||
| 232 | record AppInfo where | ||
| 233 | constructor MkAppInfo | ||
| 234 | plicit : PiInfo | ||
| 235 | |||
| 236 | export | ||
| 237 | appInf : PiInfo -> AppInfo | ||
| 238 | appInf p = MkAppInfo p | ||
| 239 | |||
| 240 | export | ||
| 241 | implApp : AppInfo | ||
| 242 | implApp = appInf Implicit | ||
| 243 | |||
| 244 | export | ||
| 245 | explApp : AppInfo | ||
| 246 | explApp = appInf Explicit | ||
| 247 | |||
| 248 | namespace CList | ||
| 249 | -- A list correspoding to another list | ||
| 250 | public export | ||
| 251 | data CList : List a -> Type -> Type where | ||
| 252 | Nil : CList [] ty | ||
| 253 | (::) : (x : ty) -> CList cs ty -> CList (c :: cs) ty | ||
| 254 | |||
| 255 | -- Typechecked terms | ||
| 256 | -- These are guaranteed to be well-scoped wrt local variables, because they are | ||
| 257 | -- indexed by the names of local variables in scope | ||
| 258 | mutual | ||
| 259 | public export | ||
| 260 | data Term : List Name -> Type where | ||
| 261 | Local : FC -> Maybe RigCount -> | ||
| 262 | (idx : Nat) -> IsVar name idx vars -> Term vars | ||
| 263 | Ref : FC -> NameType -> (name : Name) -> Term vars | ||
| 264 | -- Metavariable solution binding | ||
| 265 | ULet : Int -> Term vars -> Term vars -> Term vars | ||
| 266 | Bind : FC -> (x : Name) -> | ||
| 267 | (b : Binder (Term vars)) -> | ||
| 268 | (scope : Term (x :: vars)) -> Term vars | ||
| 269 | App : FC -> (fn : Term vars) -> (p : AppInfo) -> (arg : Term vars) -> Term vars | ||
| 270 | Case : FC -> (cs : List (Var vars)) -> | ||
| 271 | (ty : Term vars) -> | ||
| 272 | Maybe (CaseTree vars) -> | ||
| 273 | (alts : List (PatAlt cs vars)) -> | ||
| 274 | Term vars | ||
| 275 | PrimVal : FC -> (c : Constant) -> Term vars | ||
| 276 | Erased : FC -> Term vars | ||
| 277 | TType : FC -> Term vars | ||
| 278 | |||
| 279 | public export | ||
| 280 | data Pat : List Name -> Type where | ||
| 281 | PAs : FC -> (idx : Nat) -> IsVar name idx vars -> Pat vars -> Pat vars | ||
| 282 | PCon : FC -> Name -> (tag : Int) -> (arity : Nat) -> | ||
| 283 | List (Pat vars) -> Pat vars | ||
| 284 | PLoc : FC -> (idx : Nat) -> IsVar name idx vars -> Pat vars | ||
| 285 | PUnmatchable : FC -> Term vars -> Pat vars | ||
| 286 | |||
| 287 | public export | ||
| 288 | data PatAlt : List (Var vs) -> List Name -> Type where | ||
| 289 | CBind : RigCount -> (x : Name) -> (ty : Term vars) -> | ||
| 290 | PatAlt cs (x :: vars) -> PatAlt cs vars | ||
| 291 | CPats : CList cs (Pat vars) -> Term vars -> PatAlt cs vars | ||
| 292 | |||
| 293 | public export | ||
| 294 | data CaseTree : List Name -> Type where | ||
| 295 | Switch : (idx : Nat) -> IsVar name idx vars -> | ||
| 296 | (scTy : Term vars) -> List (CaseAlt vars) -> | ||
| 297 | CaseTree vars | ||
| 298 | STerm : Term vars -> CaseTree vars | ||
| 299 | Unmatched : (msg : String) -> CaseTree vars | ||
| 300 | Impossible : CaseTree vars | ||
| 301 | |||
| 302 | public export | ||
| 303 | data CaseAlt : List Name -> Type where | ||
| 304 | ConCase : Name -> (tag : Int) -> (args : List Name) -> | ||
| 305 | CaseTree (args ++ vars) -> CaseAlt vars | ||
| 306 | ConstCase : Constant -> CaseTree vars -> CaseAlt vars | ||
| 307 | DefaultCase : CaseTree vars -> CaseAlt vars | ||
| 308 | |||
| 309 | export Show (Term vars) where | ||
| 310 | show tm = "[not done yet]" | ||
| 311 | |||
| 312 | public export | ||
| 313 | ClosedTerm : Type | ||
| 314 | ClosedTerm = Term [] | ||
| 315 | |||
| 316 | export | ||
| 317 | apply : FC -> AppInfo -> Term vars -> List (Term vars) -> Term vars | ||
| 318 | apply loc p fn [] = fn | ||
| 319 | apply loc p fn (a :: args) = apply loc p (App loc fn p a) args | ||
| 320 | |||
| 321 | public export | ||
| 322 | data Visibility = Private | Export | Public | ||
| 323 | |||
| 324 | export | ||
| 325 | Show Visibility where | ||
| 326 | show Private = "private" | ||
| 327 | show Export = "export" | ||
| 328 | show Public = "public export" | ||
| 329 | |||
| 330 | export | ||
| 331 | Eq Visibility where | ||
| 332 | Private == Private = True | ||
| 333 | Export == Export = True | ||
| 334 | Public == Public = True | ||
| 335 | _ == _ = False | ||
| 336 | |||
| 337 | export | ||
| 338 | Ord Visibility where | ||
| 339 | compare Private Export = LT | ||
| 340 | compare Private Public = LT | ||
| 341 | compare Export Public = LT | ||
| 342 | |||
| 343 | compare Private Private = EQ | ||
| 344 | compare Export Export = EQ | ||
| 345 | compare Public Public = EQ | ||
| 346 | |||
| 347 | compare Export Private = GT | ||
| 348 | compare Public Private = GT | ||
| 349 | compare Public Export = GT | ||
| 350 | |||
| 351 | --- Some test stuff | ||
| 352 | loc : (n : Name) -> {auto prf : IsVar n idx vars} -> Term vars | ||
| 353 | loc n {prf} = Local emptyFC Nothing _ prf | ||
| 354 | |||
| 355 | cvar : (n : Name) -> {auto prf : IsVar n idx vars} -> Var vars | ||
| 356 | cvar n {prf} = MkVar prf | ||
| 357 | |||
| 358 | ploc : (n : Name) -> {auto prf : IsVar n idx vars} -> Pat vars | ||
| 359 | ploc n {prf} = PLoc emptyFC _ prf | ||
| 360 | |||
| 361 | lam : (n : Name) -> Term vars -> Term (n :: vars) -> Term vars | ||
| 362 | lam n ty sc = Bind emptyFC n (Lam RigW Explicit ty) sc | ||
| 363 | |||
| 364 | NatTy : Term vs | ||
| 365 | NatTy = Ref emptyFC (TyCon 0 2) (UN "Nat") | ||
| 366 | |||
| 367 | testPlus : Term [] | ||
| 368 | testPlus | ||
| 369 | = lam (UN "x") NatTy $ | ||
| 370 | lam (UN "y") NatTy $ | ||
| 371 | Case emptyFC [cvar (UN "x")] NatTy Nothing | ||
| 372 | [CPats [PCon emptyFC (UN "Z") 0 0 []] (loc (UN "y")), | ||
| 373 | CBind RigW (UN "k") NatTy | ||
| 374 | (CPats [PCon emptyFC (UN "S") 1 1 [ploc (UN "k")]] | ||
| 375 | (apply emptyFC explApp (Ref emptyFC (DataCon 1 1) (UN "S")) | ||
| 376 | [apply emptyFC explApp (Ref emptyFC Func (UN "plus")) | ||
| 377 | [loc (UN "k"), loc (UN "y")]]))] | ||
| 378 | |||
| 379 | |||
| 380 | |||
| 381 | |||
| @@ -0,0 +1,49 @@ | |||
| 1 | module Core.Value | ||
| 2 | |||
| 3 | import Core.TT | ||
| 4 | import Core.Env | ||
| 5 | |||
| 6 | public export | ||
| 7 | record EvalOpts where | ||
| 8 | constructor MkEvalOpts | ||
| 9 | holesOnly : Bool -- only evaluate hole solutions | ||
| 10 | evalAll : Bool -- evaluate everything, including private names | ||
| 11 | tcInline : Bool -- inline for totality checking | ||
| 12 | fuel : Maybe Nat -- Limit for recursion depth | ||
| 13 | |||
| 14 | mutual | ||
| 15 | public export | ||
| 16 | data LocalEnv : List Name -> List Name -> Type where | ||
| 17 | Nil : LocalEnv free [] | ||
| 18 | (::) : Closure free -> LocalEnv free vars -> LocalEnv free (x :: vars) | ||
| 19 | |||
| 20 | public export | ||
| 21 | data Closure : List Name -> Type where | ||
| 22 | MkClosure : (opts : EvalOpts) -> | ||
| 23 | LocalEnv free vars -> | ||
| 24 | Env Term free -> | ||
| 25 | Term (vars ++ free) -> Closure free | ||
| 26 | MkNFClosure : NF free -> Closure free | ||
| 27 | |||
| 28 | -- The head of a value: things you can apply arguments to | ||
| 29 | public export | ||
| 30 | data NHead : List Name -> Type where | ||
| 31 | NLocal : Maybe RigCount -> (idx : Nat) -> IsVar name idx vars -> | ||
| 32 | NHead vars | ||
| 33 | NRef : NameType -> Name -> NHead vars | ||
| 34 | NBlocked : Closure vars -> NHead vars -- complex blocked term, e.g. Case | ||
| 35 | |||
| 36 | -- Values themselves | ||
| 37 | public export | ||
| 38 | data NF : List Name -> Type where | ||
| 39 | NBind : FC -> (x : Name) -> Binder (NF vars) -> | ||
| 40 | (Closure vars -> NF vars) -> NF vars | ||
| 41 | NApp : FC -> NHead vars -> List (Closure vars) -> NF vars | ||
| 42 | NDCon : FC -> Name -> (tag : Int) -> (arity : Nat) -> | ||
| 43 | List (Closure vars) -> NF vars | ||
| 44 | NTCon : FC -> Name -> (tag : Int) -> (arity : Nat) -> | ||
| 45 | List (Closure vars) -> NF vars | ||
| 46 | NPrimVal : FC -> Constant -> NF vars | ||
| 47 | NErased : FC -> NF vars | ||
| 48 | NType : FC -> NF vars | ||
| 49 | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | module Data.Bool.Extra | ||
| 2 | |||
| 3 | %access public export | ||
| 4 | %default total | ||
| 5 | |||
| 6 | andSameNeutral : (x : Bool) -> x && x = x | ||
| 7 | andSameNeutral False = Refl | ||
| 8 | andSameNeutral True = Refl | ||
| 9 | |||
| 10 | andFalseFalse : (x : Bool) -> x && False = False | ||
| 11 | andFalseFalse False = Refl | ||
| 12 | andFalseFalse True = Refl | ||
| 13 | |||
| 14 | andTrueNeutral : (x : Bool) -> x && True = x | ||
| 15 | andTrueNeutral False = Refl | ||
| 16 | andTrueNeutral True = Refl | ||
| 17 | |||
| 18 | orSameNeutral : (x : Bool) -> x || x = x | ||
| 19 | orSameNeutral False = Refl | ||
| 20 | orSameNeutral True = Refl | ||
| 21 | |||
| 22 | orFalseNeutral : (x : Bool) -> x || False = x | ||
| 23 | orFalseNeutral False = Refl | ||
| 24 | orFalseNeutral True = Refl | ||
| 25 | |||
| 26 | orTrueTrue : (x : Bool) -> x || True = True | ||
| 27 | orTrueTrue False = Refl | ||
| 28 | orTrueTrue True = Refl | ||
| 29 | |||
| 30 | orSameAndRightNeutral : (x, right : Bool) -> x || (x && right) = x | ||
| 31 | orSameAndRightNeutral False _ = Refl | ||
| 32 | orSameAndRightNeutral True _ = Refl | ||
| @@ -0,0 +1,303 @@ | |||
| 1 | module Data.StringMap | ||
| 2 | |||
| 3 | -- Hand specialised map, for efficiency... | ||
| 4 | |||
| 5 | %default covering | ||
| 6 | |||
| 7 | Key : Type | ||
| 8 | Key = String | ||
| 9 | |||
| 10 | -- TODO: write split | ||
| 11 | |||
| 12 | private | ||
| 13 | data Tree : Nat -> Type -> Type where | ||
| 14 | Leaf : Key -> v -> Tree Z v | ||
| 15 | Branch2 : Tree n v -> Key -> Tree n v -> Tree (S n) v | ||
| 16 | Branch3 : Tree n v -> Key -> Tree n v -> Key -> Tree n v -> Tree (S n) v | ||
| 17 | |||
| 18 | branch4 : | ||
| 19 | Tree n v -> Key -> | ||
| 20 | Tree n v -> Key -> | ||
| 21 | Tree n v -> Key -> | ||
| 22 | Tree n v -> | ||
| 23 | Tree (S (S n)) v | ||
| 24 | branch4 a b c d e f g = | ||
| 25 | Branch2 (Branch2 a b c) d (Branch2 e f g) | ||
| 26 | |||
| 27 | branch5 : | ||
| 28 | Tree n v -> Key -> | ||
| 29 | Tree n v -> Key -> | ||
| 30 | Tree n v -> Key -> | ||
| 31 | Tree n v -> Key -> | ||
| 32 | Tree n v -> | ||
| 33 | Tree (S (S n)) v | ||
| 34 | branch5 a b c d e f g h i = | ||
| 35 | Branch2 (Branch2 a b c) d (Branch3 e f g h i) | ||
| 36 | |||
| 37 | branch6 : | ||
| 38 | Tree n v -> Key -> | ||
| 39 | Tree n v -> Key -> | ||
| 40 | Tree n v -> Key -> | ||
| 41 | Tree n v -> Key -> | ||
| 42 | Tree n v -> Key -> | ||
| 43 | Tree n v -> | ||
| 44 | Tree (S (S n)) v | ||
| 45 | branch6 a b c d e f g h i j k = | ||
| 46 | Branch3 (Branch2 a b c) d (Branch2 e f g) h (Branch2 i j k) | ||
| 47 | |||
| 48 | branch7 : | ||
| 49 | Tree n v -> Key -> | ||
| 50 | Tree n v -> Key -> | ||
| 51 | Tree n v -> Key -> | ||
| 52 | Tree n v -> Key -> | ||
| 53 | Tree n v -> Key -> | ||
| 54 | Tree n v -> Key -> | ||
| 55 | Tree n v -> | ||
| 56 | Tree (S (S n)) v | ||
| 57 | branch7 a b c d e f g h i j k l m = | ||
| 58 | Branch3 (Branch3 a b c d e) f (Branch2 g h i) j (Branch2 k l m) | ||
| 59 | |||
| 60 | merge1 : Tree n v -> Key -> Tree (S n) v -> Key -> Tree (S n) v -> Tree (S (S n)) v | ||
| 61 | merge1 a b (Branch2 c d e) f (Branch2 g h i) = branch5 a b c d e f g h i | ||
| 62 | merge1 a b (Branch2 c d e) f (Branch3 g h i j k) = branch6 a b c d e f g h i j k | ||
| 63 | merge1 a b (Branch3 c d e f g) h (Branch2 i j k) = branch6 a b c d e f g h i j k | ||
| 64 | merge1 a b (Branch3 c d e f g) h (Branch3 i j k l m) = branch7 a b c d e f g h i j k l m | ||
| 65 | |||
| 66 | merge2 : Tree (S n) v -> Key -> Tree n v -> Key -> Tree (S n) v -> Tree (S (S n)) v | ||
| 67 | merge2 (Branch2 a b c) d e f (Branch2 g h i) = branch5 a b c d e f g h i | ||
| 68 | merge2 (Branch2 a b c) d e f (Branch3 g h i j k) = branch6 a b c d e f g h i j k | ||
| 69 | merge2 (Branch3 a b c d e) f g h (Branch2 i j k) = branch6 a b c d e f g h i j k | ||
| 70 | merge2 (Branch3 a b c d e) f g h (Branch3 i j k l m) = branch7 a b c d e f g h i j k l m | ||
| 71 | |||
| 72 | merge3 : Tree (S n) v -> Key -> Tree (S n) v -> Key -> Tree n v -> Tree (S (S n)) v | ||
| 73 | merge3 (Branch2 a b c) d (Branch2 e f g) h i = branch5 a b c d e f g h i | ||
| 74 | merge3 (Branch2 a b c) d (Branch3 e f g h i) j k = branch6 a b c d e f g h i j k | ||
| 75 | merge3 (Branch3 a b c d e) f (Branch2 g h i) j k = branch6 a b c d e f g h i j k | ||
| 76 | merge3 (Branch3 a b c d e) f (Branch3 g h i j k) l m = branch7 a b c d e f g h i j k l m | ||
| 77 | |||
| 78 | treeLookup : Key -> Tree n v -> Maybe v | ||
| 79 | treeLookup k (Leaf k' v) = | ||
| 80 | if k == k' then | ||
| 81 | Just v | ||
| 82 | else | ||
| 83 | Nothing | ||
| 84 | treeLookup k (Branch2 t1 k' t2) = | ||
| 85 | if k <= k' then | ||
| 86 | treeLookup k t1 | ||
| 87 | else | ||
| 88 | treeLookup k t2 | ||
| 89 | treeLookup k (Branch3 t1 k1 t2 k2 t3) = | ||
| 90 | if k <= k1 then | ||
| 91 | treeLookup k t1 | ||
| 92 | else if k <= k2 then | ||
| 93 | treeLookup k t2 | ||
| 94 | else | ||
| 95 | treeLookup k t3 | ||
| 96 | |||
| 97 | treeInsert' : Key -> v -> Tree n v -> Either (Tree n v) (Tree n v, Key, Tree n v) | ||
| 98 | treeInsert' k v (Leaf k' v') = | ||
| 99 | case compare k k' of | ||
| 100 | LT => Right (Leaf k v, k, Leaf k' v') | ||
| 101 | EQ => Left (Leaf k v) | ||
| 102 | GT => Right (Leaf k' v', k', Leaf k v) | ||
| 103 | treeInsert' k v (Branch2 t1 k' t2) = | ||
| 104 | if k <= k' then | ||
| 105 | case treeInsert' k v t1 of | ||
| 106 | Left t1' => Left (Branch2 t1' k' t2) | ||
| 107 | Right (a, b, c) => Left (Branch3 a b c k' t2) | ||
| 108 | else | ||
| 109 | case treeInsert' k v t2 of | ||
| 110 | Left t2' => Left (Branch2 t1 k' t2') | ||
| 111 | Right (a, b, c) => Left (Branch3 t1 k' a b c) | ||
| 112 | treeInsert' k v (Branch3 t1 k1 t2 k2 t3) = | ||
| 113 | if k <= k1 then | ||
| 114 | case treeInsert' k v t1 of | ||
| 115 | Left t1' => Left (Branch3 t1' k1 t2 k2 t3) | ||
| 116 | Right (a, b, c) => Right (Branch2 a b c, k1, Branch2 t2 k2 t3) | ||
| 117 | else | ||
| 118 | if k <= k2 then | ||
| 119 | case treeInsert' k v t2 of | ||
| 120 | Left t2' => Left (Branch3 t1 k1 t2' k2 t3) | ||
| 121 | Right (a, b, c) => Right (Branch2 t1 k1 a, b, Branch2 c k2 t3) | ||
| 122 | else | ||
| 123 | case treeInsert' k v t3 of | ||
| 124 | Left t3' => Left (Branch3 t1 k1 t2 k2 t3') | ||
| 125 | Right (a, b, c) => Right (Branch2 t1 k1 t2, k2, Branch2 a b c) | ||
| 126 | |||
| 127 | treeInsert : Key -> v -> Tree n v -> Either (Tree n v) (Tree (S n) v) | ||
| 128 | treeInsert k v t = | ||
| 129 | case treeInsert' k v t of | ||
| 130 | Left t' => Left t' | ||
| 131 | Right (a, b, c) => Right (Branch2 a b c) | ||
| 132 | |||
| 133 | delType : Nat -> Type -> Type | ||
| 134 | delType Z v = () | ||
| 135 | delType (S n) v = Tree n v | ||
| 136 | |||
| 137 | treeDelete : Key -> Tree n v -> Either (Tree n v) (delType n v) | ||
| 138 | treeDelete k (Leaf k' v) = | ||
| 139 | if k == k' then | ||
| 140 | Right () | ||
| 141 | else | ||
| 142 | Left (Leaf k' v) | ||
| 143 | treeDelete {n=S Z} k (Branch2 t1 k' t2) = | ||
| 144 | if k <= k' then | ||
| 145 | case treeDelete k t1 of | ||
| 146 | Left t1' => Left (Branch2 t1' k' t2) | ||
| 147 | Right () => Right t2 | ||
| 148 | else | ||
| 149 | case treeDelete k t2 of | ||
| 150 | Left t2' => Left (Branch2 t1 k' t2') | ||
| 151 | Right () => Right t1 | ||
| 152 | treeDelete {n=S Z} k (Branch3 t1 k1 t2 k2 t3) = | ||
| 153 | if k <= k1 then | ||
| 154 | case treeDelete k t1 of | ||
| 155 | Left t1' => Left (Branch3 t1' k1 t2 k2 t3) | ||
| 156 | Right () => Left (Branch2 t2 k2 t3) | ||
| 157 | else if k <= k2 then | ||
| 158 | case treeDelete k t2 of | ||
| 159 | Left t2' => Left (Branch3 t1 k1 t2' k2 t3) | ||
| 160 | Right () => Left (Branch2 t1 k1 t3) | ||
| 161 | else | ||
| 162 | case treeDelete k t3 of | ||
| 163 | Left t3' => Left (Branch3 t1 k1 t2 k2 t3') | ||
| 164 | Right () => Left (Branch2 t1 k1 t2) | ||
| 165 | treeDelete {n=S (S _)} k (Branch2 t1 k' t2) = | ||
| 166 | if k <= k' then | ||
| 167 | case treeDelete k t1 of | ||
| 168 | Left t1' => Left (Branch2 t1' k' t2) | ||
| 169 | Right t1' => | ||
| 170 | case t2 of | ||
| 171 | Branch2 a b c => Right (Branch3 t1' k' a b c) | ||
| 172 | Branch3 a b c d e => Left (branch4 t1' k' a b c d e) | ||
| 173 | else | ||
| 174 | case treeDelete k t2 of | ||
| 175 | Left t2' => Left (Branch2 t1 k' t2') | ||
| 176 | Right t2' => | ||
| 177 | case t1 of | ||
| 178 | Branch2 a b c => Right (Branch3 a b c k' t2') | ||
| 179 | Branch3 a b c d e => Left (branch4 a b c d e k' t2') | ||
| 180 | treeDelete {n=(S (S _))} k (Branch3 t1 k1 t2 k2 t3) = | ||
| 181 | if k <= k1 then | ||
| 182 | case treeDelete k t1 of | ||
| 183 | Left t1' => Left (Branch3 t1' k1 t2 k2 t3) | ||
| 184 | Right t1' => Left (merge1 t1' k1 t2 k2 t3) | ||
| 185 | else if k <= k2 then | ||
| 186 | case treeDelete k t2 of | ||
| 187 | Left t2' => Left (Branch3 t1 k1 t2' k2 t3) | ||
| 188 | Right t2' => Left (merge2 t1 k1 t2' k2 t3) | ||
| 189 | else | ||
| 190 | case treeDelete k t3 of | ||
| 191 | Left t3' => Left (Branch3 t1 k1 t2 k2 t3') | ||
| 192 | Right t3' => Left (merge3 t1 k1 t2 k2 t3') | ||
| 193 | |||
| 194 | treeToList : Tree n v -> List (Key, v) | ||
| 195 | treeToList = treeToList' (:: []) | ||
| 196 | where | ||
| 197 | treeToList' : ((Key, v) -> List (Key, v)) -> Tree n v -> List (Key, v) | ||
| 198 | treeToList' cont (Leaf k v) = cont (k, v) | ||
| 199 | treeToList' cont (Branch2 t1 _ t2) = treeToList' (:: treeToList' cont t2) t1 | ||
| 200 | treeToList' cont (Branch3 t1 _ t2 _ t3) = treeToList' (:: treeToList' (:: treeToList' cont t3) t2) t1 | ||
| 201 | |||
| 202 | export | ||
| 203 | data StringMap : Type -> Type where | ||
| 204 | Empty : StringMap v | ||
| 205 | M : (n : Nat) -> Tree n v -> StringMap v | ||
| 206 | |||
| 207 | export | ||
| 208 | empty : StringMap v | ||
| 209 | empty = Empty | ||
| 210 | |||
| 211 | export | ||
| 212 | lookup : String -> StringMap v -> Maybe v | ||
| 213 | lookup _ Empty = Nothing | ||
| 214 | lookup k (M _ t) = treeLookup k t | ||
| 215 | |||
| 216 | export | ||
| 217 | insert : String -> v -> StringMap v -> StringMap v | ||
| 218 | insert k v Empty = M Z (Leaf k v) | ||
| 219 | insert k v (M _ t) = | ||
| 220 | case treeInsert k v t of | ||
| 221 | Left t' => (M _ t') | ||
| 222 | Right t' => (M _ t') | ||
| 223 | |||
| 224 | export | ||
| 225 | insertFrom : Foldable f => f (String, v) -> StringMap v -> StringMap v | ||
| 226 | insertFrom = flip $ foldl $ flip $ uncurry insert | ||
| 227 | |||
| 228 | export | ||
| 229 | delete : String -> StringMap v -> StringMap v | ||
| 230 | delete _ Empty = Empty | ||
| 231 | delete k (M Z t) = | ||
| 232 | case treeDelete k t of | ||
| 233 | Left t' => (M _ t') | ||
| 234 | Right () => Empty | ||
| 235 | delete k (M (S _) t) = | ||
| 236 | case treeDelete k t of | ||
| 237 | Left t' => (M _ t') | ||
| 238 | Right t' => (M _ t') | ||
| 239 | |||
| 240 | export | ||
| 241 | fromList : List (String, v) -> StringMap v | ||
| 242 | fromList l = foldl (flip (uncurry insert)) empty l | ||
| 243 | |||
| 244 | export | ||
| 245 | toList : StringMap v -> List (String, v) | ||
| 246 | toList Empty = [] | ||
| 247 | toList (M _ t) = treeToList t | ||
| 248 | |||
| 249 | ||| Gets the Keyeys of the map. | ||
| 250 | export | ||
| 251 | keys : StringMap v -> List String | ||
| 252 | keys = map fst . toList | ||
| 253 | |||
| 254 | ||| Gets the values of the map. Could contain duplicates. | ||
| 255 | export | ||
| 256 | values : StringMap v -> List v | ||
| 257 | values = map snd . toList | ||
| 258 | |||
| 259 | treeMap : (a -> b) -> Tree n a -> Tree n b | ||
| 260 | treeMap f (Leaf k v) = Leaf k (f v) | ||
| 261 | treeMap f (Branch2 t1 k t2) = Branch2 (treeMap f t1) k (treeMap f t2) | ||
| 262 | treeMap f (Branch3 t1 k1 t2 k2 t3) | ||
| 263 | = Branch3 (treeMap f t1) k1 (treeMap f t2) k2 (treeMap f t3) | ||
| 264 | |||
| 265 | export | ||
| 266 | implementation Functor (StringMap) where | ||
| 267 | map _ Empty = Empty | ||
| 268 | map f (M n t) = M _ (treeMap f t) | ||
| 269 | |||
| 270 | ||| Merge two maps. When encountering duplicate keys, using a function to combine the values. | ||
| 271 | ||| Uses the ordering of the first map given. | ||
| 272 | export | ||
| 273 | mergeWith : (v -> v -> v) -> StringMap v -> StringMap v -> StringMap v | ||
| 274 | mergeWith f x y = insertFrom inserted x where | ||
| 275 | inserted : List (Key, v) | ||
| 276 | inserted = do | ||
| 277 | (k, v) <- toList y | ||
| 278 | let v' = (maybe id f $ lookup k x) v | ||
| 279 | pure (k, v') | ||
| 280 | |||
| 281 | ||| Merge two maps using the Semigroup (and by extension, Monoid) operation. | ||
| 282 | ||| Uses mergeWith internally, so the ordering of the left map is kept. | ||
| 283 | export | ||
| 284 | merge : Semigroup v => StringMap v -> StringMap v -> StringMap v | ||
| 285 | merge = mergeWith (<+>) | ||
| 286 | |||
| 287 | ||| Left-biased merge, also keeps the ordering specified by the left map. | ||
| 288 | export | ||
| 289 | mergeLeft : StringMap v -> StringMap v -> StringMap v | ||
| 290 | mergeLeft = mergeWith const | ||
| 291 | |||
| 292 | -- TODO: is this the right variant of merge to use for this? I think it is, but | ||
| 293 | -- I could also see the advantages of using `mergeLeft`. The current approach is | ||
| 294 | -- strictly more powerful I believe, because `mergeLeft` can be emulated with | ||
| 295 | -- the `First` monoid. However, this does require more code to do the same | ||
| 296 | -- thing. | ||
| 297 | export | ||
| 298 | Semigroup v => Semigroup (StringMap v) where | ||
| 299 | (<+>) = merge | ||
| 300 | |||
| 301 | export | ||
| 302 | (Semigroup v) => Monoid (StringMap v) where | ||
| 303 | neutral = empty | ||
| @@ -0,0 +1,176 @@ | |||
| 1 | module Parser.Lexer | ||
| 2 | |||
| 3 | import public Text.Lexer | ||
| 4 | |||
| 5 | %default total | ||
| 6 | |||
| 7 | public export | ||
| 8 | data Token = Ident String | ||
| 9 | | HoleIdent String | ||
| 10 | | Literal Integer | ||
| 11 | | StrLit String | ||
| 12 | | CharLit String | ||
| 13 | | DoubleLit Double | ||
| 14 | | Symbol String | ||
| 15 | | Keyword String | ||
| 16 | | Unrecognised String | ||
| 17 | | Comment String | ||
| 18 | | DocComment String | ||
| 19 | | CGDirective String | ||
| 20 | | EndInput | ||
| 21 | |||
| 22 | export | ||
| 23 | Show Token where | ||
| 24 | show (Ident x) = "identifier " ++ x | ||
| 25 | show (HoleIdent x) = "hole identifier " ++ x | ||
| 26 | show (Literal x) = "literal " ++ show x | ||
| 27 | show (StrLit x) = "string " ++ show x | ||
| 28 | show (CharLit x) = "character " ++ show x | ||
| 29 | show (DoubleLit x) = "double " ++ show x | ||
| 30 | show (Symbol x) = "symbol " ++ x | ||
| 31 | show (Keyword x) = x | ||
| 32 | show (Unrecognised x) = "Unrecognised " ++ x | ||
| 33 | show (Comment x) = "comment" | ||
| 34 | show (DocComment x) = "doc comment" | ||
| 35 | show (CGDirective x) = "CGDirective " ++ x | ||
| 36 | show EndInput = "end of input" | ||
| 37 | |||
| 38 | export | ||
| 39 | Show (TokenData Token) where | ||
| 40 | show t = show (line t, col t, tok t) | ||
| 41 | |||
| 42 | comment : Lexer | ||
| 43 | comment = is '-' <+> is '-' <+> many (isNot '\n') | ||
| 44 | |||
| 45 | toEndComment : (k : Nat) -> Recognise (k /= 0) | ||
| 46 | toEndComment Z = empty | ||
| 47 | toEndComment (S k) | ||
| 48 | = some (pred (\c => c /= '-' && c /= '{')) | ||
| 49 | <+> toEndComment (S k) | ||
| 50 | <|> is '{' <+> is '-' <+> toEndComment (S (S k)) | ||
| 51 | <|> is '-' <+> is '}' <+> toEndComment k | ||
| 52 | <|> is '{' <+> toEndComment (S k) | ||
| 53 | <|> is '-' <+> toEndComment (S k) | ||
| 54 | |||
| 55 | blockComment : Lexer | ||
| 56 | blockComment = is '{' <+> is '-' <+> toEndComment 1 | ||
| 57 | |||
| 58 | docComment : Lexer | ||
| 59 | docComment = is '|' <+> is '|' <+> is '|' <+> many (isNot '\n') | ||
| 60 | |||
| 61 | ident : Lexer | ||
| 62 | ident = pred startIdent <+> many (pred validIdent) | ||
| 63 | where | ||
| 64 | startIdent : Char -> Bool | ||
| 65 | startIdent '_' = True | ||
| 66 | startIdent x = isAlpha x | ||
| 67 | |||
| 68 | validIdent : Char -> Bool | ||
| 69 | validIdent '_' = True | ||
| 70 | validIdent '\'' = True | ||
| 71 | validIdent x = isAlphaNum x | ||
| 72 | |||
| 73 | holeIdent : Lexer | ||
| 74 | holeIdent = is '?' <+> ident | ||
| 75 | |||
| 76 | doubleLit : Lexer | ||
| 77 | doubleLit | ||
| 78 | = digits <+> is '.' <+> digits <+> opt | ||
| 79 | (is 'e' <+> opt (is '-' <|> is '+') <+> digits) | ||
| 80 | |||
| 81 | -- Do this as an entire token, because the contents will be processed by | ||
| 82 | -- a specific back end | ||
| 83 | cgDirective : Lexer | ||
| 84 | cgDirective | ||
| 85 | = exact "%cg" <+> | ||
| 86 | ((some space <+> | ||
| 87 | some (pred isAlphaNum) <+> many space <+> | ||
| 88 | is '{' <+> many (isNot '}') <+> | ||
| 89 | is '}') | ||
| 90 | <|> many (isNot '\n')) | ||
| 91 | |||
| 92 | mkDirective : String -> Token | ||
| 93 | mkDirective str = CGDirective (trim (substr 3 (length str) str)) | ||
| 94 | |||
| 95 | -- Reserved words | ||
| 96 | keywords : List String | ||
| 97 | keywords = ["data", "module", "where", "let", "in", "do", "record", | ||
| 98 | "auto", "implicit", "mutual", "namespace", "parameters", | ||
| 99 | "with", "impossible", "case", "of", | ||
| 100 | "if", "then", "else", "forall", "rewrite", | ||
| 101 | "using", "interface", "implementation", "open", "import", | ||
| 102 | "public", "export", "private", | ||
| 103 | "infixl", "infixr", "infix", "prefix", | ||
| 104 | "Type", "Int", "Integer", "String", "Char", "Double", | ||
| 105 | "total", "partial", "covering"] | ||
| 106 | |||
| 107 | -- Reserved words for internal syntax | ||
| 108 | special : List String | ||
| 109 | special = ["%lam", "%pi", "%imppi", "%let"] | ||
| 110 | |||
| 111 | -- Special symbols - things which can't be a prefix of another symbol, and | ||
| 112 | -- don't match 'validSymbol' | ||
| 113 | export | ||
| 114 | symbols : List String | ||
| 115 | symbols | ||
| 116 | = [".(", -- for things such as Foo.Bar.(+) | ||
| 117 | "@{", | ||
| 118 | "(", ")", "{", "}", "[", "]", ",", ";", "_", | ||
| 119 | "`(", "`", "~"] | ||
| 120 | |||
| 121 | export | ||
| 122 | opChars : String | ||
| 123 | opChars = ":!#$%&*+./<=>?@\\^|-~" | ||
| 124 | |||
| 125 | validSymbol : Lexer | ||
| 126 | validSymbol = some (oneOf opChars) | ||
| 127 | |||
| 128 | -- Valid symbols which have a special meaning so can't be operators | ||
| 129 | export | ||
| 130 | reservedSymbols : List String | ||
| 131 | reservedSymbols | ||
| 132 | = symbols ++ | ||
| 133 | ["%", "\\", ":", "=", "|", "|||", "<-", "->", "=>", "?", "&", "**"] | ||
| 134 | |||
| 135 | symbolChar : Char -> Bool | ||
| 136 | symbolChar c = c `elem` unpack opChars | ||
| 137 | |||
| 138 | rawTokens : TokenMap Token | ||
| 139 | rawTokens = | ||
| 140 | [(comment, Comment), | ||
| 141 | (blockComment, Comment), | ||
| 142 | (docComment, DocComment), | ||
| 143 | (cgDirective, mkDirective), | ||
| 144 | (holeIdent, \x => HoleIdent (assert_total (strTail x)))] ++ | ||
| 145 | map (\x => (exact x, Symbol)) symbols ++ | ||
| 146 | [(doubleLit, \x => DoubleLit (cast x)), | ||
| 147 | (digits, \x => Literal (cast x)), | ||
| 148 | (stringLit, \x => StrLit (stripQuotes x)), | ||
| 149 | (charLit, \x => CharLit (stripQuotes x)), | ||
| 150 | (ident, \x => if x `elem` keywords then Keyword x else Ident x), | ||
| 151 | (space, Comment), | ||
| 152 | (validSymbol, Symbol), | ||
| 153 | (symbol, Unrecognised)] | ||
| 154 | where | ||
| 155 | stripQuotes : String -> String | ||
| 156 | -- ASSUMPTION! Only total because we know we're getting quoted strings. | ||
| 157 | stripQuotes = assert_total (strTail . reverse . strTail . reverse) | ||
| 158 | |||
| 159 | export | ||
| 160 | lex : String -> Either (Int, Int, String) (List (TokenData Token)) | ||
| 161 | lex str | ||
| 162 | = case lex rawTokens str of | ||
| 163 | -- Add the EndInput token so that we'll have a line and column | ||
| 164 | -- number to read when storing spans in the file | ||
| 165 | (tok, (l, c, "")) => Right (filter notComment tok ++ | ||
| 166 | [MkToken l c EndInput]) | ||
| 167 | (_, fail) => Left fail | ||
| 168 | where | ||
| 169 | notComment : TokenData Token -> Bool | ||
| 170 | notComment t = case tok t of | ||
| 171 | Comment _ => False | ||
| 172 | DocComment _ => False -- TODO! | ||
| 173 | _ => True | ||
| 174 | |||
| 175 | testLex : String -> String | ||
| 176 | testLex inp = show (Lexer.lex inp) | ||
| @@ -0,0 +1,521 @@ | |||
| 1 | module Parser.Support | ||
| 2 | |||
| 3 | import public Text.Lexer | ||
| 4 | import public Parser.Lexer | ||
| 5 | import public Text.Parser | ||
| 6 | |||
| 7 | import Core.TT | ||
| 8 | import Data.List.Views | ||
| 9 | |||
| 10 | %default total | ||
| 11 | |||
| 12 | public export | ||
| 13 | Rule : Type -> Type | ||
| 14 | Rule ty = Grammar (TokenData Token) True ty | ||
| 15 | |||
| 16 | public export | ||
| 17 | EmptyRule : Type -> Type | ||
| 18 | EmptyRule ty = Grammar (TokenData Token) False ty | ||
| 19 | |||
| 20 | public export | ||
| 21 | data ParseError = ParseFail String (Maybe (Int, Int)) (List Token) | ||
| 22 | | LexFail (Int, Int, String) | ||
| 23 | | FileFail FileError | ||
| 24 | |||
| 25 | export | ||
| 26 | Show ParseError where | ||
| 27 | show (ParseFail err loc toks) | ||
| 28 | = "Parse error: " ++ err ++ " (next tokens: " | ||
| 29 | ++ show (take 10 toks) ++ ")" | ||
| 30 | show (LexFail (c, l, str)) | ||
| 31 | = "Lex error at " ++ show (c, l) ++ " input: " ++ str | ||
| 32 | show (FileFail err) | ||
| 33 | = "File error: " ++ show err | ||
| 34 | |||
| 35 | export | ||
| 36 | eoi : EmptyRule () | ||
| 37 | eoi | ||
| 38 | = do nextIs "Expected end of input" (isEOI . tok) | ||
| 39 | pure () | ||
| 40 | where | ||
| 41 | isEOI : Token -> Bool | ||
| 42 | isEOI EndInput = True | ||
| 43 | isEOI _ = False | ||
| 44 | |||
| 45 | export | ||
| 46 | runParser : String -> Grammar (TokenData Token) e ty -> Either ParseError ty | ||
| 47 | runParser str p | ||
| 48 | = case lex str of | ||
| 49 | Left err => Left $ LexFail err | ||
| 50 | Right toks => | ||
| 51 | case parse p toks of | ||
| 52 | Left (Error err []) => | ||
| 53 | Left $ ParseFail err Nothing [] | ||
| 54 | Left (Error err (t :: ts)) => | ||
| 55 | Left $ ParseFail err (Just (line t, col t)) | ||
| 56 | (map tok (t :: ts)) | ||
| 57 | Right (val, _) => Right val | ||
| 58 | |||
| 59 | export | ||
| 60 | parseFile : (fn : String) -> Rule ty -> IO (Either ParseError ty) | ||
| 61 | parseFile fn p | ||
| 62 | = do Right str <- readFile fn | ||
| 63 | | Left err => pure (Left (FileFail err)) | ||
| 64 | pure (runParser str p) | ||
| 65 | |||
| 66 | |||
| 67 | -- Some basic parsers used by all the intermediate forms | ||
| 68 | |||
| 69 | export | ||
| 70 | location : EmptyRule (Int, Int) | ||
| 71 | location | ||
| 72 | = do tok <- peek | ||
| 73 | pure (line tok, col tok) | ||
| 74 | |||
| 75 | export | ||
| 76 | column : EmptyRule Int | ||
| 77 | column | ||
| 78 | = do (line, col) <- location | ||
| 79 | pure col | ||
| 80 | |||
| 81 | hex : Char -> Maybe Int | ||
| 82 | hex '0' = Just 0 | ||
| 83 | hex '1' = Just 1 | ||
| 84 | hex '2' = Just 2 | ||
| 85 | hex '3' = Just 3 | ||
| 86 | hex '4' = Just 4 | ||
| 87 | hex '5' = Just 5 | ||
| 88 | hex '6' = Just 6 | ||
| 89 | hex '7' = Just 7 | ||
| 90 | hex '8' = Just 8 | ||
| 91 | hex '9' = Just 9 | ||
| 92 | hex 'a' = Just 10 | ||
| 93 | hex 'b' = Just 11 | ||
| 94 | hex 'c' = Just 12 | ||
| 95 | hex 'd' = Just 13 | ||
| 96 | hex 'e' = Just 14 | ||
| 97 | hex 'f' = Just 15 | ||
| 98 | hex _ = Nothing | ||
| 99 | |||
| 100 | dec : Char -> Maybe Int | ||
| 101 | dec '0' = Just 0 | ||
| 102 | dec '1' = Just 1 | ||
| 103 | dec '2' = Just 2 | ||
| 104 | dec '3' = Just 3 | ||
| 105 | dec '4' = Just 4 | ||
| 106 | dec '5' = Just 5 | ||
| 107 | dec '6' = Just 6 | ||
| 108 | dec '7' = Just 7 | ||
| 109 | dec '8' = Just 8 | ||
| 110 | dec '9' = Just 9 | ||
| 111 | dec _ = Nothing | ||
| 112 | |||
| 113 | oct : Char -> Maybe Int | ||
| 114 | oct '0' = Just 0 | ||
| 115 | oct '1' = Just 1 | ||
| 116 | oct '2' = Just 2 | ||
| 117 | oct '3' = Just 3 | ||
| 118 | oct '4' = Just 4 | ||
| 119 | oct '5' = Just 5 | ||
| 120 | oct '6' = Just 6 | ||
| 121 | oct '7' = Just 7 | ||
| 122 | oct _ = Nothing | ||
| 123 | |||
| 124 | getEsc : String -> Maybe Char | ||
| 125 | getEsc "NUL" = Just '\NUL' | ||
| 126 | getEsc "SOH" = Just '\SOH' | ||
| 127 | getEsc "STX" = Just '\STX' | ||
| 128 | getEsc "ETX" = Just '\ETX' | ||
| 129 | getEsc "EOT" = Just '\EOT' | ||
| 130 | getEsc "ENQ" = Just '\ENQ' | ||
| 131 | getEsc "ACK" = Just '\ACK' | ||
| 132 | getEsc "BEL" = Just '\BEL' | ||
| 133 | getEsc "BS" = Just '\BS' | ||
| 134 | getEsc "HT" = Just '\HT' | ||
| 135 | getEsc "LF" = Just '\LF' | ||
| 136 | getEsc "VT" = Just '\VT' | ||
| 137 | getEsc "FF" = Just '\FF' | ||
| 138 | getEsc "CR" = Just '\CR' | ||
| 139 | getEsc "SO" = Just '\SO' | ||
| 140 | getEsc "SI" = Just '\SI' | ||
| 141 | getEsc "DLE" = Just '\DLE' | ||
| 142 | getEsc "DC1" = Just '\DC1' | ||
| 143 | getEsc "DC2" = Just '\DC2' | ||
| 144 | getEsc "DC3" = Just '\DC3' | ||
| 145 | getEsc "DC4" = Just '\DC4' | ||
| 146 | getEsc "NAK" = Just '\NAK' | ||
| 147 | getEsc "SYN" = Just '\SYN' | ||
| 148 | getEsc "ETB" = Just '\ETB' | ||
| 149 | getEsc "CAN" = Just '\CAN' | ||
| 150 | getEsc "EM" = Just '\EM' | ||
| 151 | getEsc "SUB" = Just '\SUB' | ||
| 152 | getEsc "ESC" = Just '\ESC' | ||
| 153 | getEsc "FS" = Just '\FS' | ||
| 154 | getEsc "GS" = Just '\GS' | ||
| 155 | getEsc "RS" = Just '\RS' | ||
| 156 | getEsc "US" = Just '\US' | ||
| 157 | getEsc "SP" = Just '\SP' | ||
| 158 | getEsc "DEL" = Just '\DEL' | ||
| 159 | getEsc str = Nothing | ||
| 160 | |||
| 161 | escape' : List Char -> Maybe (List Char) | ||
| 162 | escape' [] = pure [] | ||
| 163 | escape' ('\\' :: '\\' :: xs) = pure $ '\\' :: !(escape' xs) | ||
| 164 | escape' ('\\' :: '&' :: xs) = pure !(escape' xs) | ||
| 165 | escape' ('\\' :: 'a' :: xs) = pure $ '\a' :: !(escape' xs) | ||
| 166 | escape' ('\\' :: 'b' :: xs) = pure $ '\b' :: !(escape' xs) | ||
| 167 | escape' ('\\' :: 'f' :: xs) = pure $ '\f' :: !(escape' xs) | ||
| 168 | escape' ('\\' :: 'n' :: xs) = pure $ '\n' :: !(escape' xs) | ||
| 169 | escape' ('\\' :: 'r' :: xs) = pure $ '\r' :: !(escape' xs) | ||
| 170 | escape' ('\\' :: 't' :: xs) = pure $ '\t' :: !(escape' xs) | ||
| 171 | escape' ('\\' :: 'v' :: xs) = pure $ '\v' :: !(escape' xs) | ||
| 172 | escape' ('\\' :: '\'' :: xs) = pure $ '\'' :: !(escape' xs) | ||
| 173 | escape' ('\\' :: '\"' :: xs) = pure $ '\"' :: !(escape' xs) | ||
| 174 | escape' ('\\' :: 'x' :: xs) | ||
| 175 | = case span isHexDigit xs of | ||
| 176 | ([], rest) => assert_total (escape' rest) | ||
| 177 | (ds, rest) => pure $ cast !(toHex 1 (reverse ds)) :: | ||
| 178 | !(assert_total (escape' rest)) | ||
| 179 | where | ||
| 180 | toHex : Int -> List Char -> Maybe Int | ||
| 181 | toHex _ [] = Just 0 | ||
| 182 | toHex m (d :: ds) | ||
| 183 | = pure $ !(hex (toLower d)) * m + !(toHex (m*16) ds) | ||
| 184 | escape' ('\\' :: 'o' :: xs) | ||
| 185 | = case span isOctDigit xs of | ||
| 186 | ([], rest) => assert_total (escape' rest) | ||
| 187 | (ds, rest) => pure $ cast !(toOct 1 (reverse ds)) :: | ||
| 188 | !(assert_total (escape' rest)) | ||
| 189 | where | ||
| 190 | toOct : Int -> List Char -> Maybe Int | ||
| 191 | toOct _ [] = Just 0 | ||
| 192 | toOct m (d :: ds) | ||
| 193 | = pure $ !(oct (toLower d)) * m + !(toOct (m*8) ds) | ||
| 194 | escape' ('\\' :: xs) | ||
| 195 | = case span isDigit xs of | ||
| 196 | ([], (a :: b :: c :: rest)) => | ||
| 197 | case getEsc (pack (the (List _) [a, b, c])) of | ||
| 198 | Just v => Just (v :: !(assert_total (escape' rest))) | ||
| 199 | Nothing => case getEsc (pack (the (List _) [a, b])) of | ||
| 200 | Just v => Just (v :: !(assert_total (escape' (c :: rest)))) | ||
| 201 | Nothing => escape' xs | ||
| 202 | ([], (a :: b :: [])) => | ||
| 203 | case getEsc (pack (the (List _) [a, b])) of | ||
| 204 | Just v => Just (v :: []) | ||
| 205 | Nothing => escape' xs | ||
| 206 | ([], rest) => assert_total (escape' rest) | ||
| 207 | (ds, rest) => Just $ cast (cast {to=Int} (pack ds)) :: | ||
| 208 | !(assert_total (escape' rest)) | ||
| 209 | escape' (x :: xs) = Just $ x :: !(escape' xs) | ||
| 210 | |||
| 211 | escape : String -> Maybe String | ||
| 212 | escape x = pure $ pack !(escape' (unpack x)) | ||
| 213 | |||
| 214 | getCharLit : String -> Maybe Char | ||
| 215 | getCharLit str | ||
| 216 | = do e <- escape str | ||
| 217 | if length e == 1 | ||
| 218 | then Just (assert_total (strHead e)) | ||
| 219 | else if length e == 0 -- parsed the NULL character that terminated the string! | ||
| 220 | then Just '\0000' | ||
| 221 | else Nothing | ||
| 222 | |||
| 223 | export | ||
| 224 | constant : Rule Constant | ||
| 225 | constant | ||
| 226 | = terminal "Expected constant" | ||
| 227 | (\x => case tok x of | ||
| 228 | Literal i => Just (BI i) | ||
| 229 | StrLit s => case escape s of | ||
| 230 | Nothing => Nothing | ||
| 231 | Just s' => Just (Str s') | ||
| 232 | CharLit c => case getCharLit c of | ||
| 233 | Nothing => Nothing | ||
| 234 | Just c' => Just (Ch c') | ||
| 235 | DoubleLit d => Just (Db d) | ||
| 236 | Keyword "Int" => Just IntType | ||
| 237 | Keyword "Integer" => Just IntegerType | ||
| 238 | Keyword "String" => Just StringType | ||
| 239 | Keyword "Char" => Just CharType | ||
| 240 | Keyword "Double" => Just DoubleType | ||
| 241 | _ => Nothing) | ||
| 242 | |||
| 243 | export | ||
| 244 | intLit : Rule Integer | ||
| 245 | intLit | ||
| 246 | = terminal "Expected integer literal" | ||
| 247 | (\x => case tok x of | ||
| 248 | Literal i => Just i | ||
| 249 | _ => Nothing) | ||
| 250 | |||
| 251 | export | ||
| 252 | strLit : Rule String | ||
| 253 | strLit | ||
| 254 | = terminal "Expected string literal" | ||
| 255 | (\x => case tok x of | ||
| 256 | StrLit s => Just s | ||
| 257 | _ => Nothing) | ||
| 258 | |||
| 259 | export | ||
| 260 | symbol : String -> Rule () | ||
| 261 | symbol req | ||
| 262 | = terminal ("Expected '" ++ req ++ "'") | ||
| 263 | (\x => case tok x of | ||
| 264 | Symbol s => if s == req then Just () | ||
| 265 | else Nothing | ||
| 266 | _ => Nothing) | ||
| 267 | |||
| 268 | export | ||
| 269 | keyword : String -> Rule () | ||
| 270 | keyword req | ||
| 271 | = terminal ("Expected '" ++ req ++ "'") | ||
| 272 | (\x => case tok x of | ||
| 273 | Keyword s => if s == req then Just () | ||
| 274 | else Nothing | ||
| 275 | _ => Nothing) | ||
| 276 | |||
| 277 | export | ||
| 278 | exactIdent : String -> Rule () | ||
| 279 | exactIdent req | ||
| 280 | = terminal ("Expected " ++ req) | ||
| 281 | (\x => case tok x of | ||
| 282 | Ident s => if s == req then Just () | ||
| 283 | else Nothing | ||
| 284 | _ => Nothing) | ||
| 285 | |||
| 286 | export | ||
| 287 | operator : Rule String | ||
| 288 | operator | ||
| 289 | = terminal "Expected operator" | ||
| 290 | (\x => case tok x of | ||
| 291 | Symbol s => | ||
| 292 | if s `elem` reservedSymbols | ||
| 293 | then Nothing | ||
| 294 | else Just s | ||
| 295 | _ => Nothing) | ||
| 296 | |||
| 297 | identPart : Rule String | ||
| 298 | identPart | ||
| 299 | = terminal "Expected name" | ||
| 300 | (\x => case tok x of | ||
| 301 | Ident str => Just str | ||
| 302 | _ => Nothing) | ||
| 303 | |||
| 304 | export | ||
| 305 | namespace_ : Rule (List String) | ||
| 306 | namespace_ | ||
| 307 | = do ns <- sepBy1 (do col <- column | ||
| 308 | symbol "." | ||
| 309 | col' <- column | ||
| 310 | if (col' - col == 1) | ||
| 311 | then pure () | ||
| 312 | else fail "No whitepace allowed after namespace separator") | ||
| 313 | identPart | ||
| 314 | pure (reverse ns) -- innermost first, so reverse | ||
| 315 | |||
| 316 | export | ||
| 317 | unqualifiedName : Rule String | ||
| 318 | unqualifiedName = identPart | ||
| 319 | |||
| 320 | export | ||
| 321 | holeName : Rule String | ||
| 322 | holeName | ||
| 323 | = terminal "Expected hole name" | ||
| 324 | (\x => case tok x of | ||
| 325 | HoleIdent str => Just str | ||
| 326 | _ => Nothing) | ||
| 327 | |||
| 328 | export | ||
| 329 | name : Rule Name | ||
| 330 | name | ||
| 331 | = do ns <- namespace_ | ||
| 332 | (do symbol ".(" | ||
| 333 | op <- operator | ||
| 334 | symbol ")" | ||
| 335 | pure (NS ns (UN op))) <|> | ||
| 336 | (pure (mkFullName ns)) | ||
| 337 | <|> do symbol "(" | ||
| 338 | op <- operator | ||
| 339 | symbol ")" | ||
| 340 | pure (UN op) | ||
| 341 | where | ||
| 342 | mkFullName : List String -> Name | ||
| 343 | mkFullName [] = UN "NONE" -- Can't happen :) | ||
| 344 | mkFullName [n] = UN n | ||
| 345 | mkFullName (n :: ns) = NS ns (UN n) | ||
| 346 | |||
| 347 | export | ||
| 348 | IndentInfo : Type | ||
| 349 | IndentInfo = Int | ||
| 350 | |||
| 351 | export | ||
| 352 | init : IndentInfo | ||
| 353 | init = 0 | ||
| 354 | |||
| 355 | continueF : EmptyRule () -> (indent : IndentInfo) -> EmptyRule () | ||
| 356 | continueF err indent | ||
| 357 | = do eoi; err | ||
| 358 | <|> do col <- column | ||
| 359 | if (col <= indent) | ||
| 360 | then err | ||
| 361 | else pure () | ||
| 362 | |||
| 363 | -- Fail if this is the end of a block entry or end of file | ||
| 364 | export | ||
| 365 | continue : (indent : IndentInfo) -> EmptyRule () | ||
| 366 | continue = continueF (fail "Unexpected end of expression") | ||
| 367 | |||
| 368 | -- As 'continue' but failing is fatal (i.e. entire parse fails) | ||
| 369 | export | ||
| 370 | mustContinue : (indent : IndentInfo) -> Maybe String -> EmptyRule () | ||
| 371 | mustContinue indent Nothing | ||
| 372 | = continueF (fatalError "Unexpected end of expression") indent | ||
| 373 | mustContinue indent (Just req) | ||
| 374 | = continueF (fatalError ("Expected '" ++ req ++ "'")) indent | ||
| 375 | |||
| 376 | data ValidIndent | ||
| 377 | = AnyIndent -- In {}, entries can begin in any column | ||
| 378 | | AtPos Int -- Entry must begin in a specific column | ||
| 379 | | AfterPos Int -- Entry can begin in this column or later | ||
| 380 | | EndOfBlock -- Block is finished | ||
| 381 | |||
| 382 | Show ValidIndent where | ||
| 383 | show AnyIndent = "[any]" | ||
| 384 | show (AtPos i) = "[col " ++ show i ++ "]" | ||
| 385 | show (AfterPos i) = "[after " ++ show i ++ "]" | ||
| 386 | show EndOfBlock = "[EOB]" | ||
| 387 | |||
| 388 | checkValid : ValidIndent -> Int -> EmptyRule () | ||
| 389 | checkValid AnyIndent c = pure () | ||
| 390 | checkValid (AtPos x) c = if c == x | ||
| 391 | then pure () | ||
| 392 | else fail "Invalid indentation" | ||
| 393 | checkValid (AfterPos x) c = if c >= x | ||
| 394 | then pure () | ||
| 395 | else fail "Invalid indentation" | ||
| 396 | checkValid EndOfBlock c = fail "End of block" | ||
| 397 | |||
| 398 | -- Any token which indicates the end of a statement/block | ||
| 399 | isTerminator : Token -> Bool | ||
| 400 | isTerminator (Symbol ",") = True | ||
| 401 | isTerminator (Symbol "]") = True | ||
| 402 | isTerminator (Symbol ";") = True | ||
| 403 | isTerminator (Symbol "}") = True | ||
| 404 | isTerminator (Symbol ")") = True | ||
| 405 | isTerminator (Symbol "|") = True | ||
| 406 | isTerminator (Keyword "in") = True | ||
| 407 | isTerminator EndInput = True | ||
| 408 | isTerminator _ = False | ||
| 409 | |||
| 410 | -- Check we're at the end of a block entry, given the start column | ||
| 411 | -- of the block. | ||
| 412 | -- It's the end if we have a terminating token, or the next token starts | ||
| 413 | -- in or before indent. Works by looking ahead but not consuming. | ||
| 414 | export | ||
| 415 | atEnd : (indent : IndentInfo) -> EmptyRule () | ||
| 416 | atEnd indent | ||
| 417 | = eoi | ||
| 418 | <|> do nextIs "Expected end of block" (isTerminator . tok) | ||
| 419 | pure () | ||
| 420 | <|> do col <- column | ||
| 421 | if (col <= indent) | ||
| 422 | then pure () | ||
| 423 | else fail "Not the end of a block entry" | ||
| 424 | |||
| 425 | -- Parse a terminator, return where the next block entry | ||
| 426 | -- must start, given where the current block entry started | ||
| 427 | terminator : ValidIndent -> Int -> EmptyRule ValidIndent | ||
| 428 | terminator valid laststart | ||
| 429 | = do eoi | ||
| 430 | pure EndOfBlock | ||
| 431 | <|> do symbol ";" | ||
| 432 | pure (afterSemi valid) | ||
| 433 | <|> do col <- column | ||
| 434 | afterDedent valid col | ||
| 435 | <|> pure EndOfBlock | ||
| 436 | where | ||
| 437 | -- Expected indentation for the next token can either be anything (if | ||
| 438 | -- we're inside a brace delimited block) or anywhere after the initial | ||
| 439 | -- column (if we're inside an indentation delimited block) | ||
| 440 | afterSemi : ValidIndent -> ValidIndent | ||
| 441 | afterSemi AnyIndent = AnyIndent -- in braces, anything goes | ||
| 442 | afterSemi (AtPos c) = AfterPos c -- not in braces, after the last start position | ||
| 443 | afterSemi (AfterPos c) = AfterPos c | ||
| 444 | afterSemi EndOfBlock = EndOfBlock | ||
| 445 | |||
| 446 | -- Expected indentation for the next token can either be anything (if | ||
| 447 | -- we're inside a brace delimited block) or in exactly the initial column | ||
| 448 | -- (if we're inside an indentation delimited block) | ||
| 449 | afterDedent : ValidIndent -> Int -> EmptyRule ValidIndent | ||
| 450 | afterDedent AnyIndent col | ||
| 451 | = if col <= laststart | ||
| 452 | then pure AnyIndent | ||
| 453 | else fail "Not the end of a block entry" | ||
| 454 | afterDedent (AfterPos c) col | ||
| 455 | = if col <= laststart | ||
| 456 | then pure (AtPos c) | ||
| 457 | else fail "Not the end of a block entry" | ||
| 458 | afterDedent (AtPos c) col | ||
| 459 | = if col <= laststart | ||
| 460 | then pure (AtPos c) | ||
| 461 | else fail "Not the end of a block entry" | ||
| 462 | afterDedent EndOfBlock col = pure EndOfBlock | ||
| 463 | |||
| 464 | -- Parse an entry in a block | ||
| 465 | blockEntry : ValidIndent -> (IndentInfo -> Rule ty) -> | ||
| 466 | Rule (ty, ValidIndent) | ||
| 467 | blockEntry valid rule | ||
| 468 | = do col <- column | ||
| 469 | checkValid valid col | ||
| 470 | p <- rule col | ||
| 471 | valid' <- terminator valid col | ||
| 472 | pure (p, valid') | ||
| 473 | |||
| 474 | blockEntries : ValidIndent -> (IndentInfo -> Rule ty) -> | ||
| 475 | EmptyRule (List ty) | ||
| 476 | blockEntries valid rule | ||
| 477 | = do eoi; pure [] | ||
| 478 | <|> do res <- blockEntry valid rule | ||
| 479 | ts <- blockEntries (snd res) rule | ||
| 480 | pure (fst res :: ts) | ||
| 481 | <|> pure [] | ||
| 482 | |||
| 483 | export | ||
| 484 | block : (IndentInfo -> Rule ty) -> EmptyRule (List ty) | ||
| 485 | block item | ||
| 486 | = do symbol "{" | ||
| 487 | commit | ||
| 488 | ps <- blockEntries AnyIndent item | ||
| 489 | symbol "}" | ||
| 490 | pure ps | ||
| 491 | <|> do col <- column | ||
| 492 | blockEntries (AtPos col) item | ||
| 493 | |||
| 494 | export | ||
| 495 | blockAfter : Int -> (IndentInfo -> Rule ty) -> EmptyRule (List ty) | ||
| 496 | blockAfter mincol item | ||
| 497 | = do symbol "{" | ||
| 498 | commit | ||
| 499 | ps <- blockEntries AnyIndent item | ||
| 500 | symbol "}" | ||
| 501 | pure ps | ||
| 502 | <|> do col <- column | ||
| 503 | if col <= mincol | ||
| 504 | then pure [] | ||
| 505 | else blockEntries (AtPos col) item | ||
| 506 | |||
| 507 | export | ||
| 508 | nonEmptyBlock : (IndentInfo -> Rule ty) -> Rule (List ty) | ||
| 509 | nonEmptyBlock item | ||
| 510 | = do symbol "{" | ||
| 511 | commit | ||
| 512 | res <- blockEntry AnyIndent item | ||
| 513 | ps <- blockEntries (snd res) item | ||
| 514 | symbol "}" | ||
| 515 | pure (fst res :: ps) | ||
| 516 | <|> do col <- column | ||
| 517 | res <- blockEntry (AtPos col) item | ||
| 518 | ps <- blockEntries (snd res) item | ||
| 519 | pure (fst res :: ps) | ||
| 520 | |||
| 521 | |||
| @@ -0,0 +1,324 @@ | |||
| 1 | module Text.Lexer | ||
| 2 | |||
| 3 | import Data.Bool.Extra | ||
| 4 | |||
| 5 | import public Text.Lexer.Core | ||
| 6 | import public Text.Quantity | ||
| 7 | import public Text.Token | ||
| 8 | |||
| 9 | %access export | ||
| 10 | %default total | ||
| 11 | |||
| 12 | toTokenMap : List (Lexer, k) -> TokenMap (Token k) | ||
| 13 | toTokenMap = map $ \(l, kind) => (l, Tok kind) | ||
| 14 | |||
| 15 | ||| Recognise any character. | ||
| 16 | ||| /./ | ||
| 17 | any : Lexer | ||
| 18 | any = pred (const True) | ||
| 19 | |||
| 20 | ||| Recognise a lexer or recognise no input. This is not guaranteed | ||
| 21 | ||| to consume input. | ||
| 22 | ||| /`l`?/ | ||
| 23 | opt : (l : Lexer) -> Recognise False | ||
| 24 | opt l = l <|> empty | ||
| 25 | |||
| 26 | ||| Recognise any character if the sub-lexer `l` fails. | ||
| 27 | ||| /(?!`l`)./ | ||
| 28 | non : (l : Lexer) -> Lexer | ||
| 29 | non l = reject l <+> any | ||
| 30 | |||
| 31 | ||| Produce recognisers by applying a function to elements of a container, and | ||
| 32 | ||| recognise the first match. Consumes input if the function produces consuming | ||
| 33 | ||| recognisers. Fails if the container is empty. | ||
| 34 | choiceMap : {c : Bool} -> | ||
| 35 | Foldable t => (a -> Recognise c) -> t a -> Recognise c | ||
| 36 | choiceMap {c} f xs = foldr (\x, acc => rewrite sym (andSameNeutral c) in | ||
| 37 | f x <|> acc) | ||
| 38 | fail xs | ||
| 39 | |||
| 40 | ||| Recognise the first matching recogniser in a container. Consumes input if | ||
| 41 | ||| recognisers in the list consume. Fails if the container is empty. | ||
| 42 | choice : Foldable t => t (Recognise c) -> Recognise c | ||
| 43 | choice = choiceMap id | ||
| 44 | |||
| 45 | ||| Sequence a list of recognisers. Guaranteed to consume input if the list is | ||
| 46 | ||| non-empty and the recognisers consume. | ||
| 47 | concat : (xs : List (Recognise c)) -> Recognise (c && isCons xs) | ||
| 48 | concat = concatMap id | ||
| 49 | |||
| 50 | ||| Recognise a specific character. | ||
| 51 | ||| /[`x`]/ | ||
| 52 | is : (x : Char) -> Lexer | ||
| 53 | is x = pred (==x) | ||
| 54 | |||
| 55 | ||| Recognise anything but the given character. | ||
| 56 | ||| /[\^`x`]/ | ||
| 57 | isNot : (x : Char) -> Lexer | ||
| 58 | isNot x = pred (/=x) | ||
| 59 | |||
| 60 | ||| Recognise a specific character (case-insensitive). | ||
| 61 | ||| /[`x`]/i | ||
| 62 | like : (x : Char) -> Lexer | ||
| 63 | like x = pred (\y => toUpper x == toUpper y) | ||
| 64 | |||
| 65 | ||| Recognise anything but the given character (case-insensitive). | ||
| 66 | ||| /[\^`x`]/i | ||
| 67 | notLike : (x : Char) -> Lexer | ||
| 68 | notLike x = pred (\y => toUpper x /= toUpper y) | ||
| 69 | |||
| 70 | ||| Recognise a specific string. | ||
| 71 | ||| Fails if the string is empty. | ||
| 72 | ||| /`str`/ | ||
| 73 | exact : (str : String) -> Lexer | ||
| 74 | exact str = case unpack str of | ||
| 75 | [] => fail | ||
| 76 | (x :: xs) => concatMap is (x :: xs) | ||
| 77 | |||
| 78 | ||| Recognise a specific string (case-insensitive). | ||
| 79 | ||| Fails if the string is empty. | ||
| 80 | ||| /`str`/i | ||
| 81 | approx : (str : String) -> Lexer | ||
| 82 | approx str = case unpack str of | ||
| 83 | [] => fail | ||
| 84 | (x :: xs) => concatMap like (x :: xs) | ||
| 85 | |||
| 86 | ||| Recognise any of the characters in the given string. | ||
| 87 | ||| /[`chars`]/ | ||
| 88 | oneOf : (chars : String) -> Lexer | ||
| 89 | oneOf chars = pred (\x => x `elem` unpack chars) | ||
| 90 | |||
| 91 | ||| Recognise a character range. Also works in reverse! | ||
| 92 | ||| /[`start`-`end`]/ | ||
| 93 | range : (start : Char) -> (end : Char) -> Lexer | ||
| 94 | range start end = pred (\x => (x >= min start end) | ||
| 95 | && (x <= max start end)) | ||
| 96 | |||
| 97 | mutual | ||
| 98 | ||| Recognise a sequence of at least one sub-lexers | ||
| 99 | ||| /`l`+/ | ||
| 100 | some : Lexer -> Lexer | ||
| 101 | some l = l <+> many l | ||
| 102 | |||
| 103 | ||| Recognise a sequence of at zero or more sub-lexers. This is not | ||
| 104 | ||| guaranteed to consume input | ||
| 105 | ||| /`l`\*/ | ||
| 106 | many : Lexer -> Recognise False | ||
| 107 | many l = opt (some l) | ||
| 108 | |||
| 109 | ||| Repeat the sub-lexer `l` zero or more times until the lexer | ||
| 110 | ||| `stopBefore` is encountered. `stopBefore` will not be consumed. | ||
| 111 | ||| Not guaranteed to consume input. | ||
| 112 | ||| /((?!`stopBefore`)`l`)\*/ | ||
| 113 | manyUntil : (stopBefore : Recognise c) -> (l : Lexer) -> Recognise False | ||
| 114 | manyUntil stopBefore l = many (reject stopBefore <+> l) | ||
| 115 | |||
| 116 | ||| Repeat the sub-lexer `l` zero or more times until the lexer | ||
| 117 | ||| `stopAfter` is encountered, and consume it. Guaranteed to | ||
| 118 | ||| consume if `stopAfter` consumes. | ||
| 119 | ||| /`l`\*?`stopAfter`/ | ||
| 120 | manyThen : (stopAfter : Recognise c) -> (l : Lexer) -> Recognise c | ||
| 121 | manyThen stopAfter l = manyUntil stopAfter l <+> stopAfter | ||
| 122 | |||
| 123 | ||| Recognise many instances of `l` until an instance of `end` is | ||
| 124 | ||| encountered. | ||
| 125 | ||| | ||
| 126 | ||| Useful for defining comments. | ||
| 127 | manyTill : (l : Lexer) -> (end : Lexer) -> Recognise False | ||
| 128 | manyTill l end = end <|> opt (l <+> manyTill l end) | ||
| 129 | %deprecate manyTill | ||
| 130 | "Prefer `lineComment`, or `manyUntil`/`manyThen` (argument order is flipped)." | ||
| 131 | |||
| 132 | ||| Recognise a sub-lexer repeated as specified by `q`. Fails if `q` has | ||
| 133 | ||| `min` and `max` in the wrong order. Consumes input unless `min q` is zero. | ||
| 134 | ||| /`l`{`q`}/ | ||
| 135 | count : (q : Quantity) -> (l : Lexer) -> Recognise (isSucc (min q)) | ||
| 136 | count (Qty Z Nothing) l = many l | ||
| 137 | count (Qty Z (Just Z)) _ = empty | ||
| 138 | count (Qty Z (Just (S max))) l = opt $ l <+> count (atMost max) l | ||
| 139 | count (Qty (S min) Nothing) l = l <+> count (atLeast min) l | ||
| 140 | count (Qty (S min) (Just Z)) _ = fail | ||
| 141 | count (Qty (S min) (Just (S max))) l = l <+> count (between min max) l | ||
| 142 | |||
| 143 | ||| Recognise a single digit 0-9 | ||
| 144 | ||| /[0-9]/ | ||
| 145 | digit : Lexer | ||
| 146 | digit = pred isDigit | ||
| 147 | |||
| 148 | ||| Recognise one or more digits | ||
| 149 | ||| /[0-9]+/ | ||
| 150 | digits : Lexer | ||
| 151 | digits = some digit | ||
| 152 | |||
| 153 | ||| Recognise a single hexidecimal digit | ||
| 154 | ||| /[0-9A-Fa-f]/ | ||
| 155 | hexDigit : Lexer | ||
| 156 | hexDigit = pred isHexDigit | ||
| 157 | |||
| 158 | ||| Recognise one or more hexidecimal digits | ||
| 159 | ||| /[0-9A-Fa-f]+/ | ||
| 160 | hexDigits : Lexer | ||
| 161 | hexDigits = some hexDigit | ||
| 162 | |||
| 163 | ||| Recognise a single octal digit | ||
| 164 | ||| /[0-8]/ | ||
| 165 | octDigit : Lexer | ||
| 166 | octDigit = pred isHexDigit | ||
| 167 | |||
| 168 | ||| Recognise one or more octal digits | ||
| 169 | ||| /[0-8]+/ | ||
| 170 | octDigits : Lexer | ||
| 171 | octDigits = some hexDigit | ||
| 172 | |||
| 173 | ||| Recognise a single alpha character | ||
| 174 | ||| /[A-Za-z]/ | ||
| 175 | alpha : Lexer | ||
| 176 | alpha = pred isAlpha | ||
| 177 | |||
| 178 | ||| Recognise one or more alpha characters | ||
| 179 | ||| /[A-Za-z]+/ | ||
| 180 | alphas : Lexer | ||
| 181 | alphas = some alpha | ||
| 182 | |||
| 183 | ||| Recognise a lowercase alpha character | ||
| 184 | ||| /[a-z]/ | ||
| 185 | lower : Lexer | ||
| 186 | lower = pred isLower | ||
| 187 | |||
| 188 | ||| Recognise one or more lowercase alpha characters | ||
| 189 | ||| /[a-z]+/ | ||
| 190 | lowers : Lexer | ||
| 191 | lowers = some lower | ||
| 192 | |||
| 193 | ||| Recognise an uppercase alpha character | ||
| 194 | ||| /[A-Z]/ | ||
| 195 | upper : Lexer | ||
| 196 | upper = pred isUpper | ||
| 197 | |||
| 198 | ||| Recognise one or more uppercase alpha characters | ||
| 199 | ||| /[A-Z]+/ | ||
| 200 | uppers : Lexer | ||
| 201 | uppers = some upper | ||
| 202 | |||
| 203 | ||| Recognise an alphanumeric character | ||
| 204 | ||| /[A-Za-z0-9]/ | ||
| 205 | alphaNum : Lexer | ||
| 206 | alphaNum = pred isAlphaNum | ||
| 207 | |||
| 208 | ||| Recognise one or more alphanumeric characters | ||
| 209 | ||| /[A-Za-z0-9]+/ | ||
| 210 | alphaNums : Lexer | ||
| 211 | alphaNums = some alphaNum | ||
| 212 | |||
| 213 | ||| Recognise a single whitespace character | ||
| 214 | ||| /\\s/ | ||
| 215 | space : Lexer | ||
| 216 | space = pred isSpace | ||
| 217 | |||
| 218 | ||| Recognise one or more whitespace characters | ||
| 219 | ||| /\\s+/ | ||
| 220 | spaces : Lexer | ||
| 221 | spaces = some space | ||
| 222 | |||
| 223 | ||| Recognise a single newline sequence. Understands CRLF, CR, and LF | ||
| 224 | ||| /\\r\\n|[\\r\\n]/ | ||
| 225 | newline : Lexer | ||
| 226 | newline = let crlf = "\r\n" in | ||
| 227 | exact crlf <|> oneOf crlf | ||
| 228 | |||
| 229 | ||| Recognise one or more newline sequences. Understands CRLF, CR, and LF | ||
| 230 | ||| /(\\r\\n|[\\r\\n])+)/ | ||
| 231 | newlines : Lexer | ||
| 232 | newlines = some newline | ||
| 233 | |||
| 234 | ||| Recognise a single non-whitespace, non-alphanumeric character | ||
| 235 | ||| /[\^\\sA-Za-z0-9]/ | ||
| 236 | symbol : Lexer | ||
| 237 | symbol = pred (\x => not (isSpace x || isAlphaNum x)) | ||
| 238 | |||
| 239 | ||| Recognise one or more non-whitespace, non-alphanumeric characters | ||
| 240 | ||| /[\^\\sA-Za-z0-9]+/ | ||
| 241 | symbols : Lexer | ||
| 242 | symbols = some symbol | ||
| 243 | |||
| 244 | ||| Recognise a single control character | ||
| 245 | ||| /[\\x00-\\x1f\\x7f-\\x9f]/ | ||
| 246 | control : Lexer | ||
| 247 | control = pred isControl | ||
| 248 | |||
| 249 | ||| Recognise one or more control characters | ||
| 250 | ||| /[\\x00-\\x1f\\x7f-\\x9f]+/ | ||
| 251 | controls : Lexer | ||
| 252 | controls = some control | ||
| 253 | |||
| 254 | ||| Recognise zero or more occurrences of a sub-lexer between | ||
| 255 | ||| delimiting lexers | ||
| 256 | ||| /`start`(`l`)\*?`end`/ | ||
| 257 | surround : (start : Lexer) -> (end : Lexer) -> (l : Lexer) -> Lexer | ||
| 258 | surround start end l = start <+> manyThen end l | ||
| 259 | |||
| 260 | ||| Recognise zero or more occurrences of a sub-lexer surrounded | ||
| 261 | ||| by the same quote lexer on both sides (useful for strings) | ||
| 262 | ||| /`q`(`l`)\*?`q`/ | ||
| 263 | quote : (q : Lexer) -> (l : Lexer) -> Lexer | ||
| 264 | quote q l = surround q q l | ||
| 265 | |||
| 266 | ||| Recognise an escape character (often '\\') followed by a sub-lexer | ||
| 267 | ||| /[`esc`]`l`/ | ||
| 268 | escape : (esc : Char) -> Lexer -> Lexer | ||
| 269 | escape esc l = is esc <+> l | ||
| 270 | |||
| 271 | ||| Recognise a string literal, including escaped characters. | ||
| 272 | ||| (Note: doesn't yet handle escape sequences such as \123) | ||
| 273 | ||| /"(\\\\.|.)\*?"/ | ||
| 274 | stringLit : Lexer | ||
| 275 | stringLit = quote (is '"') (escape '\\' any <|> any) | ||
| 276 | |||
| 277 | ||| Recognise a character literal, including escaped characters. | ||
| 278 | ||| (Note: doesn't yet handle escape sequences such as \123) | ||
| 279 | ||| /'(\\\\.|[\^'])'/ | ||
| 280 | charLit : Lexer | ||
| 281 | charLit = let q = '\'' in | ||
| 282 | is q <+> (escape '\\' (control <|> any) <|> isNot q) <+> is q | ||
| 283 | where | ||
| 284 | lexStr : List String -> Lexer | ||
| 285 | lexStr [] = fail | ||
| 286 | lexStr (t :: ts) = exact t <|> lexStr ts | ||
| 287 | |||
| 288 | control : Lexer | ||
| 289 | control = lexStr ["NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", | ||
| 290 | "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI", | ||
| 291 | "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", | ||
| 292 | "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US", | ||
| 293 | "SP", "DEL"] | ||
| 294 | <|> (is 'x' <+> hexDigits) | ||
| 295 | <|> (is 'o' <+> octDigits) | ||
| 296 | <|> digits | ||
| 297 | |||
| 298 | ||| Recognise an integer literal (possibly with a '-' prefix) | ||
| 299 | ||| /-?[0-9]+/ | ||
| 300 | intLit : Lexer | ||
| 301 | intLit = opt (is '-') <+> digits | ||
| 302 | |||
| 303 | ||| Recognise a hexidecimal literal, prefixed by "0x" or "0X" | ||
| 304 | ||| /0[Xx][0-9A-Fa-f]+/ | ||
| 305 | hexLit : Lexer | ||
| 306 | hexLit = approx "0x" <+> hexDigits | ||
| 307 | |||
| 308 | ||| Recognise `start`, then recognise all input until a newline is encountered, | ||
| 309 | ||| and consume the newline. Will succeed if end-of-input is encountered before | ||
| 310 | ||| a newline. | ||
| 311 | ||| /`start`[\^\\r\\n]+(\\r\\n|[\\r\\n])?/ | ||
| 312 | lineComment : (start : Lexer) -> Lexer | ||
| 313 | lineComment start = start <+> manyUntil newline any <+> opt newline | ||
| 314 | |||
| 315 | ||| Recognise all input between `start` and `end` lexers. | ||
| 316 | ||| Supports balanced nesting. | ||
| 317 | ||| | ||
| 318 | ||| For block comments that don't support nesting (such as C-style comments), | ||
| 319 | ||| use `surround` | ||
| 320 | blockComment : (start : Lexer) -> (end : Lexer) -> Lexer | ||
| 321 | blockComment start end = start <+> middle <+> end | ||
| 322 | where | ||
| 323 | middle : Recognise False | ||
| 324 | middle = manyUntil end (blockComment start end <|> any) | ||
| @@ -0,0 +1,192 @@ | |||
| 1 | module Text.Lexer.Core | ||
| 2 | |||
| 3 | import Data.Bool.Extra | ||
| 4 | |||
| 5 | import public Control.Delayed | ||
| 6 | |||
| 7 | %default total | ||
| 8 | |||
| 9 | ||| A language of token recognisers. | ||
| 10 | ||| @ consumes If `True`, this recogniser is guaranteed to consume at | ||
| 11 | ||| least one character of input when it succeeds. | ||
| 12 | export | ||
| 13 | data Recognise : (consumes : Bool) -> Type where | ||
| 14 | Empty : Recognise False | ||
| 15 | Fail : Recognise c | ||
| 16 | Lookahead : (positive : Bool) -> Recognise c -> Recognise False | ||
| 17 | Pred : (Char -> Bool) -> Recognise True | ||
| 18 | SeqEat : Recognise True -> Inf (Recognise e) -> Recognise True | ||
| 19 | SeqEmpty : Recognise e1 -> Recognise e2 -> Recognise (e1 || e2) | ||
| 20 | Alt : Recognise e1 -> Recognise e2 -> Recognise (e1 && e2) | ||
| 21 | |||
| 22 | ||| A token recogniser. Guaranteed to consume at least one character. | ||
| 23 | public export | ||
| 24 | Lexer : Type | ||
| 25 | Lexer = Recognise True | ||
| 26 | |||
| 27 | ||| Sequence two recognisers. If either consumes a character, the sequence | ||
| 28 | ||| is guaranteed to consume a character. | ||
| 29 | export %inline | ||
| 30 | (<+>) : {c1 : Bool} -> | ||
| 31 | Recognise c1 -> inf c1 (Recognise c2) -> Recognise (c1 || c2) | ||
| 32 | (<+>) {c1 = False} = SeqEmpty | ||
| 33 | (<+>) {c1 = True} = SeqEat | ||
| 34 | |||
| 35 | ||| Alternative recognisers. If both consume, the combination is guaranteed | ||
| 36 | ||| to consumer a character. | ||
| 37 | export | ||
| 38 | (<|>) : Recognise c1 -> Recognise c2 -> Recognise (c1 && c2) | ||
| 39 | (<|>) = Alt | ||
| 40 | |||
| 41 | ||| A recogniser that always fails. | ||
| 42 | export | ||
| 43 | fail : Recognise c | ||
| 44 | fail = Fail | ||
| 45 | |||
| 46 | ||| Recognise no input (doesn't consume any input) | ||
| 47 | export | ||
| 48 | empty : Recognise False | ||
| 49 | empty = Empty | ||
| 50 | |||
| 51 | ||| Recognise a character that matches a predicate | ||
| 52 | export | ||
| 53 | pred : (Char -> Bool) -> Lexer | ||
| 54 | pred = Pred | ||
| 55 | |||
| 56 | ||| Positive lookahead. Never consumes input. | ||
| 57 | export | ||
| 58 | expect : Recognise c -> Recognise False | ||
| 59 | expect = Lookahead True | ||
| 60 | |||
| 61 | ||| Negative lookahead. Never consumes input. | ||
| 62 | export | ||
| 63 | reject : Recognise c -> Recognise False | ||
| 64 | reject = Lookahead False | ||
| 65 | |||
| 66 | ||| Sequence the recognisers resulting from applying a function to each element | ||
| 67 | ||| of a list. The resulting recogniser will consume input if the produced | ||
| 68 | ||| recognisers consume and the list is non-empty. | ||
| 69 | export | ||
| 70 | concatMap : {c : Bool} -> | ||
| 71 | (a -> Recognise c) -> (xs : List a) -> Recognise (c && isCons xs) | ||
| 72 | concatMap {c} _ [] = rewrite andFalseFalse c in Empty | ||
| 73 | concatMap {c} f (x :: xs) = rewrite andTrueNeutral c in | ||
| 74 | rewrite sym (orSameAndRightNeutral c (isCons xs)) in | ||
| 75 | SeqEmpty (f x) (concatMap f xs) | ||
| 76 | |||
| 77 | data StrLen : Type where | ||
| 78 | MkStrLen : String -> Nat -> StrLen | ||
| 79 | |||
| 80 | getString : StrLen -> String | ||
| 81 | getString (MkStrLen str n) = str | ||
| 82 | |||
| 83 | strIndex : StrLen -> Nat -> Maybe Char | ||
| 84 | strIndex (MkStrLen str len) i | ||
| 85 | = if cast {to = Integer} i >= cast len then Nothing | ||
| 86 | else Just (assert_total (prim__strIndex str (cast i))) | ||
| 87 | |||
| 88 | mkStr : String -> StrLen | ||
| 89 | mkStr str = MkStrLen str (length str) | ||
| 90 | |||
| 91 | strTail : Nat -> StrLen -> StrLen | ||
| 92 | strTail start (MkStrLen str len) | ||
| 93 | = MkStrLen (substr start len str) (minus len start) | ||
| 94 | |||
| 95 | -- If the string is recognised, returns the index at which the token | ||
| 96 | -- ends | ||
| 97 | scan : Recognise c -> Nat -> StrLen -> Maybe Nat | ||
| 98 | scan Empty idx str = pure idx | ||
| 99 | scan Fail idx str = Nothing | ||
| 100 | scan (Lookahead positive r) idx str | ||
| 101 | = if isJust (scan r idx str) == positive | ||
| 102 | then Just idx | ||
| 103 | else Nothing | ||
| 104 | scan (Pred f) idx (MkStrLen str len) | ||
| 105 | = if cast {to = Integer} idx >= cast len | ||
| 106 | then Nothing | ||
| 107 | else if f (assert_total (prim__strIndex str (cast idx))) | ||
| 108 | then Just (idx + 1) | ||
| 109 | else Nothing | ||
| 110 | scan (SeqEat r1 r2) idx str | ||
| 111 | = do idx' <- scan r1 idx str | ||
| 112 | -- TODO: Can we prove totality instead by showing idx has increased? | ||
| 113 | assert_total (scan r2 idx' str) | ||
| 114 | scan (SeqEmpty r1 r2) idx str | ||
| 115 | = do idx' <- scan r1 idx str | ||
| 116 | scan r2 idx' str | ||
| 117 | scan (Alt r1 r2) idx str | ||
| 118 | = maybe (scan r2 idx str) Just (scan r1 idx str) | ||
| 119 | |||
| 120 | takeToken : Lexer -> StrLen -> Maybe (String, StrLen) | ||
| 121 | takeToken lex str | ||
| 122 | = do i <- scan lex 0 str -- i must be > 0 if successful | ||
| 123 | pure (substr 0 i (getString str), strTail i str) | ||
| 124 | |||
| 125 | ||| A mapping from lexers to the tokens they produce. | ||
| 126 | ||| This is a list of pairs `(Lexer, String -> tokenType)` | ||
| 127 | ||| For each Lexer in the list, if a substring in the input matches, run | ||
| 128 | ||| the associated function to produce a token of type `tokenType` | ||
| 129 | public export | ||
| 130 | TokenMap : (tokenType : Type) -> Type | ||
| 131 | TokenMap tokenType = List (Lexer, String -> tokenType) | ||
| 132 | |||
| 133 | ||| A token, and the line and column where it was in the input | ||
| 134 | public export | ||
| 135 | record TokenData a where | ||
| 136 | constructor MkToken | ||
| 137 | line : Int | ||
| 138 | col : Int | ||
| 139 | tok : a | ||
| 140 | |||
| 141 | fspanEnd : Nat -> (Char -> Bool) -> String -> (Nat, String) | ||
| 142 | fspanEnd k p "" = (k, "") | ||
| 143 | fspanEnd k p xxs | ||
| 144 | = assert_total $ | ||
| 145 | let x = prim__strHead xxs | ||
| 146 | xs = prim__strTail xxs in | ||
| 147 | if p x then fspanEnd (S k) p xs | ||
| 148 | else (k, xxs) | ||
| 149 | |||
| 150 | -- Faster version of 'span' from the prelude (avoids unpacking) | ||
| 151 | export | ||
| 152 | fspan : (Char -> Bool) -> String -> (String, String) | ||
| 153 | fspan p xs | ||
| 154 | = let (end, rest) = fspanEnd 0 p xs in | ||
| 155 | (substr 0 end xs, rest) | ||
| 156 | |||
| 157 | tokenise : (line : Int) -> (col : Int) -> | ||
| 158 | List (TokenData a) -> TokenMap a -> | ||
| 159 | StrLen -> (List (TokenData a), (Int, Int, StrLen)) | ||
| 160 | tokenise line col acc tmap str | ||
| 161 | = case getFirstToken tmap str of | ||
| 162 | Just (tok, line', col', rest) => | ||
| 163 | -- assert total because getFirstToken must consume something | ||
| 164 | assert_total (tokenise line' col' (tok :: acc) tmap rest) | ||
| 165 | Nothing => (reverse acc, (line, col, str)) | ||
| 166 | where | ||
| 167 | countNLs : List Char -> Nat | ||
| 168 | countNLs str = List.length (filter (== '\n') str) | ||
| 169 | |||
| 170 | getCols : String -> Int -> Int | ||
| 171 | getCols x c | ||
| 172 | = case fspan (/= '\n') (reverse x) of | ||
| 173 | (incol, "") => c + cast (length incol) | ||
| 174 | (incol, _) => cast (length incol) | ||
| 175 | |||
| 176 | getFirstToken : TokenMap a -> StrLen -> Maybe (TokenData a, Int, Int, StrLen) | ||
| 177 | getFirstToken [] str = Nothing | ||
| 178 | getFirstToken ((lex, fn) :: ts) str | ||
| 179 | = case takeToken lex str of | ||
| 180 | Just (tok, rest) => Just (MkToken line col (fn tok), | ||
| 181 | line + cast (countNLs (unpack tok)), | ||
| 182 | getCols tok col, rest) | ||
| 183 | Nothing => getFirstToken ts str | ||
| 184 | |||
| 185 | ||| Given a mapping from lexers to token generating functions (the | ||
| 186 | ||| TokenMap a) and an input string, return a list of recognised tokens, | ||
| 187 | ||| and the line, column, and remainder of the input at the first point in the | ||
| 188 | ||| string where there are no recognised tokens. | ||
| 189 | export | ||
| 190 | lex : TokenMap a -> String -> (List (TokenData a), (Int, Int, String)) | ||
| 191 | lex tmap str = let (ts, (l, c, str')) = tokenise 0 0 [] tmap (mkStr str) in | ||
| 192 | (ts, (l, c, getString str')) | ||
| @@ -0,0 +1,226 @@ | |||
| 1 | module Text.Parser | ||
| 2 | |||
| 3 | import Data.Bool.Extra | ||
| 4 | |||
| 5 | import public Text.Parser.Core | ||
| 6 | import public Text.Quantity | ||
| 7 | import public Text.Token | ||
| 8 | |||
| 9 | %access export | ||
| 10 | %default total | ||
| 11 | |||
| 12 | ||| Parse a terminal based on a kind of token. | ||
| 13 | match : (Eq k, TokenKind k) => | ||
| 14 | (kind : k) -> | ||
| 15 | Grammar (Token k) True (TokType kind) | ||
| 16 | match kind = terminal "Unrecognised input" $ | ||
| 17 | \(Tok kind' text) => if kind' == kind | ||
| 18 | then Just $ tokValue kind text | ||
| 19 | else Nothing | ||
| 20 | |||
| 21 | ||| Optionally parse a thing, with a default value if the grammar doesn't | ||
| 22 | ||| match. May match the empty input. | ||
| 23 | option : {c : Bool} -> | ||
| 24 | (def : a) -> (p : Grammar tok c a) -> | ||
| 25 | Grammar tok False a | ||
| 26 | option {c = False} def p = p <|> pure def | ||
| 27 | option {c = True} def p = p <|> pure def | ||
| 28 | |||
| 29 | ||| Optionally parse a thing. | ||
| 30 | ||| To provide a default value, use `option` instead. | ||
| 31 | optional : (p : Grammar tok c a) -> | ||
| 32 | Grammar tok False (Maybe a) | ||
| 33 | optional p = option Nothing (map Just p) | ||
| 34 | |||
| 35 | ||| Try to parse one thing or the other, producing a value that indicates | ||
| 36 | ||| which option succeeded. If both would succeed, the left option | ||
| 37 | ||| takes priority. | ||
| 38 | choose : (l : Grammar tok c1 a) -> | ||
| 39 | (r : Grammar tok c2 b) -> | ||
| 40 | Grammar tok (c1 && c2) (Either a b) | ||
| 41 | choose l r = map Left l <|> map Right r | ||
| 42 | |||
| 43 | ||| Produce a grammar by applying a function to each element of a container, | ||
| 44 | ||| then try each resulting grammar until the first one succeeds. Fails if the | ||
| 45 | ||| container is empty. | ||
| 46 | choiceMap : {c : Bool} -> | ||
| 47 | (a -> Grammar tok c b) -> | ||
| 48 | Foldable t => t a -> | ||
| 49 | Grammar tok c b | ||
| 50 | choiceMap {c} f xs = foldr (\x, acc => rewrite sym (andSameNeutral c) in | ||
| 51 | f x <|> acc) | ||
| 52 | (fail "No more options") xs | ||
| 53 | |||
| 54 | ||| Try each grammar in a container until the first one succeeds. | ||
| 55 | ||| Fails if the container is empty. | ||
| 56 | choice : Foldable t => t (Grammar tok c a) -> | ||
| 57 | Grammar tok c a | ||
| 58 | choice = choiceMap id | ||
| 59 | |||
| 60 | mutual | ||
| 61 | ||| Parse one or more things | ||
| 62 | some : Grammar tok True a -> | ||
| 63 | Grammar tok True (List a) | ||
| 64 | some p = pure (!p :: !(many p)) | ||
| 65 | |||
| 66 | ||| Parse zero or more things (may match the empty input) | ||
| 67 | many : Grammar tok True a -> | ||
| 68 | Grammar tok False (List a) | ||
| 69 | many p = option [] (some p) | ||
| 70 | |||
| 71 | ||| Parse one or more instances of `p`, returning the parsed items and proof | ||
| 72 | ||| that the resulting list is non-empty. | ||
| 73 | some' : (p : Grammar tok True a) -> | ||
| 74 | Grammar tok True (xs : List a ** NonEmpty xs) | ||
| 75 | some' p = pure (!p :: !(many p) ** IsNonEmpty) | ||
| 76 | |||
| 77 | mutual | ||
| 78 | private | ||
| 79 | count1 : (q : Quantity) -> | ||
| 80 | (p : Grammar tok True a) -> | ||
| 81 | Grammar tok True (List a) | ||
| 82 | count1 q p = do x <- p | ||
| 83 | seq (count q p) | ||
| 84 | (\xs => pure (x :: xs)) | ||
| 85 | |||
| 86 | ||| Parse `p`, repeated as specified by `q`, returning the list of values. | ||
| 87 | count : (q : Quantity) -> | ||
| 88 | (p : Grammar tok True a) -> | ||
| 89 | Grammar tok (isSucc (min q)) (List a) | ||
| 90 | count (Qty Z Nothing) p = many p | ||
| 91 | count (Qty Z (Just Z)) _ = pure [] | ||
| 92 | count (Qty Z (Just (S max))) p = option [] $ count1 (atMost max) p | ||
| 93 | count (Qty (S min) Nothing) p = count1 (atLeast min) p | ||
| 94 | count (Qty (S min) (Just Z)) _ = fail "Quantity out of order" | ||
| 95 | count (Qty (S min) (Just (S max))) p = count1 (between (S min) max) p | ||
| 96 | |||
| 97 | mutual | ||
| 98 | ||| Parse one or more instances of `p` until `end` succeeds, returning the | ||
| 99 | ||| list of values from `p`. Guaranteed to consume input. | ||
| 100 | someTill : {c : Bool} -> | ||
| 101 | (end : Grammar tok c e) -> | ||
| 102 | (p : Grammar tok True a) -> | ||
| 103 | Grammar tok True (List a) | ||
| 104 | someTill {c} end p = do x <- p | ||
| 105 | seq (manyTill end p) | ||
| 106 | (\xs => pure (x :: xs)) | ||
| 107 | |||
| 108 | ||| Parse zero or more instances of `p` until `end` succeeds, returning the | ||
| 109 | ||| list of values from `p`. Guaranteed to consume input if `end` consumes. | ||
| 110 | manyTill : {c : Bool} -> | ||
| 111 | (end : Grammar tok c e) -> | ||
| 112 | (p : Grammar tok True a) -> | ||
| 113 | Grammar tok c (List a) | ||
| 114 | manyTill {c} end p = rewrite sym (andTrueNeutral c) in | ||
| 115 | map (const []) end <|> someTill end p | ||
| 116 | |||
| 117 | ||| Parse one or more instances of `p` until `end` succeeds, returning the | ||
| 118 | ||| list of values from `p`, along with a proof that the resulting list is | ||
| 119 | ||| non-empty. | ||
| 120 | someTill' : {c : Bool} -> | ||
| 121 | (end : Grammar tok c e) -> | ||
| 122 | (p : Grammar tok True a) -> | ||
| 123 | Grammar tok True (xs : List a ** NonEmpty xs) | ||
| 124 | someTill' end p | ||
| 125 | = do x <- p | ||
| 126 | seq (manyTill end p) | ||
| 127 | (\xs => pure (x :: xs ** IsNonEmpty)) | ||
| 128 | |||
| 129 | mutual | ||
| 130 | ||| Parse one or more instance of `skip` until `p` is encountered, | ||
| 131 | ||| returning its value. | ||
| 132 | afterSome : (skip : Grammar tok True s) -> | ||
| 133 | (p : Grammar tok c a) -> | ||
| 134 | Grammar tok True a | ||
| 135 | afterSome skip p = do skip | ||
| 136 | afterMany skip p | ||
| 137 | |||
| 138 | ||| Parse zero or more instance of `skip` until `p` is encountered, | ||
| 139 | ||| returning its value. | ||
| 140 | afterMany : {c : Bool} -> | ||
| 141 | (skip : Grammar tok True s) -> | ||
| 142 | (p : Grammar tok c a) -> | ||
| 143 | Grammar tok c a | ||
| 144 | afterMany {c} skip p = rewrite sym (andTrueNeutral c) in | ||
| 145 | p <|> afterSome skip p | ||
| 146 | |||
| 147 | ||| Parse one or more things, each separated by another thing. | ||
| 148 | sepBy1 : {c : Bool} -> | ||
| 149 | (sep : Grammar tok True s) -> | ||
| 150 | (p : Grammar tok c a) -> | ||
| 151 | Grammar tok c (List a) | ||
| 152 | sepBy1 {c} sep p = rewrite sym (orFalseNeutral c) in | ||
| 153 | [| p :: many (sep *> p) |] | ||
| 154 | |||
| 155 | ||| Parse zero or more things, each separated by another thing. May | ||
| 156 | ||| match the empty input. | ||
| 157 | sepBy : (sep : Grammar tok True s) -> | ||
| 158 | (p : Grammar tok c a) -> | ||
| 159 | Grammar tok False (List a) | ||
| 160 | sepBy sep p = option [] $ sepBy1 sep p | ||
| 161 | |||
| 162 | ||| Parse one or more instances of `p` separated by `sep`, returning the | ||
| 163 | ||| parsed items and proof that the resulting list is non-empty. | ||
| 164 | sepBy1' : {c : Bool} -> | ||
| 165 | (sep : Grammar tok True s) -> | ||
| 166 | (p : Grammar tok c a) -> | ||
| 167 | Grammar tok c (xs : List a ** NonEmpty xs) | ||
| 168 | sepBy1' {c} sep p | ||
| 169 | = rewrite sym (orFalseNeutral c) in | ||
| 170 | seq p (\x => do xs <- many (sep *> p) | ||
| 171 | pure (x :: xs ** IsNonEmpty)) | ||
| 172 | |||
| 173 | ||| Parse one or more instances of `p` separated by and optionally terminated by | ||
| 174 | ||| `sep`. | ||
| 175 | sepEndBy1 : {c : Bool} -> | ||
| 176 | (sep : Grammar tok True s) -> | ||
| 177 | (p : Grammar tok c a) -> | ||
| 178 | Grammar tok c (List a) | ||
| 179 | sepEndBy1 {c} sep p = rewrite sym (orFalseNeutral c) in | ||
| 180 | sepBy1 sep p <* optional sep | ||
| 181 | |||
| 182 | ||| Parse zero or more instances of `p`, separated by and optionally terminated | ||
| 183 | ||| by `sep`. Will not match a separator by itself. | ||
| 184 | sepEndBy : (sep : Grammar tok True s) -> | ||
| 185 | (p : Grammar tok c a) -> | ||
| 186 | Grammar tok False (List a) | ||
| 187 | sepEndBy sep p = option [] $ sepEndBy1 sep p | ||
| 188 | |||
| 189 | ||| Parse zero or more instances of `p`, separated by and optionally terminated | ||
| 190 | ||| by `sep`, returning the parsed items and a proof that the resulting list | ||
| 191 | ||| is non-empty. | ||
| 192 | sepEndBy1' : {c : Bool} -> | ||
| 193 | (sep : Grammar tok True s) -> | ||
| 194 | (p : Grammar tok c a) -> | ||
| 195 | Grammar tok c (xs : List a ** NonEmpty xs) | ||
| 196 | sepEndBy1' {c} sep p = rewrite sym (orFalseNeutral c) in | ||
| 197 | sepBy1' sep p <* optional sep | ||
| 198 | |||
| 199 | ||| Parse one or more instances of `p`, separated and terminated by `sep`. | ||
| 200 | endBy1 : {c : Bool} -> | ||
| 201 | (sep : Grammar tok True s) -> | ||
| 202 | (p : Grammar tok c a) -> | ||
| 203 | Grammar tok True (List a) | ||
| 204 | endBy1 {c} sep p = some $ rewrite sym (orTrueTrue c) in | ||
| 205 | p <* sep | ||
| 206 | |||
| 207 | endBy : (sep : Grammar tok True s) -> | ||
| 208 | (p : Grammar tok c a) -> | ||
| 209 | Grammar tok False (List a) | ||
| 210 | endBy sep p = option [] $ endBy1 sep p | ||
| 211 | |||
| 212 | ||| Parse zero or more instances of `p`, separated and terminated by `sep`, | ||
| 213 | ||| returning the parsed items and a proof that the resulting list is non-empty. | ||
| 214 | endBy1' : {c : Bool} -> | ||
| 215 | (sep : Grammar tok True s) -> | ||
| 216 | (p : Grammar tok c a) -> | ||
| 217 | Grammar tok True (xs : List a ** NonEmpty xs) | ||
| 218 | endBy1' {c} sep p = some' $ rewrite sym (orTrueTrue c) in | ||
| 219 | p <* sep | ||
| 220 | |||
| 221 | ||| Parse an instance of `p` that is between `left` and `right`. | ||
| 222 | between : (left : Grammar tok True l) -> | ||
| 223 | (right : Grammar tok True r) -> | ||
| 224 | (p : Grammar tok c a) -> | ||
| 225 | Grammar tok True a | ||
| 226 | between left right contents = left *> contents <* right | ||
| @@ -0,0 +1,284 @@ | |||
| 1 | module Text.Parser.Core | ||
| 2 | |||
| 3 | import public Control.Delayed | ||
| 4 | |||
| 5 | %default total | ||
| 6 | |||
| 7 | -- TODO: Add some primitives for helping with error messages. | ||
| 8 | -- e.g. perhaps set a string to state what we're currently trying to | ||
| 9 | -- parse, or to say what the next expected token is in words | ||
| 10 | |||
| 11 | ||| Description of a language's grammar. The `tok` parameter is the type | ||
| 12 | ||| of tokens, and the `consumes` flag is True if the language is guaranteed | ||
| 13 | ||| to be non-empty - that is, successfully parsing the language is guaranteed | ||
| 14 | ||| to consume some input. | ||
| 15 | export | ||
| 16 | data Grammar : (tok : Type) -> (consumes : Bool) -> Type -> Type where | ||
| 17 | Empty : (val : ty) -> Grammar tok False ty | ||
| 18 | Terminal : String -> (tok -> Maybe a) -> Grammar tok True a | ||
| 19 | NextIs : String -> (tok -> Bool) -> Grammar tok False tok | ||
| 20 | EOF : Grammar tok False () | ||
| 21 | |||
| 22 | Fail : Bool -> String -> Grammar tok c ty | ||
| 23 | Commit : Grammar tok False () | ||
| 24 | MustWork : Grammar tok c a -> Grammar tok c a | ||
| 25 | |||
| 26 | SeqEat : Grammar tok True a -> Inf (a -> Grammar tok c2 b) -> | ||
| 27 | Grammar tok True b | ||
| 28 | SeqEmpty : {c1, c2 : Bool} -> | ||
| 29 | Grammar tok c1 a -> (a -> Grammar tok c2 b) -> | ||
| 30 | Grammar tok (c1 || c2) b | ||
| 31 | Alt : {c1, c2 : Bool} -> | ||
| 32 | Grammar tok c1 ty -> Grammar tok c2 ty -> | ||
| 33 | Grammar tok (c1 && c2) ty | ||
| 34 | |||
| 35 | ||| Sequence two grammars. If either consumes some input, the sequence is | ||
| 36 | ||| guaranteed to consume some input. If the first one consumes input, the | ||
| 37 | ||| second is allowed to be recursive (because it means some input has been | ||
| 38 | ||| consumed and therefore the input is smaller) | ||
| 39 | export %inline | ||
| 40 | (>>=) : {c1 : Bool} -> | ||
| 41 | Grammar tok c1 a -> | ||
| 42 | inf c1 (a -> Grammar tok c2 b) -> | ||
| 43 | Grammar tok (c1 || c2) b | ||
| 44 | (>>=) {c1 = False} = SeqEmpty | ||
| 45 | (>>=) {c1 = True} = SeqEat | ||
| 46 | |||
| 47 | ||| Sequence two grammars. If either consumes some input, the sequence is | ||
| 48 | ||| guaranteed to consume input. This is an explicitly non-infinite version | ||
| 49 | ||| of `>>=`. | ||
| 50 | export | ||
| 51 | seq : Grammar tok c1 a -> | ||
| 52 | (a -> Grammar tok c2 b) -> | ||
| 53 | Grammar tok (c1 || c2) b | ||
| 54 | seq = SeqEmpty | ||
| 55 | |||
| 56 | ||| Sequence a grammar followed by the grammar it returns. | ||
| 57 | export | ||
| 58 | join : {c1 : Bool} -> | ||
| 59 | Grammar tok c1 (Grammar tok c2 a) -> | ||
| 60 | Grammar tok (c1 || c2) a | ||
| 61 | join {c1 = False} p = SeqEmpty p id | ||
| 62 | join {c1 = True} p = SeqEat p id | ||
| 63 | |||
| 64 | ||| Give two alternative grammars. If both consume, the combination is | ||
| 65 | ||| guaranteed to consume. | ||
| 66 | export | ||
| 67 | (<|>) : Grammar tok c1 ty -> | ||
| 68 | Grammar tok c2 ty -> | ||
| 69 | Grammar tok (c1 && c2) ty | ||
| 70 | (<|>) = Alt | ||
| 71 | |||
| 72 | ||| Allows the result of a grammar to be mapped to a different value. | ||
| 73 | export | ||
| 74 | Functor (Grammar tok c) where | ||
| 75 | map f (Empty val) = Empty (f val) | ||
| 76 | map f (Fail fatal msg) = Fail fatal msg | ||
| 77 | map f (MustWork g) = MustWork (map f g) | ||
| 78 | map f (Terminal msg g) = Terminal msg (\t => map f (g t)) | ||
| 79 | map f (Alt x y) = Alt (map f x) (map f y) | ||
| 80 | map f (SeqEat act next) | ||
| 81 | = SeqEat act (\val => map f (next val)) | ||
| 82 | map f (SeqEmpty act next) | ||
| 83 | = SeqEmpty act (\val => map f (next val)) | ||
| 84 | -- The remaining constructors (NextIs, EOF, Commit) have a fixed type, | ||
| 85 | -- so a sequence must be used. | ||
| 86 | map {c = False} f p = SeqEmpty p (Empty . f) | ||
| 87 | |||
| 88 | ||| Sequence a grammar with value type `a -> b` and a grammar | ||
| 89 | ||| with value type `a`. If both succeed, apply the function | ||
| 90 | ||| from the first grammar to the value from the second grammar. | ||
| 91 | ||| Guaranteed to consume if either grammar consumes. | ||
| 92 | export | ||
| 93 | (<*>) : Grammar tok c1 (a -> b) -> | ||
| 94 | Grammar tok c2 a -> | ||
| 95 | Grammar tok (c1 || c2) b | ||
| 96 | (<*>) x y = SeqEmpty x (\f => map f y) | ||
| 97 | |||
| 98 | ||| Sequence two grammars. If both succeed, use the value of the first one. | ||
| 99 | ||| Guaranteed to consume if either grammar consumes. | ||
| 100 | export | ||
| 101 | (<*) : Grammar tok c1 a -> | ||
| 102 | Grammar tok c2 b -> | ||
| 103 | Grammar tok (c1 || c2) a | ||
| 104 | (<*) x y = map const x <*> y | ||
| 105 | |||
| 106 | ||| Sequence two grammars. If both succeed, use the value of the second one. | ||
| 107 | ||| Guaranteed to consume if either grammar consumes. | ||
| 108 | export | ||
| 109 | (*>) : Grammar tok c1 a -> | ||
| 110 | Grammar tok c2 b -> | ||
| 111 | Grammar tok (c1 || c2) b | ||
| 112 | (*>) x y = map (const id) x <*> y | ||
| 113 | |||
| 114 | ||| Produce a grammar that can parse a different type of token by providing a | ||
| 115 | ||| function converting the new token type into the original one. | ||
| 116 | export | ||
| 117 | mapToken : (a -> b) -> Grammar b c ty -> Grammar a c ty | ||
| 118 | mapToken f (Empty val) = Empty val | ||
| 119 | mapToken f (Terminal msg g) = Terminal msg (g . f) | ||
| 120 | mapToken f (NextIs msg g) = SeqEmpty (NextIs msg (g . f)) (Empty . f) | ||
| 121 | mapToken f EOF = EOF | ||
| 122 | mapToken f (Fail fatal msg) = Fail fatal msg | ||
| 123 | mapToken f (MustWork g) = MustWork (mapToken f g) | ||
| 124 | mapToken f Commit = Commit | ||
| 125 | mapToken f (SeqEat act next) = SeqEat (mapToken f act) (\x => mapToken f (next x)) | ||
| 126 | mapToken f (SeqEmpty act next) = SeqEmpty (mapToken f act) (\x => mapToken f (next x)) | ||
| 127 | mapToken f (Alt x y) = Alt (mapToken f x) (mapToken f y) | ||
| 128 | |||
| 129 | ||| Always succeed with the given value. | ||
| 130 | export | ||
| 131 | pure : (val : ty) -> Grammar tok False ty | ||
| 132 | pure = Empty | ||
| 133 | |||
| 134 | ||| Check whether the next token satisfies a predicate | ||
| 135 | export | ||
| 136 | nextIs : String -> (tok -> Bool) -> Grammar tok False tok | ||
| 137 | nextIs = NextIs | ||
| 138 | |||
| 139 | ||| Look at the next token in the input | ||
| 140 | export | ||
| 141 | peek : Grammar tok False tok | ||
| 142 | peek = nextIs "Unrecognised token" (const True) | ||
| 143 | |||
| 144 | ||| Succeeds if running the predicate on the next token returns Just x, | ||
| 145 | ||| returning x. Otherwise fails. | ||
| 146 | export | ||
| 147 | terminal : String -> (tok -> Maybe a) -> Grammar tok True a | ||
| 148 | terminal = Terminal | ||
| 149 | |||
| 150 | ||| Always fail with a message | ||
| 151 | export | ||
| 152 | fail : String -> Grammar tok c ty | ||
| 153 | fail = Fail False | ||
| 154 | |||
| 155 | export | ||
| 156 | fatalError : String -> Grammar tok c ty | ||
| 157 | fatalError = Fail True | ||
| 158 | |||
| 159 | ||| Succeed if the input is empty | ||
| 160 | export | ||
| 161 | eof : Grammar tok False () | ||
| 162 | eof = EOF | ||
| 163 | |||
| 164 | ||| Commit to an alternative; if the current branch of an alternative | ||
| 165 | ||| fails to parse, no more branches will be tried | ||
| 166 | export | ||
| 167 | commit : Grammar tok False () | ||
| 168 | commit = Commit | ||
| 169 | |||
| 170 | ||| If the parser fails, treat it as a fatal error | ||
| 171 | export | ||
| 172 | mustWork : Grammar tok c ty -> Grammar tok c ty | ||
| 173 | mustWork = MustWork | ||
| 174 | |||
| 175 | data ParseResult : List tok -> (consumes : Bool) -> Type -> Type where | ||
| 176 | Failure : {xs : List tok} -> | ||
| 177 | (committed : Bool) -> (fatal : Bool) -> | ||
| 178 | (err : String) -> (rest : List tok) -> ParseResult xs c ty | ||
| 179 | EmptyRes : (committed : Bool) -> | ||
| 180 | (val : ty) -> (more : List tok) -> ParseResult more False ty | ||
| 181 | NonEmptyRes : (committed : Bool) -> | ||
| 182 | (val : ty) -> (more : List tok) -> | ||
| 183 | ParseResult (x :: xs ++ more) c ty | ||
| 184 | |||
| 185 | -- Take the result of an alternative branch, reset the commit flag to | ||
| 186 | -- the commit flag from the outer alternative, and weaken the 'consumes' | ||
| 187 | -- flag to take both alternatives into account | ||
| 188 | weakenRes : {whatever, c : Bool} -> {xs : List tok} -> | ||
| 189 | (com' : Bool) -> | ||
| 190 | ParseResult xs c ty -> ParseResult xs (whatever && c) ty | ||
| 191 | weakenRes com' (Failure com fatal msg ts) = Failure com' fatal msg ts | ||
| 192 | weakenRes {whatever=True} com' (EmptyRes com val xs) = EmptyRes com' val xs | ||
| 193 | weakenRes {whatever=False} com' (EmptyRes com val xs) = EmptyRes com' val xs | ||
| 194 | weakenRes com' (NonEmptyRes com val more) = NonEmptyRes com' val more | ||
| 195 | |||
| 196 | shorter : (more : List tok) -> .(ys : List tok) -> | ||
| 197 | LTE (S (length more)) (S (length (ys ++ more))) | ||
| 198 | shorter more [] = lteRefl | ||
| 199 | shorter more (x :: xs) = LTESucc (lteSuccLeft (shorter more xs)) | ||
| 200 | |||
| 201 | doParse : {c : Bool} -> | ||
| 202 | (commit : Bool) -> (xs : List tok) -> (act : Grammar tok c ty) -> | ||
| 203 | ParseResult xs c ty | ||
| 204 | -- doParse com xs act with (sizeAccessible xs) | ||
| 205 | doParse com xs (Empty val) = EmptyRes com val xs | ||
| 206 | doParse com [] (Fail fatal str) = Failure com fatal str [] | ||
| 207 | doParse com (x :: xs) (Fail fatal str) = Failure com fatal str (x :: xs) | ||
| 208 | doParse com xs Commit = EmptyRes True () xs | ||
| 209 | doParse com xs (MustWork g) = | ||
| 210 | let p' = doParse com xs g in | ||
| 211 | case p' of | ||
| 212 | Failure com' _ msg ts => Failure com' True msg ts | ||
| 213 | res => res | ||
| 214 | doParse com [] (Terminal err f) = Failure com False "End of input" [] | ||
| 215 | doParse com (x :: xs) (Terminal err f) | ||
| 216 | = maybe | ||
| 217 | (Failure com False err (x :: xs)) | ||
| 218 | (\a => NonEmptyRes com {xs=[]} a xs) | ||
| 219 | (f x) | ||
| 220 | doParse com [] EOF = EmptyRes com () [] | ||
| 221 | doParse com (x :: xs) EOF | ||
| 222 | = Failure com False "Expected end of input" (x :: xs) | ||
| 223 | doParse com [] (NextIs err f) = Failure com False "End of input" [] | ||
| 224 | doParse com (x :: xs) (NextIs err f) | ||
| 225 | = if f x | ||
| 226 | then EmptyRes com x (x :: xs) | ||
| 227 | else Failure com False err (x :: xs) | ||
| 228 | doParse com xs (Alt x y) | ||
| 229 | = let p' = doParse False xs x in | ||
| 230 | case p' of | ||
| 231 | Failure com' fatal msg ts | ||
| 232 | => if com' || fatal | ||
| 233 | -- If the alternative had committed, don't try the | ||
| 234 | -- other branch (and reset commit flag) | ||
| 235 | then Failure com fatal msg ts | ||
| 236 | else weakenRes com (doParse False xs y) | ||
| 237 | -- Successfully parsed the first option, so use the outer commit flag | ||
| 238 | EmptyRes _ val xs => EmptyRes com val xs | ||
| 239 | NonEmptyRes _ val more => NonEmptyRes com val more | ||
| 240 | doParse com xs (SeqEmpty act next) | ||
| 241 | = let p' = assert_total (doParse com xs act) in | ||
| 242 | case p' of | ||
| 243 | Failure com fatal msg ts => Failure com fatal msg ts | ||
| 244 | EmptyRes com val xs => | ||
| 245 | case assert_total (doParse com xs (next val)) of | ||
| 246 | Failure com' fatal msg ts => Failure com' fatal msg ts | ||
| 247 | EmptyRes com' val xs => EmptyRes com' val xs | ||
| 248 | NonEmptyRes com' val more => NonEmptyRes com' val more | ||
| 249 | NonEmptyRes {x} {xs=ys} com val more => | ||
| 250 | case (assert_total (doParse com more (next val))) of | ||
| 251 | Failure com' fatal msg ts => Failure com' fatal msg ts | ||
| 252 | EmptyRes com' val _ => NonEmptyRes com' val more | ||
| 253 | NonEmptyRes {x=x1} {xs=xs1} com' val more' => | ||
| 254 | rewrite appendAssociative (x :: ys) (x1 :: xs1) more' in | ||
| 255 | NonEmptyRes com' val more' | ||
| 256 | doParse com xs (SeqEat act next) with (doParse com xs act) | ||
| 257 | doParse com xs (SeqEat act next) | Failure com' fatal msg ts | ||
| 258 | = Failure com' fatal msg ts | ||
| 259 | doParse com (x :: (ys ++ more)) (SeqEat act next) | (NonEmptyRes com' val more) | ||
| 260 | = let p' = assert_total (doParse com' more (next val)) in | ||
| 261 | case p' of | ||
| 262 | Failure com' fatal msg ts => Failure com' fatal msg ts | ||
| 263 | EmptyRes com' val _ => NonEmptyRes com' val more | ||
| 264 | NonEmptyRes {x=x1} {xs=xs1} com' val more' => | ||
| 265 | rewrite appendAssociative (x :: ys) (x1 :: xs1) more' in | ||
| 266 | NonEmptyRes com' val more' | ||
| 267 | -- This next line is not strictly necessary, but it stops the coverage | ||
| 268 | -- checker taking a really long time and eating lots of memory... | ||
| 269 | -- doParse _ _ _ | sml = Failure True "Help the coverage checker!" [] | ||
| 270 | |||
| 271 | public export | ||
| 272 | data ParseError tok = Error String (List tok) | ||
| 273 | |||
| 274 | ||| Parse a list of tokens according to the given grammar. If successful, | ||
| 275 | ||| returns a pair of the parse result and the unparsed tokens (the remaining | ||
| 276 | ||| input). | ||
| 277 | export | ||
| 278 | parse : (act : Grammar tok c ty) -> (xs : List tok) -> | ||
| 279 | Either (ParseError tok) (ty, List tok) | ||
| 280 | parse act xs | ||
| 281 | = case doParse False xs act of | ||
| 282 | Failure _ _ msg ts => Left (Error msg ts) | ||
| 283 | EmptyRes _ val rest => pure (val, rest) | ||
| 284 | NonEmptyRes _ val rest => pure (val, rest) | ||
| @@ -0,0 +1,38 @@ | |||
| 1 | module Text.Quantity | ||
| 2 | |||
| 3 | %access public export | ||
| 4 | %default total | ||
| 5 | |||
| 6 | record Quantity where | ||
| 7 | constructor Qty | ||
| 8 | min : Nat | ||
| 9 | max : Maybe Nat | ||
| 10 | |||
| 11 | Show Quantity where | ||
| 12 | show (Qty Z Nothing) = "*" | ||
| 13 | show (Qty Z (Just (S Z))) = "?" | ||
| 14 | show (Qty (S Z) Nothing) = "+" | ||
| 15 | show (Qty min max) = "{" ++ show min ++ showMax ++ "}" | ||
| 16 | where | ||
| 17 | showMax : String | ||
| 18 | showMax = case max of | ||
| 19 | Nothing => "," | ||
| 20 | Just max' => if min == max' | ||
| 21 | then "" | ||
| 22 | else "," ++ show max' | ||
| 23 | |||
| 24 | between : Nat -> Nat -> Quantity | ||
| 25 | between min max = Qty min (Just max) | ||
| 26 | |||
| 27 | atLeast : Nat -> Quantity | ||
| 28 | atLeast min = Qty min Nothing | ||
| 29 | |||
| 30 | atMost : Nat -> Quantity | ||
| 31 | atMost max = Qty 0 (Just max) | ||
| 32 | |||
| 33 | exactly : Nat -> Quantity | ||
| 34 | exactly n = Qty n (Just n) | ||
| 35 | |||
| 36 | inOrder : Quantity -> Bool | ||
| 37 | inOrder (Qty min Nothing) = True | ||
| 38 | inOrder (Qty min (Just max)) = min <= max | ||
| @@ -0,0 +1,38 @@ | |||
| 1 | module Text.Token | ||
| 2 | |||
| 3 | %access public export | ||
| 4 | %default total | ||
| 5 | |||
| 6 | ||| For a type `kind`, specify a way of converting the recognised | ||
| 7 | ||| string into a value. | ||
| 8 | ||| | ||
| 9 | ||| ```idris example | ||
| 10 | ||| data SimpleKind = SKString | SKInt | SKComma | ||
| 11 | ||| | ||
| 12 | ||| TokenKind SimpleKind where | ||
| 13 | ||| TokType SKString = String | ||
| 14 | ||| TokType SKInt = Int | ||
| 15 | ||| TokType SKComma = () | ||
| 16 | ||| | ||
| 17 | ||| tokValue SKString x = x | ||
| 18 | ||| tokValue SKInt x = cast x | ||
| 19 | ||| tokValue SKComma x = () | ||
| 20 | ||| ``` | ||
| 21 | interface TokenKind (k : Type) where | ||
| 22 | ||| The type that a token of this kind converts to. | ||
| 23 | TokType : k -> Type | ||
| 24 | |||
| 25 | ||| Convert a recognised string into a value. The type is determined | ||
| 26 | ||| by the kind of token. | ||
| 27 | tokValue : (kind : k) -> String -> TokType kind | ||
| 28 | |||
| 29 | ||| A token of a particular kind and the text that was recognised. | ||
| 30 | record Token k where | ||
| 31 | constructor Tok | ||
| 32 | kind : k | ||
| 33 | text : String | ||
| 34 | |||
| 35 | ||| Get the value of a `Token k`. The resulting type depends upon | ||
| 36 | ||| the kind of token. | ||
| 37 | value : TokenKind k => (t : Token k) -> TokType (kind t) | ||
| 38 | value (Tok k x) = tokValue k x | ||
| @@ -0,0 +1,31 @@ | |||
| 1 | package yaffle | ||
| 2 | |||
| 3 | modules = | ||
| 4 | Control.Delayed, | ||
| 5 | |||
| 6 | Core.Core, | ||
| 7 | Core.Env, | ||
| 8 | Core.FC, | ||
| 9 | Core.Name, | ||
| 10 | Core.TT, | ||
| 11 | Core.Value, | ||
| 12 | |||
| 13 | Data.Bool.Extra, | ||
| 14 | Data.StringMap, | ||
| 15 | |||
| 16 | Parser.Lexer, | ||
| 17 | Parser.Support, | ||
| 18 | |||
| 19 | Text.Lexer, | ||
| 20 | Text.Lexer.Core, | ||
| 21 | Text.Parser, | ||
| 22 | Text.Parser.Core, | ||
| 23 | Text.Quantity, | ||
| 24 | Text.Token | ||
| 25 | |||
| 26 | sourcedir = src | ||
| 27 | -- executable = ttimp | ||
| 28 | opts = "--partial-eval" | ||
| 29 | |||
| 30 | -- main = TTImp.Main | ||
| 31 | |||