Annotation of pkgsrc/pkgtools/pkglint/files/mkparser.go, Revision 1.17
1.1 rillig 1: package main
2:
3: import (
1.7 rillig 4: "netbsd.org/pkglint/regex"
5: "netbsd.org/pkglint/trace"
1.1 rillig 6: "strings"
7: )
8:
1.17 ! rillig 9: // MkParser wraps a Parser and provides methods for parsing
! 10: // things related to Makefiles.
1.1 rillig 11: type MkParser struct {
12: *Parser
13: }
14:
1.10 rillig 15: func NewMkParser(line Line, text string, emitWarnings bool) *MkParser {
1.1 rillig 16: return &MkParser{NewParser(line, text, emitWarnings)}
17: }
18:
19: func (p *MkParser) MkTokens() []*MkToken {
20: repl := p.repl
21:
22: var tokens []*MkToken
23: for !p.EOF() {
24: if repl.AdvanceStr("#") {
25: repl.AdvanceRest()
26: }
27:
28: mark := repl.Mark()
29: if varuse := p.VarUse(); varuse != nil {
30: tokens = append(tokens, &MkToken{Text: repl.Since(mark), Varuse: varuse})
31: continue
32: }
33:
34: again:
1.7 rillig 35: dollar := strings.IndexByte(repl.Rest(), '$')
1.1 rillig 36: if dollar == -1 {
1.7 rillig 37: dollar = len(repl.Rest())
1.1 rillig 38: }
39: repl.Skip(dollar)
40: if repl.AdvanceStr("$$") {
41: goto again
42: }
43: text := repl.Since(mark)
44: if text != "" {
45: tokens = append(tokens, &MkToken{Text: text})
46: continue
47: }
48:
49: break
50: }
51: return tokens
52: }
53:
54: func (p *MkParser) VarUse() *MkVarUse {
55: repl := p.repl
56:
57: mark := repl.Mark()
58: if repl.AdvanceStr("${") || repl.AdvanceStr("$(") {
59: usingRoundParen := repl.Since(mark) == "$("
60: closing := ifelseStr(usingRoundParen, ")", "}")
61:
62: varnameMark := repl.Mark()
63: varname := p.Varname()
64: if varname != "" {
65: modifiers := p.VarUseModifiers(varname, closing)
66: if repl.AdvanceStr(closing) {
1.6 rillig 67: if usingRoundParen && p.EmitWarnings {
68: parenVaruse := repl.Since(mark)
69: bracesVaruse := "${" + parenVaruse[2:len(parenVaruse)-1] + "}"
1.12 rillig 70: fix := p.Line.Autofix()
71: fix.Warnf("Please use curly braces {} instead of round parentheses () for %s.", varname)
72: fix.Replace(parenVaruse, bracesVaruse)
73: fix.Apply()
1.6 rillig 74: }
1.1 rillig 75: return &MkVarUse{varname, modifiers}
76: }
77: }
78:
1.9 rillig 79: for p.VarUse() != nil || repl.AdvanceRegexp(regex.Pattern(`^([^$:`+closing+`]|\$\$)+`)) {
1.1 rillig 80: }
81: rest := p.Rest()
82: if hasPrefix(rest, ":L") || hasPrefix(rest, ":?") {
83: varexpr := repl.Since(varnameMark)
84: modifiers := p.VarUseModifiers(varexpr, closing)
85: if repl.AdvanceStr(closing) {
86: return &MkVarUse{varexpr, modifiers}
87: }
88: }
89: repl.Reset(mark)
90: }
91:
92: if repl.AdvanceStr("$@") {
93: return &MkVarUse{"@", nil}
94: }
95: if repl.AdvanceStr("$<") {
96: return &MkVarUse{"<", nil}
97: }
1.2 rillig 98: if repl.PeekByte() == '$' && repl.AdvanceRegexp(`^\$(\w)`) {
1.7 rillig 99: varname := repl.Group(1)
1.1 rillig 100: if p.EmitWarnings {
1.5 rillig 101: p.Line.Warnf("$%[1]s is ambiguous. Use ${%[1]s} if you mean a Makefile variable or $$%[1]s if you mean a shell variable.", varname)
1.1 rillig 102: }
103: return &MkVarUse{varname, nil}
104: }
105: return nil
106: }
107:
108: func (p *MkParser) VarUseModifiers(varname, closing string) []string {
109: repl := p.repl
110:
111: var modifiers []string
112: mayOmitColon := false
1.15 rillig 113: loop:
1.1 rillig 114: for repl.AdvanceStr(":") || mayOmitColon {
115: mayOmitColon = false
116: modifierMark := repl.Mark()
117:
118: switch repl.PeekByte() {
119: case 'E', 'H', 'L', 'O', 'Q', 'R', 'T', 's', 't', 'u':
120: if repl.AdvanceRegexp(`^(E|H|L|Ox?|Q|R|T|sh|tA|tW|tl|tu|tw|u)`) {
121: modifiers = append(modifiers, repl.Since(modifierMark))
122: continue
123: }
124: if repl.AdvanceStr("ts") {
1.7 rillig 125: rest := repl.Rest()
1.1 rillig 126: if len(rest) >= 2 && (rest[1] == closing[0] || rest[1] == ':') {
127: repl.Skip(1)
128: } else if len(rest) >= 1 && (rest[0] == closing[0] || rest[0] == ':') {
129: } else if repl.AdvanceRegexp(`^\\\d+`) {
130: } else {
1.15 rillig 131: break loop
1.1 rillig 132: }
133: modifiers = append(modifiers, repl.Since(modifierMark))
134: continue
135: }
136:
137: case '=', 'D', 'M', 'N', 'U':
138: if repl.AdvanceRegexp(`^[=DMNU]`) {
1.16 rillig 139: for p.VarUse() != nil || repl.AdvanceRegexp(regex.Pattern(`^([^$:\\`+closing+`]|\$\$|\\.)+`)) {
1.1 rillig 140: }
1.16 rillig 141: arg := repl.Since(modifierMark)
142: modifiers = append(modifiers, strings.Replace(arg, "\\:", ":", -1))
1.1 rillig 143: continue
144: }
145:
146: case 'C', 'S':
147: if repl.AdvanceRegexp(`^[CS]([%,/:;@^|])`) {
1.7 rillig 148: separator := repl.Group(1)
1.1 rillig 149: repl.AdvanceStr("^")
1.9 rillig 150: re := regex.Pattern(`^([^\` + separator + `$` + closing + `\\]|\$\$|\\.)+`)
1.1 rillig 151: for p.VarUse() != nil || repl.AdvanceRegexp(re) {
152: }
153: repl.AdvanceStr("$")
154: if repl.AdvanceStr(separator) {
155: for p.VarUse() != nil || repl.AdvanceRegexp(re) {
156: }
157: if repl.AdvanceStr(separator) {
158: repl.AdvanceRegexp(`^[1gW]`)
159: modifiers = append(modifiers, repl.Since(modifierMark))
160: mayOmitColon = true
161: continue
162: }
163: }
164: }
165:
166: case '@':
167: if repl.AdvanceRegexp(`^@([\w.]+)@`) {
1.7 rillig 168: loopvar := repl.Group(1)
1.9 rillig 169: for p.VarUse() != nil || repl.AdvanceRegexp(regex.Pattern(`^([^$:@`+closing+`\\]|\$\$|\\.)+`)) {
1.1 rillig 170: }
171: if !repl.AdvanceStr("@") && p.EmitWarnings {
1.5 rillig 172: p.Line.Warnf("Modifier ${%s:@%s@...@} is missing the final \"@\".", varname, loopvar)
1.1 rillig 173: }
174: modifiers = append(modifiers, repl.Since(modifierMark))
175: continue
176: }
177:
178: case '[':
1.11 rillig 179: if repl.AdvanceRegexp(`^\[(?:[-.\d]+|#)\]`) {
1.1 rillig 180: modifiers = append(modifiers, repl.Since(modifierMark))
181: continue
182: }
183:
184: case '?':
185: repl.AdvanceStr("?")
1.9 rillig 186: re := regex.Pattern(`^([^$:` + closing + `]|\$\$)+`)
1.1 rillig 187: for p.VarUse() != nil || repl.AdvanceRegexp(re) {
188: }
189: if repl.AdvanceStr(":") {
190: for p.VarUse() != nil || repl.AdvanceRegexp(re) {
191: }
192: modifiers = append(modifiers, repl.Since(modifierMark))
193: continue
194: }
195: }
196:
197: repl.Reset(modifierMark)
1.17 ! rillig 198: // FIXME: Why AdvanceRegexp? This accepts :S,a,b,c,d,e,f but shouldn't.
1.9 rillig 199: for p.VarUse() != nil || repl.AdvanceRegexp(regex.Pattern(`^([^:$`+closing+`]|\$\$)+`)) {
1.1 rillig 200: }
201: if suffixSubst := repl.Since(modifierMark); contains(suffixSubst, "=") {
202: modifiers = append(modifiers, suffixSubst)
203: continue
204: }
205: }
206: return modifiers
207: }
208:
1.13 rillig 209: // MkCond parses a condition like ${OPSYS} == "NetBSD".
210: // See devel/bmake/files/cond.c.
1.14 rillig 211: func (p *MkParser) MkCond() MkCond {
1.1 rillig 212: and := p.mkCondAnd()
213: if and == nil {
214: return nil
215: }
216:
1.14 rillig 217: ands := []MkCond{and}
1.1 rillig 218: for {
219: mark := p.repl.Mark()
220: if !p.repl.AdvanceRegexp(`^\s*\|\|\s*`) {
221: break
222: }
223: next := p.mkCondAnd()
224: if next == nil {
225: p.repl.Reset(mark)
226: break
227: }
228: ands = append(ands, next)
229: }
230: if len(ands) == 1 {
231: return and
232: }
1.14 rillig 233: return &mkCond{Or: ands}
1.1 rillig 234: }
235:
1.14 rillig 236: func (p *MkParser) mkCondAnd() MkCond {
1.1 rillig 237: atom := p.mkCondAtom()
238: if atom == nil {
239: return nil
240: }
241:
1.14 rillig 242: atoms := []MkCond{atom}
1.1 rillig 243: for {
244: mark := p.repl.Mark()
245: if !p.repl.AdvanceRegexp(`^\s*&&\s*`) {
246: break
247: }
248: next := p.mkCondAtom()
249: if next == nil {
250: p.repl.Reset(mark)
251: break
252: }
253: atoms = append(atoms, next)
254: }
255: if len(atoms) == 1 {
256: return atom
257: }
1.14 rillig 258: return &mkCond{And: atoms}
1.1 rillig 259: }
260:
1.14 rillig 261: func (p *MkParser) mkCondAtom() MkCond {
1.7 rillig 262: if trace.Tracing {
263: defer trace.Call1(p.Rest())()
1.1 rillig 264: }
265:
266: repl := p.repl
267: mark := repl.Mark()
268: repl.SkipSpace()
269: switch {
270: case repl.AdvanceStr("!"):
271: cond := p.mkCondAtom()
272: if cond != nil {
1.14 rillig 273: return &mkCond{Not: cond}
1.1 rillig 274: }
275: case repl.AdvanceStr("("):
276: cond := p.MkCond()
277: if cond != nil {
278: repl.SkipSpace()
279: if repl.AdvanceStr(")") {
280: return cond
281: }
282: }
283: case repl.AdvanceRegexp(`^defined\s*\(`):
284: if varname := p.Varname(); varname != "" {
285: if repl.AdvanceStr(")") {
1.14 rillig 286: return &mkCond{Defined: varname}
1.1 rillig 287: }
288: }
289: case repl.AdvanceRegexp(`^empty\s*\(`):
290: if varname := p.Varname(); varname != "" {
291: modifiers := p.VarUseModifiers(varname, ")")
292: if repl.AdvanceStr(")") {
1.14 rillig 293: return &mkCond{Empty: &MkVarUse{varname, modifiers}}
1.1 rillig 294: }
295: }
296: case repl.AdvanceRegexp(`^(commands|exists|make|target)\s*\(`):
1.7 rillig 297: funcname := repl.Group(1)
1.1 rillig 298: argMark := repl.Mark()
299: for p.VarUse() != nil || repl.AdvanceRegexp(`^[^$)]+`) {
300: }
301: arg := repl.Since(argMark)
302: if repl.AdvanceStr(")") {
1.14 rillig 303: return &mkCond{Call: &MkCondCall{funcname, arg}}
1.1 rillig 304: }
305: default:
306: lhs := p.VarUse()
307: mark := repl.Mark()
308: if lhs == nil && repl.AdvanceStr("\"") {
309: if quotedLHS := p.VarUse(); quotedLHS != nil && repl.AdvanceStr("\"") {
310: lhs = quotedLHS
311: } else {
312: repl.Reset(mark)
313: }
314: }
315: if lhs != nil {
316: if repl.AdvanceRegexp(`^\s*(<|<=|==|!=|>=|>)\s*(\d+(?:\.\d+)?)`) {
1.14 rillig 317: return &mkCond{CompareVarNum: &MkCondCompareVarNum{lhs, repl.Group(1), repl.Group(2)}}
1.1 rillig 318: }
319: if repl.AdvanceRegexp(`^\s*(<|<=|==|!=|>=|>)\s*`) {
1.7 rillig 320: op := repl.Group(1)
1.1 rillig 321: if (op == "!=" || op == "==") && repl.AdvanceRegexp(`^"([^"\$\\]*)"`) {
1.14 rillig 322: return &mkCond{CompareVarStr: &MkCondCompareVarStr{lhs, op, repl.Group(1)}}
1.1 rillig 323: } else if repl.AdvanceRegexp(`^\w+`) {
1.14 rillig 324: return &mkCond{CompareVarStr: &MkCondCompareVarStr{lhs, op, repl.Group(0)}}
1.1 rillig 325: } else if rhs := p.VarUse(); rhs != nil {
1.14 rillig 326: return &mkCond{CompareVarVar: &MkCondCompareVarVar{lhs, op, rhs}}
1.4 rillig 327: } else if repl.PeekByte() == '"' {
328: mark := repl.Mark()
329: if repl.AdvanceStr("\"") {
330: if quotedRHS := p.VarUse(); quotedRHS != nil {
331: if repl.AdvanceStr("\"") {
1.14 rillig 332: return &mkCond{CompareVarVar: &MkCondCompareVarVar{lhs, op, quotedRHS}}
1.4 rillig 333: }
334: }
335: }
336: repl.Reset(mark)
1.1 rillig 337: }
338: } else {
1.14 rillig 339: return &mkCond{Not: &mkCond{Empty: lhs}} // See devel/bmake/files/cond.c:/\* For \.if \$/
1.1 rillig 340: }
341: }
342: if repl.AdvanceRegexp(`^\d+(?:\.\d+)?`) {
1.14 rillig 343: return &mkCond{Num: repl.Group(0)}
1.1 rillig 344: }
345: }
346: repl.Reset(mark)
347: return nil
348: }
349:
350: func (p *MkParser) Varname() string {
351: repl := p.repl
352:
353: mark := repl.Mark()
354: repl.AdvanceStr(".")
355: isVarnameChar := func(c byte) bool {
356: return 'A' <= c && c <= 'Z' || c == '_' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '+' || c == '-' || c == '.' || c == '*'
357: }
358: for p.VarUse() != nil || repl.AdvanceBytesFunc(isVarnameChar) {
359: }
360: return repl.Since(mark)
361: }
1.14 rillig 362:
363: type MkCond = *mkCond
364:
365: type mkCond struct {
366: Or []*mkCond
367: And []*mkCond
368: Not *mkCond
369:
370: Defined string
371: Empty *MkVarUse
372: CompareVarNum *MkCondCompareVarNum
373: CompareVarStr *MkCondCompareVarStr
374: CompareVarVar *MkCondCompareVarVar
375: Call *MkCondCall
376: Num string
377: }
378: type MkCondCompareVarNum struct {
379: Var *MkVarUse
380: Op string // One of <, <=, ==, !=, >=, >.
381: Num string
382: }
383: type MkCondCompareVarStr struct {
384: Var *MkVarUse
385: Op string // One of ==, !=.
386: Str string
387: }
388: type MkCondCompareVarVar struct {
389: Left *MkVarUse
390: Op string // One of <, <=, ==, !=, >=, >.
391: Right *MkVarUse
392: }
393: type MkCondCall struct {
394: Name string
395: Arg string
396: }
397:
398: type MkCondCallback struct {
399: Defined func(varname string)
400: Empty func(empty *MkVarUse)
401: CompareVarNum func(varuse *MkVarUse, op string, num string)
402: CompareVarStr func(varuse *MkVarUse, op string, str string)
403: CompareVarVar func(left *MkVarUse, op string, right *MkVarUse)
404: Call func(name string, arg string)
405: }
406:
407: type MkCondWalker struct{}
408:
409: func NewMkCondWalker() *MkCondWalker { return &MkCondWalker{} }
410:
411: func (w *MkCondWalker) Walk(cond MkCond, callback *MkCondCallback) {
412: switch {
413: case cond.Or != nil:
414: for _, or := range cond.Or {
415: w.Walk(or, callback)
416: }
417: case cond.And != nil:
418: for _, and := range cond.And {
419: w.Walk(and, callback)
420: }
421: case cond.Not != nil:
422: w.Walk(cond.Not, callback)
423:
424: case cond.Defined != "":
425: if callback.Defined != nil {
426: callback.Defined(cond.Defined)
427: }
428: case cond.Empty != nil:
429: if callback.Empty != nil {
430: callback.Empty(cond.Empty)
431: }
432: case cond.CompareVarVar != nil:
433: if callback.CompareVarVar != nil {
434: cvv := cond.CompareVarVar
435: callback.CompareVarVar(cvv.Left, cvv.Op, cvv.Right)
436: }
437: case cond.CompareVarStr != nil:
438: if callback.CompareVarStr != nil {
439: cvs := cond.CompareVarStr
440: callback.CompareVarStr(cvs.Var, cvs.Op, cvs.Str)
441: }
442: case cond.CompareVarNum != nil:
443: if callback.CompareVarNum != nil {
444: cvn := cond.CompareVarNum
445: callback.CompareVarNum(cvn.Var, cvn.Op, cvn.Num)
446: }
447: case cond.Call != nil:
448: if callback.Call != nil {
449: call := cond.Call
450: callback.Call(call.Name, call.Arg)
451: }
452: }
453: }
CVSweb <webmaster@jp.NetBSD.org>