blob: 12dffe36f07d0eafa07c62e285b74eb0c8754cf7 [file] [log] [blame]
Fumitoshi Ukai119dc912015-03-30 16:52:41 +09001package main
2
3import (
4 "bufio"
5 "errors"
6 "io"
7 "os"
8 "strings"
9)
10
11type Makefile struct {
12 stmts []AST
13}
14
15type parser struct {
16 rd *bufio.Reader
17 mk Makefile
18 lineno int
19 done bool
20}
21
22func exists(filename string) bool {
23 f, err := os.Open(filename)
24 if err != nil {
25 return false
26 }
27 f.Close()
28 return true
29}
30
31func isdigit(ch byte) bool {
32 return ch >= '0' && ch <= '9'
33}
34
35func isident(ch byte) bool {
36 return (ch >= 'A' && ch <= 'Z' || ch >= 'a' && ch <= 'z' || ch == '_' || ch == '.')
37}
38
39func newParser(rd io.Reader) *parser {
40 return &parser{
41 rd: bufio.NewReader(rd),
42 }
43}
44
45func (p *parser) readByte() (byte, error) {
46 ch, err := p.rd.ReadByte()
47 if err != nil {
48 p.done = true
49 }
50 return ch, err
51}
52
53func (p *parser) unreadByte() {
54 p.rd.UnreadByte()
55}
56
57func (p *parser) skipWhiteSpaces() error {
58 for {
59 ch, err := p.readByte()
60 if err != nil {
61 return err
62 }
63 switch ch {
64 case '\n':
65 p.lineno++
66 fallthrough
67 case '\r', ' ':
68 continue
69 default:
70 p.unreadByte()
71 return nil
72 }
73 }
74}
75
76func (p *parser) getNextToken() (string, error) {
77 if err := p.skipWhiteSpaces(); err != nil {
78 return "", err
79 }
80 ch, err := p.readByte()
81 if err != nil {
82 return "", errors.New("TODO")
83 }
84 switch ch {
85 case '$', '=':
86 return string(ch), nil
87 case ':':
88 var s []byte
89 s = append(s, ch)
90 ch, err := p.readByte()
91 if ch == ':' {
92 ch, err = p.readByte()
93 }
94 if err != nil {
95 return string(s), err
96 }
97 if ch == '=' {
98 s = append(s, ch)
99 } else {
100 p.unreadByte()
101 }
102 return string(s), nil
103 default:
104 if isident(ch) {
105 var s []byte
106 s = append(s, ch)
107 for {
108 ch, err := p.readByte()
109 if err != nil {
110 return string(s), err
111 }
112 if isident(ch) || isdigit(ch) {
113 s = append(s, ch)
114 } else {
115 p.unreadByte()
116 return string(s), nil
117 }
118 }
119 }
120 }
121
122 return "", errors.New("foobar")
123}
124
125func (p *parser) readUntilEol() string {
126 var r []byte
127 for {
128 ch, err := p.readByte()
129 if err != nil || ch == '\n' {
130 return string(r)
131 }
132 r = append(r, ch)
133 }
134}
135
136func (p *parser) parseAssign(lhs string) AST {
137 ast := &AssignAST{lhs: lhs}
138 ast.lineno = p.lineno
139 ast.rhs = strings.TrimSpace(p.readUntilEol())
140 return ast
141}
142
143func (p *parser) parseRule(lhs string) AST {
144 ast := &RuleAST{lhs: lhs}
145 ast.lineno = p.lineno
146 ast.rhs = strings.TrimSpace(p.readUntilEol())
147 for {
148 ch, err := p.readByte()
149 if err != nil {
150 return ast
151 }
152 switch ch {
153 case '\n':
154 continue
155 case '\t':
156 ast.cmds = append(ast.cmds, strings.TrimSpace(p.readUntilEol()))
157 continue
158 default:
159 p.unreadByte()
160 return ast
161 }
162 }
163}
164
165func (p *parser) parse() (Makefile, error) {
166 for {
167 tok, err := p.getNextToken()
168 Log("tok=%s", tok)
169 if err == io.EOF {
170 return p.mk, nil
171 } else if err != nil {
172 return p.mk, err
173 }
174 switch tok {
175 default:
176 ntok, err := p.getNextToken()
177 if err != nil {
178 return p.mk, err
179 }
180 switch ntok {
181 case "=":
182 ast := p.parseAssign(tok)
183 p.mk.stmts = append(p.mk.stmts, ast)
184 case ":":
185 ast := p.parseRule(tok)
186 p.mk.stmts = append(p.mk.stmts, ast)
187 }
188 }
189 }
190 return p.mk, nil
191}
192
193func ParseMakefile(filename string) (Makefile, error) {
194 f, err := os.Open(filename)
195 if err != nil {
196 return Makefile{}, err
197 }
198 parser := newParser(f)
199 return parser.parse()
200}
201
202func ParseDefaultMakefile() (Makefile, error) {
203 candidates := []string{"GNUmakefile", "makefile", "Makefile"}
204 for _, filename := range candidates {
205 if exists(filename) {
206 return ParseMakefile(filename)
207 }
208 }
209 return Makefile{}, errors.New("No targets specified and no makefile found.")
210}