refactor(license): use goyacc for license parser (#3824)

This commit is contained in:
Teppei Fukuda
2023-03-14 09:27:17 +02:00
committed by GitHub
parent 00c763bc10
commit 2bb25e766b
22 changed files with 1493 additions and 705 deletions

View File

@@ -1,74 +1,80 @@
package expression
import (
"fmt"
"strings"
"unicode"
"github.com/aquasecurity/trivy/pkg/licensing/expression/lexer"
"github.com/aquasecurity/trivy/pkg/licensing/expression/parser"
"golang.org/x/xerrors"
)
type Operator string
const (
AND Operator = "AND"
OR Operator = "OR"
WITH Operator = "WITH"
var (
ErrInvalidExpression = xerrors.New("invalid expression error")
)
func (o Operator) String() string {
return fmt.Sprintf(" %s ", string(o))
type NormalizeFunc func(license string) string
func parse(license string) (Expression, error) {
l := NewLexer(strings.NewReader(license))
if yyParse(l) != 0 {
return nil, xerrors.Errorf("license parse error: %w", l.Err())
} else if err := l.Err(); err != nil {
return nil, err
}
return l.result, nil
}
func Normalize(license string, fn ...parser.NormalizeFunc) string {
lex := lexer.New(license)
licenseParser := parser.New(lex).RegisterNormalizeFunc(
fn...,
)
expression, err := licenseParser.Parse()
func Normalize(license string, fn ...NormalizeFunc) (string, error) {
expr, err := parse(license)
if err != nil {
return license
return "", xerrors.Errorf("license (%s) parse error: %w", license, err)
}
return licenseParser.Normalize(expression)
expr = normalize(expr, fn...)
return expr.String(), nil
}
func Join(elems []string, sep Operator) string {
var licenses []string
for i, license := range elems {
var mid Operator
if sep == AND {
mid = OR
} else if sep == OR {
mid = AND
func normalize(expr Expression, fn ...NormalizeFunc) Expression {
switch e := expr.(type) {
case SimpleExpr:
for _, f := range fn {
e.license = f(e.license)
}
if i != 0 && strings.Contains(strings.ToUpper(license), mid.String()) {
license = fmt.Sprintf("(%s)", license)
}
licenses = append(licenses, license)
return e
case CompoundExpr:
e.left = normalize(e.left, fn...)
e.right = normalize(e.right, fn...)
e.conjunction.literal = strings.ToUpper(e.conjunction.literal) // e.g. "and" => "AND"
return e
}
return strings.Join(licenses, sep.String())
return expr
}
// NormalizeForSPDX is normalized license-id replace ' ' to '-'.
// NormalizeForSPDX replaces ' ' to '-' in license-id.
// SPDX license MUST NOT be white space between a license-id.
// There MUST be white space on either side of the operator "WITH".
// ref: https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions
func NormalizeForSPDX(name string) string {
i := strings.Index(strings.ToUpper(name), WITH.String())
if i < 0 {
return strings.Replace(name, " ", "-", -1)
func NormalizeForSPDX(s string) string {
var b strings.Builder
for _, c := range s {
// idstring = 1*(ALPHA / DIGIT / "-" / "." )
if isAlphabet(c) || unicode.IsNumber(c) || c == '-' || c == '.' {
_, _ = b.WriteRune(c)
} else if c == ':' {
// TODO: Support DocumentRef
_, _ = b.WriteRune(c)
} else {
// Replace invalid characters with '-'
_, _ = b.WriteRune('-')
}
}
// Convert "WITH" expression split by " " to "-".
// examples:
// GPL-2+ with distribution exception => GPL-2+ with distribution-exception
// GPL-2 with Linux-syscall-note exception => GPL-2 with Linux-syscall-note-exception
// AFL 2.0 with Linux-syscall-note exception => AFL-2.0 with Linux-syscall-note-exception
withSection := strings.Replace(name[i+len(WITH.String()):], " ", "-", -1)
if i > 0 {
return strings.Replace(name[:i], " ", "-", -1) + WITH.String() + withSection
}
return name
return b.String()
}
func isAlphabet(r rune) bool {
if (r < 'a' || r > 'z') && (r < 'A' || r > 'Z') {
return false
}
return true
}