@@ -17,6 +17,8 @@ FILE *diffpos = NULL;
1717int judgeans_pos = 0 , stdin_pos = 0 ;
1818int judgeans_line = 1 , stdin_line = 1 ;
1919
20+ // At some point we should rewrite this to something more C++. Now that we truncate long tokens, having this
21+ // require c_str() calls gets even messier with object lifetimes.
2022void wrong_answer (const char *err, ...) {
2123 va_list pvar;
2224 va_start (pvar, err);
@@ -64,6 +66,49 @@ FILE *openfeedback(const char *feedbackdir, const char *feedback, const char *wh
6466 return res;
6567}
6668
69+ /* Truncate string to avoid huge messages when teams forgot to print spaces.
70+ * If string is longer than limit (plus 5, as we don't want to replace just a
71+ * few characters with ...), we truncate and append "...". String may be in
72+ * arbitrary encoding, but as utf-8 is common, we make a small attempt to avoid
73+ * cutting in a utf-8 character. So output can be a few bytes longer than limit.
74+ */
75+ std::string truncate (const std::string &str, size_t limit = 30 ) {
76+ if (str.length () <= limit + 5 ) {
77+ return str;
78+ }
79+ size_t cut = limit;
80+ // Heuristic to avoid cutting in the middle of a UTF-8 character.
81+ // A continuation byte in UTF-8 starts with binary 10.
82+ // We scan forwards from the limit to include the rest of a character,
83+ // but at most 3 extra bytes (for up to a 4-byte character).
84+ while (cut < str.length () && cut < limit + 4 && (str[cut] & 0xC0 ) == 0x80 ) {
85+ cut++;
86+ }
87+ return str.substr (0 , cut) + " ..." ;
88+ }
89+
90+ /* Truncate a pair of strings (judge and user tokens). This preserves the first
91+ * few bytes of the common prefix, then adds ..., and then the first few bytes
92+ * starting from where the strings differ.
93+ */
94+ std::pair<std::string, std::string> truncate_pair (const std::string &str1, const std::string &str2) {
95+ size_t diff_idx = 0 ;
96+ while (diff_idx < str1.length () && diff_idx < str2.length () && str1[diff_idx] == str2[diff_idx]) {
97+ diff_idx++;
98+ }
99+
100+ std::string common_prefix = str1.substr (0 , diff_idx);
101+ std::string s1 = str1.substr (diff_idx);
102+ std::string s2 = str2.substr (diff_idx);
103+
104+ std::string p_part = truncate (common_prefix, 15 );
105+
106+ return std::make_pair (
107+ p_part + truncate (s1, 15 ),
108+ p_part + truncate (s2, 15 )
109+ );
110+ }
111+
67112const char *USAGE = " Usage: %s judge_in judge_ans feedback_file [options] < user_out" ;
68113
69114int main (int argc, char **argv) {
@@ -82,7 +127,7 @@ int main(int argc, char **argv) {
82127 double float_rel_tol = -1 ;
83128
84129 for (int a = 4 ; a < argc; ++a) {
85- if (!strcmp (argv[a], " case_sensitive" )) {
130+ if (!strcmp (argv[a], " case_sensitive" )) {
86131 case_sensitive = true ;
87132 } else if (!strcmp (argv[a], " space_change_sensitive" )) {
88133 space_change_sensitive = true ;
@@ -108,7 +153,7 @@ int main(int argc, char **argv) {
108153 }
109154 use_floats = float_abs_tol >= 0 || float_rel_tol >= 0 ;
110155
111- std::string judge, team;
156+ std::string judge, team, judge_trunc, team_trunc ;
112157 for (int token = 0 ; true ; token++) {
113158 // Space! Can't live with it, can't live without it...
114159 while (isspace (judgeans.peek ())) {
@@ -140,46 +185,57 @@ int main(int argc, char **argv) {
140185 if (!(std::cin >> team)) {
141186 if (token == 0 ) {
142187 if (stdin_pos == 0 ) {
188+ judge_trunc = truncate (judge);
143189 wrong_answer (
144190 " User EOF while judge had more output; user output was empty.\n (Next judge token: %s)" ,
145- judge .c_str ()
191+ judge_trunc .c_str ()
146192 );
147193 } else {
194+ judge_trunc = truncate (judge);
148195 wrong_answer (
149196 " User EOF while judge had more output; user output contained only whitespace.\n (Next judge token: %s)" ,
150- judge .c_str ()
197+ judge_trunc .c_str ()
151198 );
152199 }
153200 } else {
154- wrong_answer (" User EOF while judge had more output\n (Next judge token: %s)" , judge.c_str ());
201+ judge_trunc = truncate (judge);
202+ wrong_answer (" User EOF while judge had more output\n (Next judge token: %s)" , judge_trunc.c_str ());
155203 }
156204 }
157-
205+
158206 double jval, tval;
159207 if (use_floats && isfloat (judge.c_str (), jval)) {
160208 if (!isfloat (team.c_str (), tval)) {
161- wrong_answer (" Expected float, got: %s" , team.c_str ());
209+ team_trunc = truncate (team);
210+ wrong_answer (" Expected float, got: %s" , team_trunc.c_str ());
162211 }
163212 if (!(fabs (jval - tval) <= float_abs_tol) &&
164213 !(fabs (jval - tval) <= float_rel_tol * fabs (jval))) {
214+ // We don't want to truncate as a pair here, that just gets more confusing for floats (and something has
215+ // gone very wrong if we're dealing with floats so long we need to truncate anyway :)
216+ judge_trunc = truncate (judge);
217+ team_trunc = truncate (team);
165218 wrong_answer (" Too large difference.\n Judge: %s\n User: %s\n Difference: %le\n (abs tol %le rel tol %le)" ,
166- judge .c_str (), team .c_str (), jval-tval, float_abs_tol, float_rel_tol);
219+ judge_trunc .c_str (), team_trunc .c_str (), jval-tval, float_abs_tol, float_rel_tol);
167220 }
168221 } else if (case_sensitive) {
169222 if (strcmp (judge.c_str (), team.c_str ()) != 0 ) {
170- wrong_answer (" String tokens mismatch\n Judge: \" %s\"\n User: \" %s\" " , judge.c_str (), team.c_str ());
223+ std::tie (judge_trunc, team_trunc) = truncate_pair (judge, team);
224+ wrong_answer (" String tokens mismatch\n Judge: \" %s\"\n User: \" %s\" " , judge_trunc.c_str (), team_trunc.c_str ());
171225 }
172226 } else {
173227 if (strcasecmp (judge.c_str (), team.c_str ()) != 0 ) {
174- wrong_answer (" String tokens mismatch\n Judge: \" %s\"\n User: \" %s\" " , judge.c_str (), team.c_str ());
228+ std::tie (judge_trunc, team_trunc) = truncate_pair (judge, team);
229+ wrong_answer (" String tokens mismatch\n Judge: \" %s\"\n User: \" %s\" " , judge_trunc.c_str (), team_trunc.c_str ());
175230 }
176231 }
177232 judgeans_pos += judge.length ();
178233 stdin_pos += team.length ();
179234 }
180235
181236 if (std::cin >> team) {
182- wrong_answer (" Trailing output:\n %s" , team.c_str ());
237+ team_trunc = truncate (team);
238+ wrong_answer (" Trailing output:\n %s" , team_trunc.c_str ());
183239 }
184240
185241 exit (EXIT_AC);
0 commit comments