[m-rev.] for review: escape characters in strings returned by deconstruct.functor/4
Julien Fischer
jfischer at opturion.com
Wed Jul 11 00:56:50 AEST 2018
For review by anyone.
--------------------------
Escape characters in strings returned by deconstruct.functor/4.
runtime/mercury_string.[ch]:
Add a function that returns a copy of a string inside double
quotes and with escapes for any control characters inserted.
Add macro that tests if a character is a control character.
runtime/mercury_ml_expand_body.h:
Use the above function to escape characters in the representation
of string functors.
library/rtti_implementation.m:
Do the same for the C# and Java backends.
NEWS:
Announce the above change to deconstruct.functor/4, as well
the earlier change to its handling of characters.
tests/hard_coded/deconstruct_arg.m:
tests/hard_coded/deconstruct_arg.exp:
Extend this test case to cover strings.
Julien.
diff --git a/NEWS b/NEWS
index cb97697..3fdc989 100644
--- a/NEWS
+++ b/NEWS
@@ -525,6 +525,10 @@ Changes to the Mercury standard library:
- get_uint8/1, unsafe_get_uint8/1
- set_uint8/4, unsafe_set_uint8/4
+* The functor/4 predicate in the deconstruct module has been modified so
+ that for character and string data any control characters in the functor
+ representation are escaped.
+
Changes to the Mercury compiler:
* We have extended tail call optimization from self recursive calls only
diff --git a/library/rtti_implementation.m b/library/rtti_implementation.m
index 51320ff..5133d6d 100644
--- a/library/rtti_implementation.m
+++ b/library/rtti_implementation.m
@@ -145,6 +145,7 @@
:- import_module maybe.
:- import_module require.
:- import_module string.
+:- import_module term_io.
:- import_module type_desc.
%---------------------------------------------------------------------------%
@@ -2846,7 +2847,7 @@ deconstruct_2(Term, TypeInfo, TypeCtorInfo, TypeCtorRep, NonCanon,
;
TypeCtorRep = tcr_string,
det_dynamic_cast(Term, String),
- Functor = string.append_list(["\"", String, "\""]),
+ Functor = term_io.quoted_string(String),
Ordinal = -1,
Arity = 0,
Arguments = []
diff --git a/library/term_io.m b/library/term_io.m
index 6c78246..c8ab618 100644
--- a/library/term_io.m
+++ b/library/term_io.m
@@ -728,8 +728,9 @@ write_escaped_char(Char, !IO) :-
write_escaped_char(Stream, Char, !State) :-
% Note: the code of add_escaped_char and write_escaped_char
% should be kept in sync. The code of both is similar to code in
- % compiler/parse_tree_out_pragma.m; any changes here may require
- % similar changes there.
+ % compiler/parse_tree_out_pragma.m and MR_escape_string_quote
+ % in runtime/mercury_string.c; any changes here may require similar
+ % changes in those spots.
( if mercury_escape_special_char(Char, QuoteChar) then
stream.put(Stream, ('\\'), !State),
stream.put(Stream, QuoteChar, !State)
diff --git a/runtime/mercury_ml_expand_body.h b/runtime/mercury_ml_expand_body.h
index 8ec6089..c998f44 100644
--- a/runtime/mercury_ml_expand_body.h
+++ b/runtime/mercury_ml_expand_body.h
@@ -895,10 +895,7 @@ EXPAND_FUNCTION_NAME(MR_TypeInfo type_info, MR_Word *data_word_ptr,
default:
// Print remaining control characters using octal
// escapes.
- if (
- (0x00 <= data_word && data_word <= 0x1f) ||
- (0x7f <= data_word && data_word <= 0x9f)
- ) {
+ if (MR_is_control(data_word)) {
sprintf(buf,
"\'\\%03" MR_INTEGER_LENGTH_MODIFIER "o\\\'",
data_word);
@@ -952,14 +949,17 @@ EXPAND_FUNCTION_NAME(MR_TypeInfo type_info, MR_Word *data_word_ptr,
case MR_TYPECTOR_REP_STRING:
#ifdef EXPAND_FUNCTOR_FIELD
{
- // XXX Should escape characters correctly.
MR_Word data_word;
char *str;
-
+
data_word = *data_word_ptr;
- MR_make_aligned_string_copy_saved_hp_quote(str,
- (MR_String) data_word, NULL);
- expand_info->EXPAND_FUNCTOR_FIELD = str;
+ if (MR_escape_string_quote(&str, (MR_ConstString)data_word)) {
+ expand_info->EXPAND_FUNCTOR_FIELD = str;
+ } else {
+ // XXX should throw an exception.
+ MR_fatal_error(MR_STRINGIFY(EXPAND_FUNCTION_NAME)
+ ": invalid string encoding");
+ }
}
#endif // EXPAND_FUNCTOR_FIELD
diff --git a/runtime/mercury_string.c b/runtime/mercury_string.c
index 4c11f3f..a061509 100644
--- a/runtime/mercury_string.c
+++ b/runtime/mercury_string.c
@@ -91,6 +91,129 @@ MR_make_string(MR_AllocSiteInfoPtr alloc_id, const char *fmt, ...)
return result;
}
+// The code for this function should be kept in sync with that of the
+// quote_string predicates in library/term_io.m.
+MR_bool
+MR_escape_string_quote(MR_String *ptr, const char * string)
+{
+ MR_Integer pos = 0;
+ size_t num_code_units = 0;
+ MR_Char ch;
+ MR_bool must_escape = MR_FALSE;
+
+ // Check if we need to add character escapes to the string.
+ //
+ while ((ch = MR_utf8_get_next((MR_String)string, &pos)) > 0) {
+ switch (ch) {
+ case '\a':
+ case '\b':
+ case '\f':
+ case '\n':
+ case '\t':
+ case '\r':
+ case '\v':
+ case '\"':
+ case '\\':
+ num_code_units += 2;
+ must_escape = MR_TRUE;
+ break;
+ default:
+ if (MR_is_control(ch)) {
+ // All control characters that do not have a specific
+ // backslash escape are octal escaped.
+ // This takes five code units.
+ num_code_units += 5;
+ must_escape = MR_TRUE;
+ } else {
+ num_code_units += MR_utf8_width(ch);
+ }
+ }
+ }
+
+ // Check that there the string's encoding was valid.
+ //
+ if (ch < 0) {
+ *ptr = NULL;
+ return MR_FALSE;
+ }
+
+ if (must_escape) {
+
+ char *dst;
+
+ MR_allocate_aligned_string_saved_hp(*ptr,
+ num_code_units + 2 /* quotes */ + 1 /* \0 */,
+ NULL);
+
+ dst = *ptr;
+ dst[0] = '\"';
+ dst++;
+ pos = 0;
+ while ((ch = MR_utf8_get_next((MR_String)string, &pos)) > 0) {
+ switch (ch) {
+ case '\a':
+ dst[0] = '\\';
+ dst[1] = 'a';
+ dst += 2;
+ break;
+ case '\b':
+ dst[0] = '\\';
+ dst[1] = 'b';
+ dst += 2;
+ break;
+ case '\f':
+ dst[0] = '\\';
+ dst[1] = 'f';
+ dst += 2;
+ break;
+ case '\n':
+ dst[0] = '\\';
+ dst[1] = 'n';
+ dst += 2;
+ break;
+ case '\t':
+ dst[0] = '\\';
+ dst[1] = 't';
+ dst += 2;
+ break;
+ case '\r':
+ dst[0] = '\\';
+ dst[1] = 'r';
+ dst += 2;
+ break;
+ case '\v':
+ dst[0] = '\\';
+ dst[1] = 'b';
+ dst += 2;
+ break;
+ case '\"':
+ dst[0] = '\\';
+ dst[1] = '\"';
+ dst += 2;
+ break;
+ case '\\':
+ dst[0] = '\\';
+ dst[1] = '\\';
+ dst += 2;
+ break;
+ default:
+ if (MR_is_control(ch)) {
+ sprintf(dst, "\\%03" MR_INTEGER_LENGTH_MODIFIER "o\\",
+ (MR_Integer)ch);
+ dst += 5;
+ } else {
+ dst += MR_utf8_encode(dst, ch);
+ }
+ }
+ }
+ dst[0] = '\"';
+ dst[1] = '\0';
+ } else {
+ MR_make_aligned_string_copy_saved_hp_quote(*ptr, string, NULL);
+ }
+ return MR_TRUE;
+}
+
// Note that MR_hash_string{,2,3,4,5,6} are actually defined as macros in
// mercury_string.h, if we are using GNU C.
// We define them here whether or not we are using gcc, so that users
diff --git a/runtime/mercury_string.h b/runtime/mercury_string.h
index 7d1bd99..4d65c39 100644
--- a/runtime/mercury_string.h
+++ b/runtime/mercury_string.h
@@ -383,6 +383,15 @@ MR_Integer MR_hash_string6(MR_ConstString);
MR_String MR_make_string(MR_AllocSiteInfoPtr alloc_id, const char *fmt, ...);
+// Given a Mercury string `string', make a copy that inserts any required
+// character escapes and places double quote marks at the start and end of
+// the copy. On success, returns MR_TRUE and sets `ptr' to point to the copy
+// of the string. Returns MR_FALSE if `string' is not a valid UTF-8 encoded
+// string.
+//
+extern MR_bool MR_escape_string_quote(MR_String *ptr,
+ const char * string);
+
// True if c is an ASCII code point, i.e. U+0000..U+007f.
#define MR_is_ascii(c) ((unsigned)(c) <= 0x7f)
@@ -391,6 +400,12 @@ MR_String MR_make_string(MR_AllocSiteInfoPtr alloc_id, const char *fmt, ...);
#define MR_is_surrogate(c) (((unsigned)(c) & 0xF800) == 0xD800)
+// True if c is a Unicode control code point, i.e. U+0000..U+001f,
+// U+007f..U+009f.
+
+#define MR_is_control(c) ((0x00 <= (unsigned)(c) && (unsigned)(c) <= 0x1f) || \
+ (0x7f <= (unsigned)(c) && (unsigned)(c) <= 0x9f))
+
// UTF-8 manipulation
#define MR_utf8_is_single_byte(c) (((unsigned)(c) & 0x80) == 0)
diff --git a/tests/hard_coded/deconstruct_arg.exp b/tests/hard_coded/deconstruct_arg.exp
index 7f4ef4f..49ed6e9 100644
--- a/tests/hard_coded/deconstruct_arg.exp
+++ b/tests/hard_coded/deconstruct_arg.exp
@@ -376,6 +376,104 @@ deconstruct deconstruct: functor 'Ω' arity 0
deconstruct limited deconstruct 3 of 'Ω'
functor 'Ω' arity 0 []
+deconstruct functor: ""/0
+deconstruct argument 0 of "" doesn't exist
+deconstruct argument 1 of "" doesn't exist
+deconstruct argument 2 of "" doesn't exist
+deconstruct argument 'moo' doesn't exist
+deconstruct argument 'mooo!' doesn't exist
+deconstruct argument 'packed1' doesn't exist
+deconstruct argument 'packed2' doesn't exist
+deconstruct argument 'packed3' doesn't exist
+deconstruct deconstruct: functor "" arity 0
+[]
+deconstruct limited deconstruct 3 of ""
+functor "" arity 0 []
+
+deconstruct functor: "azBZ09"/0
+deconstruct argument 0 of "azBZ09" doesn't exist
+deconstruct argument 1 of "azBZ09" doesn't exist
+deconstruct argument 2 of "azBZ09" doesn't exist
+deconstruct argument 'moo' doesn't exist
+deconstruct argument 'mooo!' doesn't exist
+deconstruct argument 'packed1' doesn't exist
+deconstruct argument 'packed2' doesn't exist
+deconstruct argument 'packed3' doesn't exist
+deconstruct deconstruct: functor "azBZ09" arity 0
+[]
+deconstruct limited deconstruct 3 of "azBZ09"
+functor "azBZ09" arity 0 []
+
+deconstruct functor: "α∀🜓"/0
+deconstruct argument 0 of "α∀🜓" doesn't exist
+deconstruct argument 1 of "α∀🜓" doesn't exist
+deconstruct argument 2 of "α∀🜓" doesn't exist
+deconstruct argument 'moo' doesn't exist
+deconstruct argument 'mooo!' doesn't exist
+deconstruct argument 'packed1' doesn't exist
+deconstruct argument 'packed2' doesn't exist
+deconstruct argument 'packed3' doesn't exist
+deconstruct deconstruct: functor "α∀🜓" arity 0
+[]
+deconstruct limited deconstruct 3 of "α∀🜓"
+functor "α∀🜓" arity 0 []
+
+deconstruct functor: "\a\b\f\n\t\r\b\"\\"/0
+deconstruct argument 0 of "\a\b\f\n\t\r\v\"\\" doesn't exist
+deconstruct argument 1 of "\a\b\f\n\t\r\v\"\\" doesn't exist
+deconstruct argument 2 of "\a\b\f\n\t\r\v\"\\" doesn't exist
+deconstruct argument 'moo' doesn't exist
+deconstruct argument 'mooo!' doesn't exist
+deconstruct argument 'packed1' doesn't exist
+deconstruct argument 'packed2' doesn't exist
+deconstruct argument 'packed3' doesn't exist
+deconstruct deconstruct: functor "\a\b\f\n\t\r\b\"\\" arity 0
+[]
+deconstruct limited deconstruct 3 of "\a\b\f\n\t\r\v\"\\"
+functor "\a\b\f\n\t\r\b\"\\" arity 0 []
+
+deconstruct functor: "\001\\a\037\AZ[`az~\177\"/0
+deconstruct argument 0 of "\001\\a\037\AZ[`az~\177\" doesn't exist
+deconstruct argument 1 of "\001\\a\037\AZ[`az~\177\" doesn't exist
+deconstruct argument 2 of "\001\\a\037\AZ[`az~\177\" doesn't exist
+deconstruct argument 'moo' doesn't exist
+deconstruct argument 'mooo!' doesn't exist
+deconstruct argument 'packed1' doesn't exist
+deconstruct argument 'packed2' doesn't exist
+deconstruct argument 'packed3' doesn't exist
+deconstruct deconstruct: functor "\001\\a\037\AZ[`az~\177\" arity 0
+[]
+deconstruct limited deconstruct 3 of "\001\\a\037\AZ[`az~\177\"
+functor "\001\\a\037\AZ[`az~\177\" arity 0 []
+
+deconstruct functor: "\200\\a\237\ Àÿ"/0
+deconstruct argument 0 of "\200\\a\237\ Àÿ" doesn't exist
+deconstruct argument 1 of "\200\\a\237\ Àÿ" doesn't exist
+deconstruct argument 2 of "\200\\a\237\ Àÿ" doesn't exist
+deconstruct argument 'moo' doesn't exist
+deconstruct argument 'mooo!' doesn't exist
+deconstruct argument 'packed1' doesn't exist
+deconstruct argument 'packed2' doesn't exist
+deconstruct argument 'packed3' doesn't exist
+deconstruct deconstruct: functor "\200\\a\237\ Àÿ" arity 0
+[]
+deconstruct limited deconstruct 3 of "\200\\a\237\ Àÿ"
+functor "\200\\a\237\ Àÿ" arity 0 []
+
+deconstruct functor: "α\nβ\tγ,a\nα\001\α\001\a\001\α"/0
+deconstruct argument 0 of "α\nβ\tγ,a\nα\001\α\001\a\001\α" doesn't exist
+deconstruct argument 1 of "α\nβ\tγ,a\nα\001\α\001\a\001\α" doesn't exist
+deconstruct argument 2 of "α\nβ\tγ,a\nα\001\α\001\a\001\α" doesn't exist
+deconstruct argument 'moo' doesn't exist
+deconstruct argument 'mooo!' doesn't exist
+deconstruct argument 'packed1' doesn't exist
+deconstruct argument 'packed2' doesn't exist
+deconstruct argument 'packed3' doesn't exist
+deconstruct deconstruct: functor "α\nβ\tγ,a\nα\001\α\001\a\001\α" arity 0
+[]
+deconstruct limited deconstruct 3 of "α\nβ\tγ,a\nα\001\α\001\a\001\α"
+functor "α\nβ\tγ,a\nα\001\α\001\a\001\α" arity 0 []
+
deconstruct functor: 0.12345678901234566/0
deconstruct argument 0 of 0.12345678901234566 doesn't exist
deconstruct argument 1 of 0.12345678901234566 doesn't exist
@@ -600,7 +698,7 @@ deconstruct deconstruct: functor newline arity 0
deconstruct limited deconstruct 3 of '<<predicate>>'
functor newline arity 0 []
-deconstruct functor: lambda_deconstruct_arg_m_182/1
+deconstruct functor: lambda_deconstruct_arg_m_193/1
deconstruct argument 0 of '<<predicate>>' is [1, 2]
deconstruct argument 1 of '<<predicate>>' doesn't exist
deconstruct argument 2 of '<<predicate>>' doesn't exist
@@ -609,10 +707,10 @@ deconstruct argument 'mooo!' doesn't exist
deconstruct argument 'packed1' doesn't exist
deconstruct argument 'packed2' doesn't exist
deconstruct argument 'packed3' doesn't exist
-deconstruct deconstruct: functor lambda_deconstruct_arg_m_182 arity 1
+deconstruct deconstruct: functor lambda_deconstruct_arg_m_193 arity 1
[[1, 2]]
deconstruct limited deconstruct 3 of '<<predicate>>'
-functor lambda_deconstruct_arg_m_182 arity 1 [[1, 2]]
+functor lambda_deconstruct_arg_m_193 arity 1 [[1, 2]]
deconstruct functor: p/3
deconstruct argument 0 of '<<predicate>>' is 1
diff --git a/tests/hard_coded/deconstruct_arg.exp2 b/tests/hard_coded/deconstruct_arg.exp2
index bc508fa..eb16866 100644
--- a/tests/hard_coded/deconstruct_arg.exp2
+++ b/tests/hard_coded/deconstruct_arg.exp2
@@ -376,6 +376,104 @@ deconstruct deconstruct: functor 'Ω' arity 0
deconstruct limited deconstruct 3 of 'Ω'
functor 'Ω' arity 0 []
+deconstruct functor: ""/0
+deconstruct argument 0 of "" doesn't exist
+deconstruct argument 1 of "" doesn't exist
+deconstruct argument 2 of "" doesn't exist
+deconstruct argument 'moo' doesn't exist
+deconstruct argument 'mooo!' doesn't exist
+deconstruct argument 'packed1' doesn't exist
+deconstruct argument 'packed2' doesn't exist
+deconstruct argument 'packed3' doesn't exist
+deconstruct deconstruct: functor "" arity 0
+[]
+deconstruct limited deconstruct 3 of ""
+functor "" arity 0 []
+
+deconstruct functor: "azBZ09"/0
+deconstruct argument 0 of "azBZ09" doesn't exist
+deconstruct argument 1 of "azBZ09" doesn't exist
+deconstruct argument 2 of "azBZ09" doesn't exist
+deconstruct argument 'moo' doesn't exist
+deconstruct argument 'mooo!' doesn't exist
+deconstruct argument 'packed1' doesn't exist
+deconstruct argument 'packed2' doesn't exist
+deconstruct argument 'packed3' doesn't exist
+deconstruct deconstruct: functor "azBZ09" arity 0
+[]
+deconstruct limited deconstruct 3 of "azBZ09"
+functor "azBZ09" arity 0 []
+
+deconstruct functor: "α∀🜓"/0
+deconstruct argument 0 of "α∀🜓" doesn't exist
+deconstruct argument 1 of "α∀🜓" doesn't exist
+deconstruct argument 2 of "α∀🜓" doesn't exist
+deconstruct argument 'moo' doesn't exist
+deconstruct argument 'mooo!' doesn't exist
+deconstruct argument 'packed1' doesn't exist
+deconstruct argument 'packed2' doesn't exist
+deconstruct argument 'packed3' doesn't exist
+deconstruct deconstruct: functor "α∀🜓" arity 0
+[]
+deconstruct limited deconstruct 3 of "α∀🜓"
+functor "α∀🜓" arity 0 []
+
+deconstruct functor: "\a\b\f\n\t\r\b\"\\"/0
+deconstruct argument 0 of "\a\b\f\n\t\r\v\"\\" doesn't exist
+deconstruct argument 1 of "\a\b\f\n\t\r\v\"\\" doesn't exist
+deconstruct argument 2 of "\a\b\f\n\t\r\v\"\\" doesn't exist
+deconstruct argument 'moo' doesn't exist
+deconstruct argument 'mooo!' doesn't exist
+deconstruct argument 'packed1' doesn't exist
+deconstruct argument 'packed2' doesn't exist
+deconstruct argument 'packed3' doesn't exist
+deconstruct deconstruct: functor "\a\b\f\n\t\r\b\"\\" arity 0
+[]
+deconstruct limited deconstruct 3 of "\a\b\f\n\t\r\v\"\\"
+functor "\a\b\f\n\t\r\b\"\\" arity 0 []
+
+deconstruct functor: "\001\\a\037\AZ[`az~\177\"/0
+deconstruct argument 0 of "\001\\a\037\AZ[`az~\177\" doesn't exist
+deconstruct argument 1 of "\001\\a\037\AZ[`az~\177\" doesn't exist
+deconstruct argument 2 of "\001\\a\037\AZ[`az~\177\" doesn't exist
+deconstruct argument 'moo' doesn't exist
+deconstruct argument 'mooo!' doesn't exist
+deconstruct argument 'packed1' doesn't exist
+deconstruct argument 'packed2' doesn't exist
+deconstruct argument 'packed3' doesn't exist
+deconstruct deconstruct: functor "\001\\a\037\AZ[`az~\177\" arity 0
+[]
+deconstruct limited deconstruct 3 of "\001\\a\037\AZ[`az~\177\"
+functor "\001\\a\037\AZ[`az~\177\" arity 0 []
+
+deconstruct functor: "\200\\a\237\ Àÿ"/0
+deconstruct argument 0 of "\200\\a\237\ Àÿ" doesn't exist
+deconstruct argument 1 of "\200\\a\237\ Àÿ" doesn't exist
+deconstruct argument 2 of "\200\\a\237\ Àÿ" doesn't exist
+deconstruct argument 'moo' doesn't exist
+deconstruct argument 'mooo!' doesn't exist
+deconstruct argument 'packed1' doesn't exist
+deconstruct argument 'packed2' doesn't exist
+deconstruct argument 'packed3' doesn't exist
+deconstruct deconstruct: functor "\200\\a\237\ Àÿ" arity 0
+[]
+deconstruct limited deconstruct 3 of "\200\\a\237\ Àÿ"
+functor "\200\\a\237\ Àÿ" arity 0 []
+
+deconstruct functor: "α\nβ\tγ,a\nα\001\α\001\a\001\α"/0
+deconstruct argument 0 of "α\nβ\tγ,a\nα\001\α\001\a\001\α" doesn't exist
+deconstruct argument 1 of "α\nβ\tγ,a\nα\001\α\001\a\001\α" doesn't exist
+deconstruct argument 2 of "α\nβ\tγ,a\nα\001\α\001\a\001\α" doesn't exist
+deconstruct argument 'moo' doesn't exist
+deconstruct argument 'mooo!' doesn't exist
+deconstruct argument 'packed1' doesn't exist
+deconstruct argument 'packed2' doesn't exist
+deconstruct argument 'packed3' doesn't exist
+deconstruct deconstruct: functor "α\nβ\tγ,a\nα\001\α\001\a\001\α" arity 0
+[]
+deconstruct limited deconstruct 3 of "α\nβ\tγ,a\nα\001\α\001\a\001\α"
+functor "α\nβ\tγ,a\nα\001\α\001\a\001\α" arity 0 []
+
deconstruct functor: 0.12345678901234566/0
deconstruct argument 0 of 0.12345678901234566 doesn't exist
deconstruct argument 1 of 0.12345678901234566 doesn't exist
diff --git a/tests/hard_coded/deconstruct_arg.m b/tests/hard_coded/deconstruct_arg.m
index 4e7f89f..a530ac6 100644
--- a/tests/hard_coded/deconstruct_arg.m
+++ b/tests/hard_coded/deconstruct_arg.m
@@ -146,6 +146,17 @@ main(!IO) :-
% UTF-8 encoding.
test_all('Ω', !IO),
+ % test strings (that do not require escapes)
+ test_all("", !IO),
+ test_all("azBZ09", !IO),
+ test_all("\u03b1\u2200\U0001f713", !IO),
+
+ % test strings (that do require escapes)
+ test_all("\a\b\f\n\t\r\v\"\\", !IO),
+ test_all("\x1\\a\x1f\AZ[`az~\x7f\", !IO),
+ test_all("\x80\\a\x9f\\xa0\\xc0\\xff\", !IO),
+ test_all("α\nβ\tγ,a\nα\001\α\001\a\001\α", !IO),
+
% test a float which requires 17 digits of precision
test_all(0.12345678901234566, !IO),
@@ -157,7 +168,7 @@ main(!IO) :-
% test integers
test_all(4, !IO),
- % test unigned integers
+ % test unsigned integers
test_all(561u, !IO),
% test fixed size integers.
More information about the reviews
mailing list