package runner import ( "fmt" "math" "regexp" "sort" "strconv" "strings" "github.com/Mond1c/judge/dsl" ) func applyMatcher(label string, m dsl.Matcher, actual string) []string { switch m := m.(type) { case dsl.NoMatcher: return nil case dsl.ExactMatcher: if actual != m.Value { return []string{fmt.Sprintf( "%s mismatch:\n expected: %q\n actual: %q", label, m.Value, actual, )} } return nil case dsl.ContainsMatcher: if !strings.Contains(actual, m.Substr) { return []string{fmt.Sprintf( "%s: expected to contain %q, got %q", label, m.Substr, actual, )} } return nil case dsl.RegexMatcher: re, err := regexp.Compile(m.Pattern) if err != nil { return []string{fmt.Sprintf("%s: invalid regex %q: %v", label, m.Pattern, err)} } if !re.MatchString(actual) { return []string{fmt.Sprintf( "%s: %q does not match regex %q", label, actual, m.Pattern, )} } return nil case dsl.NumericEpsMatcher: errs := matchNumericEps(label, m, actual) return errs case dsl.AnyOrderMatcher: return matchAnyOrder(label, m, actual) default: return []string{fmt.Sprintf("unknown matcher type %T", m)} } } func matchNumericEps(label string, m dsl.NumericEpsMatcher, actual string) []string { expectedNums, err := parseNumbers(m.Value) if err != nil { return []string{fmt.Sprintf("%s: cannot parse expected numbers %q: %v", label, m.Value, err)} } actualNums, err := parseNumbers(actual) if err != nil { return []string{fmt.Sprintf("%s: cannot parse actual numbers %q: %v", label, actual, err)} } if len(expectedNums) != len(actualNums) { return []string{fmt.Sprintf( "%s: expected %d numbers, got %d (expected=%q, actual=%q)", label, len(expectedNums), len(actualNums), m.Value, actual, )} } var errs []string for i, exp := range expectedNums { act := actualNums[i] if math.Abs(exp-act) > m.Epsilon { errs = append(errs, fmt.Sprintf( "%s: number[%d] expected %.10g ± %.10g, got %.10g", label, i, exp, m.Epsilon, act, )) } } return errs } func parseNumbers(s string) ([]float64, error) { fields := strings.Fields(s) nums := make([]float64, 0, len(fields)) for _, f := range fields { n, err := strconv.ParseFloat(f, 64) if err != nil { return nil, fmt.Errorf("not a number: %q", f) } nums = append(nums, n) } return nums, nil } func matchAnyOrder(label string, m dsl.AnyOrderMatcher, actual string) []string { actualLines := splitLines(actual) expectedLines := make([]string, len(m.Lines)) copy(expectedLines, m.Lines) sort.Strings(actualLines) sort.Strings(expectedLines) if len(actualLines) != len(expectedLines) { return []string{fmt.Sprintf( "%s anyOrder: expected %d lines, got %d", label, len(expectedLines), len(actualLines), )} } var errs []string for i := range expectedLines { if actualLines[i] != expectedLines[i] { errs = append(errs, fmt.Sprintf( "%s anyOrder: line mismatch: expected %q, got %q", label, expectedLines[i], actualLines[i], )) } } return errs } func splitLines(s string) []string { s = strings.TrimRight(s, "\n") if s == "" { return []string{} } return strings.Split(s, "\n") }