File: [cvs.NetBSD.org] / pkgsrc / pkgtools / pkglint / files / Attic / mkparser.go (download)
Revision 1.3, Sat Jul 9 09:43:48 2016 UTC (7 years, 9 months ago) by rillig
Branch: MAIN
Changes since 1.2: +6 -6
lines
Updated pkglint to 5.4.4.
Changes since 5.4.3:
* Improved detection for undocumented patches from Git
* Added warning about unnecessary space after variable names
* Lots of internal housekeeping
|
package main
import (
"strings"
)
type MkParser struct {
*Parser
}
func NewMkParser(line *Line, text string, emitWarnings bool) *MkParser {
return &MkParser{NewParser(line, text, emitWarnings)}
}
func (p *MkParser) MkTokens() []*MkToken {
repl := p.repl
var tokens []*MkToken
for !p.EOF() {
if repl.AdvanceStr("#") {
repl.AdvanceRest()
}
mark := repl.Mark()
if varuse := p.VarUse(); varuse != nil {
tokens = append(tokens, &MkToken{Text: repl.Since(mark), Varuse: varuse})
continue
}
again:
dollar := strings.IndexByte(repl.rest, '$')
if dollar == -1 {
dollar = len(repl.rest)
}
repl.Skip(dollar)
if repl.AdvanceStr("$$") {
goto again
}
text := repl.Since(mark)
if text != "" {
tokens = append(tokens, &MkToken{Text: text})
continue
}
break
}
return tokens
}
func (p *MkParser) VarUse() *MkVarUse {
repl := p.repl
mark := repl.Mark()
if repl.AdvanceStr("${") || repl.AdvanceStr("$(") {
usingRoundParen := repl.Since(mark) == "$("
closing := ifelseStr(usingRoundParen, ")", "}")
varnameMark := repl.Mark()
varname := p.Varname()
if varname != "" {
if usingRoundParen && p.EmitWarnings {
p.Line.Warn1("Please use curly braces {} instead of round parentheses () for %s.", varname)
}
modifiers := p.VarUseModifiers(varname, closing)
if repl.AdvanceStr(closing) {
return &MkVarUse{varname, modifiers}
}
}
for p.VarUse() != nil || repl.AdvanceRegexp(RegexPattern(`^([^$:`+closing+`]|\$\$)+`)) {
}
rest := p.Rest()
if hasPrefix(rest, ":L") || hasPrefix(rest, ":?") {
varexpr := repl.Since(varnameMark)
modifiers := p.VarUseModifiers(varexpr, closing)
if repl.AdvanceStr(closing) {
return &MkVarUse{varexpr, modifiers}
}
}
repl.Reset(mark)
}
if repl.AdvanceStr("$@") {
return &MkVarUse{"@", nil}
}
if repl.AdvanceStr("$<") {
return &MkVarUse{"<", nil}
}
if repl.PeekByte() == '$' && repl.AdvanceRegexp(`^\$(\w)`) {
varname := repl.m[1]
if p.EmitWarnings {
p.Line.Warn1("$%[1]s is ambiguous. Use ${%[1]s} if you mean a Makefile variable or $$%[1]s if you mean a shell variable.", varname)
}
return &MkVarUse{varname, nil}
}
return nil
}
func (p *MkParser) VarUseModifiers(varname, closing string) []string {
repl := p.repl
var modifiers []string
mayOmitColon := false
for repl.AdvanceStr(":") || mayOmitColon {
mayOmitColon = false
modifierMark := repl.Mark()
switch repl.PeekByte() {
case 'E', 'H', 'L', 'O', 'Q', 'R', 'T', 's', 't', 'u':
if repl.AdvanceRegexp(`^(E|H|L|Ox?|Q|R|T|sh|tA|tW|tl|tu|tw|u)`) {
modifiers = append(modifiers, repl.Since(modifierMark))
continue
}
if repl.AdvanceStr("ts") {
rest := repl.rest
if len(rest) >= 2 && (rest[1] == closing[0] || rest[1] == ':') {
repl.Skip(1)
} else if len(rest) >= 1 && (rest[0] == closing[0] || rest[0] == ':') {
} else if repl.AdvanceRegexp(`^\\\d+`) {
} else {
break
}
modifiers = append(modifiers, repl.Since(modifierMark))
continue
}
case '=', 'D', 'M', 'N', 'U':
if repl.AdvanceRegexp(`^[=DMNU]`) {
for p.VarUse() != nil || repl.AdvanceRegexp(RegexPattern(`^([^$:`+closing+`]|\$\$)+`)) {
}
modifiers = append(modifiers, repl.Since(modifierMark))
continue
}
case 'C', 'S':
if repl.AdvanceRegexp(`^[CS]([%,/:;@^|])`) {
separator := repl.m[1]
repl.AdvanceStr("^")
re := RegexPattern(`^([^\` + separator + `$` + closing + `\\]|\$\$|\\.)+`)
for p.VarUse() != nil || repl.AdvanceRegexp(re) {
}
repl.AdvanceStr("$")
if repl.AdvanceStr(separator) {
for p.VarUse() != nil || repl.AdvanceRegexp(re) {
}
if repl.AdvanceStr(separator) {
repl.AdvanceRegexp(`^[1gW]`)
modifiers = append(modifiers, repl.Since(modifierMark))
mayOmitColon = true
continue
}
}
}
case '@':
if repl.AdvanceRegexp(`^@([\w.]+)@`) {
loopvar := repl.m[1]
for p.VarUse() != nil || repl.AdvanceRegexp(RegexPattern(`^([^$:@`+closing+`\\]|\$\$|\\.)+`)) {
}
if !repl.AdvanceStr("@") && p.EmitWarnings {
p.Line.Warn2("Modifier ${%s:@%s@...@} is missing the final \"@\".", varname, loopvar)
}
modifiers = append(modifiers, repl.Since(modifierMark))
continue
}
case '[':
if repl.AdvanceRegexp(`^\[[-.\d]+\]`) {
modifiers = append(modifiers, repl.Since(modifierMark))
continue
}
case '?':
repl.AdvanceStr("?")
re := RegexPattern(`^([^$:` + closing + `]|\$\$)+`)
for p.VarUse() != nil || repl.AdvanceRegexp(re) {
}
if repl.AdvanceStr(":") {
for p.VarUse() != nil || repl.AdvanceRegexp(re) {
}
modifiers = append(modifiers, repl.Since(modifierMark))
continue
}
}
repl.Reset(modifierMark)
for p.VarUse() != nil || repl.AdvanceRegexp(RegexPattern(`^([^:$`+closing+`]|\$\$)+`)) {
}
if suffixSubst := repl.Since(modifierMark); contains(suffixSubst, "=") {
modifiers = append(modifiers, suffixSubst)
continue
}
}
return modifiers
}
func (p *MkParser) MkCond() *Tree {
return p.mkCondOr()
}
func (p *MkParser) mkCondOr() *Tree {
and := p.mkCondAnd()
if and == nil {
return nil
}
ands := append([]interface{}(nil), and)
for {
mark := p.repl.Mark()
if !p.repl.AdvanceRegexp(`^\s*\|\|\s*`) {
break
}
next := p.mkCondAnd()
if next == nil {
p.repl.Reset(mark)
break
}
ands = append(ands, next)
}
if len(ands) == 1 {
return and
}
return NewTree("or", ands...)
}
func (p *MkParser) mkCondAnd() *Tree {
atom := p.mkCondAtom()
if atom == nil {
return nil
}
atoms := append([]interface{}(nil), atom)
for {
mark := p.repl.Mark()
if !p.repl.AdvanceRegexp(`^\s*&&\s*`) {
break
}
next := p.mkCondAtom()
if next == nil {
p.repl.Reset(mark)
break
}
atoms = append(atoms, next)
}
if len(atoms) == 1 {
return atom
}
return NewTree("and", atoms...)
}
func (p *MkParser) mkCondAtom() *Tree {
if G.opts.Debug {
defer tracecall1(p.Rest())()
}
repl := p.repl
mark := repl.Mark()
repl.SkipSpace()
switch {
case repl.AdvanceStr("!"):
cond := p.mkCondAtom()
if cond != nil {
return NewTree("not", cond)
}
case repl.AdvanceStr("("):
cond := p.MkCond()
if cond != nil {
repl.SkipSpace()
if repl.AdvanceStr(")") {
return cond
}
}
case repl.AdvanceRegexp(`^defined\s*\(`):
if varname := p.Varname(); varname != "" {
if repl.AdvanceStr(")") {
return NewTree("defined", varname)
}
}
case repl.AdvanceRegexp(`^empty\s*\(`):
if varname := p.Varname(); varname != "" {
modifiers := p.VarUseModifiers(varname, ")")
if repl.AdvanceStr(")") {
return NewTree("empty", MkVarUse{varname, modifiers})
}
}
case repl.AdvanceRegexp(`^(commands|exists|make|target)\s*\(`):
funcname := repl.m[1]
argMark := repl.Mark()
for p.VarUse() != nil || repl.AdvanceRegexp(`^[^$)]+`) {
}
arg := repl.Since(argMark)
if repl.AdvanceStr(")") {
return NewTree(funcname, arg)
}
default:
lhs := p.VarUse()
mark := repl.Mark()
if lhs == nil && repl.AdvanceStr("\"") {
if quotedLHS := p.VarUse(); quotedLHS != nil && repl.AdvanceStr("\"") {
lhs = quotedLHS
} else {
repl.Reset(mark)
}
}
if lhs != nil {
if repl.AdvanceRegexp(`^\s*(<|<=|==|!=|>=|>)\s*(\d+(?:\.\d+)?)`) {
return NewTree("compareVarNum", *lhs, repl.m[1], repl.m[2])
}
if repl.AdvanceRegexp(`^\s*(<|<=|==|!=|>=|>)\s*`) {
op := repl.m[1]
if (op == "!=" || op == "==") && repl.AdvanceRegexp(`^"([^"\$\\]*)"`) {
return NewTree("compareVarStr", *lhs, op, repl.m[1])
} else if repl.AdvanceRegexp(`^\w+`) {
return NewTree("compareVarStr", *lhs, op, repl.m[0])
} else if rhs := p.VarUse(); rhs != nil {
return NewTree("compareVarVar", *lhs, op, *rhs)
}
} else {
return NewTree("not", NewTree("empty", *lhs)) // See devel/bmake/files/cond.c:/\* For \.if \$/
}
}
if repl.AdvanceRegexp(`^\d+(?:\.\d+)?`) {
return NewTree("literalNum", repl.m[0])
}
}
repl.Reset(mark)
return nil
}
func (p *MkParser) Varname() string {
repl := p.repl
mark := repl.Mark()
repl.AdvanceStr(".")
isVarnameChar := func(c byte) bool {
return 'A' <= c && c <= 'Z' || c == '_' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '+' || c == '-' || c == '.' || c == '*'
}
for p.VarUse() != nil || repl.AdvanceBytesFunc(isVarnameChar) {
}
return repl.Since(mark)
}