pkgsrc-Changes archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
CVS commit: pkgsrc/pkgtools/pkglint
Module Name: pkgsrc
Committed By: rillig
Date: Sat Jun 20 07:00:44 UTC 2020
Modified Files:
pkgsrc/pkgtools/pkglint: Makefile
pkgsrc/pkgtools/pkglint/files: check_test.go scope.go scope_test.go
substcontext.go substcontext_test.go vardefs.go
pkgsrc/pkgtools/pkglint/files/makepat: pat.go pat_test.go
Log Message:
pkgtools/pkglint: update to 20.1.18
Changes since 20.1.17:
Fixed the algorithm for checking whether two patterns overlap, such as
MACHINE_PLATFORM:MNetBSD-[0-9].*-*.
Fixed wrong warning about foreign variable in SUBST block, as seen in
geography/qgis.
To generate a diff of this commit:
cvs rdiff -u -r1.657 -r1.658 pkgsrc/pkgtools/pkglint/Makefile
cvs rdiff -u -r1.73 -r1.74 pkgsrc/pkgtools/pkglint/files/check_test.go
cvs rdiff -u -r1.1 -r1.2 pkgsrc/pkgtools/pkglint/files/scope.go \
pkgsrc/pkgtools/pkglint/files/scope_test.go
cvs rdiff -u -r1.38 -r1.39 pkgsrc/pkgtools/pkglint/files/substcontext.go
cvs rdiff -u -r1.36 -r1.37 pkgsrc/pkgtools/pkglint/files/substcontext_test.go
cvs rdiff -u -r1.99 -r1.100 pkgsrc/pkgtools/pkglint/files/vardefs.go
cvs rdiff -u -r1.1 -r1.2 pkgsrc/pkgtools/pkglint/files/makepat/pat.go \
pkgsrc/pkgtools/pkglint/files/makepat/pat_test.go
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Modified files:
Index: pkgsrc/pkgtools/pkglint/Makefile
diff -u pkgsrc/pkgtools/pkglint/Makefile:1.657 pkgsrc/pkgtools/pkglint/Makefile:1.658
--- pkgsrc/pkgtools/pkglint/Makefile:1.657 Wed Jun 17 09:54:12 2020
+++ pkgsrc/pkgtools/pkglint/Makefile Sat Jun 20 07:00:44 2020
@@ -1,7 +1,6 @@
-# $NetBSD: Makefile,v 1.657 2020/06/17 09:54:12 bsiegert Exp $
+# $NetBSD: Makefile,v 1.658 2020/06/20 07:00:44 rillig Exp $
-PKGNAME= pkglint-20.1.17
-PKGREVISION= 1
+PKGNAME= pkglint-20.1.18
CATEGORIES= pkgtools
DISTNAME= tools
MASTER_SITES= ${MASTER_SITE_GITHUB:=golang/}
Index: pkgsrc/pkgtools/pkglint/files/check_test.go
diff -u pkgsrc/pkgtools/pkglint/files/check_test.go:1.73 pkgsrc/pkgtools/pkglint/files/check_test.go:1.74
--- pkgsrc/pkgtools/pkglint/files/check_test.go:1.73 Sun Jun 14 11:35:54 2020
+++ pkgsrc/pkgtools/pkglint/files/check_test.go Sat Jun 20 07:00:44 2020
@@ -128,7 +128,6 @@ func Test__qa(t *testing.T) {
ck.Configure("pkglint.go", "*", "*", -intqa.EMissingTest) // TODO
ck.Configure("pkgsrc.go", "*", "*", -intqa.EMissingTest) // TODO
ck.Configure("redundantscope.go", "*", "*", -intqa.EMissingTest) // TODO
- ck.Configure("scope.go", "*", "*", -intqa.EMissingTest) // TODO
ck.Configure("shell.go", "*", "*", -intqa.EMissingTest) // TODO
ck.Configure("shtokenizer.go", "*", "*", -intqa.EMissingTest) // TODO
ck.Configure("shtypes.go", "*", "*", -intqa.EMissingTest) // TODO
Index: pkgsrc/pkgtools/pkglint/files/scope.go
diff -u pkgsrc/pkgtools/pkglint/files/scope.go:1.1 pkgsrc/pkgtools/pkglint/files/scope.go:1.2
--- pkgsrc/pkgtools/pkglint/files/scope.go:1.1 Sun Jun 14 11:35:54 2020
+++ pkgsrc/pkgtools/pkglint/files/scope.go Sat Jun 20 07:00:44 2020
@@ -114,13 +114,13 @@ func (s *Scope) Fallback(varname string,
}
// Use marks the variable and its canonicalized form as used.
-func (s *Scope) Use(varname string, line *MkLine, time VucTime) {
+func (s *Scope) Use(varname string, mkline *MkLine, time VucTime) {
use := func(name string) {
v := s.create(name)
if v.used == nil {
- v.used = line
+ v.used = mkline
if trace.Tracing {
- trace.Step2("Using %q in %s", name, line.String())
+ trace.Step2("Using %q in %s", name, mkline.String())
}
}
if time == VucLoadTime {
Index: pkgsrc/pkgtools/pkglint/files/scope_test.go
diff -u pkgsrc/pkgtools/pkglint/files/scope_test.go:1.1 pkgsrc/pkgtools/pkglint/files/scope_test.go:1.2
--- pkgsrc/pkgtools/pkglint/files/scope_test.go:1.1 Sun Jun 14 11:35:54 2020
+++ pkgsrc/pkgtools/pkglint/files/scope_test.go Sat Jun 20 07:00:44 2020
@@ -34,6 +34,43 @@ func (s *Suite) Test_Scope__commented_va
t.CheckEquals(indeterminate, false)
}
+func (s *Suite) Test_NewScope(c *check.C) {
+ t := s.Init(c)
+
+ scope := NewScope()
+
+ t.Check(scope.names, check.IsNil)
+ t.Check(scope.vs, check.NotNil)
+ t.Check(scope.vs, check.HasLen, 0)
+}
+
+func (s *Suite) Test_Scope_varnames(c *check.C) {
+ t := s.Init(c)
+
+ scope := NewScope()
+ mkline := t.NewMkLine("filename.mk", 3, "DEFINED=\t${USED}")
+
+ t.Check(scope.varnames(), check.IsNil)
+
+ scope.Define("DEFINED", mkline)
+ scope.Use("USED", mkline, VucRunTime)
+
+ t.CheckDeepEquals(scope.varnames(), []string{"DEFINED", "USED"})
+ scope.varnames()[0] = "modified" // just to demonstrate the caching
+ t.CheckDeepEquals(scope.varnames(), []string{"modified", "USED"})
+}
+
+func (s *Suite) Test_Scope_create(c *check.C) {
+ t := s.Init(c)
+
+ scope := NewScope()
+
+ v1 := scope.create("VAR")
+ v2 := scope.create("VAR")
+
+ t.CheckEquals(v1, v2)
+}
+
func (s *Suite) Test_Scope_Define(c *check.C) {
t := s.Init(c)
@@ -75,6 +112,45 @@ func (s *Suite) Test_Scope_Define(c *che
true, true, "")
}
+func (s *Suite) Test_Scope_def(c *check.C) {
+ t := s.Init(c)
+
+ mkline := t.NewMkLine("filename.mk", 3, "VAR=\tvalue")
+ scope := NewScope()
+
+ scope.def("VAR.param", mkline)
+
+ t.Check(scope.FirstDefinition("VAR"), check.IsNil)
+ t.CheckEquals(scope.FirstDefinition("VAR.param"), mkline)
+ t.Check(scope.FirstDefinition("VAR.*"), check.IsNil)
+}
+
+func (s *Suite) Test_Scope_Fallback(c *check.C) {
+ t := s.Init(c)
+
+ mkline := t.NewMkLine("filename.mk", 3, "VAR=\tvalue")
+ scope := NewScope()
+ scope.def("VAR.param", mkline)
+
+ scope.Fallback("FALLBACK", "fallback")
+
+ t.CheckEquals(scope.LastValue("VAR.param"), "value")
+ t.CheckEquals(scope.LastValue("FALLBACK"), "fallback")
+ t.CheckEquals(scope.LastValue("UNDEFINED"), "")
+}
+
+func (s *Suite) Test_Scope_Use(c *check.C) {
+ t := s.Init(c)
+
+ mkline := t.NewMkLine("filename.mk", 3, "VAR=\t${USED}")
+ scope := NewScope()
+ scope.Define("VAR", mkline)
+ scope.Use("USED", mkline, VucRunTime)
+
+ t.CheckEquals(scope.LastValue("VAR"), "${USED}")
+ t.CheckEquals(scope.LastValue("USED"), "")
+}
+
func (s *Suite) Test_Scope_Mentioned(c *check.C) {
t := s.Init(c)
@@ -102,6 +178,13 @@ func (s *Suite) Test_Scope_IsDefined(c *
t.CheckEquals(scope.IsDefined("VAR.param"), true)
t.CheckEquals(scope.IsDefined("VAR.other"), false)
t.CheckEquals(scope.IsDefined("VARIABLE.*"), false)
+}
+
+func (s *Suite) Test_Scope_IsDefinedSimilar(c *check.C) {
+ t := s.Init(c)
+
+ scope := NewScope()
+ scope.Define("VAR.param", t.NewMkLine("file.mk", 1, "VAR.param=value"))
t.CheckEquals(scope.IsDefinedSimilar("VAR.param"), true)
t.CheckEquals(scope.IsDefinedSimilar("VAR.other"), true)
@@ -118,23 +201,47 @@ func (s *Suite) Test_Scope_IsUsed(c *che
t.CheckEquals(scope.IsUsed("VAR.param"), true)
t.CheckEquals(scope.IsUsed("VAR.other"), false)
t.CheckEquals(scope.IsUsed("VARIABLE.*"), false)
+}
+
+func (s *Suite) Test_Scope_IsUsedSimilar(c *check.C) {
+ t := s.Init(c)
+
+ scope := NewScope()
+ mkline := t.NewMkLine("file.mk", 1, "\techo ${VAR.param}")
+ scope.Use("VAR.param", mkline, VucRunTime)
t.CheckEquals(scope.IsUsedSimilar("VAR.param"), true)
t.CheckEquals(scope.IsUsedSimilar("VAR.other"), true)
t.CheckEquals(scope.IsUsedSimilar("VARIABLE.*"), false)
}
+func (s *Suite) Test_Scope_IsUsedAtLoadTime(c *check.C) {
+ t := s.Init(c)
+
+ scope := NewScope()
+ mkline := t.NewMkLine("file.mk", 1, "\techo ${VAR.param}")
+
+ scope.Use("LOAD_TIME", mkline, VucLoadTime)
+ scope.Use("RUN_TIME", mkline, VucRunTime)
+
+ t.CheckEquals(scope.IsUsedAtLoadTime("LOAD_TIME"), true)
+ t.CheckEquals(scope.IsUsedAtLoadTime("RUN_TIME"), false)
+ t.CheckEquals(scope.IsUsedAtLoadTime("UNDEFINED"), false)
+}
+
func (s *Suite) Test_Scope_FirstDefinition(c *check.C) {
t := s.Init(c)
- mkline1 := t.NewMkLine("fname.mk", 3, "VAR=\tvalue")
- mkline2 := t.NewMkLine("fname.mk", 3, ".if ${SNEAKY::=value}")
+ mkline3 := t.NewMkLine("fname.mk", 3, "VAR=\tvalue")
+ mkline4 := t.NewMkLine("fname.mk", 4, ".if ${SNEAKY::=value}")
+ mkline5 := t.NewMkLine("fname.mk", 5, ".if ${USED}")
scope := NewScope()
- scope.Define("VAR", mkline1)
- scope.Define("SNEAKY", mkline2)
+ scope.Define("VAR", mkline3)
+ scope.Define("SNEAKY", mkline4)
+ scope.Use("USED", mkline5, VucLoadTime)
- t.CheckEquals(scope.FirstDefinition("VAR"), mkline1)
+ t.CheckEquals(scope.FirstDefinition("VAR"), mkline3)
// This call returns nil because it's not a variable assignment
// and the calling code typically assumes a variable definition.
@@ -142,27 +249,68 @@ func (s *Suite) Test_Scope_FirstDefiniti
// case that only few people actually know. It's better that way.
t.Check(scope.FirstDefinition("SNEAKY"), check.IsNil)
+ t.Check(scope.FirstDefinition("USED"), check.IsNil)
+
t.CheckOutputLines(
- "ERROR: fname.mk:3: Assignment modifiers like \":=\" " +
+ "ERROR: fname.mk:4: Assignment modifiers like \":=\" " +
"must not be used at all.")
}
+func (s *Suite) Test_Scope_LastDefinition(c *check.C) {
+ t := s.Init(c)
+
+ mkline3 := t.NewMkLine("fname.mk", 3, "VAR=\tfirst")
+ mkline4 := t.NewMkLine("fname.mk", 4, "VAR=\t${USED}")
+
+ scope := NewScope()
+ scope.Define("VAR", mkline3)
+ scope.Define("VAR", mkline4)
+ scope.Use("USED", mkline4, VucRunTime)
+
+ t.CheckEquals(scope.LastDefinition("VAR"), mkline4)
+ t.Check(scope.LastDefinition("UNDEFINED"), check.IsNil)
+ t.Check(scope.LastDefinition("USED"), check.IsNil)
+}
+
func (s *Suite) Test_Scope_Commented(c *check.C) {
t := s.Init(c)
assigned := t.NewMkLine("filename.mk", 3, "VAR=\tvalue")
commented := t.NewMkLine("filename.mk", 4, "#COMMENTED=\tvalue")
documented := t.NewMkLine("filename.mk", 5, "# DOCUMENTED is a variable.")
+ used := t.NewMkLine("filename.mk", 6, "ANY=\t${USED}")
scope := NewScope()
scope.Define("VAR", assigned)
scope.Define("COMMENTED", commented)
scope.Define("DOCUMENTED", documented)
+ scope.Use("USED", used, VucRunTime)
t.Check(scope.Commented("VAR"), check.IsNil)
t.CheckEquals(scope.Commented("COMMENTED"), commented)
t.Check(scope.Commented("DOCUMENTED"), check.IsNil)
t.Check(scope.Commented("UNKNOWN"), check.IsNil)
+ t.Check(scope.Commented("USED"), check.IsNil)
+}
+
+func (s *Suite) Test_Scope_FirstUse(c *check.C) {
+ t := s.Init(c)
+
+ mklines := t.NewMkLines("file.mk",
+ MkCvsID,
+ "VAR1=\t${USED}",
+ "VAR2=\t${USED}")
+
+ mklines.Check()
+
+ scope := mklines.allVars
+ t.CheckEquals(scope.FirstUse("USED"), mklines.mklines[1])
+ t.Check(scope.FirstUse("UNUSED"), check.IsNil)
+
+ t.CheckOutputLines(
+ "WARN: file.mk:2: VAR1 is defined but not used.",
+ "WARN: file.mk:2: USED is used but not defined.",
+ "WARN: file.mk:3: VAR2 is defined but not used.")
}
func (s *Suite) Test_Scope_LastValue(c *check.C) {
@@ -210,10 +358,37 @@ func (s *Suite) Test_Scope_LastValue__ap
t.CheckEquals(pkg.vars.LastValue("PLIST_VARS"), "one two")
}
+func (s *Suite) Test_Scope_LastValueFound(c *check.C) {
+ t := s.Init(c)
+
+ mklines := t.NewMkLines("file.mk",
+ MkCvsID,
+ "VAR=\tfirst",
+ "VAR=\tsecond",
+ ".if 1",
+ "VAR=\tthird (conditional)",
+ ".endif")
+
+ mklines.Check()
+
+ value, found, indeterminate := mklines.allVars.LastValueFound("VAR")
+ t.CheckEquals(value, "third (conditional)")
+ t.CheckEquals(found, true)
+ t.CheckEquals(indeterminate, false) // TODO: why?
+
+ t.CheckOutputLines(
+ "WARN: file.mk:2: VAR is defined but not used.")
+}
+
+// Scope.DefineAll copies only the variable definitions,
+// but not the uses of variables.
func (s *Suite) Test_Scope_DefineAll(c *check.C) {
t := s.Init(c)
+ mkline := t.NewMkLine("filename.mk", 123, "VAR=\t${USED}")
+
src := NewScope()
+ src.Use("USED", mkline, VucRunTime)
dst := NewScope()
dst.DefineAll(&src)
@@ -225,3 +400,22 @@ func (s *Suite) Test_Scope_DefineAll(c *
t.CheckEquals(dst.IsDefined("VAR"), true)
}
+
+func (s *Suite) Test_Scope_forEach(c *check.C) {
+ t := s.Init(c)
+
+ mkline := t.NewMkLine("filename.mk", 123, "VAR=\t${USED}")
+
+ scope := NewScope()
+ scope.Define("VAR", mkline)
+ scope.Use("USED", mkline, VucRunTime)
+
+ var result []string
+ scope.forEach(func(varname string, data *scopeVar) {
+ result = append(result, varname+"="+data.value)
+ })
+
+ t.CheckDeepEquals(result, []string{
+ "USED=",
+ "VAR=${USED}"})
+}
Index: pkgsrc/pkgtools/pkglint/files/substcontext.go
diff -u pkgsrc/pkgtools/pkglint/files/substcontext.go:1.38 pkgsrc/pkgtools/pkglint/files/substcontext.go:1.39
--- pkgsrc/pkgtools/pkglint/files/substcontext.go:1.38 Sun May 24 19:12:29 2020
+++ pkgsrc/pkgtools/pkglint/files/substcontext.go Sat Jun 20 07:00:44 2020
@@ -420,6 +420,8 @@ func (b *substBlock) varassign(mkline *M
case "SUBST_FILTER_CMD.*":
b.varassignFilterCmd(mkline)
}
+
+ b.varassignAllowForeign(mkline)
}
func (b *substBlock) varassignStage(mkline *MkLine, pkg *Package) {
@@ -491,6 +493,12 @@ func (b *substBlock) varassignFilterCmd(
b.addSeen(ssTransform)
}
+func (b *substBlock) varassignAllowForeign(mkline *MkLine) {
+ mkline.ForEachUsed(func(varUse *MkVarUse, time VucTime) {
+ b.allowVar(varUse.varname)
+ })
+}
+
func (b *substBlock) suggestSubstVars(mkline *MkLine) {
tokens, _ := splitIntoShellTokens(mkline.Line, mkline.Value())
Index: pkgsrc/pkgtools/pkglint/files/substcontext_test.go
diff -u pkgsrc/pkgtools/pkglint/files/substcontext_test.go:1.36 pkgsrc/pkgtools/pkglint/files/substcontext_test.go:1.37
--- pkgsrc/pkgtools/pkglint/files/substcontext_test.go:1.36 Sun May 24 19:12:29 2020
+++ pkgsrc/pkgtools/pkglint/files/substcontext_test.go Sat Jun 20 07:00:44 2020
@@ -978,6 +978,22 @@ func (s *Suite) Test_substScope_finish__
"Please add only one class at a time to SUBST_CLASSES.")
}
+func (s *Suite) Test_substScope_finish__indirect_SUBST_FILES(c *check.C) {
+ t := s.Init(c)
+
+ t.RunSubst(
+ "SUBST_CLASSES+= 1",
+ "SUBST_STAGE.1= pre-configure",
+ "S1_CMD= echo file",
+ "SUBST_FILES.1= ${S1_CMD:sh}",
+ "SUBST_VARS.1= PREFIX")
+
+ // Since S1_CMD is used in SUBST_FILES, it is not foreign
+ // but obviously belongs to the SUBST block.
+ // Before 2020-06-20, pkglint had warned about this.
+ t.CheckOutputEmpty()
+}
+
func (s *Suite) Test_substScope_prepareSubstClasses(c *check.C) {
t := s.Init(c)
Index: pkgsrc/pkgtools/pkglint/files/vardefs.go
diff -u pkgsrc/pkgtools/pkglint/files/vardefs.go:1.99 pkgsrc/pkgtools/pkglint/files/vardefs.go:1.100
--- pkgsrc/pkgtools/pkglint/files/vardefs.go:1.99 Sun Jun 7 15:49:23 2020
+++ pkgsrc/pkgtools/pkglint/files/vardefs.go Sat Jun 20 07:00:44 2020
@@ -1155,7 +1155,7 @@ func (reg *VarTypeRegistry) Init(src *Pk
reg.sys("EXTRACT_CMD", BtShellCommand)
reg.pkg("EXTRACT_DIR", BtPathname)
reg.pkg("EXTRACT_DIR.*", BtPathname)
- reg.pkglist("EXTRACT_ELEMENTS", BtPathPattern) // TODO: No slashes allowed
+ reg.pkglist("EXTRACT_ELEMENTS", BtPathPattern)
reg.pkglist("EXTRACT_ENV", BtShellWord)
reg.pkglist("EXTRACT_ONLY", BtPathname)
reg.pkglist("EXTRACT_OPTS", BtShellWord)
Index: pkgsrc/pkgtools/pkglint/files/makepat/pat.go
diff -u pkgsrc/pkgtools/pkglint/files/makepat/pat.go:1.1 pkgsrc/pkgtools/pkglint/files/makepat/pat.go:1.2
--- pkgsrc/pkgtools/pkglint/files/makepat/pat.go:1.1 Sun Jun 14 11:35:54 2020
+++ pkgsrc/pkgtools/pkglint/files/makepat/pat.go Sat Jun 20 07:00:44 2020
@@ -27,22 +27,20 @@ type StateID uint16
// Compile parses a pattern, including the error checking that is missing
// from bmake.
func Compile(pattern string) (*Pattern, error) {
- var a Pattern
- s := a.AddState(false)
-
- var deadEnd StateID
+ var p Pattern
+ s := p.AddState(false)
lex := textproc.NewLexer(pattern)
for !lex.EOF() {
if lex.SkipByte('*') {
- a.AddTransition(s, 0, 255, s)
+ p.AddTransition(s, 0, 255, s)
continue
}
if lex.SkipByte('?') {
- next := a.AddState(false)
- a.AddTransition(s, 0, 255, next)
+ next := p.AddState(false)
+ p.AddTransition(s, 0, 255, next)
s = next
continue
}
@@ -52,112 +50,194 @@ func Compile(pattern string) (*Pattern,
return nil, errors.New("unfinished escape sequence")
}
ch := lex.NextByte()
- next := a.AddState(false)
- a.AddTransition(s, ch, ch, next)
+ next := p.AddState(false)
+ p.AddTransition(s, ch, ch, next)
s = next
continue
}
ch := lex.NextByte()
if ch != '[' {
- next := a.AddState(false)
- a.AddTransition(s, ch, ch, next)
+ next := p.AddState(false)
+ p.AddTransition(s, ch, ch, next)
s = next
continue
}
- negate := lex.SkipByte('^')
- if negate && deadEnd == 0 {
- deadEnd = a.AddState(false)
+ next, err := compileCharClass(&p, lex, ch, s)
+ if err != nil {
+ return nil, err
+ }
+
+ s = next
+ }
+
+ p.states[s].end = true
+ return &p, nil
+}
+
+func compileCharClass(p *Pattern, lex *textproc.Lexer, ch byte, s StateID) (StateID, error) {
+ negate := lex.SkipByte('^')
+ chars := make([]bool, 256)
+ next := p.AddState(false)
+ for {
+ if lex.EOF() {
+ return 0, errors.New("unfinished character class")
+ }
+ ch = lex.NextByte()
+ if ch == ']' {
+ break
}
- next := a.AddState(false)
- for {
+ if lex.SkipByte('-') {
if lex.EOF() {
- return nil, errors.New("unfinished character class")
+ return 0, errors.New("unfinished character range")
}
- ch = lex.NextByte()
- if ch == ']' {
- break
- }
- max := ch
- if lex.SkipByte('-') {
- if lex.EOF() {
- return nil, errors.New("unfinished character range")
- }
- max = lex.NextByte()
+ max := lex.NextByte()
+ if ch > max {
+ ch, max = max, ch
}
-
- to := next
- if negate {
- to = deadEnd
+ for i := int(ch); i <= int(max); i++ {
+ chars[i] = true
}
- a.AddTransition(s, bmin(ch, max), bmax(ch, max), to)
+ } else {
+ chars[ch] = true
}
- if negate {
- a.AddTransition(s, 0, 255, next)
+ }
+ if negate {
+ for i, b := range chars {
+ chars[i] = !b
}
- s = next
}
- a.states[s].end = true
- return &a, nil
+ start := 0
+ for start < len(chars) && !chars[start] {
+ start++
+ }
+
+ for start < len(chars) {
+ end := start
+ for end < len(chars) && chars[end] {
+ end++
+ }
+
+ if start < end {
+ p.AddTransition(s, byte(start), byte(end-1), next)
+ }
+
+ start = end
+ for start < len(chars) && !chars[start] {
+ start++
+ }
+ }
+ return next, nil
}
-func (a *Pattern) AddState(end bool) StateID {
- a.states = append(a.states, state{nil, end})
- return StateID(len(a.states) - 1)
+func (p *Pattern) AddState(end bool) StateID {
+ p.states = append(p.states, state{nil, end})
+ return StateID(len(p.states) - 1)
}
-func (a *Pattern) AddTransition(from StateID, min, max byte, to StateID) {
- state := &a.states[from]
+func (p *Pattern) AddTransition(from StateID, min, max byte, to StateID) {
+ state := &p.states[from]
state.transitions = append(state.transitions, transition{min, max, to})
}
// Match tests whether a pattern matches the given string.
-func (a *Pattern) Match(s string) bool {
- state := StateID(0)
+func (p *Pattern) Match(s string) bool {
+ curr := make([]bool, len(p.states))
+ next := make([]bool, len(p.states))
+
+ curr[0] = true
for _, ch := range []byte(s) {
- for _, tr := range a.states[state].transitions {
- if tr.min <= ch && ch <= tr.max {
- state = tr.to
- goto nextByte
+ ok := false
+ for i, _ := range next {
+ next[i] = false
+ }
+
+ for si := range curr {
+ if !curr[si] {
+ continue
+ }
+ for _, tr := range p.states[si].transitions {
+ if tr.min <= ch && ch <= tr.max {
+ next[tr.to] = true
+ ok = true
+ }
}
}
- return false
- nextByte:
+ if !ok {
+ return false
+ }
+ curr, next = next, curr
}
- return a.states[state].end
+
+ for i, curr := range curr {
+ if curr && p.states[i].end {
+ return true
+ }
+ }
+ return false
}
// Intersect computes a pattern that only matches if both given patterns
// match at the same time.
-func Intersect(a, b *Pattern) *Pattern {
- var is Pattern
- for i := 0; i < len(a.states); i++ {
- for j := 0; j < len(b.states); j++ {
- is.AddState(a.states[i].end && b.states[j].end)
+func Intersect(p1, p2 *Pattern) *Pattern {
+ var res Pattern
+ for i1 := 0; i1 < len(p1.states); i1++ {
+ for i2 := 0; i2 < len(p2.states); i2++ {
+ res.AddState(p1.states[i1].end && p2.states[i2].end)
}
}
- for i := 0; i < len(a.states); i++ {
- for j := 0; j < len(b.states); j++ {
- for _, at := range a.states[i].transitions {
- for _, bt := range b.states[j].transitions {
- min := bmax(at.min, bt.min)
- max := bmin(at.max, bt.max)
+ for i1 := 0; i1 < len(p1.states); i1++ {
+ for i2 := 0; i2 < len(p2.states); i2++ {
+ for _, t1 := range p1.states[i1].transitions {
+ for _, t2 := range p2.states[i2].transitions {
+ min := bmax(t1.min, t2.min)
+ max := bmin(t1.max, t2.max)
if min <= max {
- from := StateID(i*len(b.states) + j)
- to := at.to*StateID(len(b.states)) + bt.to
- is.AddTransition(from, min, max, to)
+ from := StateID(i1*len(p2.states) + i2)
+ to := t1.to*StateID(len(p2.states)) + t2.to
+ res.AddTransition(from, min, max, to)
}
}
}
}
}
- // TODO: optimize: remove transitions that point to a dead end
+ return res.optimized()
+}
+
+func (p *Pattern) optimized() *Pattern {
+ var opt Pattern
+
+ var todo []StateID
+ hasNewID := make([]bool, len(p.states))
+ newIDs := make([]StateID, len(p.states))
+
+ todo = append(todo, 0)
+ newIDs[0] = opt.AddState(p.states[0].end)
+ hasNewID[0] = true
+
+ for len(todo) > 0 {
+ oldStateID := todo[len(todo)-1]
+ todo = todo[:len(todo)-1]
+
+ oldState := p.states[oldStateID]
+
+ for _, t := range oldState.transitions {
+ if !hasNewID[t.to] {
+ hasNewID[t.to] = true
+ newIDs[t.to] = opt.AddState(p.states[t.to].end)
+ todo = append(todo, t.to)
+ }
+ opt.AddTransition(newIDs[oldStateID], t.min, t.max, newIDs[t.to])
+ }
+ }
+
+ // TODO: remove transitions that point to a dead end
- return &is
+ return &opt
}
// CanMatch tests whether the pattern can match some string.
@@ -165,13 +245,13 @@ func Intersect(a, b *Pattern) *Pattern {
// Typical counterexamples are:
// [^]
// Intersect("*.c", "*.h")
-func (a *Pattern) CanMatch() bool {
- reachable := make([]bool, len(a.states))
+func (p *Pattern) CanMatch() bool {
+ reachable := make([]bool, len(p.states))
reachable[0] = true
again:
changed := false
- for i, s := range a.states {
+ for i, s := range p.states {
if reachable[i] {
for _, t := range s.transitions {
if !reachable[t.to] {
@@ -185,7 +265,7 @@ again:
goto again
}
- for i, s := range a.states {
+ for i, s := range p.states {
if reachable[i] && s.end {
return true
}
Index: pkgsrc/pkgtools/pkglint/files/makepat/pat_test.go
diff -u pkgsrc/pkgtools/pkglint/files/makepat/pat_test.go:1.1 pkgsrc/pkgtools/pkglint/files/makepat/pat_test.go:1.2
--- pkgsrc/pkgtools/pkglint/files/makepat/pat_test.go:1.1 Sun Jun 14 11:35:54 2020
+++ pkgsrc/pkgtools/pkglint/files/makepat/pat_test.go Sat Jun 20 07:00:44 2020
@@ -1,10 +1,87 @@
package makepat
import (
+ "netbsd.org/pkglint/intqa"
+ "reflect"
"testing"
)
-func Test_Automaton_Match(t *testing.T) {
+func Test_Compile__errors(t *testing.T) {
+ tests := []struct {
+ pattern string
+ msg string
+ }{
+ {"\\", "unfinished escape sequence"},
+ {"[", "unfinished character class"},
+ {"[a-", "unfinished character range"},
+ {"[\\", "unfinished character class"},
+ }
+ for _, tt := range tests {
+ t.Run(tt.pattern, func(t *testing.T) {
+ _, err := Compile(tt.pattern)
+ if err == nil {
+ t.Fail()
+ } else if err.Error() != tt.msg {
+ t.Errorf("err = %v, want %v", err, tt.msg)
+ }
+ })
+ }
+}
+
+func Test_compileCharClass(t *testing.T) {
+ tests := []struct {
+ pattern string
+ str string
+ want bool
+ }{
+ {"[0-9]", "/", false},
+ {"[0-9]", "0", true},
+ {"[0-9]", "9", true},
+ {"[0-9]", ":", false},
+ }
+ for _, tt := range tests {
+ t.Run(tt.pattern, func(t *testing.T) {
+ p, err := Compile(tt.pattern)
+ if err != nil {
+ t.Fail()
+ }
+ got := p.Match(tt.str)
+ if got != tt.want {
+ t.Errorf("got %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_Pattern_AddState(t *testing.T) {
+ var p Pattern
+
+ p.AddState(false)
+ p.AddState(true)
+
+ expected := Pattern{states: []state{{nil, false}, {nil, true}}}
+ if !reflect.DeepEqual(p, expected) {
+ t.Errorf("%#v", p)
+ }
+}
+
+func Test_Pattern_AddTransition(t *testing.T) {
+ var p Pattern
+
+ p.AddState(false)
+ p.AddState(true)
+ p.AddTransition(0, '0', '9', 1)
+ p.AddTransition(1, '0', '9', 0)
+
+ expected := Pattern{states: []state{
+ {[]transition{{'0', '9', 1}}, false},
+ {[]transition{{'0', '9', 0}}, true}}}
+ if !reflect.DeepEqual(p, expected) {
+ t.Errorf("%#v", p)
+ }
+}
+
+func Test_Pattern_Match(t *testing.T) {
tests := []struct {
pattern string
str string
@@ -67,6 +144,12 @@ func Test_Automaton_Match(t *testing.T)
{"[9-0]", "5", true},
{"[9-0]", "9", true},
{"[9-0]", ":", false},
+ {"*.c", ".c", true},
+ {"*.c", "a.c", true},
+ {"*.c", "c.c", true},
+ {"*.c", "..c", true},
+ {"*.c", ".c.c", true},
+ {"*.c", "a.c.c", true},
}
for _, tt := range tests {
t.Run(tt.pattern+" "+tt.str, func(t *testing.T) {
@@ -81,28 +164,6 @@ func Test_Automaton_Match(t *testing.T)
}
}
-func Test_Automaton_Compile__errors(t *testing.T) {
- tests := []struct {
- pattern string
- msg string
- }{
- {"\\", "unfinished escape sequence"},
- {"[", "unfinished character class"},
- {"[a-", "unfinished character range"},
- {"[\\", "unfinished character class"},
- }
- for _, tt := range tests {
- t.Run(tt.pattern, func(t *testing.T) {
- _, err := Compile(tt.pattern)
- if err == nil {
- t.Fail()
- } else if err.Error() != tt.msg {
- t.Errorf("err = %v, want %v", err, tt.msg)
- }
- })
- }
-}
-
func Test_Intersect(t *testing.T) {
tests := []struct {
pattern1 string
@@ -137,3 +198,79 @@ func Test_Intersect(t *testing.T) {
})
}
}
+
+func Test_Pattern_optimized(t *testing.T) {
+ var p Pattern
+ p.AddState(false)
+ p.AddState(false)
+ p.AddState(false)
+ p.AddState(true)
+ p.AddTransition(0, '1', '1', 1)
+ p.AddTransition(1, '2', '2', 3)
+
+ opt := p.optimized()
+
+ expected := Pattern{[]state{
+ {[]transition{{'1', '1', 1}}, false},
+ {[]transition{{'2', '2', 2}}, false},
+ {nil, true}}}
+ if !reflect.DeepEqual(opt, &expected) {
+ t.Errorf("%#v", p)
+ }
+}
+
+func Test_Pattern_CanMatch(t *testing.T) {
+ tests := []struct {
+ p1 string
+ p2 string
+ want bool
+ }{
+ {"*.c", "*.h", false},
+ {"*.c", "????.?", true},
+ {"[1-9]", "5", true},
+ {"[1-9]", ":", false},
+ {"[1-9A-Za-z]", "[ -/]", false},
+ }
+ for _, tt := range tests {
+ t.Run(tt.p1+" "+tt.p2, func(t *testing.T) {
+ p1, err1 := Compile(tt.p1)
+ p2, err2 := Compile(tt.p2)
+ if err1 != nil {
+ t.Fatal(err1)
+ }
+ if err2 != nil {
+ t.Fatal(err2)
+ }
+ both := Intersect(p1, p2)
+ got := both.CanMatch()
+ if got != tt.want {
+ t.Errorf("CanMatch() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+
+}
+
+func Test_bmin(t *testing.T) {
+ if bmin(0, 255) != 0 {
+ t.Error()
+ }
+ if bmin(128, 127) != 127 {
+ t.Error()
+ }
+}
+
+func Test_bmax(t *testing.T) {
+ if bmax(0, 255) != 255 {
+ t.Error()
+ }
+ if bmax(128, 127) != 128 {
+ t.Error()
+ }
+}
+
+func Test(t *testing.T) {
+ ck := intqa.NewQAChecker(t.Errorf)
+ ck.Configure("*", "*", "", -intqa.EMissingTest)
+ ck.Check()
+}
Home |
Main Index |
Thread Index |
Old Index