Files
d2go/pkg/nip/nip_parser.go
2023-03-13 21:28:49 +09:00

173 lines
4.1 KiB
Go

package nip
import (
"errors"
"regexp"
"strconv"
"strings"
)
var (
errEmptyLine = errors.New("empty line")
operandRegex = regexp.MustCompile(`(\|{2}|\&{2})`)
comparableRegex = regexp.MustCompile(`(\={2}|\<\=|\>\=|\>|\<|\!\=)`)
propertyNameRegex = regexp.MustCompile(`\[(.*)\]`)
)
func ParseLine(line string) (Rule, error) {
line = lineCleanup(line)
if line == "" {
return Rule{}, errEmptyLine
}
lineSplit := strings.Split(line, "#")
properties, err := buildGroups(lineSplit[0])
if err != nil {
return Rule{}, err
}
var stats []Group
if len(lineSplit) > 1 {
stats, err = buildGroups(lineSplit[1])
if err != nil {
return Rule{}, err
}
}
var maxQuantity []Group
if len(lineSplit) > 2 {
maxQuantity, err = buildGroups(lineSplit[2])
if err != nil {
return Rule{}, err
}
}
return Rule{
Properties: properties,
Stats: stats,
MaxQuantity: maxQuantity,
}, nil
}
func lineCleanup(line string) string {
l := strings.Split(line, "//")
line = strings.TrimSpace(l[0])
return strings.ToLower(line)
}
func buildGroups(block string) ([]Group, error) {
operands := operandRegex.FindAllString(block, -1)
groupLines := operandRegex.Split(block, -1)
operands, groupLines = reGroupParentheses(operands, groupLines)
if len(operands) != len(groupLines)-1 {
return nil, errors.New("syntax error")
}
groups := make([]Group, 0)
for i, groupLine := range groupLines {
cmp, err := buildGroupComparables(groupLine)
if err != nil {
return nil, err
}
group := Group{Comparable: cmp}
if i < len(operands) {
group.Operand = Operand(operands[i])
}
groups = append(groups, group)
}
return groups, nil
}
func reGroupParentheses(operands, groups []string) ([]string, []string) {
cleanOperands := make([]string, 0)
cleanGroups := make([]string, 0)
inGroup := false
currentGroupContent := ""
for i, group := range groups {
group = strings.TrimSpace(group)
if strings.Contains(group, "(") && strings.Contains(group, ")") {
group = strings.ReplaceAll(group, "(", "")
group = strings.ReplaceAll(group, ")", "")
cleanGroups = append(cleanGroups, group)
if i < len(operands) {
cleanOperands = append(cleanOperands, operands[i])
}
continue
}
if strings.Contains(group, "(") || (inGroup && !strings.Contains(group, ")")) {
inGroup = true
currentGroupContent += strings.ReplaceAll(group, "(", "") + operands[i]
continue
}
if strings.Contains(group, ")") {
inGroup = false
currentGroupContent += strings.ReplaceAll(group, ")", "")
cleanGroups = append(cleanGroups, currentGroupContent)
currentGroupContent = ""
if i < len(operands) {
cleanOperands = append(cleanOperands, operands[i])
}
continue
}
if !inGroup {
cleanGroups = append(cleanGroups, group)
if i < len(operands) {
cleanOperands = append(cleanOperands, operands[i])
}
}
}
return cleanOperands, cleanGroups
}
func buildGroupComparables(group string) ([]Comparable, error) {
operands := operandRegex.FindAllString(group, -1)
properties := operandRegex.Split(group, -1)
if len(properties)-1 != len(operands) {
return nil, errors.New("invalid NIP block")
}
comparables := make([]Comparable, 0)
for i, prop := range properties {
comparisonSymbol := comparableRegex.FindString(prop)
values := comparableRegex.Split(prop, -1)
if len(values) != 2 {
return nil, errors.New("invalid NIP line")
}
aggregatedProperties := strings.Split(values[0], "+")
for propNum, prop := range aggregatedProperties {
propertyNameGr := propertyNameRegex.FindStringSubmatch(prop)
propertyName := strings.TrimSpace(propertyNameGr[1])
cmp := Comparable{
Keyword: propertyName,
Comparison: Operand(strings.TrimSpace(comparisonSymbol)),
}
if len(aggregatedProperties) > 0 && propNum < len(aggregatedProperties)-1 {
cmp.Operand = OperandOr
} else if i < len(operands) {
cmp.Operand = Operand(operands[i])
}
value := strings.TrimSpace(values[1])
if val, err := strconv.Atoi(value); err == nil {
cmp.ValueInt = val
} else {
cmp.ValueString = value
}
comparables = append(comparables, cmp)
}
}
return comparables, nil
}