forked from commercialhaskell/stack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathInterpreter.hs
More file actions
152 lines (125 loc) · 5.66 KB
/
Interpreter.hs
File metadata and controls
152 lines (125 loc) · 5.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{- | This module implements parsing of additional arguments embedded in a
comment when stack is invoked as a script interpreter
===Specifying arguments in script interpreter mode
@/stack/@ can execute a Haskell source file using @/runghc/@ and if required
it can also install and setup the compiler and any package dependencies
automatically.
For using a Haskell source file as an executable script on a Unix like OS,
the first line of the file must specify @stack@ as the interpreter using a
shebang directive e.g.
> #!/usr/bin/env stack
Additional arguments can be specified in a haskell comment following the
@#!@ line. The contents inside the comment must be a single valid stack
command line, starting with @stack@ as the command and followed by the
options to use for executing this file.
The comment must be on the line immediately following the @#!@ line. The
comment must start in the first column of the line. When using a block style
comment the command can be split on multiple lines.
Here is an example of a single line comment:
> #!/usr/bin/env stack
> -- stack --resolver lts-3.14 --install-ghc runghc --package random
Here is an example of a multi line block comment:
@
#!\/usr\/bin\/env stack
{\- stack
--resolver lts-3.14
--install-ghc
runghc
--package random
-\}
@
When the @#!@ line is not present, the file can still be executed
using @stack \<file name\>@ command if the file starts with a valid stack
interpreter comment. This can be used to execute the file on Windows for
example.
Nested block comments are not supported.
-}
module Data.Attoparsec.Interpreter
( interpreterArgsParser -- for unit tests
, getInterpreterArgs
) where
import Data.Attoparsec.Args
import Data.Attoparsec.Text ((<?>))
import qualified Data.Attoparsec.Text as P
import Data.Char (isSpace)
import Conduit
import Data.Conduit.Attoparsec
import Data.List (intercalate)
import Data.Text (pack)
import Stack.Prelude
import System.FilePath (takeExtension)
import System.IO (stderr, hPutStrLn)
-- | Parser to extract the stack command line embedded inside a comment
-- after validating the placement and formatting rules for a valid
-- interpreter specification.
interpreterArgsParser :: Bool -> String -> P.Parser String
interpreterArgsParser isLiterate progName = P.option "" sheBangLine *> interpreterComment
where
sheBangLine = P.string "#!"
*> P.manyTill P.anyChar P.endOfLine
commentStart psr = (psr <?> (progName ++ " options comment"))
*> P.skipSpace
*> (P.string (pack progName) <?> show progName)
-- Treat newlines as spaces inside the block comment
anyCharNormalizeSpace = let normalizeSpace c = if isSpace c then ' ' else c
in P.satisfyWith normalizeSpace $ const True
comment start end = commentStart start
*> ((end >> return "")
<|> (P.space *> (P.manyTill anyCharNormalizeSpace end <?> "-}")))
horizontalSpace = P.satisfy P.isHorizontalSpace
lineComment = comment "--" (P.endOfLine <|> P.endOfInput)
literateLineComment = comment
(">" *> horizontalSpace *> "--")
(P.endOfLine <|> P.endOfInput)
blockComment = comment "{-" (P.string "-}")
literateBlockComment =
(">" *> horizontalSpace *> "{-")
*> P.skipMany (("" <$ horizontalSpace) <|> (P.endOfLine *> ">"))
*> (P.string (pack progName) <?> progName)
*> P.manyTill' (P.satisfy (not . P.isEndOfLine)
<|> (' ' <$ (P.endOfLine *> ">" <?> ">"))) "-}"
interpreterComment = if isLiterate
then literateLineComment <|> literateBlockComment
else lineComment <|> blockComment
-- | Extract stack arguments from a correctly placed and correctly formatted
-- comment when it is being used as an interpreter
getInterpreterArgs :: String -> IO [String]
getInterpreterArgs file = do
eArgStr <- withSourceFile file parseFile
case eArgStr of
Left err -> handleFailure $ decodeError err
Right str -> parseArgStr str
where
parseFile src =
runConduit
$ src
.| decodeUtf8C
.| sinkParserEither (interpreterArgsParser isLiterate stackProgName)
isLiterate = takeExtension file == ".lhs"
-- FIXME We should print anything only when explicit verbose mode is
-- specified by the user on command line. But currently the
-- implementation does not accept or parse any command line flags in
-- interpreter mode. We can only invoke the interpreter as
-- "stack <file name>" strictly without any options.
stackWarn s = hPutStrLn stderr $ stackProgName ++ ": WARNING! " ++ s
handleFailure err = do
mapM_ stackWarn (lines err)
stackWarn "Missing or unusable stack options specification"
stackWarn "Using runghc without any additional stack options"
return ["runghc"]
parseArgStr str =
case P.parseOnly (argsParser Escaping) (pack str) of
Left err -> handleFailure ("Error parsing command specified in the "
++ "stack options comment: " ++ err)
Right [] -> handleFailure "Empty argument list in stack options comment"
Right args -> return args
decodeError e =
case e of
ParseError ctxs _ (Position line col _) ->
if null ctxs
then "Parse error"
else ("Expecting " ++ intercalate " or " ctxs)
++ " at line " ++ show line ++ ", column " ++ show col
DivergentParser -> "Divergent parser"