feat(parser/diff): fail on non-accepted status
This commit is contained in:
parent
b3f76c24a7
commit
505f776e6e
196
internal/parser/diff/diff.go
Normal file
196
internal/parser/diff/diff.go
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
package diff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// compareStrings compares two strings character by character, optionally ignoring whitespace.
|
||||||
|
func compareStrings(str1, str2 string, compareSpace bool) bool {
|
||||||
|
if compareSpace {
|
||||||
|
return str1 == str2
|
||||||
|
}
|
||||||
|
var i, j int
|
||||||
|
l1 := len(str1)
|
||||||
|
l2 := len(str2)
|
||||||
|
for i < l1 && j < l2 {
|
||||||
|
for i < l1 && isWhitespace(str1[i]) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
for j < l2 && isWhitespace(str2[j]) {
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
if i < l1 && j < l2 && str1[i] != str2[j] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if i < l1 {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if j < l2 {
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i < l1 && isWhitespace(str1[i]) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
for j < l2 && isWhitespace(str2[j]) {
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
return i == l1 && j == l2
|
||||||
|
}
|
||||||
|
|
||||||
|
func isWhitespace(b byte) bool {
|
||||||
|
return b == ' ' ||
|
||||||
|
b == '\t' ||
|
||||||
|
b == '\n' ||
|
||||||
|
b == '\r' ||
|
||||||
|
b == '\v' ||
|
||||||
|
b == '\f' ||
|
||||||
|
b == 0x85 ||
|
||||||
|
b == 0xA0
|
||||||
|
}
|
||||||
|
|
||||||
|
// myersDiff computes the Myers' diff between two slices of strings.
|
||||||
|
// src: https://github.com/cj1128/myers-diff/blob/master/main.go
|
||||||
|
func myersDiff(src, dst []string, compareSpace bool) []operation {
|
||||||
|
n := len(src)
|
||||||
|
m := len(dst)
|
||||||
|
max := n + m
|
||||||
|
var trace []map[int]int
|
||||||
|
var x, y int
|
||||||
|
|
||||||
|
loop:
|
||||||
|
for d := 0; d <= max; d += 1 {
|
||||||
|
v := make(map[int]int, d+2)
|
||||||
|
trace = append(trace, v)
|
||||||
|
|
||||||
|
if d == 0 {
|
||||||
|
t := 0
|
||||||
|
for len(src) > t &&
|
||||||
|
len(dst) > t &&
|
||||||
|
compareStrings(src[t], dst[t], compareSpace) {
|
||||||
|
t += 1
|
||||||
|
}
|
||||||
|
v[0] = t
|
||||||
|
if t == len(src) && len(src) == len(dst) {
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
lastV := trace[d-1]
|
||||||
|
|
||||||
|
for k := -d; k <= d; k += 2 {
|
||||||
|
if k == -d || (k != d && lastV[k-1] < lastV[k+1]) {
|
||||||
|
x = lastV[k+1]
|
||||||
|
} else {
|
||||||
|
x = lastV[k-1] + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
y = x - k
|
||||||
|
|
||||||
|
for x < n && y < m && compareStrings(src[x], dst[y], compareSpace) {
|
||||||
|
x, y = x+1, y+1
|
||||||
|
}
|
||||||
|
|
||||||
|
v[k] = x
|
||||||
|
|
||||||
|
if x == n && y == m {
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var script []operation
|
||||||
|
x = n
|
||||||
|
y = m
|
||||||
|
var k, prevK, prevX, prevY int
|
||||||
|
|
||||||
|
for d := len(trace) - 1; d > 0; d -= 1 {
|
||||||
|
k = x - y
|
||||||
|
lastV := trace[d-1]
|
||||||
|
|
||||||
|
if k == -d || (k != d && lastV[k-1] < lastV[k+1]) {
|
||||||
|
prevK = k + 1
|
||||||
|
} else {
|
||||||
|
prevK = k - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
prevX = lastV[prevK]
|
||||||
|
prevY = prevX - prevK
|
||||||
|
|
||||||
|
for x > prevX && y > prevY {
|
||||||
|
script = append(script, MOVE)
|
||||||
|
x -= 1
|
||||||
|
y -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if x == prevX {
|
||||||
|
script = append(script, INSERT)
|
||||||
|
} else {
|
||||||
|
script = append(script, DELETE)
|
||||||
|
}
|
||||||
|
|
||||||
|
x, y = prevX, prevY
|
||||||
|
}
|
||||||
|
|
||||||
|
if trace[0][0] != 0 {
|
||||||
|
for i := 0; i < trace[0][0]; i += 1 {
|
||||||
|
script = append(script, MOVE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return reverse(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
// reverse reverses a slice of operations.
|
||||||
|
func reverse(s []operation) []operation {
|
||||||
|
result := make([]operation, len(s))
|
||||||
|
for i, v := range s {
|
||||||
|
result[len(s)-1-i] = v
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateDiffWithContext creates a diff block with surrounding context from stdout and result.
|
||||||
|
func generateDiffWithContext(
|
||||||
|
stdoutLines, resultLines []string, ops []operation, maxLength int,
|
||||||
|
) string {
|
||||||
|
var diffBuilder strings.Builder
|
||||||
|
|
||||||
|
srcIndex, dstIndex, lineCount := 0, 0, 0
|
||||||
|
|
||||||
|
for _, op := range ops {
|
||||||
|
s := ""
|
||||||
|
switch op {
|
||||||
|
case INSERT:
|
||||||
|
if dstIndex < len(resultLines) {
|
||||||
|
s = fmt.Sprintf("+ %s\n", resultLines[dstIndex])
|
||||||
|
dstIndex += 1
|
||||||
|
}
|
||||||
|
case MOVE:
|
||||||
|
if srcIndex < len(stdoutLines) {
|
||||||
|
s = fmt.Sprintf(" %s\n", stdoutLines[srcIndex])
|
||||||
|
srcIndex += 1
|
||||||
|
dstIndex += 1
|
||||||
|
}
|
||||||
|
case DELETE:
|
||||||
|
if srcIndex < len(stdoutLines) {
|
||||||
|
s = fmt.Sprintf("- %s\n", stdoutLines[srcIndex])
|
||||||
|
srcIndex += 1
|
||||||
|
lineCount += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if maxLength > 0 && diffBuilder.Len()+len(s) > maxLength {
|
||||||
|
remaining := maxLength - diffBuilder.Len()
|
||||||
|
if remaining > 0 {
|
||||||
|
diffBuilder.WriteString(s[:remaining])
|
||||||
|
}
|
||||||
|
diffBuilder.WriteString("\n\n(truncated)")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
diffBuilder.WriteString(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return diffBuilder.String()
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/criyle/go-judge/envexec"
|
||||||
"github.com/joint-online-judge/JOJ3/internal/stage"
|
"github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,6 +22,8 @@ const (
|
||||||
type Conf struct {
|
type Conf struct {
|
||||||
PassComment string `default:"🥳Passed!\n"`
|
PassComment string `default:"🥳Passed!\n"`
|
||||||
FailComment string `default:"🧐Failed...\n"`
|
FailComment string `default:"🧐Failed...\n"`
|
||||||
|
PassNonAcceptedStatus bool
|
||||||
|
ForceQuitOnFailed bool
|
||||||
Cases []struct {
|
Cases []struct {
|
||||||
Outputs []struct {
|
Outputs []struct {
|
||||||
Score int
|
Score int
|
||||||
|
@ -53,6 +56,15 @@ func (*Diff) Run(results []stage.ExecutorResult, confAny any) (
|
||||||
result := results[i]
|
result := results[i]
|
||||||
score := 0
|
score := 0
|
||||||
comment := ""
|
comment := ""
|
||||||
|
if !conf.PassNonAcceptedStatus &&
|
||||||
|
result.Status != stage.Status(envexec.StatusAccepted) {
|
||||||
|
if conf.ForceQuitOnFailed {
|
||||||
|
forceQuit = true
|
||||||
|
}
|
||||||
|
comment += conf.FailComment
|
||||||
|
comment += "Executor status not `Accepted`\n"
|
||||||
|
continue
|
||||||
|
}
|
||||||
for _, output := range caseConf.Outputs {
|
for _, output := range caseConf.Outputs {
|
||||||
answer, err := os.ReadFile(output.AnswerPath)
|
answer, err := os.ReadFile(output.AnswerPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -68,7 +80,7 @@ func (*Diff) Run(results []stage.ExecutorResult, confAny any) (
|
||||||
score += output.Score
|
score += output.Score
|
||||||
comment += conf.PassComment
|
comment += conf.PassComment
|
||||||
} else {
|
} else {
|
||||||
if output.ForceQuitOnDiff {
|
if output.ForceQuitOnDiff || conf.ForceQuitOnFailed {
|
||||||
forceQuit = true
|
forceQuit = true
|
||||||
}
|
}
|
||||||
comment += conf.FailComment
|
comment += conf.FailComment
|
||||||
|
@ -107,193 +119,3 @@ func (*Diff) Run(results []stage.ExecutorResult, confAny any) (
|
||||||
|
|
||||||
return res, forceQuit, nil
|
return res, forceQuit, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// compareStrings compares two strings character by character, optionally ignoring whitespace.
|
|
||||||
func compareStrings(str1, str2 string, compareSpace bool) bool {
|
|
||||||
if compareSpace {
|
|
||||||
return str1 == str2
|
|
||||||
}
|
|
||||||
var i, j int
|
|
||||||
l1 := len(str1)
|
|
||||||
l2 := len(str2)
|
|
||||||
for i < l1 && j < l2 {
|
|
||||||
for i < l1 && isWhitespace(str1[i]) {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
for j < l2 && isWhitespace(str2[j]) {
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
if i < l1 && j < l2 && str1[i] != str2[j] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if i < l1 {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
if j < l2 {
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i < l1 && isWhitespace(str1[i]) {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
for j < l2 && isWhitespace(str2[j]) {
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
return i == l1 && j == l2
|
|
||||||
}
|
|
||||||
|
|
||||||
func isWhitespace(b byte) bool {
|
|
||||||
return b == ' ' ||
|
|
||||||
b == '\t' ||
|
|
||||||
b == '\n' ||
|
|
||||||
b == '\r' ||
|
|
||||||
b == '\v' ||
|
|
||||||
b == '\f' ||
|
|
||||||
b == 0x85 ||
|
|
||||||
b == 0xA0
|
|
||||||
}
|
|
||||||
|
|
||||||
// myersDiff computes the Myers' diff between two slices of strings.
|
|
||||||
// src: https://github.com/cj1128/myers-diff/blob/master/main.go
|
|
||||||
func myersDiff(src, dst []string, compareSpace bool) []operation {
|
|
||||||
n := len(src)
|
|
||||||
m := len(dst)
|
|
||||||
max := n + m
|
|
||||||
var trace []map[int]int
|
|
||||||
var x, y int
|
|
||||||
|
|
||||||
loop:
|
|
||||||
for d := 0; d <= max; d += 1 {
|
|
||||||
v := make(map[int]int, d+2)
|
|
||||||
trace = append(trace, v)
|
|
||||||
|
|
||||||
if d == 0 {
|
|
||||||
t := 0
|
|
||||||
for len(src) > t &&
|
|
||||||
len(dst) > t &&
|
|
||||||
compareStrings(src[t], dst[t], compareSpace) {
|
|
||||||
t += 1
|
|
||||||
}
|
|
||||||
v[0] = t
|
|
||||||
if t == len(src) && len(src) == len(dst) {
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
lastV := trace[d-1]
|
|
||||||
|
|
||||||
for k := -d; k <= d; k += 2 {
|
|
||||||
if k == -d || (k != d && lastV[k-1] < lastV[k+1]) {
|
|
||||||
x = lastV[k+1]
|
|
||||||
} else {
|
|
||||||
x = lastV[k-1] + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
y = x - k
|
|
||||||
|
|
||||||
for x < n && y < m && compareStrings(src[x], dst[y], compareSpace) {
|
|
||||||
x, y = x+1, y+1
|
|
||||||
}
|
|
||||||
|
|
||||||
v[k] = x
|
|
||||||
|
|
||||||
if x == n && y == m {
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var script []operation
|
|
||||||
x = n
|
|
||||||
y = m
|
|
||||||
var k, prevK, prevX, prevY int
|
|
||||||
|
|
||||||
for d := len(trace) - 1; d > 0; d -= 1 {
|
|
||||||
k = x - y
|
|
||||||
lastV := trace[d-1]
|
|
||||||
|
|
||||||
if k == -d || (k != d && lastV[k-1] < lastV[k+1]) {
|
|
||||||
prevK = k + 1
|
|
||||||
} else {
|
|
||||||
prevK = k - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
prevX = lastV[prevK]
|
|
||||||
prevY = prevX - prevK
|
|
||||||
|
|
||||||
for x > prevX && y > prevY {
|
|
||||||
script = append(script, MOVE)
|
|
||||||
x -= 1
|
|
||||||
y -= 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if x == prevX {
|
|
||||||
script = append(script, INSERT)
|
|
||||||
} else {
|
|
||||||
script = append(script, DELETE)
|
|
||||||
}
|
|
||||||
|
|
||||||
x, y = prevX, prevY
|
|
||||||
}
|
|
||||||
|
|
||||||
if trace[0][0] != 0 {
|
|
||||||
for i := 0; i < trace[0][0]; i += 1 {
|
|
||||||
script = append(script, MOVE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return reverse(script)
|
|
||||||
}
|
|
||||||
|
|
||||||
// reverse reverses a slice of operations.
|
|
||||||
func reverse(s []operation) []operation {
|
|
||||||
result := make([]operation, len(s))
|
|
||||||
for i, v := range s {
|
|
||||||
result[len(s)-1-i] = v
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateDiffWithContext creates a diff block with surrounding context from stdout and result.
|
|
||||||
func generateDiffWithContext(
|
|
||||||
stdoutLines, resultLines []string, ops []operation, maxLength int,
|
|
||||||
) string {
|
|
||||||
var diffBuilder strings.Builder
|
|
||||||
|
|
||||||
srcIndex, dstIndex, lineCount := 0, 0, 0
|
|
||||||
|
|
||||||
for _, op := range ops {
|
|
||||||
s := ""
|
|
||||||
switch op {
|
|
||||||
case INSERT:
|
|
||||||
if dstIndex < len(resultLines) {
|
|
||||||
s = fmt.Sprintf("+ %s\n", resultLines[dstIndex])
|
|
||||||
dstIndex += 1
|
|
||||||
}
|
|
||||||
case MOVE:
|
|
||||||
if srcIndex < len(stdoutLines) {
|
|
||||||
s = fmt.Sprintf(" %s\n", stdoutLines[srcIndex])
|
|
||||||
srcIndex += 1
|
|
||||||
dstIndex += 1
|
|
||||||
}
|
|
||||||
case DELETE:
|
|
||||||
if srcIndex < len(stdoutLines) {
|
|
||||||
s = fmt.Sprintf("- %s\n", stdoutLines[srcIndex])
|
|
||||||
srcIndex += 1
|
|
||||||
lineCount += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if maxLength > 0 && diffBuilder.Len()+len(s) > maxLength {
|
|
||||||
remaining := maxLength - diffBuilder.Len()
|
|
||||||
if remaining > 0 {
|
|
||||||
diffBuilder.WriteString(s[:remaining])
|
|
||||||
}
|
|
||||||
diffBuilder.WriteString("\n\n(truncated)")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
diffBuilder.WriteString(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
return diffBuilder.String()
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user