1
1
mirror of https://github.com/github/semantic.git synced 2025-01-03 04:51:57 +03:00

Construct better errors at the end of branches.

This commit is contained in:
Rob Rix 2017-08-21 15:33:42 -04:00
parent c572e8d3bf
commit 6b7114ebc8

View File

@ -148,6 +148,10 @@ tracing f = case getCallStack callStack of
(_ : site : _) -> Tracing (Just site) f
_ -> Tracing Nothing f
assignmentCallSite :: Assignment ast grammar a -> Maybe (String, SrcLoc)
assignmentCallSite (Tracing site _ `Then` _) = site
assignmentCallSite _ = Nothing
-- | Zero-width production of the current location.
--
-- If assigning at the end of input or at the end of a list of children, the loccation will be returned as an empty Range and Span at the current offset. Otherwise, it will be the Range and Span of the current node.
@ -243,7 +247,7 @@ runAssignment :: forall grammar a ast. (Bounded grammar, Ix grammar, Symbol gram
-> Assignment ast grammar a -- ^ The 'Assignment' to run.
-> State ast grammar -- ^ The current state.
-> Either (Error (Either String grammar)) (a, State ast grammar) -- ^ 'Either' an 'Error' or an assigned value & updated state.
runAssignment source = \ assignment state -> go assignment state >>= requireExhaustive
runAssignment source = \ assignment state -> go assignment state >>= requireExhaustive (assignmentCallSite assignment)
-- Note: We explicitly bind source above in order to ensure that the where clause can close over them; they dont change through the course of the run, so holding one reference is sufficient. On the other hand, we dont want to accidentally capture the assignment and state in the where clause, since they change at every step—and capturing when you meant to shadow is an easy mistake to make, & results in hard-to-debug errors. Binding them in a lambda avoids that problem while also being easier to follow than a pointfree definition.
where go :: Assignment ast grammar result -> State ast grammar -> Either (Error (Either String grammar)) (result, State ast grammar)
go assignment = iterFreer run ((pure .) . (,) <$> assignment)
@ -259,7 +263,7 @@ runAssignment source = \ assignment state -> go assignment state >>= requireExha
CurrentNode -> yield (node CofreeF.:< (() <$ f)) state
Source -> yield (Source.sourceBytes (Source.slice (nodeByteRange node) source)) (advanceState state)
Children child -> do
(a, state') <- go child state { stateNodes = toList f, stateCallSites = maybe id (:) (tracingCallSite t) stateCallSites } >>= requireExhaustive
(a, state') <- go child state { stateNodes = toList f, stateCallSites = maybe id (:) (tracingCallSite t) stateCallSites } >>= requireExhaustive (tracingCallSite t)
yield a (advanceState state' { stateNodes = stateNodes, stateCallSites = stateCallSites })
Advance -> yield () (advanceState state)
Choose _ choices _ | Just choice <- IntMap.lookup (toIndex (nodeSymbol node)) choices -> yield choice state
@ -267,7 +271,7 @@ runAssignment source = \ assignment state -> go assignment state >>= requireExha
_ -> anywhere (Just node)
anywhere node = case runTracing t of
End -> requireExhaustive ((), state) >>= uncurry yield
End -> requireExhaustive (tracingCallSite t) ((), state) >>= uncurry yield
Location -> yield (Info.Range stateOffset stateOffset :. Info.Span statePos statePos :. Nil) state
Many rule -> fix (\ recur state -> (go rule state >>= \ (a, state') -> first (a:) <$> if state == state' then pure ([], state') else recur state') `catchError` const (pure ([], state))) state >>= uncurry yield
Alt (a:as) -> sconcat (flip yield state <$> a:|as)
@ -279,12 +283,15 @@ runAssignment source = \ assignment state -> go assignment state >>= requireExha
state@State{..} = if not (null expectedSymbols) && all ((== Regular) . symbolType) expectedSymbols then skipTokens initialState else initialState
expectedSymbols = firstSet (t `Then` return)
makeError = withCallStack (freezeCallStack (fromCallSiteList (maybe id (:) (tracingCallSite t) stateCallSites))) $ maybe (Error (Info.Span statePos statePos) (fmap Right expectedSymbols) Nothing) (nodeError (fmap Right expectedSymbols))
makeError = withStateCallStack (tracingCallSite t) state $ maybe (Error (Info.Span statePos statePos) (fmap Right expectedSymbols) Nothing) (nodeError (fmap Right expectedSymbols))
requireExhaustive :: (Symbol grammar, HasCallStack) => (result, State ast grammar) -> Either (Error (Either String grammar)) (result, State ast grammar)
requireExhaustive (a, state) = let state' = skipTokens state in case stateNodes state' of
requireExhaustive :: (Symbol grammar, HasCallStack) => Maybe (String, SrcLoc) -> (result, State ast grammar) -> Either (Error (Either String grammar)) (result, State ast grammar)
requireExhaustive callSite (a, state) = let state' = skipTokens state in case stateNodes state' of
[] -> Right (a, state')
(node :< _) : _ -> Left (nodeError [] node)
(node :< _) : _ -> Left (withStateCallStack callSite state (nodeError [] node))
withStateCallStack :: Maybe (String, SrcLoc) -> State ast grammar -> (HasCallStack => a) -> a
withStateCallStack callSite state action = withCallStack (freezeCallStack (fromCallSiteList (maybe id (:) callSite (stateCallSites state)))) action
skipTokens :: Symbol grammar => State ast grammar -> State ast grammar
skipTokens state = state { stateNodes = dropWhile ((/= Regular) . symbolType . nodeSymbol . CofreeF.headF . runCofree) (stateNodes state) }