Skip to content

Commit

Permalink
Allow parsing arbitrary coproc names (fixes koalaman#3048)
Browse files Browse the repository at this point in the history
  • Loading branch information
koalaman committed Sep 8, 2024
1 parent ca65071 commit 79e43c4
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 13 deletions.
2 changes: 1 addition & 1 deletion src/ShellCheck/AST.hs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ data InnerToken t =
| Inner_T_WhileExpression [t] [t]
| Inner_T_Annotation [Annotation] t
| Inner_T_Pipe String
| Inner_T_CoProc (Maybe String) t
| Inner_T_CoProc (Maybe Token) t
| Inner_T_CoProcBody t
| Inner_T_Include t
| Inner_T_SourceCommand t t
Expand Down
8 changes: 6 additions & 2 deletions src/ShellCheck/AnalyzerLib.hs
Original file line number Diff line number Diff line change
Expand Up @@ -559,8 +559,12 @@ getModifiedVariables t =
T_FdRedirect _ ('{':var) op -> -- {foo}>&2 modifies foo
[(t, t, takeWhile (/= '}') var, DataString SourceInteger) | not $ isClosingFileOp op]

T_CoProc _ name _ ->
[(t, t, fromMaybe "COPROC" name, DataArray SourceInteger)]
T_CoProc _ Nothing _ ->
[(t, t, "COPROC", DataArray SourceInteger)]

T_CoProc _ (Just token) _ -> do
name <- maybeToList $ getLiteralString token
[(t, t, name, DataArray SourceInteger)]

--Points to 'for' rather than variable
T_ForIn id str [] _ -> [(t, t, str, DataString SourceExternal)]
Expand Down
14 changes: 11 additions & 3 deletions src/ShellCheck/CFG.hs
Original file line number Diff line number Diff line change
Expand Up @@ -668,10 +668,18 @@ build t = do
status <- newNodeRange $ CFSetExitCode id
linkRange cond status

T_CoProc id maybeName t -> do
let name = fromMaybe "COPROC" maybeName
T_CoProc id maybeNameToken t -> do
-- If unspecified, "COPROC". If not a constant string, Nothing.
let maybeName = case maybeNameToken of
Just x -> getLiteralString x
Nothing -> Just "COPROC"

let parentNode = case maybeName of
Just str -> applySingle $ IdTagged id $ CFWriteVariable str CFValueArray
Nothing -> CFStructuralNode

start <- newStructuralNode
parent <- newNodeRange $ applySingle $ IdTagged id $ CFWriteVariable name CFValueArray
parent <- newNodeRange parentNode
child <- subshell id "coproc" $ build t
end <- newNodeRange $ CFSetExitCode id

Expand Down
35 changes: 28 additions & 7 deletions src/ShellCheck/Parser.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2795,17 +2795,29 @@ readFunctionDefinition = called "function" $ do
prop_readCoProc1 = isOk readCoProc "coproc foo { echo bar; }"
prop_readCoProc2 = isOk readCoProc "coproc { echo bar; }"
prop_readCoProc3 = isOk readCoProc "coproc echo bar"
prop_readCoProc4 = isOk readCoProc "coproc a=b echo bar"
prop_readCoProc5 = isOk readCoProc "coproc 'foo' { echo bar; }"
prop_readCoProc6 = isOk readCoProc "coproc \"foo$$\" { echo bar; }"
prop_readCoProc7 = isOk readCoProc "coproc 'foo' ( echo bar )"
prop_readCoProc8 = isOk readCoProc "coproc \"foo$$\" while true; do true; done"
readCoProc = called "coproc" $ do
start <- startSpan
try $ do
string "coproc"
whitespace
spacing1
choice [ try $ readCompoundCoProc start, readSimpleCoProc start ]
where
readCompoundCoProc start = do
var <- optionMaybe $
readVariableName `thenSkip` whitespace
body <- readBody readCompoundCommand
notFollowedBy2 readAssignmentWord
(var, body) <- choice [
try $ do
body <- readBody readCompoundCommand
return (Nothing, body),
try $ do
var <- readNormalWord `thenSkip` spacing
body <- readBody readCompoundCommand
return (Just var, body)
]
id <- endSpan start
return $ T_CoProc id var body
readSimpleCoProc start = do
Expand Down Expand Up @@ -3436,13 +3448,22 @@ isOk p s = parsesCleanly p s == Just True -- The string parses with no wa
isWarning p s = parsesCleanly p s == Just False -- The string parses with warnings
isNotOk p s = parsesCleanly p s == Nothing -- The string does not parse

parsesCleanly parser string = runIdentity $ do
-- If the parser matches the string, return Right [ParseNotes+ParseProblems]
-- If it does not match the string, return Left [ParseProblems]
getParseOutput parser string = runIdentity $ do
(res, sys) <- runParser testEnvironment
(parser >> eof >> getState) "-" string
case (res, sys) of
(Right userState, systemState) ->
return $ Just . null $ parseNotes userState ++ parseProblems systemState
(Left _, _) -> return Nothing
return $ Right $ parseNotes userState ++ parseProblems systemState
(Left _, systemState) -> return $ Left $ parseProblems systemState

-- If the parser matches the string, return Just whether it was clean (without emitting suggestions)
-- Otherwise, Nothing
parsesCleanly parser string =
case getParseOutput parser string of
Right list -> Just $ null list
Left _ -> Nothing

parseWithNotes parser = do
item <- parser
Expand Down

0 comments on commit 79e43c4

Please sign in to comment.