Annotation of pkgsrc/pkgtools/pkglint/files/mkparser.go, Revision 1.29
1.21 rillig 1: package pkglint
1.1 rillig 2:
3: import (
1.7 rillig 4: "netbsd.org/pkglint/regex"
1.20 rillig 5: "netbsd.org/pkglint/textproc"
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 {
1.22 rillig 12: Line Line
13: lexer *textproc.Lexer
14: EmitWarnings bool
15: }
16:
17: func (p *MkParser) EOF() bool {
18: return p.lexer.EOF()
19: }
20:
21: func (p *MkParser) Rest() string {
22: return p.lexer.Rest()
1.1 rillig 23: }
24:
1.19 rillig 25: // NewMkParser creates a new parser for the given text.
26: // If emitWarnings is false, line may be nil.
1.21 rillig 27: //
1.23 rillig 28: // The text argument is assumed to be after unescaping the # character,
29: // which means the # is a normal character and does not introduce a Makefile comment.
1.25 rillig 30: // For VarUse, this distinction is irrelevant.
1.10 rillig 31: func NewMkParser(line Line, text string, emitWarnings bool) *MkParser {
1.29 ! rillig 32: assertf((line != nil) == emitWarnings, "line must be given iff emitWarnings is set")
1.22 rillig 33: return &MkParser{line, textproc.NewLexer(text), emitWarnings}
1.1 rillig 34: }
35:
1.21 rillig 36: // MkTokens splits a text like in the following example:
37: // Text${VAR:Mmodifier}${VAR2}more text${VAR3}
38: // into tokens like these:
39: // Text
40: // ${VAR:Mmodifier}
41: // ${VAR2}
42: // more text
43: // ${VAR3}
1.1 rillig 44: func (p *MkParser) MkTokens() []*MkToken {
1.20 rillig 45: lexer := p.lexer
1.1 rillig 46:
47: var tokens []*MkToken
48: for !p.EOF() {
1.20 rillig 49: mark := lexer.Mark()
1.1 rillig 50: if varuse := p.VarUse(); varuse != nil {
1.20 rillig 51: tokens = append(tokens, &MkToken{Text: lexer.Since(mark), Varuse: varuse})
1.1 rillig 52: continue
53: }
54:
1.21 rillig 55: for lexer.NextBytesFunc(func(b byte) bool { return b != '$' }) != "" || lexer.SkipString("$$") {
1.1 rillig 56: }
1.20 rillig 57: text := lexer.Since(mark)
1.1 rillig 58: if text != "" {
59: tokens = append(tokens, &MkToken{Text: text})
60: continue
61: }
62:
63: break
64: }
65: return tokens
66: }
67:
68: func (p *MkParser) VarUse() *MkVarUse {
1.27 rillig 69: rest := p.lexer.Rest()
70: if len(rest) < 2 || rest[0] != '$' {
71: return nil
72: }
73:
74: switch rest[1] {
75: case '{', '(':
76: return p.varUseBrace(rest[1] == '(')
1.1 rillig 77:
1.27 rillig 78: case '$':
79: // This is an escaped dollar character and not a variable use.
1.20 rillig 80: return nil
1.27 rillig 81:
82: case '@', '<', ' ':
83: // These variable names are known to exist.
84: //
85: // Many others are also possible but not used in practice.
86: // In particular, when parsing the :C or :S modifier,
87: // the $ must not be interpreted as a variable name,
88: // even when it looks like $/ could refer to the "/" variable.
89: //
90: // TODO: Find out whether $" is a variable use when it appears in the :M modifier.
91: p.lexer.Skip(2)
92: return &MkVarUse{rest[1:2], nil}
93:
94: default:
95: return p.varUseAlnum()
1.20 rillig 96: }
1.27 rillig 97: }
1.20 rillig 98:
1.27 rillig 99: // varUseBrace parses:
100: // ${VAR}
101: // ${arbitrary text:L}
102: // ${variable with invalid chars}
103: // $(PARENTHESES)
104: // ${VAR:Mpattern:C,:,colon,g:Q:Q:Q}
105: func (p *MkParser) varUseBrace(usingRoundParen bool) *MkVarUse {
106: lexer := p.lexer
1.1 rillig 107:
1.27 rillig 108: beforeDollar := lexer.Mark()
109: lexer.Skip(2)
1.21 rillig 110:
1.27 rillig 111: closing := byte('}')
112: if usingRoundParen {
113: closing = ')'
114: }
1.20 rillig 115:
1.27 rillig 116: beforeVarname := lexer.Mark()
117: varname := p.Varname()
118: p.varUseText(closing)
119: varExpr := lexer.Since(beforeVarname)
1.23 rillig 120:
1.27 rillig 121: modifiers := p.VarUseModifiers(varExpr, closing)
1.23 rillig 122:
1.27 rillig 123: closed := lexer.SkipByte(closing)
1.23 rillig 124:
1.27 rillig 125: if p.EmitWarnings {
126: if !closed {
127: p.Line.Warnf("Missing closing %q for %q.", string(rune(closing)), varExpr)
1.1 rillig 128: }
129:
1.27 rillig 130: if usingRoundParen && closed {
131: parenVaruse := lexer.Since(beforeDollar)
132: edit := []byte(parenVaruse)
133: edit[1] = '{'
134: edit[len(edit)-1] = '}'
135: bracesVaruse := string(edit)
1.21 rillig 136:
1.27 rillig 137: fix := p.Line.Autofix()
138: fix.Warnf("Please use curly braces {} instead of round parentheses () for %s.", varExpr)
139: fix.Replace(parenVaruse, bracesVaruse)
140: fix.Apply()
1.1 rillig 141: }
1.21 rillig 142:
1.27 rillig 143: if len(varExpr) > len(varname) && !(&MkVarUse{varExpr, modifiers}).IsExpression() {
144: p.Line.Warnf("Invalid part %q after variable name %q.", varExpr[len(varname):], varname)
1.1 rillig 145: }
1.27 rillig 146: }
147:
148: return &MkVarUse{varExpr, modifiers}
149: }
150:
151: func (p *MkParser) varUseAlnum() *MkVarUse {
152: lexer := p.lexer
1.21 rillig 153:
1.27 rillig 154: apparentVarname := textproc.NewLexer(lexer.Rest()[1:]).NextBytesSet(textproc.AlnumU)
155: if apparentVarname == "" {
1.25 rillig 156: return nil
1.1 rillig 157: }
1.21 rillig 158:
1.27 rillig 159: lexer.Skip(2)
1.21 rillig 160:
1.27 rillig 161: if p.EmitWarnings {
162: if len(apparentVarname) > 1 {
163: p.Line.Errorf("$%[1]s is ambiguous. Use ${%[1]s} if you mean a Make variable or $$%[1]s if you mean a shell variable.",
164: apparentVarname)
165: p.Line.Explain(
166: "Only the first letter after the dollar is the variable name.",
167: "Everything following it is normal text, even if it looks like a variable name to human readers.")
168: } else {
169: p.Line.Warnf("$%[1]s is ambiguous. Use ${%[1]s} if you mean a Make variable or $$%[1]s if you mean a shell variable.", apparentVarname)
170: p.Line.Explain(
171: "In its current form, this variable is parsed as a Make variable.",
172: "For human readers though, $x looks more like a shell variable than a Make variable,",
173: "since Make variables are usually written using braces (BSD-style) or parentheses (GNU-style).")
1.25 rillig 174: }
175: }
176:
1.27 rillig 177: return &MkVarUse{apparentVarname[:1], nil}
1.1 rillig 178: }
179:
1.21 rillig 180: // VarUseModifiers parses the modifiers of a variable being used, such as :Q, :Mpattern.
181: //
182: // See the bmake manual page.
1.20 rillig 183: func (p *MkParser) VarUseModifiers(varname string, closing byte) []MkVarUseModifier {
184: lexer := p.lexer
1.1 rillig 185:
1.27 rillig 186: // TODO: Split into VarUseModifier for parsing a single modifier.
187:
1.19 rillig 188: var modifiers []MkVarUseModifier
189: appendModifier := func(s string) { modifiers = append(modifiers, MkVarUseModifier{s}) }
1.21 rillig 190:
191: // The :S and :C modifiers may be chained without using the : as separator.
1.1 rillig 192: mayOmitColon := false
1.21 rillig 193:
1.20 rillig 194: for lexer.SkipByte(':') || mayOmitColon {
1.1 rillig 195: mayOmitColon = false
1.20 rillig 196: modifierMark := lexer.Mark()
1.1 rillig 197:
1.20 rillig 198: switch lexer.PeekByte() {
1.1 rillig 199: case 'E', 'H', 'L', 'O', 'Q', 'R', 'T', 's', 't', 'u':
1.21 rillig 200: mod := lexer.NextBytesSet(textproc.Alnum)
201: switch mod {
202:
203: case
204: "E", // Extension, e.g. path/file.suffix => suffix
205: "H", // Head, e.g. dir/subdir/file.suffix => dir/subdir
206: "L", // XXX: Shouldn't this be handled specially?
207: "O", // Order alphabetically
208: "Ox", // Shuffle
209: "Q", // Quote shell meta-characters
210: "R", // Strip the file suffix, e.g. path/file.suffix => file
211: "T", // Basename, e.g. path/file.suffix => file.suffix
212: "sh", // Evaluate the variable value as shell command
213: "tA", // Try to convert to absolute path
214: "tW", // Causes the value to be treated as a single word
215: "tl", // To lowercase
216: "tu", // To uppercase
217: "tw", // Causes the value to be treated as list of words
218: "u": // Remove adjacent duplicate words (like uniq(1))
219: appendModifier(mod)
1.1 rillig 220: continue
1.21 rillig 221:
222: case "ts":
223: // See devel/bmake/files/var.c:/case 't'
1.27 rillig 224: sep := p.varUseText(closing)
1.21 rillig 225: switch {
1.27 rillig 226: case sep == "":
227: lexer.SkipString(":")
228: case len(sep) == 1:
1.21 rillig 229: break
1.27 rillig 230: case matches(sep, `^\\\d+`):
1.21 rillig 231: break
232: default:
1.27 rillig 233: if p.EmitWarnings {
234: p.Line.Warnf("Invalid separator %q for :ts modifier of %q.", sep, varname)
235: }
1.1 rillig 236: }
1.20 rillig 237: appendModifier(lexer.Since(modifierMark))
1.1 rillig 238: continue
239: }
240:
241: case '=', 'D', 'M', 'N', 'U':
1.20 rillig 242: lexer.Skip(1)
243: re := G.res.Compile(regex.Pattern(ifelseStr(closing == '}', `^([^$:\\}]|\$\$|\\.)+`, `^([^$:\\)]|\$\$|\\.)+`)))
244: for p.VarUse() != nil || lexer.SkipRegexp(re) {
1.1 rillig 245: }
1.20 rillig 246: arg := lexer.Since(modifierMark)
247: appendModifier(strings.Replace(arg, "\\:", ":", -1))
248: continue
1.1 rillig 249:
250: case 'C', 'S':
1.27 rillig 251: if ok, _, _, _, _ := p.varUseModifierSubst(closing); ok {
1.21 rillig 252: appendModifier(lexer.Since(modifierMark))
253: mayOmitColon = true
254: continue
1.1 rillig 255: }
256:
257: case '@':
1.27 rillig 258: if p.varUseModifierAt(lexer, varname) {
1.20 rillig 259: appendModifier(lexer.Since(modifierMark))
1.1 rillig 260: continue
261: }
262:
263: case '[':
1.20 rillig 264: if lexer.SkipRegexp(G.res.Compile(`^\[(?:[-.\d]+|#)\]`)) {
265: appendModifier(lexer.Since(modifierMark))
1.1 rillig 266: continue
267: }
268:
269: case '?':
1.20 rillig 270: lexer.Skip(1)
1.27 rillig 271: p.varUseText(closing)
1.20 rillig 272: if lexer.SkipByte(':') {
1.27 rillig 273: p.varUseText(closing)
1.20 rillig 274: appendModifier(lexer.Since(modifierMark))
1.1 rillig 275: continue
276: }
277: }
278:
1.20 rillig 279: lexer.Reset(modifierMark)
1.22 rillig 280:
281: re := G.res.Compile(regex.Pattern(ifelseStr(closing == '}', `^([^:$}]|\$\$)+`, `^([^:$)]|\$\$)+`)))
1.20 rillig 282: for p.VarUse() != nil || lexer.SkipRegexp(re) {
1.1 rillig 283: }
1.23 rillig 284: modifier := lexer.Since(modifierMark)
1.22 rillig 285:
1.23 rillig 286: // ${SOURCES:%.c=%.o} or ${:!uname -a:[2]}
287: if contains(modifier, "=") || (hasPrefix(modifier, "!") && hasSuffix(modifier, "!")) {
288: appendModifier(modifier)
1.1 rillig 289: continue
290: }
1.23 rillig 291:
292: if p.EmitWarnings && modifier != "" {
293: p.Line.Warnf("Invalid variable modifier %q for %q.", modifier, varname)
294: }
295:
1.1 rillig 296: }
297: return modifiers
298: }
299:
1.27 rillig 300: // varUseText parses any text up to the next colon or closing mark.
301: // Nested variable uses are parsed as well.
302: //
303: // This is used for the :L and :? modifiers since they accept arbitrary
304: // text as the "variable name" and effectively interpret it as the variable
305: // value instead.
306: func (p *MkParser) varUseText(closing byte) string {
307: lexer := p.lexer
308: start := lexer.Mark()
309: re := G.res.Compile(regex.Pattern(ifelseStr(closing == '}', `^([^$:}]|\$\$)+`, `^([^$:)]|\$\$)+`)))
310: for p.VarUse() != nil || lexer.SkipRegexp(re) {
311: }
312: return lexer.Since(start)
313: }
314:
1.22 rillig 315: // varUseModifierSubst parses a :S,from,to, or a :C,from,to, modifier.
1.27 rillig 316: func (p *MkParser) varUseModifierSubst(closing byte) (ok bool, regex bool, from string, to string, options string) {
317: lexer := p.lexer
318: regex = lexer.PeekByte() == 'C'
1.22 rillig 319: lexer.Skip(1 /* the initial S or C */)
320:
1.21 rillig 321: sep := lexer.PeekByte() // bmake allows _any_ separator, even letters.
1.22 rillig 322: if sep == -1 || byte(sep) == closing {
1.27 rillig 323: return
1.21 rillig 324: }
325:
326: lexer.Skip(1)
327: separator := byte(sep)
328:
329: isOther := func(b byte) bool {
1.27 rillig 330: return b != separator && b != '$' && b != '\\'
1.21 rillig 331: }
332:
333: skipOther := func() {
334: for p.VarUse() != nil ||
335: lexer.SkipString("$$") ||
1.27 rillig 336: (len(lexer.Rest()) >= 2 && lexer.PeekByte() == '\\' && separator != '\\' && lexer.Skip(2)) ||
1.21 rillig 337: lexer.NextBytesFunc(isOther) != "" {
338: }
339: }
340:
1.27 rillig 341: fromStart := lexer.Mark()
1.21 rillig 342: lexer.SkipByte('^')
343: skipOther()
344: lexer.SkipByte('$')
1.27 rillig 345: from = lexer.Since(fromStart)
1.21 rillig 346:
347: if !lexer.SkipByte(separator) {
1.27 rillig 348: return
1.21 rillig 349: }
350:
1.27 rillig 351: toStart := lexer.Mark()
1.21 rillig 352: skipOther()
1.27 rillig 353: to = lexer.Since(toStart)
1.21 rillig 354:
355: if !lexer.SkipByte(separator) {
1.27 rillig 356: return
1.21 rillig 357: }
358:
1.27 rillig 359: optionsStart := lexer.Mark()
1.23 rillig 360: lexer.NextBytesFunc(func(b byte) bool { return b == '1' || b == 'g' || b == 'W' })
1.27 rillig 361: options = lexer.Since(optionsStart)
1.21 rillig 362:
1.27 rillig 363: ok = true
364: return
1.21 rillig 365: }
366:
1.22 rillig 367: // varUseModifierAt parses a variable modifier like ":@v@echo ${v};@",
368: // which expands the variable value in a loop.
1.27 rillig 369: func (p *MkParser) varUseModifierAt(lexer *textproc.Lexer, varname string) bool {
1.22 rillig 370: lexer.Skip(1 /* the initial @ */)
371:
1.21 rillig 372: loopVar := lexer.NextBytesSet(AlnumDot)
373: if loopVar == "" || !lexer.SkipByte('@') {
374: return false
375: }
376:
1.27 rillig 377: re := G.res.Compile(`^([^$@\\]|\\.)+`)
1.21 rillig 378: for p.VarUse() != nil || lexer.SkipString("$$") || lexer.SkipRegexp(re) {
379: }
380:
381: if !lexer.SkipByte('@') && p.EmitWarnings {
382: p.Line.Warnf("Modifier ${%s:@%s@...@} is missing the final \"@\".", varname, loopVar)
383: }
384:
385: return true
386: }
387:
1.13 rillig 388: // MkCond parses a condition like ${OPSYS} == "NetBSD".
1.22 rillig 389: //
1.13 rillig 390: // See devel/bmake/files/cond.c.
1.14 rillig 391: func (p *MkParser) MkCond() MkCond {
1.1 rillig 392: and := p.mkCondAnd()
393: if and == nil {
394: return nil
395: }
396:
1.14 rillig 397: ands := []MkCond{and}
1.1 rillig 398: for {
1.20 rillig 399: mark := p.lexer.Mark()
400: p.lexer.SkipHspace()
401: if !(p.lexer.SkipString("||")) {
1.1 rillig 402: break
403: }
404: next := p.mkCondAnd()
405: if next == nil {
1.20 rillig 406: p.lexer.Reset(mark)
1.1 rillig 407: break
408: }
409: ands = append(ands, next)
410: }
411: if len(ands) == 1 {
412: return and
413: }
1.14 rillig 414: return &mkCond{Or: ands}
1.1 rillig 415: }
416:
1.14 rillig 417: func (p *MkParser) mkCondAnd() MkCond {
1.1 rillig 418: atom := p.mkCondAtom()
419: if atom == nil {
420: return nil
421: }
422:
1.14 rillig 423: atoms := []MkCond{atom}
1.1 rillig 424: for {
1.20 rillig 425: mark := p.lexer.Mark()
426: p.lexer.SkipHspace()
427: if p.lexer.NextString("&&") == "" {
1.1 rillig 428: break
429: }
430: next := p.mkCondAtom()
431: if next == nil {
1.20 rillig 432: p.lexer.Reset(mark)
1.1 rillig 433: break
434: }
435: atoms = append(atoms, next)
436: }
437: if len(atoms) == 1 {
438: return atom
439: }
1.14 rillig 440: return &mkCond{And: atoms}
1.1 rillig 441: }
442:
1.14 rillig 443: func (p *MkParser) mkCondAtom() MkCond {
1.7 rillig 444: if trace.Tracing {
445: defer trace.Call1(p.Rest())()
1.1 rillig 446: }
447:
1.20 rillig 448: lexer := p.lexer
449: mark := lexer.Mark()
450: lexer.SkipHspace()
1.1 rillig 451: switch {
1.20 rillig 452: case lexer.SkipByte('!'):
1.1 rillig 453: cond := p.mkCondAtom()
454: if cond != nil {
1.14 rillig 455: return &mkCond{Not: cond}
1.1 rillig 456: }
1.20 rillig 457:
458: case lexer.SkipByte('('):
1.1 rillig 459: cond := p.MkCond()
460: if cond != nil {
1.20 rillig 461: lexer.SkipHspace()
462: if lexer.SkipByte(')') {
1.1 rillig 463: return cond
464: }
465: }
1.20 rillig 466:
1.21 rillig 467: case lexer.TestByteSet(textproc.Lower):
1.20 rillig 468: return p.mkCondFunc()
469:
1.1 rillig 470: default:
471: lhs := p.VarUse()
1.20 rillig 472: mark := lexer.Mark()
473: if lhs == nil && lexer.SkipByte('"') {
474: if quotedLHS := p.VarUse(); quotedLHS != nil && lexer.SkipByte('"') {
1.1 rillig 475: lhs = quotedLHS
476: } else {
1.20 rillig 477: lexer.Reset(mark)
1.1 rillig 478: }
479: }
1.21 rillig 480:
1.1 rillig 481: if lhs != nil {
1.27 rillig 482: lexer.SkipHspace()
483:
484: if m := lexer.NextRegexp(G.res.Compile(`^(<|<=|==|!=|>=|>)[\t ]*(0x[0-9A-Fa-f]+|\d+(?:\.\d+)?)`)); m != nil {
1.20 rillig 485: return &mkCond{CompareVarNum: &MkCondCompareVarNum{lhs, m[1], m[2]}}
1.1 rillig 486: }
1.21 rillig 487:
1.27 rillig 488: m := lexer.NextRegexp(G.res.Compile(`^(?:<|<=|==|!=|>=|>)`))
1.21 rillig 489: if m == nil {
1.24 rillig 490: return &mkCond{Var: lhs} // See devel/bmake/files/cond.c:/\* For \.if \$/
1.21 rillig 491: }
1.27 rillig 492: lexer.SkipHspace()
1.21 rillig 493:
1.27 rillig 494: op := m[0]
1.21 rillig 495: if op == "==" || op == "!=" {
496: if mrhs := lexer.NextRegexp(G.res.Compile(`^"([^"\$\\]*)"`)); mrhs != nil {
497: return &mkCond{CompareVarStr: &MkCondCompareVarStr{lhs, op, mrhs[1]}}
1.20 rillig 498: }
1.21 rillig 499: }
500:
501: if str := lexer.NextBytesSet(textproc.AlnumU); str != "" {
502: return &mkCond{CompareVarStr: &MkCondCompareVarStr{lhs, op, str}}
503: }
504:
505: if rhs := p.VarUse(); rhs != nil {
506: return &mkCond{CompareVarVar: &MkCondCompareVarVar{lhs, op, rhs}}
507: }
508:
509: if lexer.PeekByte() == '"' {
510: mark := lexer.Mark()
511: lexer.Skip(1)
512: if quotedRHS := p.VarUse(); quotedRHS != nil {
1.20 rillig 513: if lexer.SkipByte('"') {
1.21 rillig 514: return &mkCond{CompareVarVar: &MkCondCompareVarVar{lhs, op, quotedRHS}}
1.4 rillig 515: }
1.1 rillig 516: }
1.21 rillig 517: lexer.Reset(mark)
1.25 rillig 518:
519: lexer.Skip(1)
520: var rhsText strings.Builder
521: loop:
522: for {
523: m := lexer.Mark()
524: switch {
525: case p.VarUse() != nil,
526: lexer.NextBytesSet(textproc.Alnum) != "",
527: lexer.NextBytesFunc(func(b byte) bool { return b != '"' && b != '\\' }) != "":
528: rhsText.WriteString(lexer.Since(m))
529:
530: case lexer.SkipString("\\\""),
531: lexer.SkipString("\\\\"):
532: rhsText.WriteByte(lexer.Since(m)[1])
533:
534: case lexer.SkipByte('"'):
535: return &mkCond{CompareVarStr: &MkCondCompareVarStr{lhs, op, rhsText.String()}}
536: default:
537: break loop
538: }
539: }
540: lexer.Reset(mark)
1.1 rillig 541: }
542: }
1.21 rillig 543:
544: // See devel/bmake/files/cond.c:/^CondCvtArg
545: if m := lexer.NextRegexp(G.res.Compile(`^(?:0x[0-9A-Fa-f]+|\d+(?:\.\d+)?)`)); m != nil {
1.20 rillig 546: return &mkCond{Num: m[0]}
1.1 rillig 547: }
548: }
1.20 rillig 549: lexer.Reset(mark)
550: return nil
551: }
552:
553: func (p *MkParser) mkCondFunc() *mkCond {
554: lexer := p.lexer
555: mark := lexer.Mark()
556:
1.21 rillig 557: funcName := lexer.NextBytesSet(textproc.Lower)
1.20 rillig 558: lexer.SkipHspace()
559: if !lexer.SkipByte('(') {
560: return nil
561: }
562:
563: switch funcName {
564: case "defined":
565: varname := p.Varname()
566: if varname != "" && lexer.SkipByte(')') {
567: return &mkCond{Defined: varname}
568: }
569:
570: case "empty":
571: if varname := p.Varname(); varname != "" {
572: modifiers := p.VarUseModifiers(varname, ')')
573: if lexer.SkipByte(')') {
574: return &mkCond{Empty: &MkVarUse{varname, modifiers}}
575: }
576: }
577:
1.21 rillig 578: // TODO: Consider suggesting ${VAR} instead of !empty(VAR) since it is shorter and
1.23 rillig 579: // avoids unnecessary negation, which makes the expression less confusing.
580: // This applies especially to the ${VAR:Mpattern} form.
1.21 rillig 581:
1.20 rillig 582: case "commands", "exists", "make", "target":
583: argMark := lexer.Mark()
584: for p.VarUse() != nil || lexer.NextBytesFunc(func(b byte) bool { return b != '$' && b != ')' }) != "" {
585: }
586: arg := lexer.Since(argMark)
587: if lexer.SkipByte(')') {
588: return &mkCond{Call: &MkCondCall{funcName, arg}}
589: }
590: }
591:
592: lexer.Reset(mark)
1.1 rillig 593: return nil
594: }
595:
596: func (p *MkParser) Varname() string {
1.20 rillig 597: lexer := p.lexer
1.1 rillig 598:
1.25 rillig 599: // TODO: duplicated code in MatchVarassign
1.20 rillig 600: mark := lexer.Mark()
601: lexer.SkipByte('.')
1.25 rillig 602: for lexer.NextBytesSet(VarbaseBytes) != "" || p.VarUse() != nil {
603: }
604: if lexer.SkipByte('.') || hasPrefix(lexer.Since(mark), "SITES_") {
605: for lexer.NextBytesSet(VarparamBytes) != "" || p.VarUse() != nil {
606: }
1.1 rillig 607: }
1.20 rillig 608: return lexer.Since(mark)
1.1 rillig 609: }
1.14 rillig 610:
1.22 rillig 611: func (p *MkParser) PkgbasePattern() string {
1.23 rillig 612:
613: // isVersion returns true for "1.2", "[0-9]*", "${PKGVERSION}", "${PKGNAME:C/^.*-//}",
614: // but not for "client", "${PKGNAME}", "[a-z]".
615: isVersion := func(s string) bool {
616: lexer := textproc.NewLexer(s)
617:
618: lexer.SkipByte('[')
619: if lexer.NextByteSet(textproc.Digit) != -1 {
620: return true
621: }
622:
623: lookaheadParser := NewMkParser(nil, lexer.Rest(), false)
624: varUse := lookaheadParser.VarUse()
625: if varUse != nil {
626: if contains(varUse.varname, "VER") || len(varUse.modifiers) > 0 {
627: return true
628: }
629: }
630:
631: return false
632: }
633:
1.22 rillig 634: lexer := p.lexer
635: start := lexer.Mark()
636:
637: for {
638: if p.VarUse() != nil ||
639: lexer.SkipRegexp(G.res.Compile(`^[\w.*+,{}]+`)) ||
1.26 rillig 640: lexer.SkipRegexp(G.res.Compile(`^\[[\w-]+\]`)) {
1.22 rillig 641: continue
642: }
643:
1.23 rillig 644: if lexer.PeekByte() != '-' || isVersion(lexer.Rest()[1:]) {
1.22 rillig 645: break
646: }
647:
648: lexer.Skip(1 /* the hyphen */)
649: }
650:
651: pkgbase := lexer.Since(start)
652: if strings.Count(pkgbase, "{") == strings.Count(pkgbase, "}") {
653: return pkgbase
654: }
655:
656: // Unbalanced braces, as in "{ssh{,6}-[0-9]".
657: lexer.Reset(start)
658: return ""
659: }
660:
661: type DependencyPattern struct {
662: Pkgbase string // "freeciv-client", "{gcc48,gcc48-libs}", "${EMACS_REQD}"
663: LowerOp string // ">=", ">"
664: Lower string // "2.5.0", "${PYVER}"
665: UpperOp string // "<", "<="
666: Upper string // "3.0", "${PYVER}"
667: Wildcard string // "[0-9]*", "1.5.*", "${PYVER}"
668: }
669:
1.26 rillig 670: // Dependency parses a dependency pattern like "pkg>=1<2" or "pkg-[0-9]*".
1.22 rillig 671: func (p *MkParser) Dependency() *DependencyPattern {
672: lexer := p.lexer
673:
674: parseVersion := func() string {
675: mark := lexer.Mark()
676:
677: for p.VarUse() != nil {
678: }
679: if lexer.Since(mark) != "" {
680: return lexer.Since(mark)
681: }
682:
683: m := lexer.NextRegexp(G.res.Compile(`^\d[\w.]*`))
684: if m != nil {
685: return m[0]
686: }
687:
688: return ""
689: }
690:
691: var dp DependencyPattern
692: mark := lexer.Mark()
693: dp.Pkgbase = p.PkgbasePattern()
694: if dp.Pkgbase == "" {
695: return nil
696: }
697:
698: mark2 := lexer.Mark()
699: op := lexer.NextString(">=")
700: if op == "" {
701: op = lexer.NextString(">")
702: }
703:
704: if op != "" {
705: version := parseVersion()
706: if version != "" {
707: dp.LowerOp = op
708: dp.Lower = version
709: } else {
710: lexer.Reset(mark2)
711: }
712: }
713:
714: op = lexer.NextString("<=")
715: if op == "" {
716: op = lexer.NextString("<")
717: }
718:
719: if op != "" {
720: version := parseVersion()
721: if version != "" {
722: dp.UpperOp = op
723: dp.Upper = version
724: } else {
725: lexer.Reset(mark2)
726: }
727: }
728:
729: if dp.LowerOp != "" || dp.UpperOp != "" {
730: return &dp
731: }
732:
733: if lexer.SkipByte('-') && lexer.Rest() != "" {
734: versionMark := lexer.Mark()
735:
1.23 rillig 736: for p.VarUse() != nil || lexer.SkipRegexp(G.res.Compile(`^[\w\[\]*_.\-]+`)) {
1.22 rillig 737: }
738:
739: if !lexer.SkipString("{,nb*}") {
740: lexer.SkipString("{,nb[0-9]*}")
741: }
742:
743: dp.Wildcard = lexer.Since(versionMark)
744: return &dp
745: }
746:
1.28 rillig 747: if ToVarUse(dp.Pkgbase) != nil {
1.22 rillig 748: return &dp
749: }
750:
751: if hasSuffix(dp.Pkgbase, "-*") {
752: dp.Pkgbase = strings.TrimSuffix(dp.Pkgbase, "-*")
753: dp.Wildcard = "*"
754: return &dp
755: }
756:
757: lexer.Reset(mark)
758: return nil
759: }
760:
1.28 rillig 761: // ToVarUse converts the given string into a MkVarUse, or returns nil
762: // if there is a parse error or some trailing text.
763: // Parse errors are silently ignored.
764: func ToVarUse(str string) *MkVarUse {
765: p := NewMkParser(nil, str, false)
766: varUse := p.VarUse()
767: if varUse == nil || !p.EOF() {
768: return nil
769: }
770: return varUse
771: }
772:
1.21 rillig 773: // MkCond is a condition in a Makefile, such as ${OPSYS} == NetBSD.
774: //
775: // The representation is somewhere between syntactic and semantic.
776: // Unnecessary parentheses are omitted in this representation,
777: // but !empty(VARNAME) is represented differently from ${VARNAME} != "".
778: // For higher level analysis, a unified representation might be better.
1.14 rillig 779: type MkCond = *mkCond
780:
781: type mkCond struct {
782: Or []*mkCond
783: And []*mkCond
784: Not *mkCond
785:
786: Defined string
787: Empty *MkVarUse
1.24 rillig 788: Var *MkVarUse
1.14 rillig 789: CompareVarNum *MkCondCompareVarNum
790: CompareVarStr *MkCondCompareVarStr
791: CompareVarVar *MkCondCompareVarVar
792: Call *MkCondCall
793: Num string
794: }
795: type MkCondCompareVarNum struct {
796: Var *MkVarUse
797: Op string // One of <, <=, ==, !=, >=, >.
798: Num string
799: }
800: type MkCondCompareVarStr struct {
801: Var *MkVarUse
802: Op string // One of ==, !=.
803: Str string
804: }
805: type MkCondCompareVarVar struct {
806: Left *MkVarUse
807: Op string // One of <, <=, ==, !=, >=, >.
808: Right *MkVarUse
809: }
810: type MkCondCall struct {
811: Name string
812: Arg string
813: }
814:
815: type MkCondCallback struct {
1.28 rillig 816: Not func(cond MkCond)
1.14 rillig 817: Defined func(varname string)
818: Empty func(empty *MkVarUse)
819: CompareVarNum func(varuse *MkVarUse, op string, num string)
820: CompareVarStr func(varuse *MkVarUse, op string, str string)
821: CompareVarVar func(left *MkVarUse, op string, right *MkVarUse)
822: Call func(name string, arg string)
1.24 rillig 823: Var func(varuse *MkVarUse)
1.18 rillig 824: VarUse func(varuse *MkVarUse)
1.14 rillig 825: }
826:
1.20 rillig 827: func (cond *mkCond) Walk(callback *MkCondCallback) {
828: (&MkCondWalker{}).Walk(cond, callback)
829: }
830:
1.14 rillig 831: type MkCondWalker struct{}
832:
833: func (w *MkCondWalker) Walk(cond MkCond, callback *MkCondCallback) {
834: switch {
835: case cond.Or != nil:
836: for _, or := range cond.Or {
837: w.Walk(or, callback)
838: }
1.21 rillig 839:
1.14 rillig 840: case cond.And != nil:
841: for _, and := range cond.And {
842: w.Walk(and, callback)
843: }
1.21 rillig 844:
1.14 rillig 845: case cond.Not != nil:
1.28 rillig 846: if callback.Not != nil {
847: callback.Not(cond.Not)
848: }
1.14 rillig 849: w.Walk(cond.Not, callback)
850:
851: case cond.Defined != "":
852: if callback.Defined != nil {
853: callback.Defined(cond.Defined)
854: }
1.18 rillig 855: if callback.VarUse != nil {
1.21 rillig 856: // This is not really a VarUse, it's more a VarUseDefined.
857: // But in practice they are similar enough to be treated the same.
1.18 rillig 858: callback.VarUse(&MkVarUse{cond.Defined, nil})
859: }
1.21 rillig 860:
1.24 rillig 861: case cond.Var != nil:
862: if callback.Var != nil {
863: callback.Var(cond.Var)
864: }
865: if callback.VarUse != nil {
866: callback.VarUse(cond.Var)
867: }
868:
1.14 rillig 869: case cond.Empty != nil:
870: if callback.Empty != nil {
871: callback.Empty(cond.Empty)
872: }
1.18 rillig 873: if callback.VarUse != nil {
874: callback.VarUse(cond.Empty)
875: }
1.21 rillig 876:
1.14 rillig 877: case cond.CompareVarVar != nil:
878: if callback.CompareVarVar != nil {
879: cvv := cond.CompareVarVar
880: callback.CompareVarVar(cvv.Left, cvv.Op, cvv.Right)
881: }
1.18 rillig 882: if callback.VarUse != nil {
883: cvv := cond.CompareVarVar
884: callback.VarUse(cvv.Left)
885: callback.VarUse(cvv.Right)
886: }
1.21 rillig 887:
1.14 rillig 888: case cond.CompareVarStr != nil:
889: if callback.CompareVarStr != nil {
890: cvs := cond.CompareVarStr
891: callback.CompareVarStr(cvs.Var, cvs.Op, cvs.Str)
892: }
1.18 rillig 893: if callback.VarUse != nil {
894: callback.VarUse(cond.CompareVarStr.Var)
895: }
1.25 rillig 896: w.walkStr(cond.CompareVarStr.Str, callback)
1.21 rillig 897:
1.14 rillig 898: case cond.CompareVarNum != nil:
899: if callback.CompareVarNum != nil {
900: cvn := cond.CompareVarNum
901: callback.CompareVarNum(cvn.Var, cvn.Op, cvn.Num)
902: }
1.18 rillig 903: if callback.VarUse != nil {
904: callback.VarUse(cond.CompareVarNum.Var)
905: }
1.21 rillig 906:
1.14 rillig 907: case cond.Call != nil:
908: if callback.Call != nil {
909: call := cond.Call
910: callback.Call(call.Name, call.Arg)
911: }
1.25 rillig 912: w.walkStr(cond.Call.Arg, callback)
913: }
914: }
915:
916: func (w *MkCondWalker) walkStr(str string, callback *MkCondCallback) {
917: if callback.VarUse != nil {
918: tokens := NewMkParser(nil, str, false).MkTokens()
919: for _, token := range tokens {
920: if token.Varuse != nil {
921: callback.VarUse(token.Varuse)
922: }
923: }
1.14 rillig 924: }
925: }
CVSweb <webmaster@jp.NetBSD.org>