blob: b9083b8961a63452684fbb89b381c8bca37347b5 [file] [log] [blame]
Fumitoshi Ukai119dc912015-03-30 16:52:41 +09001package main
2
3import (
4 "bufio"
Shinichiro Hamajie1841582015-03-30 17:20:33 +09005 "bytes"
Fumitoshi Ukai119dc912015-03-30 16:52:41 +09006 "errors"
Fumitoshi Ukaif8efa0a2015-03-30 18:10:11 +09007 "fmt"
Fumitoshi Ukai119dc912015-03-30 16:52:41 +09008 "io"
9 "os"
Shinichiro Hamajid7bef602015-03-30 19:55:32 +090010 "strings"
Fumitoshi Ukai119dc912015-03-30 16:52:41 +090011)
12
13type Makefile struct {
14 stmts []AST
15}
16
Shinichiro Hamaji497754d2015-03-31 02:02:11 +090017type ifState struct {
Fumitoshi Ukai0293c7a2015-03-31 16:26:53 +090018 ast *IfAST
19 inElse bool
Shinichiro Hamaji497754d2015-03-31 02:02:11 +090020}
21
Fumitoshi Ukai119dc912015-03-30 16:52:41 +090022type parser struct {
Fumitoshi Ukaicf2b0382015-03-30 17:48:54 +090023 rd *bufio.Reader
24 mk Makefile
Shinichiro Hamaji685fecf2015-03-30 18:28:12 +090025 filename string
Fumitoshi Ukaicf2b0382015-03-30 17:48:54 +090026 lineno int
27 elineno int // lineno == elineno unless there is trailing '\'.
28 unBuf []byte
29 hasUnBuf bool
30 done bool
Shinichiro Hamaji497754d2015-03-31 02:02:11 +090031 outStmts *[]AST
32 ifStack []ifState
Fumitoshi Ukai3d54db82015-04-01 11:03:31 +090033 inDef []string
Fumitoshi Ukai119dc912015-03-30 16:52:41 +090034}
35
36func exists(filename string) bool {
37 f, err := os.Open(filename)
38 if err != nil {
39 return false
40 }
41 f.Close()
42 return true
43}
44
Shinichiro Hamaji685fecf2015-03-30 18:28:12 +090045func newParser(rd io.Reader, filename string) *parser {
Shinichiro Hamaji497754d2015-03-31 02:02:11 +090046 p := &parser{
Shinichiro Hamaji685fecf2015-03-30 18:28:12 +090047 rd: bufio.NewReader(rd),
48 filename: filename,
Fumitoshi Ukai119dc912015-03-30 16:52:41 +090049 }
Shinichiro Hamaji497754d2015-03-31 02:02:11 +090050 p.outStmts = &p.mk.stmts
51 return p
52}
53
Shinichiro Hamajiae32b782015-03-31 14:41:19 +090054func (p *parser) addStatement(ast AST) {
Shinichiro Hamaji497754d2015-03-31 02:02:11 +090055 *p.outStmts = append(*p.outStmts, ast)
Fumitoshi Ukai119dc912015-03-30 16:52:41 +090056}
57
Shinichiro Hamajie1841582015-03-30 17:20:33 +090058func (p *parser) readLine() []byte {
Fumitoshi Ukaicf2b0382015-03-30 17:48:54 +090059 if p.hasUnBuf {
60 p.hasUnBuf = false
61 return p.unBuf
Shinichiro Hamajie1841582015-03-30 17:20:33 +090062 }
63
64 p.lineno = p.elineno
65 line, err := p.rd.ReadBytes('\n')
66 p.lineno++
Shinichiro Hamaji685fecf2015-03-30 18:28:12 +090067 p.elineno = p.lineno
Shinichiro Hamajie1841582015-03-30 17:20:33 +090068 if err == io.EOF {
69 p.done = true
70 } else if err != nil {
71 panic(err)
72 }
73
Shinichiro Hamajibc702b02015-04-06 10:45:48 +090074 line = bytes.TrimRight(line, "\n")
Shinichiro Hamajie1841582015-03-30 17:20:33 +090075
Shinichiro Hamaji52e83aa2015-04-06 17:20:28 +090076 return line
77}
78
79func removeComment(line []byte) []byte {
80 var parenStack []byte
81 for i, ch := range line {
82 switch ch {
83 case '(', '{':
84 parenStack = append(parenStack, ch)
85 case ')', '}':
86 if len(parenStack) > 0 {
87 parenStack = parenStack[:len(parenStack)-1]
88 }
89 case '#':
90 if len(parenStack) == 0 {
91 return line[:i]
92 }
93 }
94 }
95 return line
96}
97
98func (p *parser) readMakefileLine() []byte {
99 line := p.readLine()
100
Shinichiro Hamajie1841582015-03-30 17:20:33 +0900101 // TODO: Handle \\ at the end of the line?
102 for len(line) > 0 && line[len(line)-1] == '\\' {
103 line = line[:len(line)-1]
Shinichiro Hamaji3e4533d2015-04-06 14:03:58 +0900104 lineno := p.lineno
Shinichiro Hamajie1841582015-03-30 17:20:33 +0900105 nline := p.readLine()
Shinichiro Hamaji3e4533d2015-04-06 14:03:58 +0900106 p.lineno = lineno
Shinichiro Hamajie1841582015-03-30 17:20:33 +0900107 line = append(line, nline...)
108 }
Shinichiro Hamaji52e83aa2015-04-06 17:20:28 +0900109 return removeComment(line)
110}
Shinichiro Hamajie1841582015-03-30 17:20:33 +0900111
Shinichiro Hamaji52e83aa2015-04-06 17:20:28 +0900112func (p *parser) readRecipeLine() []byte {
113 line := p.readLine()
114
115 // TODO: Handle \\ at the end of the line?
116 for len(line) > 0 && line[len(line)-1] == '\\' {
117 line = append(line, '\n')
118 lineno := p.lineno
119 nline := p.readLine()
120 p.lineno = lineno
121 line = append(line, nline...)
Shinichiro Hamajie1841582015-03-30 17:20:33 +0900122 }
Shinichiro Hamajie1841582015-03-30 17:20:33 +0900123 return line
124}
125
126func (p *parser) unreadLine(line []byte) {
Fumitoshi Ukaicf2b0382015-03-30 17:48:54 +0900127 if p.hasUnBuf {
Shinichiro Hamajie1841582015-03-30 17:20:33 +0900128 panic("unreadLine twice!")
129 }
Fumitoshi Ukaicf2b0382015-03-30 17:48:54 +0900130 p.unBuf = line
131 p.hasUnBuf = true
Shinichiro Hamajie1841582015-03-30 17:20:33 +0900132}
133
Fumitoshi Ukaie1b813c2015-03-30 18:38:21 +0900134func (p *parser) parseAssign(line []byte, sep, esep int) AST {
Fumitoshi Ukai8773e5e2015-04-01 11:23:18 +0900135 Log("parseAssign %q op:%q", line, line[sep:esep])
Fumitoshi Ukaie1b813c2015-03-30 18:38:21 +0900136 ast := &AssignAST{
137 lhs: string(bytes.TrimSpace(line[:sep])),
138 rhs: string(bytes.TrimLeft(line[esep:], " \t")),
139 op: string(line[sep:esep]),
Shinichiro Hamaji7f1ede32015-03-30 18:05:22 +0900140 }
Shinichiro Hamaji685fecf2015-03-30 18:28:12 +0900141 ast.filename = p.filename
Fumitoshi Ukai119dc912015-03-30 16:52:41 +0900142 ast.lineno = p.lineno
Fumitoshi Ukai119dc912015-03-30 16:52:41 +0900143 return ast
144}
145
Shinichiro Hamajide829712015-03-31 18:26:56 +0900146func (p *parser) parseMaybeRule(line string) AST {
147 if len(strings.TrimSpace(line)) == 0 {
148 return nil
149 }
150 if line[0] == '\t' {
151 Error(p.filename, p.lineno, "*** commands commence before first target.")
152 }
153
Shinichiro Hamaji19f4bf62015-04-02 04:55:57 +0900154 ast := &MaybeRuleAST{}
155 if i := strings.IndexByte(line, ';'); i >= 0 {
156 ast.expr = line[:i]
157 ast.cmds = append(ast.cmds, strings.TrimSpace(line[i+1:]))
158 } else {
159 ast.expr = line
Shinichiro Hamaji7f1ede32015-03-30 18:05:22 +0900160 }
Shinichiro Hamaji685fecf2015-03-30 18:28:12 +0900161 ast.filename = p.filename
Fumitoshi Ukai119dc912015-03-30 16:52:41 +0900162 ast.lineno = p.lineno
Shinichiro Hamaji7c4e3252015-03-30 23:04:25 +0900163 ast.cmdLineno = p.elineno + 1
Fumitoshi Ukai119dc912015-03-30 16:52:41 +0900164 for {
Shinichiro Hamaji52e83aa2015-04-06 17:20:28 +0900165 line := p.readRecipeLine()
Shinichiro Hamaji7f1ede32015-03-30 18:05:22 +0900166 if len(line) == 0 {
167 break
168 } else if line[0] == '\t' {
Shinichiro Hamaji79abd182015-04-03 11:07:38 +0900169 ast.cmds = append(ast.cmds, string(bytes.TrimLeft(line, " \t")))
Shinichiro Hamaji7f1ede32015-03-30 18:05:22 +0900170 } else {
171 p.unreadLine(line)
172 break
Fumitoshi Ukai119dc912015-03-30 16:52:41 +0900173 }
174 }
Shinichiro Hamaji7f1ede32015-03-30 18:05:22 +0900175 return ast
Fumitoshi Ukai119dc912015-03-30 16:52:41 +0900176}
177
Shinichiro Hamajid7bef602015-03-30 19:55:32 +0900178func (p *parser) parseInclude(line string, oplen int) AST {
179 ast := &IncludeAST{
180 expr: line[oplen+1:],
181 op: line[:oplen],
182 }
183 ast.filename = p.filename
184 ast.lineno = p.lineno
185 return ast
186}
187
Shinichiro Hamaji497754d2015-03-31 02:02:11 +0900188func (p *parser) parseIfdef(line string, oplen int) AST {
189 ast := &IfAST{
Shinichiro Hamajiae32b782015-03-31 14:41:19 +0900190 op: line[:oplen],
Shinichiro Hamaji497754d2015-03-31 02:02:11 +0900191 lhs: strings.TrimSpace(line[oplen+1:]),
192 }
193 ast.filename = p.filename
194 ast.lineno = p.lineno
195 p.addStatement(ast)
196 p.ifStack = append(p.ifStack, ifState{ast: ast})
197 p.outStmts = &ast.trueStmts
198 return ast
199}
200
Fumitoshi Ukaie520f262015-03-31 17:27:03 +0900201func closeParen(ch byte) (byte, error) {
202 switch ch {
203 case '(':
204 return ')', nil
205 case '{':
206 return '}', nil
207 default:
208 return 0, fmt.Errorf("unexpected paren %c", ch)
Shinichiro Hamajiaf1e8162015-03-31 02:15:37 +0900209 }
Fumitoshi Ukaie520f262015-03-31 17:27:03 +0900210}
Shinichiro Hamajiaf1e8162015-03-31 02:15:37 +0900211
Fumitoshi Ukaie520f262015-03-31 17:27:03 +0900212// parseExpr parses s as expr.
213// The expr should starts with '(' or '{' and returns strings
214// separeted by ',' before ')' or '}' respectively, and an index for the rest.
215func parseExpr(s string) ([]string, int, error) {
216 if len(s) == 0 {
217 return nil, 0, errors.New("empty expr")
218 }
219 paren, err := closeParen(s[0])
220 if err != nil {
221 return nil, 0, err
222 }
223 parenCnt := make(map[byte]int)
Shinichiro Hamajiaf1e8162015-03-31 02:15:37 +0900224 i := 0
Fumitoshi Ukaie520f262015-03-31 17:27:03 +0900225 ia := 1
226 var args []string
227Loop:
Shinichiro Hamajiaf1e8162015-03-31 02:15:37 +0900228 for {
229 i++
230 if i == len(s) {
Fumitoshi Ukaie520f262015-03-31 17:27:03 +0900231 return nil, 0, errors.New("unexpected end of expr")
Shinichiro Hamajiaf1e8162015-03-31 02:15:37 +0900232 }
233 ch := s[i]
Fumitoshi Ukaie520f262015-03-31 17:27:03 +0900234 switch ch {
235 case '(', '{':
236 cch, err := closeParen(ch)
237 if err != nil {
238 return nil, 0, err
Shinichiro Hamajiaf1e8162015-03-31 02:15:37 +0900239 }
Fumitoshi Ukaie520f262015-03-31 17:27:03 +0900240 parenCnt[cch]++
241 case ')', '}':
242 parenCnt[ch]--
243 if ch == paren && parenCnt[ch] < 0 {
244 break Loop
Shinichiro Hamajiaf1e8162015-03-31 02:15:37 +0900245 }
Fumitoshi Ukaie520f262015-03-31 17:27:03 +0900246 case ',':
247 if parenCnt[')'] == 0 && parenCnt['}'] == 0 {
248 args = append(args, s[ia:i])
249 ia = i + 1
250 }
Shinichiro Hamajiaf1e8162015-03-31 02:15:37 +0900251 }
252 }
Fumitoshi Ukaie520f262015-03-31 17:27:03 +0900253 args = append(args, s[ia:i])
254 return args, i + 1, nil
255}
256
Shinichiro Hamaji76de43e2015-04-03 10:40:18 +0900257func parseTwoQuotes(s string) ([]string, bool) {
258 toks := splitSpaces(s)
259 if len(toks) != 2 {
260 return nil, false
261 }
262 var args []string
263 for _, tok := range toks {
264 if len(tok) < 2 {
265 return nil, false
266 }
267 ti := len(tok) - 1
268 if tok[0] != tok[ti] || (tok[0] != '\'' && tok[ti] != '"') {
269 return nil, false
270 }
271 args = append(args, tok[1:ti])
272 }
273 return args, true
274}
275
Fumitoshi Ukaie520f262015-03-31 17:27:03 +0900276func parseEq(s string) (string, string, bool) {
277 args, _, err := parseExpr(s)
278 if err != nil {
Shinichiro Hamaji76de43e2015-04-03 10:40:18 +0900279 args, ok := parseTwoQuotes(s)
280 if ok {
281 return args[0], args[1], true
282 }
Fumitoshi Ukaie520f262015-03-31 17:27:03 +0900283 return "", "", false
284 }
285 if len(args) != 2 {
286 return "", "", false
287 }
288 // TODO: check rest?
289 return args[0], args[1], true
Shinichiro Hamajiaf1e8162015-03-31 02:15:37 +0900290}
291
Shinichiro Hamaji497754d2015-03-31 02:02:11 +0900292func (p *parser) parseIfeq(line string, oplen int) AST {
Fumitoshi Ukaie520f262015-03-31 17:27:03 +0900293 lhs, rhs, ok := parseEq(strings.TrimSpace(line[oplen+1:]))
Shinichiro Hamajiaf1e8162015-03-31 02:15:37 +0900294 if !ok {
295 Error(p.filename, p.lineno, `*** invalid syntax in conditional.`)
296 }
297
Shinichiro Hamaji497754d2015-03-31 02:02:11 +0900298 ast := &IfAST{
Shinichiro Hamajiae32b782015-03-31 14:41:19 +0900299 op: line[:oplen],
Shinichiro Hamajiaf1e8162015-03-31 02:15:37 +0900300 lhs: lhs,
301 rhs: rhs,
Shinichiro Hamaji497754d2015-03-31 02:02:11 +0900302 }
303 ast.filename = p.filename
304 ast.lineno = p.lineno
305 p.addStatement(ast)
306 p.ifStack = append(p.ifStack, ifState{ast: ast})
307 p.outStmts = &ast.trueStmts
308 return ast
309}
310
311func (p *parser) checkIfStack(curKeyword string) {
312 if len(p.ifStack) == 0 {
313 Error(p.filename, p.lineno, `*** extraneous %q.`, curKeyword)
314 }
315}
316
317func (p *parser) parseElse(line string) {
318 p.checkIfStack("else")
319 state := &p.ifStack[len(p.ifStack)-1]
Fumitoshi Ukai0293c7a2015-03-31 16:26:53 +0900320 if state.inElse {
Shinichiro Hamaji497754d2015-03-31 02:02:11 +0900321 Error(p.filename, p.lineno, `*** only one "else" per conditional.`)
322 }
Fumitoshi Ukai0293c7a2015-03-31 16:26:53 +0900323 state.inElse = true
Shinichiro Hamaji497754d2015-03-31 02:02:11 +0900324 p.outStmts = &state.ast.falseStmts
325}
326
327func (p *parser) parseEndif(line string) {
328 p.checkIfStack("endif")
Shinichiro Hamajiae32b782015-03-31 14:41:19 +0900329 p.ifStack = p.ifStack[0 : len(p.ifStack)-1]
Shinichiro Hamaji497754d2015-03-31 02:02:11 +0900330 if len(p.ifStack) == 0 {
331 p.outStmts = &p.mk.stmts
332 } else {
333 state := p.ifStack[len(p.ifStack)-1]
Fumitoshi Ukai0293c7a2015-03-31 16:26:53 +0900334 if state.inElse {
Shinichiro Hamaji497754d2015-03-31 02:02:11 +0900335 p.outStmts = &state.ast.falseStmts
336 } else {
337 p.outStmts = &state.ast.trueStmts
338 }
339 }
340}
341
Fumitoshi Ukai82096302015-04-01 10:37:47 +0900342var directives = map[string]func(*parser, string){
343 "include ": includeDirective,
344 "-include ": sincludeDirective,
345 "sinclude": sincludeDirective,
346 "ifdef ": ifdefDirective,
347 "ifndef ": ifndefDirective,
348 "ifeq ": ifeqDirective,
349 "ifneq ": ifneqDirective,
350 "else": elseDirective,
351 "endif": endifDirective,
Fumitoshi Ukai3d54db82015-04-01 11:03:31 +0900352 "define ": defineDirective,
Fumitoshi Ukai82096302015-04-01 10:37:47 +0900353}
354
Shinichiro Hamajia66a1792015-03-31 18:04:08 +0900355func (p *parser) parseKeywords(line string) bool {
Shinichiro Hamajie3a94632015-03-31 01:12:52 +0900356 stripped := strings.TrimLeft(line, " \t")
Fumitoshi Ukai82096302015-04-01 10:37:47 +0900357 for prefix, f := range directives {
358 if strings.HasPrefix(stripped, prefix) {
359 f(p, stripped)
360 return true
361 }
Shinichiro Hamaji497754d2015-03-31 02:02:11 +0900362 }
Shinichiro Hamajia66a1792015-03-31 18:04:08 +0900363 return false
364}
365
Fumitoshi Ukai82096302015-04-01 10:37:47 +0900366func includeDirective(p *parser, line string) {
367 p.addStatement(p.parseInclude(line, len("include")))
368}
369
370func sincludeDirective(p *parser, line string) {
371 p.addStatement(p.parseInclude(line, len("-include")))
372}
373
374func ifdefDirective(p *parser, line string) {
375 p.parseIfdef(line, len("ifdef"))
376}
377
378func ifndefDirective(p *parser, line string) {
379 p.parseIfdef(line, len("ifndef"))
380}
381
382func ifeqDirective(p *parser, line string) {
383 p.parseIfeq(line, len("ifeq"))
384}
385
386func ifneqDirective(p *parser, line string) {
387 p.parseIfeq(line, len("ifneq"))
388}
389
390func elseDirective(p *parser, line string) {
391 p.parseElse(line)
392}
393
394func endifDirective(p *parser, line string) {
395 p.parseEndif(line)
396}
397
Fumitoshi Ukai3d54db82015-04-01 11:03:31 +0900398func defineDirective(p *parser, line string) {
399 p.inDef = []string{strings.TrimLeft(line[len("define "):], " \t")}
400}
401
Fumitoshi Ukaif8efa0a2015-03-30 18:10:11 +0900402func (p *parser) parse() (mk Makefile, err error) {
403 defer func() {
404 if r := recover(); r != nil {
405 err = fmt.Errorf("panic: %v", r)
406 }
407 }()
Shinichiro Hamajie1841582015-03-30 17:20:33 +0900408 for !p.done {
Shinichiro Hamaji52e83aa2015-04-06 17:20:28 +0900409 line := p.readMakefileLine()
Shinichiro Hamajie1841582015-03-30 17:20:33 +0900410
Fumitoshi Ukai3d54db82015-04-01 11:03:31 +0900411 if len(p.inDef) > 0 {
412 if strings.TrimLeft(string(line), " ") == "endef" {
413 Log("multilineAssign %q", p.inDef)
414 ast := &AssignAST{
415 lhs: p.inDef[0],
416 rhs: strings.Join(p.inDef[1:], "\n"),
417 op: "=",
418 }
419 ast.filename = p.filename
420 ast.lineno = p.lineno - len(p.inDef)
421 p.addStatement(ast)
422 p.inDef = nil
423 continue
424 }
425 p.inDef = append(p.inDef, string(line))
426 continue
427 }
428
Shinichiro Hamajia66a1792015-03-31 18:04:08 +0900429 if p.parseKeywords(string(line)) {
430 continue
431 }
432
Shinichiro Hamaji7f1ede32015-03-30 18:05:22 +0900433 var ast AST
Shinichiro Hamaji34e23242015-04-06 15:44:50 +0900434 var parenStack []byte
Shinichiro Hamajie1841582015-03-30 17:20:33 +0900435 for i, ch := range line {
436 switch ch {
Shinichiro Hamaji34e23242015-04-06 15:44:50 +0900437 case '(', '{':
438 parenStack = append(parenStack, ch)
439 case ')', '}':
440 if len(parenStack) == 0 {
441 Warn(p.filename, p.lineno, "Unmatched parens: %s", line)
442 } else {
443 parenStack = parenStack[:len(parenStack)-1]
444 }
445 }
446 if len(parenStack) > 0 {
447 continue
448 }
449
450 switch ch {
Shinichiro Hamajie1841582015-03-30 17:20:33 +0900451 case ':':
Shinichiro Hamaji7f1ede32015-03-30 18:05:22 +0900452 if i+1 < len(line) && line[i+1] == '=' {
Fumitoshi Ukaie1b813c2015-03-30 18:38:21 +0900453 ast = p.parseAssign(line, i, i+2)
Shinichiro Hamaji7f1ede32015-03-30 18:05:22 +0900454 } else {
Shinichiro Hamajide829712015-03-31 18:26:56 +0900455 ast = p.parseMaybeRule(string(line))
Shinichiro Hamajie1841582015-03-30 17:20:33 +0900456 }
Shinichiro Hamajie1841582015-03-30 17:20:33 +0900457 case '=':
Fumitoshi Ukaie1b813c2015-03-30 18:38:21 +0900458 ast = p.parseAssign(line, i, i+1)
Shinichiro Hamaji69b7f652015-03-31 01:01:59 +0900459 case '?', '+':
460 if i+1 < len(line) && line[i+1] == '=' {
461 ast = p.parseAssign(line, i, i+2)
462 }
Shinichiro Hamajie1841582015-03-30 17:20:33 +0900463 }
Shinichiro Hamaji7f1ede32015-03-30 18:05:22 +0900464 if ast != nil {
Shinichiro Hamaji497754d2015-03-31 02:02:11 +0900465 p.addStatement(ast)
Shinichiro Hamaji7f1ede32015-03-30 18:05:22 +0900466 break
467 }
Fumitoshi Ukai119dc912015-03-30 16:52:41 +0900468 }
Shinichiro Hamajide829712015-03-31 18:26:56 +0900469 if ast == nil {
470 ast = p.parseMaybeRule(string(line))
471 if ast != nil {
472 p.addStatement(ast)
473 }
Shinichiro Hamaji685fecf2015-03-30 18:28:12 +0900474 }
Fumitoshi Ukai119dc912015-03-30 16:52:41 +0900475 }
476 return p.mk, nil
477}
478
479func ParseMakefile(filename string) (Makefile, error) {
480 f, err := os.Open(filename)
481 if err != nil {
482 return Makefile{}, err
483 }
Fumitoshi Ukaicf2b0382015-03-30 17:48:54 +0900484 defer f.Close()
Shinichiro Hamaji685fecf2015-03-30 18:28:12 +0900485 parser := newParser(f, filename)
Fumitoshi Ukai119dc912015-03-30 16:52:41 +0900486 return parser.parse()
487}
488
489func ParseDefaultMakefile() (Makefile, error) {
490 candidates := []string{"GNUmakefile", "makefile", "Makefile"}
491 for _, filename := range candidates {
492 if exists(filename) {
493 return ParseMakefile(filename)
494 }
495 }
Fumitoshi Ukaicf2b0382015-03-30 17:48:54 +0900496 return Makefile{}, errors.New("no targets specified and no makefile found.")
Fumitoshi Ukai119dc912015-03-30 16:52:41 +0900497}
Shinichiro Hamajic3840812015-04-02 02:10:20 +0900498
Shinichiro Hamaji29ffc972015-04-06 16:13:27 +0900499func ParseMakefileString(s string, name string, lineno int) (Makefile, error) {
Shinichiro Hamajia5dee372015-04-03 16:41:30 +0900500 rd := strings.NewReader(s)
501 parser := newParser(rd, name)
Shinichiro Hamaji29ffc972015-04-06 16:13:27 +0900502 parser.lineno = lineno - 1
503 parser.elineno = lineno
Shinichiro Hamajia5dee372015-04-03 16:41:30 +0900504 return parser.parse()
Shinichiro Hamajic3840812015-04-02 02:10:20 +0900505}