[BACK]Return to mkparser.go CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / pkgsrc / pkgtools / pkglint / files

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>