diff --git a/.trinity/seals/t27o_T27oRust.json b/.trinity/seals/t27o_T27oRust.json index 4679f8cc73..ce849a261a 100644 --- a/.trinity/seals/t27o_T27oRust.json +++ b/.trinity/seals/t27o_T27oRust.json @@ -1,28 +1,28 @@ { - "built_by": "t27c-bootstrap@0.5.2+9448ab4be", + "built_by": "t27c-bootstrap@0.5.2+9b556b7c3", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:87b54ee8d3e1284ef86d8398385523d7fe54a4aace9792ee4ecd71f9328c1a9a", - "gen_hash_rust": "sha256:98414503241f77f40bc27e00d857317eb73ade5523e2401e68b4e6289ba8c924", - "gen_hash_verilog": "sha256:d090710a678d290c895470e92dd6d8d7a28036f1a81ecf66431e5e12bfb4ca1d", - "gen_hash_zig": "sha256:7a1cfb84e9cf8fbb351dda5ac96174c3f19b318b105923f6d6b0cf5ebda91812", + "gen_hash_c": "sha256:29bb116b657fcdc121805ff200cfb290c063ebea7dec2bf12248ce0da72243c2", + "gen_hash_rust": "sha256:76e6a5a1909f62ea0e848bf97396dbb19c7429a01473f7ff4fd363f70e9de621", + "gen_hash_verilog": "sha256:cb1b3ba68e8144c8d99aa30b1131e67b1360a2700aebb6fb7d2da8f75fb0e6e8", + "gen_hash_zig": "sha256:2c4d556d3f5d79f4ef40ffaba73ee640394ac05231c67179f6911475ca11c88f", "module": "T27oRust", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T07:05:26Z", + "sealed_at": "2026-10-18T18:02:34Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:8c8390a800ed8c1bb291e59d6c2013e7d27fcf1d087b81da1495bbcaf85499c9", + "spec_hash": "sha256:dea290dff726c285eb490368c99ef12eb289202c5ffdf5f17b54266072c56ea1", "spec_path": "specs/tools/t27o/rust_convert.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 66, - "total": 66, + "passed": 81, + "total": 81, "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/gen/rust/tools/t27o/rust_convert.rs b/gen/rust/tools/t27o/rust_convert.rs index 99102edd9f..9b01d8a036 100644 --- a/gen/rust/tools/t27o/rust_convert.rs +++ b/gen/rust/tools/t27o/rust_convert.rs @@ -1,6 +1,6 @@ // Generated from .t27 spec // DO NOT EDIT — generated by t27c -// NOT LOWERED BY THIS BACKEND: 65 test(s), 0 invariant(s), 0 function(s) with no body. +// NOT LOWERED BY THIS BACKEND: 81 test(s), 0 invariant(s), 0 function(s) with no body. // This backend emits declarations only. The spec's checks live in // the Zig and Verilog outputs; do not read this file as verified. @@ -276,6 +276,8 @@ pub const R_STRUSE: usize = 30; pub const R_SPLICE: usize = 31; +pub const R_EMPTY: usize = 32; + pub const B_TAB: u8 = 9; pub const B_NL: u8 = 10; @@ -1189,6 +1191,42 @@ pub fn out_has(w: usize, out: &[u8], from: usize, c: u8) -> bool { return false; } +pub fn out_op_space(w: usize, out: &[u8], from: usize) -> bool { + let mut depth: usize = 0; + let mut quote: u8 = 0; + let mut i: usize = from; + while ((i < w) && (i < out.len())) { + let c: u8 = out[(i) as usize]; + if (quote != 0) { + if (c == B_BACKSLASH) { + i = (i + 1); + } else { + if (c == quote) { + quote = 0; + } + } + } else { + if ((c == B_QUOTE) || (c == B_APOS)) { + quote = c; + } else { + if is_open(c) { + depth = (depth + 1); + } else { + if (is_close(c) && (depth > 0)) { + depth = (depth - 1); + } else { + if ((c == B_SPACE) && (depth == 0)) { + return true; + } + } + } + } + } + i = (i + 1); + } + return false; +} + pub fn out_is(w: usize, out: &[u8], from: usize, s: &[u8]) -> bool { if (((w < from) || ((w - from) != s.len())) || (w > out.len())) { return false; @@ -1607,7 +1645,7 @@ pub fn expr_bp(src: &[u8], st: &mut [usize], out: &mut [u8], min: usize, ns: boo ty(src, st, out); lvl = L_AS; } else { - if ((word_is(src, a, b, b"==") || word_is(src, a, b, b"!=")) && ((out_at(st[(W) as usize], out, start) == B_QUOTE) || str_tok(src, st[(P) as usize]))) { + if ((word_is(src, a, b, b"==") || word_is(src, a, b, b"!=")) && (((out_at(st[(W) as usize], out, start) == B_QUOTE) || str_tok(src, st[(P) as usize])) || raw_str_at(src, st[(P) as usize]))) { if word_is(src, a, b, b"!=") { insert(st, out, start, b"!type_text::same("); } else { @@ -1726,12 +1764,188 @@ pub fn call_args(src: &[u8], st: &mut [usize], out: &mut [u8]) -> () { } pub fn args_list(src: &[u8], st: &mut [usize], out: &mut [u8]) -> () { - expr(src, st, out, false); + let lp: usize = open_before(src, st[(P) as usize]); + let mut k: usize = 0; + arg_expr(src, st, out, lp, k); while (live(src, st[(P) as usize], st[(ERR) as usize]) && take(src, st, b",")) { if !(at(src, st[(P) as usize], b")")) { puts(st, out, b", "); - expr(src, st, out, false); + k = (k + 1); + arg_expr(src, st, out, lp, k); + } + } +} + +pub fn open_before(src: &[u8], p: usize) -> usize { + let mut i: usize = p; + while ((i > 0) && is_space(src[((i - 1)) as usize])) { + i = (i - 1); + } + if ((i > 0) && (src[((i - 1)) as usize] == B_LPAREN)) { + return (i - 1); + } + return src.len(); +} + +pub fn empty_ref(src: &[u8], p: usize) -> bool { + if !(tok_is(src, p, b"&")) { + return false; + } + let b: usize = skip_ws(src, tok_end(src, p)); + if ((b >= src.len()) || (src[(b) as usize] != B_LBRACKET)) { + return false; + } + let c: usize = skip_ws(src, tok_end(src, b)); + if ((c >= src.len()) || (src[(c) as usize] != B_RBRACKET)) { + return false; + } + let d: usize = skip_ws(src, tok_end(src, c)); + return (tok_is(src, d, b",") || tok_is(src, d, b")")); +} + +pub fn param_at(src: &[u8], lp: usize, idx: usize) -> usize { + let close: usize = (block_end(src, lp) - 1); + let mut q: usize = skip_ws(src, (lp + 1)); + let mut i: usize = 0; + while (q < close) { + if tok_is(src, q, b"<") { + return src.len(); + } + q = skip_ws(src, tok_end(src, q)); + } + q = skip_ws(src, (lp + 1)); + while (q < close) { + if (i == idx) { + return q; + } + let c: usize = find_at(src, q, B_COMMA); + if (c >= close) { + return src.len(); + } + q = skip_ws(src, (c + 1)); + i = (i + 1); + } + return src.len(); +} + +pub fn decl_slice_elem(src: &[u8], lp: usize, method: bool, k: usize) -> usize { + let mut idx: usize = k; + if method { + idx = (k + 1); + let mut s: usize = param_at(src, lp, 0); + if (s >= src.len()) { + return src.len(); + } + if tok_is(src, s, b"&") { + s = skip_ws(src, tok_end(src, s)); + } + if ((s < src.len()) && (src[(s) as usize] == B_APOS)) { + s = skip_ws(src, tok_end(src, s)); + } + if tok_is(src, s, b"mut") { + s = skip_ws(src, tok_end(src, s)); + } + if !(tok_is(src, s, b"self")) { + return src.len(); + } + } + let pa: usize = param_at(src, lp, idx); + if (pa >= src.len()) { + return src.len(); + } + let colon: usize = skip_ws(src, tok_end(src, pa)); + if !(tok_is(src, colon, b":")) { + return src.len(); + } + let mut t: usize = skip_ws(src, tok_end(src, colon)); + if !(tok_is(src, t, b"&")) { + return src.len(); + } + t = skip_ws(src, tok_end(src, t)); + if ((t < src.len()) && (src[(t) as usize] == B_APOS)) { + t = skip_ws(src, tok_end(src, t)); + } + if !(tok_is(src, t, b"[")) { + return src.len(); + } + let el: usize = skip_ws(src, tok_end(src, t)); + if ((el >= src.len()) || !(is_alpha(src[(el) as usize]))) { + return src.len(); + } + if !(tok_is(src, skip_ws(src, tok_end(src, el)), b"]")) { + return src.len(); + } + return el; +} + +pub fn slice_param_elem(src: &[u8], na: usize, nb: usize, method: bool, k: usize) -> usize { + let mut found: usize = src.len(); + let mut q: usize = skip_ws(src, 0); + while (q < src.len()) { + let e: usize = tok_end(src, q); + if word_is(src, q, e, b"fn") { + let n: usize = skip_ws(src, e); + let ne: usize = tok_end(src, n); + let lp: usize = skip_ws(src, ne); + if same_text(src, n, ne, na, nb) { + let mut elem: usize = src.len(); + if tok_is(src, lp, b"(") { + elem = decl_slice_elem(src, lp, method, k); + } + if (elem >= src.len()) { + return src.len(); + } + if ((found < src.len()) && !(same_text(src, found, tok_end(src, found), elem, tok_end(src, elem)))) { + return src.len(); + } + found = elem; + } + } + q = skip_ws(src, e); + } + return found; +} + +pub fn empty_arg(src: &[u8], st: &mut [usize], out: &mut [u8], lp: usize, k: usize) -> () { + let a: usize = st[(P) as usize]; + let b: usize = skip_ws(src, tok_end(src, a)); + let c: usize = skip_ws(src, tok_end(src, b)); + let d: usize = tok_end(src, c); + let mut elem: usize = src.len(); + if (lp < src.len()) { + let mut ne: usize = lp; + while ((ne > 0) && is_space(src[((ne - 1)) as usize])) { + ne = (ne - 1); + } + let mut na: usize = ne; + while ((na > 0) && is_word(src[((na - 1)) as usize])) { + na = (na - 1); } + if (na < ne) { + let mut m: usize = na; + while ((m > 0) && is_space(src[((m - 1)) as usize])) { + m = (m - 1); + } + let method: bool = (((m > 0) && (src[((m - 1)) as usize] == B_DOT)) && !(((m > 1) && (src[((m - 2)) as usize] == B_DOT)))); + elem = slice_param_elem(src, na, ne, method, k); + } + } + if (elem >= src.len()) { + refuse(st, R_EMPTY, a, a, d); + return (); + } + puts(st, out, b"[0]"); + st[(P) as usize] = elem; + ty(src, st, out); + puts(st, out, b"{}"); + st[(P) as usize] = skip_ws(src, d); +} + +pub fn arg_expr(src: &[u8], st: &mut [usize], out: &mut [u8], lp: usize, k: usize) -> () { + if empty_ref(src, st[(P) as usize]) { + empty_arg(src, st, out, lp, k); + } else { + expr(src, st, out, false); } } @@ -2015,7 +2229,7 @@ pub fn name_class(src: &[u8], st: &mut [usize], a: usize, b: usize, field: bool) if ((init < src.len()) && is_digit(src[(init) as usize])) { return literal_class(src, st, init); } - if ((init < src.len()) && (src[(init) as usize] == B_QUOTE)) { + if ((init < src.len()) && ((src[(init) as usize] == B_QUOTE) || raw_str_at(src, init))) { return C_STR; } if ((init < src.len()) && is_alpha(src[(init) as usize])) { @@ -2161,7 +2375,7 @@ pub fn recv_class(src: &[u8], st: &mut [usize], ra: usize, rb: usize) -> u8 { let e: usize = tok_end(src, ra); let nx: usize = skip_ws(src, e); if (nx >= rb) { - if (src[(ra) as usize] == B_QUOTE) { + if ((src[(ra) as usize] == B_QUOTE) || raw_str_at(src, ra)) { return C_STR; } if is_digit(src[(ra) as usize]) { @@ -2506,7 +2720,7 @@ pub fn unroll_contains(src: &[u8], st: &mut [usize], out: &mut [u8], start: usiz if (n > 0) { puts(st, out, b" or "); } - let text: bool = (src[(q) as usize] == B_QUOTE); + let text: bool = ((src[(q) as usize] == B_QUOTE) || raw_str_at(src, q)); if text { puts(st, out, b"type_text::same("); st[(NEQ) as usize] = 1; @@ -2992,7 +3206,95 @@ pub fn string_escape(e: u8) -> bool { return ((((e == B_LOWER_N) || (e == B_LOWER_T)) || (e == B_BACKSLASH)) || (e == B_QUOTE)); } +pub fn is_cont(src: &[u8], i: usize, hi: usize) -> bool { + if (((i + 1) >= hi) || (src[(i) as usize] != B_BACKSLASH)) { + return false; + } + return ((src[((i + 1)) as usize] == B_NL) || (((src[((i + 1)) as usize] == B_CR) && ((i + 2) < hi)) && (src[((i + 2)) as usize] == B_NL))); +} + +pub fn cont_end(src: &[u8], i: usize, hi: usize) -> usize { + let mut j: usize = (i + 1); + while ((j < hi) && ((((src[(j) as usize] == B_NL) || (src[(j) as usize] == B_CR)) || (src[(j) as usize] == B_SPACE)) || (src[(j) as usize] == B_TAB))) { + j = (j + 1); + } + return j; +} + +pub fn raw_str_at(src: &[u8], q: usize) -> bool { + if ((q >= src.len()) || (src[(q) as usize] != B_LOWER_R)) { + return false; + } + let mut p: usize = (q + 1); + while ((p < src.len()) && (src[(p) as usize] == B_HASH)) { + p = (p + 1); + } + return ((p < src.len()) && (src[(p) as usize] == B_QUOTE)); +} + +pub fn put_raw_string(src: &[u8], st: &mut [usize], out: &mut [u8], a: usize, b: usize) -> () { + let mut h: usize = 0; + let mut s: usize = (a + 1); + while ((s < b) && (src[(s) as usize] == B_HASH)) { + h = (h + 1); + s = (s + 1); + } + let lo: usize = (s + 1); + if ((b < ((lo + 1) + h)) || (src[(((b - 1) - h)) as usize] != B_QUOTE)) { + refuse(st, R_LITERAL, a, a, b); + return (); + } + let hi: usize = ((b - 1) - h); + let mut i: usize = lo; + while (i < hi) { + let c: u8 = src[(i) as usize]; + if (((c == B_CR) && ((i + 1) < hi)) && (src[((i + 1)) as usize] == B_NL)) { + i = (i + 1); + } else { + if (((c != B_NL) && (c != B_TAB)) && ((c < B_SPACE) || (c >= B_DEL))) { + refuse(st, R_LITERAL, a, a, b); + return (); + } + } + i = (i + 1); + } + put(st, out, B_QUOTE); + i = lo; + while (i < hi) { + let c: u8 = src[(i) as usize]; + if (c == B_BACKSLASH) { + put(st, out, B_BACKSLASH); + put(st, out, B_BACKSLASH); + } else { + if (c == B_QUOTE) { + put(st, out, B_BACKSLASH); + put(st, out, B_QUOTE); + } else { + if (c == B_NL) { + put(st, out, B_BACKSLASH); + put(st, out, B_LOWER_N); + } else { + if (c == B_TAB) { + put(st, out, B_BACKSLASH); + put(st, out, B_LOWER_T); + } else { + if (c != B_CR) { + put(st, out, c); + } + } + } + } + } + i = (i + 1); + } + put(st, out, B_QUOTE); +} + pub fn put_string(src: &[u8], st: &mut [usize], out: &mut [u8], a: usize, b: usize) -> () { + if (src[(a) as usize] == B_LOWER_R) { + put_raw_string(src, st, out, a, b); + return (); + } let mut s: usize = a; if (src[(s) as usize] == B_LOWER_B) { s = (s + 1); @@ -3000,22 +3302,43 @@ pub fn put_string(src: &[u8], st: &mut [usize], out: &mut [u8], a: usize, b: usi let mut i: usize = (s + 1); while ((i + 1) < b) { let c: u8 = src[(i) as usize]; - if ((c < B_SPACE) || (c >= B_DEL)) { - refuse(st, R_LITERAL, a, a, b); - return (); - } - if (c == B_BACKSLASH) { - let e: u8 = src[((i + 1)) as usize]; - if !(string_escape(e)) { - refuse(st, R_LITERAL, a, i, (i + 2)); + if is_cont(src, i, (b - 1)) { + i = cont_end(src, i, (b - 1)); + } else { + if ((c < B_SPACE) || (c >= B_DEL)) { + refuse(st, R_LITERAL, a, a, b); return (); + } else { + if (c == B_BACKSLASH) { + let e: u8 = src[((i + 1)) as usize]; + if !(string_escape(e)) { + refuse(st, R_LITERAL, a, i, (i + 2)); + return (); + } + i = (i + 2); + } else { + i = (i + 1); + } } - i = (i + 2); + } + } + put(st, out, src[(s) as usize]); + i = (s + 1); + while ((i + 1) < b) { + if is_cont(src, i, (b - 1)) { + i = cont_end(src, i, (b - 1)); } else { - i = (i + 1); + if (src[(i) as usize] == B_BACKSLASH) { + put(st, out, src[(i) as usize]); + put(st, out, src[((i + 1)) as usize]); + i = (i + 2); + } else { + put(st, out, src[(i) as usize]); + i = (i + 1); + } } } - put_src(src, st, out, s, b); + put(st, out, src[((b - 1)) as usize]); } pub fn put_limit(src: &[u8], st: &mut [usize], out: &mut [u8], a: usize, b: usize, max: bool) -> bool { @@ -3266,10 +3589,11 @@ pub fn struct_lit(src: &[u8], st: &mut [usize], out: &mut [u8], a: usize, b: usi } pub fn array_lit(src: &[u8], st: &mut [usize], out: &mut [u8]) -> () { + let lb: usize = st[(P) as usize]; step(src, st); puts(st, out, b"["); if at(src, st[(P) as usize], b"]") { - refuse_here(src, st, R_VALUE); + refuse(st, R_EMPTY, lb, lb, tok_end(src, st[(P) as usize])); return (); } expr(src, st, out, false); @@ -3494,6 +3818,11 @@ pub fn primary(src: &[u8], st: &mut [usize], out: &mut [u8], ns: bool) -> () { step(src, st); return (); } + if raw_str_at(src, a) { + put_string(src, st, out, a, b); + step(src, st); + return (); + } if ((c == B_LOWER_R) && ((c2 == B_QUOTE) || (c2 == B_HASH))) { refuse(st, R_LITERAL, a, a, (a + 2)); return (); @@ -3990,8 +4319,13 @@ pub fn let_value(src: &[u8], st: &mut [usize], out: &mut [u8], mutable: bool, na put_digits(src, st, out, init, (ie - k)); st[(P) as usize] = skip_ws(src, ie); } else { - puts(st, out, b" = "); - expr(src, st, out, false); + if ((((typed && tok_is(src, st[(LETTY) as usize], b"[")) && tok_is(src, init, b"[")) && peek_is(src, init, b"]")) && tok_is(src, skip_ws(src, tok_end(src, skip_ws(src, tok_end(src, init)))), b";")) { + puts(st, out, b" = []"); + st[(P) as usize] = skip_ws(src, tok_end(src, skip_ws(src, tok_end(src, init)))); + } else { + puts(st, out, b" = "); + expr(src, st, out, false); + } } want(src, st, b";"); puts(st, out, b";"); @@ -4344,7 +4678,7 @@ pub fn array_for(src: &[u8], st: &mut [usize], out: &mut [u8], fpos: usize, va: if (is_digit(head(src, t0)) && (k > 0)) { put_src(src, st, out, (te - k), te); } else { - if (src[(t0) as usize] == B_QUOTE) { + if ((src[(t0) as usize] == B_QUOTE) || raw_str_at(src, t0)) { puts(st, out, b"[]const u8"); } else { if (tok_is(src, t0, b"true") || tok_is(src, t0, b"false")) { @@ -4424,8 +4758,12 @@ pub fn for_stmt(src: &[u8], st: &mut [usize], out: &mut [u8]) -> () { } return (); } - if ((((dots >= lbrace) && (ib == ia)) && (ra < src.len())) && (src[(ra) as usize] == B_LBRACKET)) { - array_for(src, st, out, fpos, va, vb, ra); + let mut lit: usize = ra; + if tok_is(src, ra, b"&") { + lit = skip_ws(src, tok_end(src, ra)); + } + if ((((dots >= lbrace) && (ib == ia)) && (lit < src.len())) && (src[(lit) as usize] == B_LBRACKET)) { + array_for(src, st, out, fpos, va, vb, lit); return (); } if (dots >= lbrace) { @@ -4527,7 +4865,7 @@ pub fn pattern(src: &[u8], st: &mut [usize], out: &mut [u8], sa: usize, tmp: usi return (); } let c: u8 = src[(a) as usize]; - if str_tok(src, a) { + if (str_tok(src, a) || raw_str_at(src, a)) { puts(st, out, b"type_text::same("); put_scrut(src, st, out, sa, tmp, wrapit); puts(st, out, b", "); @@ -4808,6 +5146,146 @@ pub fn struct_value(src: &[u8], st: &mut [usize], e: usize) -> bool { return ((find_struct(src, r, re) < src.len()) || named(src, st, K_TUPLE, r, re)); } +pub fn result_arg(src: &[u8], p: usize) -> usize { + let c: usize = find_at(src, p, B_COMMA); + if (c >= src.len()) { + return src.len(); + } + let r: usize = skip_ws(src, (c + 1)); + if ((tok_is(src, r, b"Ok") || tok_is(src, r, b"Err")) && peek_is(src, r, b"(")) { + return r; + } + return src.len(); +} + +pub fn put_result_ref(src: &[u8], st: &mut [usize], out: &mut [u8], ea: usize, tmp: bool, spaced: bool, r: usize) -> () { + if tmp { + puts(st, out, b"t27o_r"); + put_num(st, out, r); + return (); + } + let save: usize = st[(P) as usize]; + let w: usize = st[(W) as usize]; + st[(P) as usize] = ea; + expr(src, st, out, false); + st[(P) as usize] = save; + if spaced { + wrap(st, out, w); + } +} + +pub fn result_assert(src: &[u8], st: &mut [usize], out: &mut [u8], r: usize, ne: bool) -> () { + let ea: usize = st[(P) as usize]; + let ok: bool = tok_is(src, r, b"Ok"); + let lp: usize = skip_ws(src, tok_end(src, r)); + let pv: usize = skip_ws(src, (lp + 1)); + let rp: usize = block_end(src, lp); + let unit: bool = unit_result_call(src, st, ea); + let nothing: bool = (tok_is(src, pv, b"(") && peek_is(src, pv, b")")); + if (ok && (unit != nothing)) { + refuse(st, R_TYPE, r, r, tok_end(src, r)); + return (); + } + if ((ok && !(unit)) && struct_value(src, st, pv)) { + refuse(st, R_STRUCTEQ, pv, pv, tok_end(src, pv)); + return (); + } + if !(ok) { + if unit { + puts(st, out, b"// t27o: Err payload dropped (Result<(), E> is a bool)"); + } else { + puts(st, out, b"// t27o: Err payload dropped (Result is ?T)"); + } + newline(st, out); + } + let w0: usize = st[(W) as usize]; + expr(src, st, out, false); + if bad(st[(ERR) as usize]) { + return (); + } + let calls: bool = out_has(st[(W) as usize], out, w0, B_LPAREN); + let spaced: bool = out_op_space(st[(W) as usize], out, w0); + want(src, st, b","); + if bad(st[(ERR) as usize]) { + return (); + } + if (!(ok) || unit) { + let fails: bool = (ok == ne); + if (unit || spaced) { + wrap(st, out, w0); + } + if (unit && fails) { + puts(st, out, b" == false"); + } else { + if unit { + puts(st, out, b" == true"); + } else { + if fails { + puts(st, out, b" == null"); + } else { + puts(st, out, b" != null"); + } + } + } + puts(st, out, b");"); + insert(st, out, w0, b"assert("); + st[(P) as usize] = skip_ws(src, rp); + return (); + } + let text: bool = ((str_tok(src, pv) || raw_str_at(src, pv)) || (recv_class(src, st, pv, (rp - 1)) == C_STR)); + st[(W) as usize] = w0; + if calls { + puts(st, out, b"const "); + put_result_ref(src, st, out, ea, true, spaced, r); + puts(st, out, b" = "); + let back: usize = st[(P) as usize]; + st[(P) as usize] = ea; + expr(src, st, out, false); + st[(P) as usize] = back; + puts(st, out, b";"); + newline(st, out); + } + if !(ne) { + puts(st, out, b"assert("); + put_result_ref(src, st, out, ea, calls, spaced, r); + puts(st, out, b" != null);"); + newline(st, out); + } + puts(st, out, b"assert("); + if ne { + put_result_ref(src, st, out, ea, calls, spaced, r); + puts(st, out, b" == null or "); + } + if text { + if ne { + puts(st, out, b"!type_text::same("); + } else { + puts(st, out, b"type_text::same("); + } + put_result_ref(src, st, out, ea, calls, spaced, r); + puts(st, out, b".?, "); + st[(P) as usize] = pv; + expr(src, st, out, false); + puts(st, out, b")"); + st[(NEQ) as usize] = 1; + } else { + put_result_ref(src, st, out, ea, calls, spaced, r); + if ne { + puts(st, out, b".? != "); + } else { + puts(st, out, b".? == "); + } + st[(P) as usize] = pv; + let v0: usize = st[(W) as usize]; + let vl: usize = expr(src, st, out, false); + if !(keep_bare(vl, L_CMP, false)) { + wrap(st, out, v0); + } + } + puts(st, out, b");"); + st[(P) as usize] = skip_ws(src, rp); +} + pub fn macro_stmt(src: &[u8], st: &mut [usize], out: &mut [u8], a: usize, b: usize) -> () { let plain: bool = (word_is(src, a, b, b"assert") || word_is(src, a, b, b"debug_assert")); let eq: bool = (word_is(src, a, b, b"assert_eq") || word_is(src, a, b, b"debug_assert_eq")); @@ -4819,6 +5297,21 @@ pub fn macro_stmt(src: &[u8], st: &mut [usize], out: &mut [u8], a: usize, b: usi step(src, st); step(src, st); want(src, st, b"("); + if ((eq || ne) && (result_arg(src, st[(P) as usize]) < src.len())) { + result_assert(src, st, out, result_arg(src, st[(P) as usize]), ne); + if bad(st[(ERR) as usize]) { + return (); + } + if take(src, st, b",") { + let close: usize = find_at(src, st[(P) as usize], B_RPAREN); + if (close < src.len()) { + st[(P) as usize] = close; + } + } + want(src, st, b")"); + take(src, st, b";"); + return (); + } puts(st, out, b"assert("); if plain { expr(src, st, out, false); @@ -4832,7 +5325,7 @@ pub fn macro_stmt(src: &[u8], st: &mut [usize], out: &mut [u8], a: usize, b: usi refuse(st, R_STRUCTEQ, la, la, tok_end(src, la)); return (); } - if (lstr || str_tok(src, st[(P) as usize])) { + if ((lstr || str_tok(src, st[(P) as usize])) || raw_str_at(src, st[(P) as usize])) { if ne { insert(st, out, w0, b"!type_text::same("); } else { @@ -5008,10 +5501,14 @@ pub fn put_reason(src: &[u8], st: &mut [usize], out: &mut [u8]) -> () { if (c == R_STRUSE) { puts(st, out, b"an owned String outside the out-buffer form"); } else { - if (c == R_SPLICE) { - puts(st, out, b"a used module's own use t27c does not splice (its type_text:: names stay unresolved, the gap specs/compiler/int_type_names.t27 records)"); + if (c == R_EMPTY) { + puts(st, out, b"empty slice with no known element type"); } else { - puts(st, out, b"syntax"); + if (c == R_SPLICE) { + puts(st, out, b"a used module's own use t27c does not splice (its type_text:: names stay unresolved, the gap specs/compiler/int_type_names.t27 records)"); + } else { + puts(st, out, b"syntax"); + } } } } @@ -6723,12 +7220,16 @@ pub fn str_lit_len(src: &[u8], a: usize) -> usize { let mut i: usize = (a + 1); let mut n: usize = 0; while ((i + 1) < q) { - if (src[(i) as usize] == B_BACKSLASH) { - i = (i + 2); + if is_cont(src, i, (q - 1)) { + i = cont_end(src, i, (q - 1)); } else { - i = (i + 1); + if (src[(i) as usize] == B_BACKSLASH) { + i = (i + 2); + } else { + i = (i + 1); + } + n = (n + 1); } - n = (n + 1); } return n; } @@ -7089,7 +7590,7 @@ pub fn put_out_text(src: &[u8], st: &mut [usize], out: &mut [u8], a: usize, b: u return (); } puts(st, out, b"t27o_w = rust_std::append_text(out, t27o_w, "); - if str_tok(src, a) { + if (str_tok(src, a) || raw_str_at(src, a)) { put_string(src, st, out, a, b); } else { put_ident(src, st, out, a, b); @@ -7109,22 +7610,41 @@ pub fn put_out_piece(src: &[u8], st: &mut [usize], out: &mut [u8], a: usize, b: let mut i: usize = a; while (i < b) { let c: u8 = src[(i) as usize]; - if ((c < B_SPACE) || (c >= B_DEL)) { - refuse(st, R_LITERAL, a, a, b); - return (); - } - if (c == B_BACKSLASH) { - if (((i + 1) >= b) || !(string_escape(src[((i + 1)) as usize]))) { - refuse(st, R_LITERAL, a, i, (i + 2)); + if is_cont(src, i, b) { + i = cont_end(src, i, b); + } else { + if ((c < B_SPACE) || (c >= B_DEL)) { + refuse(st, R_LITERAL, a, a, b); return (); + } else { + if (c == B_BACKSLASH) { + if (((i + 1) >= b) || !(string_escape(src[((i + 1)) as usize]))) { + refuse(st, R_LITERAL, a, i, (i + 2)); + return (); + } + i = (i + 2); + } else { + i = (i + 1); + } } - i = (i + 2); - } else { - i = (i + 1); } } puts(st, out, b"t27o_w = rust_std::append_text(out, t27o_w, \""); - put_src(src, st, out, a, b); + i = a; + while (i < b) { + if is_cont(src, i, b) { + i = cont_end(src, i, b); + } else { + if (src[(i) as usize] == B_BACKSLASH) { + put(st, out, src[(i) as usize]); + put(st, out, src[((i + 1)) as usize]); + i = (i + 2); + } else { + put(st, out, src[(i) as usize]); + i = (i + 1); + } + } + } puts(st, out, b"\");"); use_mod(st, 1); } @@ -7174,6 +7694,10 @@ pub fn out_fmt_arg(src: &[u8], st: &mut [usize], out: &mut [u8], a: usize, b: us put_out_text(src, st, out, a, quoted_end(src, a)); return (); } + if raw_str_at(src, a) { + put_out_text(src, st, out, a, tok_end(src, a)); + return (); + } if (src[(a) as usize] == B_APOS) { let ae: usize = tok_end(src, a); puts(st, out, b"t27o_w = rust_std::append_char(out, t27o_w, "); @@ -7297,7 +7821,11 @@ pub fn out_format(src: &[u8], st: &mut [usize], out: &mut [u8], a: usize, ret: b refuse(st, R_STRUSE, a, a, fe); return (); } - out_fmt_arg(src, st, out, arg, ae); + let mut at: usize = ae; + while ((at > arg) && is_space(src[((at - 1)) as usize])) { + at = (at - 1); + } + out_fmt_arg(src, st, out, arg, at); if bad(st[(ERR) as usize]) { return (); } @@ -8585,12 +9113,12 @@ pub fn put_report(st: &mut [usize], out: &mut [u8]) -> () { put_num(st, out, st[(NCFG) as usize]); puts(st, out, b" cfg-off fn dropped; "); } - puts(st, out, b"coverage "); if (st[(NFN) as usize] == 0) { - puts(st, out, b"100"); - } else { - put_num(st, out, ((st[(NOK) as usize] * 100) / st[(NFN) as usize])); + puts(st, out, b"coverage unknown: no fn seen\n"); + return (); } + puts(st, out, b"coverage "); + put_num(st, out, ((st[(NOK) as usize] * 100) / st[(NFN) as usize])); puts(st, out, b"%\n"); } diff --git a/specs/tools/t27o/rust_convert.t27 b/specs/tools/t27o/rust_convert.t27 index 6c9020836c..86bfab58c2 100644 --- a/specs/tools/t27o/rust_convert.t27 +++ b/specs/tools/t27o/rust_convert.t27 @@ -292,6 +292,8 @@ pub const R_STRUCTEQ : usize = 29; pub const R_STRUSE : usize = 30; /// The name of the module a use-splice would pull in (#7176): the word the splice needs. pub const R_SPLICE : usize = 31; +/// An empty array or slice literal (`[]`, `&[]`) whose element type is not known where it stands. +pub const R_EMPTY : usize = 32; // The bytes the converter reads, as numbers (#7604). pub const B_TAB : u8 = 9; @@ -1063,6 +1065,34 @@ pub fn out_has(w: usize, out: []const u8, from: usize, c: u8) -> bool { return false; } +/// Does what was written from `from` on hold a space outside every bracket and quote -- an operator's, +/// which a `.?` or a `== null` after it would bind to its last operand (`w` is st[W])? +pub fn out_op_space(w: usize, out: []const u8, from: usize) -> bool { + var depth : usize = 0; + var quote : u8 = 0; + var i : usize = from; + while (i < w and i < out.len) { + const c : u8 = out[i]; + if (quote != 0) { + if (c == B_BACKSLASH) { + i = i + 1; + } else if (c == quote) { + quote = 0; + } + } else if (c == B_QUOTE or c == B_APOS) { + quote = c; + } else if (is_open(c)) { + depth = depth + 1; + } else if (is_close(c) and depth > 0) { + depth = depth - 1; + } else if (c == B_SPACE and depth == 0) { + return true; + } + i = i + 1; + } + return false; +} + /// Is what was written from `from` on exactly `s` (`w` is st[W])? pub fn out_is(w: usize, out: []const u8, from: usize, s: []const u8) -> bool { if (w < from or w - from != s.len or w > out.len) { return false; } @@ -1432,7 +1462,7 @@ pub fn expr_bp(src: []const u8, st: []usize, out: []u8, min: usize, ns: bool) -> puts(st, out, " as "); ty(src, st, out); lvl = L_AS; - } else if ((word_is(src, a, b, "==") or word_is(src, a, b, "!=")) and (out_at(st[W], out, start) == B_QUOTE or str_tok(src, st[P]))) { + } else if ((word_is(src, a, b, "==") or word_is(src, a, b, "!=")) and (out_at(st[W], out, start) == B_QUOTE or str_tok(src, st[P]) or raw_str_at(src, st[P]))) { if (word_is(src, a, b, "!=")) { insert(st, out, start, "!type_text::same("); } else { insert(st, out, start, "type_text::same("); } puts(st, out, ", "); expr_bp(src, st, out, lv + 1, ns); @@ -1538,12 +1568,152 @@ pub fn call_args(src: []const u8, st: []usize, out: []u8) -> void { } pub fn args_list(src: []const u8, st: []usize, out: []u8) -> void { - expr(src, st, out, false); + const lp : usize = open_before(src, st[P]); + var k : usize = 0; + arg_expr(src, st, out, lp, k); while (live(src, st[P], st[ERR]) and take(src, st, ",")) { if (!at(src, st[P], ")")) { puts(st, out, ", "); - expr(src, st, out, false); + k = k + 1; + arg_expr(src, st, out, lp, k); + } + } +} + +/// Where the `(` that opens the argument list whose first argument is src[p] stands, src.len when the +/// byte before p (past white space) is not one. +pub fn open_before(src: []const u8, p: usize) -> usize { + var i : usize = p; + while (i > 0 and is_space(src[i - 1])) { i = i - 1; } + if (i > 0 and src[i - 1] == B_LPAREN) { return i - 1; } + return src.len; +} + +/// Is the argument at src[p] an empty slice `&[]`, followed by `,` or `)`? +pub fn empty_ref(src: []const u8, p: usize) -> bool { + if (!tok_is(src, p, "&")) { return false; } + const b : usize = skip_ws(src, tok_end(src, p)); + if (b >= src.len or src[b] != B_LBRACKET) { return false; } + const c : usize = skip_ws(src, tok_end(src, b)); + if (c >= src.len or src[c] != B_RBRACKET) { return false; } + const d : usize = skip_ws(src, tok_end(src, c)); + return tok_is(src, d, ",") or tok_is(src, d, ")"); +} + +/// The start of parameter number idx (self is 0) of the list that opens at src[lp] `(`; src.len when +/// there is none, or when the list holds a `<` (a generic type's commas are not the list's). +pub fn param_at(src: []const u8, lp: usize, idx: usize) -> usize { + const close : usize = block_end(src, lp) - 1; + var q : usize = skip_ws(src, lp + 1); + var i : usize = 0; + while (q < close) { + if (tok_is(src, q, "<")) { return src.len; } + q = skip_ws(src, tok_end(src, q)); + } + q = skip_ws(src, lp + 1); + while (q < close) { + if (i == idx) { return q; } + const c : usize = find_at(src, q, B_COMMA); + if (c >= close) { return src.len; } + q = skip_ws(src, c + 1); + i = i + 1; + } + return src.len; +} + +/// The element type token T of the parameter `p: &[T]` (a plain word T, no lifetime on the slice's +/// elements) that the argument number k of a call lands on, in the parameter list that opens at +/// src[lp]. method: the call is `x.f(..)`, so the declaration must take self and the arguments follow +/// it. src.len when the parameter is anything else. +pub fn decl_slice_elem(src: []const u8, lp: usize, method: bool, k: usize) -> usize { + var idx : usize = k; + if (method) { + idx = k + 1; + var s : usize = param_at(src, lp, 0); + if (s >= src.len) { return src.len; } + if (tok_is(src, s, "&")) { s = skip_ws(src, tok_end(src, s)); } + if (s < src.len and src[s] == B_APOS) { s = skip_ws(src, tok_end(src, s)); } + if (tok_is(src, s, "mut")) { s = skip_ws(src, tok_end(src, s)); } + if (!tok_is(src, s, "self")) { return src.len; } + } + const pa : usize = param_at(src, lp, idx); + if (pa >= src.len) { return src.len; } + const colon : usize = skip_ws(src, tok_end(src, pa)); + if (!tok_is(src, colon, ":")) { return src.len; } + var t : usize = skip_ws(src, tok_end(src, colon)); + if (!tok_is(src, t, "&")) { return src.len; } + t = skip_ws(src, tok_end(src, t)); + if (t < src.len and src[t] == B_APOS) { t = skip_ws(src, tok_end(src, t)); } + if (!tok_is(src, t, "[")) { return src.len; } + const el : usize = skip_ws(src, tok_end(src, t)); + if (el >= src.len or !is_alpha(src[el])) { return src.len; } + if (!tok_is(src, skip_ws(src, tok_end(src, el)), "]")) { return src.len; } + return el; +} + +/// The element type token of the `&[T]` parameter that argument k of a call of `fn` src[na .. nb) lands +/// on, when the file declares that name and every declaration agrees; src.len otherwise. +pub fn slice_param_elem(src: []const u8, na: usize, nb: usize, method: bool, k: usize) -> usize { + var found : usize = src.len; + var q : usize = skip_ws(src, 0); + while (q < src.len) { + const e : usize = tok_end(src, q); + if (word_is(src, q, e, "fn")) { + const n : usize = skip_ws(src, e); + const ne : usize = tok_end(src, n); + const lp : usize = skip_ws(src, ne); + if (same_text(src, n, ne, na, nb)) { + var elem : usize = src.len; + if (tok_is(src, lp, "(")) { elem = decl_slice_elem(src, lp, method, k); } + if (elem >= src.len) { return src.len; } + if (found < src.len and !same_text(src, found, tok_end(src, found), elem, tok_end(src, elem))) { return src.len; } + found = elem; + } } + q = skip_ws(src, e); + } + return found; +} + +/// The empty slice `&[]` at st[P], argument number k of the call whose `(` is src[lp]: the empty array +/// of the element type its parameter names, `[0]T{}`, which a `[]const T` parameter takes. The +/// parameter is found in this file's own declarations of the callee; when it is not, `&[]` is refused +/// (R_EMPTY), for t27 cannot tell what the empty slice holds. +pub fn empty_arg(src: []const u8, st: []usize, out: []u8, lp: usize, k: usize) -> void { + const a : usize = st[P]; + const b : usize = skip_ws(src, tok_end(src, a)); + const c : usize = skip_ws(src, tok_end(src, b)); + const d : usize = tok_end(src, c); + var elem : usize = src.len; + if (lp < src.len) { + var ne : usize = lp; + while (ne > 0 and is_space(src[ne - 1])) { ne = ne - 1; } + var na : usize = ne; + while (na > 0 and is_word(src[na - 1])) { na = na - 1; } + if (na < ne) { + var m : usize = na; + while (m > 0 and is_space(src[m - 1])) { m = m - 1; } + const method : bool = m > 0 and src[m - 1] == B_DOT and !(m > 1 and src[m - 2] == B_DOT); + elem = slice_param_elem(src, na, ne, method, k); + } + } + if (elem >= src.len) { + refuse(st, R_EMPTY, a, a, d); + return; + } + puts(st, out, "[0]"); + st[P] = elem; + ty(src, st, out); + puts(st, out, "{}"); + st[P] = skip_ws(src, d); +} + +/// One call argument: an empty slice `&[]` is empty_arg's, any other is an expression. +pub fn arg_expr(src: []const u8, st: []usize, out: []u8, lp: usize, k: usize) -> void { + if (empty_ref(src, st[P])) { + empty_arg(src, st, out, lp, k); + } else { + expr(src, st, out, false); } } @@ -1800,7 +1970,7 @@ pub fn name_class(src: []const u8, st: []usize, a: usize, b: usize, field: bool) q = skip_ws(src, e); } if (init < src.len and is_digit(src[init])) { return literal_class(src, st, init); } - if (init < src.len and src[init] == B_QUOTE) { return C_STR; } + if (init < src.len and (src[init] == B_QUOTE or raw_str_at(src, init))) { return C_STR; } if (init < src.len and is_alpha(src[init])) { // FR-002: the class of `let v = ;` is the call's -- a text-keeping chain is a text, // a fn call is its return type @@ -1919,7 +2089,7 @@ pub fn recv_class(src: []const u8, st: []usize, ra: usize, rb: usize) -> u8 { const e : usize = tok_end(src, ra); const nx : usize = skip_ws(src, e); if (nx >= rb) { - if (src[ra] == B_QUOTE) { return C_STR; } + if (src[ra] == B_QUOTE or raw_str_at(src, ra)) { return C_STR; } if (is_digit(src[ra])) { return literal_class(src, st, ra); } if (is_alpha(src[ra])) { return name_class(src, st, ra, e, false); } return C_ANY; @@ -2238,7 +2408,7 @@ pub fn unroll_contains(src: []const u8, st: []usize, out: []u8, start: usize, ra var n : usize = 0; while (q < close and !bad(st[ERR])) { if (n > 0) { puts(st, out, " or "); } - const text : bool = src[q] == B_QUOTE; + const text : bool = src[q] == B_QUOTE or raw_str_at(src, q); if (text) { puts(st, out, "type_text::same("); st[NEQ] = 1; @@ -2669,18 +2839,100 @@ pub fn string_escape(e: u8) -> bool { return e == B_LOWER_N or e == B_LOWER_T or e == B_BACKSLASH or e == B_QUOTE; } -/// A string literal: printable ASCII and the escapes t27 reads, else refused. +/// Is src[i] the `\` of a line continuation: the backslash right before a newline (LF, or CR LF) +/// inside the string literal whose closing quote is src[hi]? +pub fn is_cont(src: []const u8, i: usize, hi: usize) -> bool { + if (i + 1 >= hi or src[i] != B_BACKSLASH) { return false; } + return src[i + 1] == B_NL or (src[i + 1] == B_CR and i + 2 < hi and src[i + 2] == B_NL); +} + +/// Where the line continuation whose backslash is src[i] ends: Rust drops the backslash, the newline +/// and every space, tab, newline and carriage return that follows, up to the next byte of the text. +pub fn cont_end(src: []const u8, i: usize, hi: usize) -> usize { + var j : usize = i + 1; + while (j < hi and (src[j] == B_NL or src[j] == B_CR or src[j] == B_SPACE or src[j] == B_TAB)) { j = j + 1; } + return j; +} + +/// Is src[q] the start of a raw string literal: `r"`, `r#"`, `r##"` .. (`r#name` is a raw identifier)? +pub fn raw_str_at(src: []const u8, q: usize) -> bool { + if (q >= src.len or src[q] != B_LOWER_R) { return false; } + var p : usize = q + 1; + while (p < src.len and src[p] == B_HASH) { p = p + 1; } + return p < src.len and src[p] == B_QUOTE; +} + +/// A raw string literal src[a .. b] (`r"..."`, `r#"..."#`) as the ordinary t27 string with the same +/// bytes: a backslash, a quote, a newline and a tab take the escapes t27 reads. A CR LF pair is the +/// LF Rust reads; any other control byte, or a byte past ASCII, is refused as in an ordinary literal. +pub fn put_raw_string(src: []const u8, st: []usize, out: []u8, a: usize, b: usize) -> void { + var h : usize = 0; + var s : usize = a + 1; + while (s < b and src[s] == B_HASH) { + h = h + 1; + s = s + 1; + } + // src[s] is the opening quote, src[hi] the closing one: the text is src[lo .. hi) + const lo : usize = s + 1; + if (b < lo + 1 + h or src[b - 1 - h] != B_QUOTE) { + refuse(st, R_LITERAL, a, a, b); + return; + } + const hi : usize = b - 1 - h; + var i : usize = lo; + while (i < hi) { + const c : u8 = src[i]; + if (c == B_CR and i + 1 < hi and src[i + 1] == B_NL) { + i = i + 1; + } else if (c != B_NL and c != B_TAB and (c < B_SPACE or c >= B_DEL)) { + refuse(st, R_LITERAL, a, a, b); + return; + } + i = i + 1; + } + put(st, out, B_QUOTE); + i = lo; + while (i < hi) { + const c : u8 = src[i]; + if (c == B_BACKSLASH) { + put(st, out, B_BACKSLASH); + put(st, out, B_BACKSLASH); + } else if (c == B_QUOTE) { + put(st, out, B_BACKSLASH); + put(st, out, B_QUOTE); + } else if (c == B_NL) { + put(st, out, B_BACKSLASH); + put(st, out, B_LOWER_N); + } else if (c == B_TAB) { + put(st, out, B_BACKSLASH); + put(st, out, B_LOWER_T); + } else if (c != B_CR) { + put(st, out, c); + } + i = i + 1; + } + put(st, out, B_QUOTE); +} + +/// A string literal: printable ASCII and the escapes t27 reads, else refused. A raw string (`r"..."`) +/// is rewritten with those escapes, and Rust's line continuation (a `\` right before a newline: the +/// backslash, the newline and the next line's leading whitespace are not part of the text) is joined. pub fn put_string(src: []const u8, st: []usize, out: []u8, a: usize, b: usize) -> void { + if (src[a] == B_LOWER_R) { + put_raw_string(src, st, out, a, b); + return; + } var s : usize = a; if (src[s] == B_LOWER_B) { s = s + 1; } var i : usize = s + 1; while (i + 1 < b) { const c : u8 = src[i]; - if (c < B_SPACE or c >= B_DEL) { + if (is_cont(src, i, b - 1)) { + i = cont_end(src, i, b - 1); + } else if (c < B_SPACE or c >= B_DEL) { refuse(st, R_LITERAL, a, a, b); return; - } - if (c == B_BACKSLASH) { + } else if (c == B_BACKSLASH) { const e : u8 = src[i + 1]; if (!string_escape(e)) { refuse(st, R_LITERAL, a, i, i + 2); @@ -2691,7 +2943,21 @@ pub fn put_string(src: []const u8, st: []usize, out: []u8, a: usize, b: usize) - i = i + 1; } } - put_src(src, st, out, s, b); + put(st, out, src[s]); + i = s + 1; + while (i + 1 < b) { + if (is_cont(src, i, b - 1)) { + i = cont_end(src, i, b - 1); + } else if (src[i] == B_BACKSLASH) { + put(st, out, src[i]); + put(st, out, src[i + 1]); + i = i + 2; + } else { + put(st, out, src[i]); + i = i + 1; + } + } + put(st, out, src[b - 1]); } /// The value of T::MAX or T::MIN for an integer type T (usize and isize as 64-bit). @@ -2898,10 +3164,11 @@ pub fn struct_lit(src: []const u8, st: []usize, out: []u8, a: usize, b: usize) - /// An array literal `[a, b]` or `[v; n]` (as `[v] ** n`), from `[`. pub fn array_lit(src: []const u8, st: []usize, out: []u8) -> void { + const lb : usize = st[P]; step(src, st); puts(st, out, "["); if (at(src, st[P], "]")) { - refuse_here(src, st, R_VALUE); + refuse(st, R_EMPTY, lb, lb, tok_end(src, st[P])); return; } expr(src, st, out, false); @@ -3086,6 +3353,11 @@ pub fn primary(src: []const u8, st: []usize, out: []u8, ns: bool) -> void { step(src, st); return; } + if (raw_str_at(src, a)) { + put_string(src, st, out, a, b); + step(src, st); + return; + } if (c == B_LOWER_R and (c2 == B_QUOTE or c2 == B_HASH)) { refuse(st, R_LITERAL, a, a, a + 2); return; @@ -3523,6 +3795,10 @@ pub fn let_value(src: []const u8, st: []usize, out: []u8, mutable: bool, na: usi puts(st, out, " = "); put_digits(src, st, out, init, ie - k); st[P] = skip_ws(src, ie); + } else if (typed and tok_is(src, st[LETTY], "[") and tok_is(src, init, "[") and peek_is(src, init, "]") and tok_is(src, skip_ws(src, tok_end(src, skip_ws(src, tok_end(src, init)))), ";")) { + // `let a: [T; 0] = [];` -- the type in the declaration is the array's + puts(st, out, " = []"); + st[P] = skip_ws(src, tok_end(src, skip_ws(src, tok_end(src, init)))); } else { puts(st, out, " = "); expr(src, st, out, false); @@ -3837,9 +4113,9 @@ pub fn for_each(src: []const u8, st: []usize, out: []u8, ia: usize, ib: usize, v } } -/// `for v in [a, b, c] { .. }`: the literal becomes a typed local first, `const t27o_a : [3]T = ..;` -/// (Zig's for reads an untyped one as a tuple). T comes from the first element: a number's suffix, an -/// enum variant of this file, a text or a bool; any other literal is refused. +/// `for v in [a, b, c] { .. }` (or over `&[a, b, c]`, ra the `[`): the literal becomes a typed local first, +/// `const t27o_a : [3]T = ..;` (Zig's for reads an untyped one as a tuple). T comes from the first +/// element: a number's suffix, an enum variant of this file, a text or a bool; any other literal is refused. pub fn array_for(src: []const u8, st: []usize, out: []u8, fpos: usize, va: usize, vb: usize, ra: usize) -> void { const close : usize = block_end(src, ra); const e0 : usize = skip_ws(src, ra + 1); @@ -3866,7 +4142,7 @@ pub fn array_for(src: []const u8, st: []usize, out: []u8, fpos: usize, va: usize puts(st, out, "]"); if (is_digit(head(src, t0)) and k > 0) { put_src(src, st, out, te - k, te); - } else if (src[t0] == B_QUOTE) { + } else if (src[t0] == B_QUOTE or raw_str_at(src, t0)) { puts(st, out, "[]const u8"); } else if (tok_is(src, t0, "true") or tok_is(src, t0, "false")) { puts(st, out, "bool"); @@ -3939,8 +4215,11 @@ pub fn for_stmt(src: []const u8, st: []usize, out: []u8) -> void { } return; } - if (dots >= lbrace and ib == ia and ra < src.len and src[ra] == B_LBRACKET) { - array_for(src, st, out, fpos, va, vb, ra); + // `for x in &[a, b, c]` borrows the literal: the same loop as over `[a, b, c]` (a `&mut` one stays refused) + var lit : usize = ra; + if (tok_is(src, ra, "&")) { lit = skip_ws(src, tok_end(src, ra)); } + if (dots >= lbrace and ib == ia and lit < src.len and src[lit] == B_LBRACKET) { + array_for(src, st, out, fpos, va, vb, lit); return; } if (dots >= lbrace) { @@ -4036,7 +4315,7 @@ pub fn pattern(src: []const u8, st: []usize, out: []u8, sa: usize, tmp: usize, w return; } const c : u8 = src[a]; - if (str_tok(src, a)) { + if (str_tok(src, a) or raw_str_at(src, a)) { puts(st, out, "type_text::same("); put_scrut(src, st, out, sa, tmp, wrapit); puts(st, out, ", "); @@ -4280,8 +4559,132 @@ pub fn struct_value(src: []const u8, st: []usize, e: usize) -> bool { return find_struct(src, r, re) < src.len or named(src, st, K_TUPLE, r, re); } +/// Where the second argument of the assert_eq! / assert_ne! whose first argument starts at src[p] begins +/// when it is `Ok(..)` or `Err(..)`; src.len when it is anything else. +pub fn result_arg(src: []const u8, p: usize) -> usize { + const c : usize = find_at(src, p, B_COMMA); + if (c >= src.len) { return src.len; } + const r : usize = skip_ws(src, c + 1); + if ((tok_is(src, r, "Ok") or tok_is(src, r, "Err")) and peek_is(src, r, "(")) { return r; } + return src.len; +} + +/// The Result the first argument src[ea ..] of an assert said, as the asserts below name it: the temp +/// `t27o_r` when a call was read once into it, else the expression again (parenthesized when it +/// has an operator, for a `.?` after it). +pub fn put_result_ref(src: []const u8, st: []usize, out: []u8, ea: usize, tmp: bool, spaced: bool, r: usize) -> void { + if (tmp) { + puts(st, out, "t27o_r"); + put_num(st, out, r); + return; + } + const save : usize = st[P]; + const w : usize = st[W]; + st[P] = ea; + expr(src, st, out, false); + st[P] = save; + if (spaced) { wrap(st, out, w); } +} + +/// A test's `assert_eq!(e, Ok(v))` / `assert_ne!(e, Ok(v))` / `assert_eq!(e, Err(..))` / `assert_ne!(e, +/// Err(..))`, from the first argument: a Result is `?T`, so `Ok(v)` is `e.? == v` and `Err(_)` is +/// `e == null` -- the payload of an Err is dropped as the oracle drops it, and a comment says so (any +/// error passes, which is weaker than Rust's comparison). A call is read once, into `t27o_r`, and +/// the value is asserted not null before `.?` reads it. A `Result<(), E>` call is a bool: `Ok(())` is +/// `e == true`, an Err `e == false`. An `Ok(())` of something that is not such a call is refused (no +/// way to tell a bool from a `?void`), and so is a struct payload (Zig has no == on structs). +/// st[P] is left past the second argument's `)`; r is its `Ok` / `Err`. +pub fn result_assert(src: []const u8, st: []usize, out: []u8, r: usize, ne: bool) -> void { + const ea : usize = st[P]; + const ok : bool = tok_is(src, r, "Ok"); + const lp : usize = skip_ws(src, tok_end(src, r)); + const pv : usize = skip_ws(src, lp + 1); + const rp : usize = block_end(src, lp); + const unit : bool = unit_result_call(src, st, ea); + const nothing : bool = tok_is(src, pv, "(") and peek_is(src, pv, ")"); + if (ok and unit != nothing) { + refuse(st, R_TYPE, r, r, tok_end(src, r)); + return; + } + if (ok and !unit and struct_value(src, st, pv)) { + refuse(st, R_STRUCTEQ, pv, pv, tok_end(src, pv)); + return; + } + if (!ok) { + if (unit) { puts(st, out, "// t27o: Err payload dropped (Result<(), E> is a bool)"); } else { puts(st, out, "// t27o: Err payload dropped (Result is ?T)"); } + newline(st, out); + } + const w0 : usize = st[W]; + expr(src, st, out, false); + if (bad(st[ERR])) { return; } + const calls : bool = out_has(st[W], out, w0, B_LPAREN); + const spaced : bool = out_op_space(st[W], out, w0); + want(src, st, ","); + if (bad(st[ERR])) { return; } + if (!ok or unit) { + // only the success of e is compared; a failing one is `== null` (`== false` for a bool) + const fails : bool = ok == ne; + if (unit or spaced) { wrap(st, out, w0); } + if (unit and fails) { + puts(st, out, " == false"); + } else if (unit) { + puts(st, out, " == true"); + } else if (fails) { + puts(st, out, " == null"); + } else { + puts(st, out, " != null"); + } + puts(st, out, ");"); + insert(st, out, w0, "assert("); + st[P] = skip_ws(src, rp); + return; + } + const text : bool = str_tok(src, pv) or raw_str_at(src, pv) or recv_class(src, st, pv, rp - 1) == C_STR; + st[W] = w0; + if (calls) { + puts(st, out, "const "); + put_result_ref(src, st, out, ea, true, spaced, r); + puts(st, out, " = "); + const back : usize = st[P]; + st[P] = ea; + expr(src, st, out, false); + st[P] = back; + puts(st, out, ";"); + newline(st, out); + } + if (!ne) { + puts(st, out, "assert("); + put_result_ref(src, st, out, ea, calls, spaced, r); + puts(st, out, " != null);"); + newline(st, out); + } + puts(st, out, "assert("); + if (ne) { + put_result_ref(src, st, out, ea, calls, spaced, r); + puts(st, out, " == null or "); + } + if (text) { + if (ne) { puts(st, out, "!type_text::same("); } else { puts(st, out, "type_text::same("); } + put_result_ref(src, st, out, ea, calls, spaced, r); + puts(st, out, ".?, "); + st[P] = pv; + expr(src, st, out, false); + puts(st, out, ")"); + st[NEQ] = 1; + } else { + put_result_ref(src, st, out, ea, calls, spaced, r); + if (ne) { puts(st, out, ".? != "); } else { puts(st, out, ".? == "); } + st[P] = pv; + const v0 : usize = st[W]; + const vl : usize = expr(src, st, out, false); + if (!keep_bare(vl, L_CMP, false)) { wrap(st, out, v0); } + } + puts(st, out, ");"); + st[P] = skip_ws(src, rp); +} + /// assert!, assert_eq! and assert_ne! (and their debug_ forms) as `assert`; the message is dropped. -/// Any other macro is refused. +/// A comparison with `Ok(..)` / `Err(..)` is result_assert's. Any other macro is refused. pub fn macro_stmt(src: []const u8, st: []usize, out: []u8, a: usize, b: usize) -> void { const plain : bool = word_is(src, a, b, "assert") or word_is(src, a, b, "debug_assert"); const eq : bool = word_is(src, a, b, "assert_eq") or word_is(src, a, b, "debug_assert_eq"); @@ -4293,6 +4696,17 @@ pub fn macro_stmt(src: []const u8, st: []usize, out: []u8, a: usize, b: usize) - step(src, st); step(src, st); want(src, st, "("); + if ((eq or ne) and result_arg(src, st[P]) < src.len) { + result_assert(src, st, out, result_arg(src, st[P]), ne); + if (bad(st[ERR])) { return; } + if (take(src, st, ",")) { + const close : usize = find_at(src, st[P], B_RPAREN); + if (close < src.len) { st[P] = close; } + } + want(src, st, ")"); + take(src, st, ";"); + return; + } puts(st, out, "assert("); if (plain) { expr(src, st, out, false); @@ -4307,7 +4721,7 @@ pub fn macro_stmt(src: []const u8, st: []usize, out: []u8, a: usize, b: usize) - refuse(st, R_STRUCTEQ, la, la, tok_end(src, la)); return; } - if (lstr or str_tok(src, st[P])) { + if (lstr or str_tok(src, st[P]) or raw_str_at(src, st[P])) { if (ne) { insert(st, out, w0, "!type_text::same("); } else { insert(st, out, w0, "type_text::same("); } puts(st, out, ", "); expr(src, st, out, false); @@ -4430,6 +4844,8 @@ pub fn put_reason(src: []const u8, st: []usize, out: []u8) -> void { puts(st, out, "== of structs"); } else if (c == R_STRUSE) { puts(st, out, "an owned String outside the out-buffer form"); + } else if (c == R_EMPTY) { + puts(st, out, "empty slice with no known element type"); } else if (c == R_SPLICE) { puts(st, out, "a used module's own use t27c does not splice (its type_text:: names stay unresolved, the gap specs/compiler/int_type_names.t27 records)"); } else { @@ -5737,14 +6153,19 @@ pub fn ret_is_string(src: []const u8, pn: usize) -> bool { return tok_is(src, t, "String") and tok_is(src, skip_ws(src, tok_end(src, t)), "{"); } -/// The value length of the string literal at src[a] (an escape counts one byte). +/// The value length of the string literal at src[a] (an escape counts one byte; a line continuation +/// counts none). pub fn str_lit_len(src: []const u8, a: usize) -> usize { const q : usize = quoted_end(src, a); var i : usize = a + 1; var n : usize = 0; while (i + 1 < q) { - if (src[i] == B_BACKSLASH) { i = i + 2; } else { i = i + 1; } - n = n + 1; + if (is_cont(src, i, q - 1)) { + i = cont_end(src, i, q - 1); + } else { + if (src[i] == B_BACKSLASH) { i = i + 2; } else { i = i + 1; } + n = n + 1; + } } return n; } @@ -6058,7 +6479,7 @@ pub fn put_out_text(src: []const u8, st: []usize, out: []u8, a: usize, b: usize) return; } puts(st, out, "t27o_w = rust_std::append_text(out, t27o_w, "); - if (str_tok(src, a)) { + if (str_tok(src, a) or raw_str_at(src, a)) { put_string(src, st, out, a, b); } else { put_ident(src, st, out, a, b); @@ -6076,13 +6497,18 @@ pub fn put_out_lit(st: []usize, out: []u8, t: []const u8) -> void { } /// One literal piece of a format! string -- src[a .. b] its raw source bytes, escapes as Rust -/// spelled them -- as one append_text (the same escapes put_string accepts, verified on the piece). +/// spelled them -- as one append_text (the same escapes put_string accepts, verified on the piece; a +/// line continuation is joined out of it as there). pub fn put_out_piece(src: []const u8, st: []usize, out: []u8, a: usize, b: usize) -> void { var i : usize = a; while (i < b) { const c : u8 = src[i]; - if (c < B_SPACE or c >= B_DEL) { refuse(st, R_LITERAL, a, a, b); return; } - if (c == B_BACKSLASH) { + if (is_cont(src, i, b)) { + i = cont_end(src, i, b); + } else if (c < B_SPACE or c >= B_DEL) { + refuse(st, R_LITERAL, a, a, b); + return; + } else if (c == B_BACKSLASH) { if (i + 1 >= b or !string_escape(src[i + 1])) { refuse(st, R_LITERAL, a, i, i + 2); return; } i = i + 2; } else { @@ -6090,7 +6516,19 @@ pub fn put_out_piece(src: []const u8, st: []usize, out: []u8, a: usize, b: usize } } puts(st, out, "t27o_w = rust_std::append_text(out, t27o_w, \""); - put_src(src, st, out, a, b); + i = a; + while (i < b) { + if (is_cont(src, i, b)) { + i = cont_end(src, i, b); + } else if (src[i] == B_BACKSLASH) { + put(st, out, src[i]); + put(st, out, src[i + 1]); + i = i + 2; + } else { + put(st, out, src[i]); + i = i + 1; + } + } puts(st, out, "\");"); use_mod(st, 1); } @@ -6134,6 +6572,7 @@ pub fn out_text_from(src: []const u8, st: []usize, out: []u8, init: usize) -> vo /// refuses (R_STRUSE). pub fn out_fmt_arg(src: []const u8, st: []usize, out: []u8, a: usize, b: usize) -> void { if (str_tok(src, a)) { put_out_text(src, st, out, a, quoted_end(src, a)); return; } + if (raw_str_at(src, a)) { put_out_text(src, st, out, a, tok_end(src, a)); return; } if (src[a] == B_APOS) { const ae : usize = tok_end(src, a); puts(st, out, "t27o_w = rust_std::append_char(out, t27o_w, "); @@ -6231,7 +6670,9 @@ pub fn out_format(src: []const u8, st: []usize, out: []u8, a: usize, ret: bool) if (c == B_LBRACE and src[p + 1] == B_RBRACE) { const ae : usize = fmt_arg_end(src, arg, rp); if (ae <= arg or arg >= rp) { refuse(st, R_STRUSE, a, a, fe); return; } - out_fmt_arg(src, st, out, arg, ae); + var at : usize = ae; + while (at > arg and is_space(src[at - 1])) { at = at - 1; } + out_fmt_arg(src, st, out, arg, at); if (bad(st[ERR])) { return; } newline(st, out); arg = skip_ws(src, ae + 1); @@ -8053,3 +8494,147 @@ test the_oracle_samples_the_arms_discriminants { // the eight fixed texts, then the predicate's own \"x\" alone and wrapped in one space (#9102) assert(out_is(n, &out, 0, "// t27o oracle harness (specs/tools/t27o/rust_convert.t27): the converted items, run to record\n// their behaviour as t27 tests.\n#![allow(warnings)]\n\nfn tag(s: &str) -> bool { s == \"x\" }\n\nfn main() {\n std::panic::set_hook(Box::new(|_| {}));\n {\n let mut v: Vec = Vec::new();\n if let Ok(r) = std::panic::catch_unwind(|| tag(\"\")) { let c = \"tag(\\\"\\\")\"; v.push(format!(\" assert({} == {:?});\", c, r)); }\n if let Ok(r) = std::panic::catch_unwind(|| tag(\"a\")) { let c = \"tag(\\\"a\\\")\"; v.push(format!(\" assert({} == {:?});\", c, r)); }\n if let Ok(r) = std::panic::catch_unwind(|| tag(\"abc\")) { let c = \"tag(\\\"abc\\\")\"; v.push(format!(\" assert({} == {:?});\", c, r)); }\n if let Ok(r) = std::panic::catch_unwind(|| tag(\"0\")) { let c = \"tag(\\\"0\\\")\"; v.push(format!(\" assert({} == {:?});\", c, r)); }\n if let Ok(r) = std::panic::catch_unwind(|| tag(\"42\")) { let c = \"tag(\\\"42\\\")\"; v.push(format!(\" assert({} == {:?});\", c, r)); }\n if let Ok(r) = std::panic::catch_unwind(|| tag(\"0x1f\")) { let c = \"tag(\\\"0x1f\\\")\"; v.push(format!(\" assert({} == {:?});\", c, r)); }\n if let Ok(r) = std::panic::catch_unwind(|| tag(\"a b\")) { let c = \"tag(\\\"a b\\\")\"; v.push(format!(\" assert({} == {:?});\", c, r)); }\n if let Ok(r) = std::panic::catch_unwind(|| tag(\"-7\")) { let c = \"tag(\\\"-7\\\")\"; v.push(format!(\" assert({} == {:?});\", c, r)); }\n if let Ok(r) = std::panic::catch_unwind(|| tag(\"x\")) { let c = \"tag(\\\"x\\\")\"; v.push(format!(\" assert({} == {:?});\", c, r)); }\n if let Ok(r) = std::panic::catch_unwind(|| tag(\" x \")) { let c = \"tag(\\\" x \\\")\"; v.push(format!(\" assert({} == {:?});\", c, r)); }\n if !v.is_empty() { println!(\"test oracle_tag {{\"); for l in &v { println!(\"{}\", l); } println!(\"}}\\n\"); }\n }\n}\n")); } + +// ---------------------------------------------------------------- refusal classes from the roadmap dogfood (#10526) + +test raw_strings_become_ordinary_strings { + var out : [1024]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + const n : usize = body("fn a() -> &'static str { r\"plain\" }\nfn b() -> &'static str { r#\"say \"hi\" \\ now\"# }\nfn c() -> &'static str { r##\"x\"#y\"## }\nfn d() -> &'static str { r\"two\nlines\ttab\\n\" }", &out, &st); + assert(out_is(n, &out, 0, "use tri::t27b::type_text;\n\nfn a() -> []const u8 {\n return \"plain\";\n}\n\nfn b() -> []const u8 {\n return \"say \\\"hi\\\" \\\\ now\";\n}\n\nfn c() -> []const u8 {\n return \"x\\\"#y\";\n}\n\nfn d() -> []const u8 {\n return \"two\\nlines\\ttab\\\\n\";\n}\n\n")); + assert(st[NOK] == 4); +} + +test a_raw_string_is_a_text_in_a_comparison_and_a_match { + var out : [1024]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + const n : usize = body("fn is_x(s: &str) -> bool { s == r\"x\" }\nfn kind(s: &str) -> u32 { match s { r#\"a\"b\"# => 1, _ => 0 } }", &out, &st); + assert(out_is(n, &out, 0, "use tri::t27b::type_text;\n\nfn is_x(s: []const u8) -> bool {\n return type_text::same(s, \"x\");\n}\n\nfn kind(s: []const u8) -> u32 {\n if (type_text::same(s, \"a\\\"b\")) {\n return 1;\n } else {\n return 0;\n }\n}\n\n")); + assert(st[NOK] == 2); +} + +test negative_control_a_raw_identifier_and_an_unterminated_raw_string_stay_refused { + var out : [1024]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + const n : usize = body("fn a() -> u32 { let r#type = 1; r#type }\nfn b() -> &'static str { r#\"abc\" }", &out, &st); + assert(out_is(n, &out, 0, "// t27o: refused fn a (line 1): syntax `#` at line 1\n\nfn a() -> u32 {\n}\n\n// t27o: refused fn b (line 2): literal `r#\"abc\" }` at line 2\n\nfn b() -> []const u8 {\n}\n\n")); + assert(st[NOK] == 0); +} + +test a_control_byte_in_a_raw_string_is_refused { + var out : [1024]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + var buf : [64]u8 = [0] ** 64; + var bs : [1024]usize = [0] ** 1024; + puts(&bs, &buf, "fn a() -> &'static str { r\"x"); + put(&bs, &buf, B_CR); + puts(&bs, &buf, "y\" }"); + const n : usize = body(buf[0 .. bs[W]], &out, &st); + assert(out_is(n, &out, 0, "// t27o: refused fn a (line 1): literal `r\"x?y\"` at line 1\n\nfn a() -> []const u8 {\n}\n\n")); + assert(st[NOK] == 0); +} + +test a_raw_strings_crlf_is_the_lf_rust_reads { + var out : [1024]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + var buf : [64]u8 = [0] ** 64; + var bs : [1024]usize = [0] ** 1024; + puts(&bs, &buf, "fn a() -> &'static str { r\"x"); + put(&bs, &buf, B_CR); + put(&bs, &buf, B_NL); + puts(&bs, &buf, "y\" }"); + const n : usize = body(buf[0 .. bs[W]], &out, &st); + assert(out_is(n, &out, 0, "use tri::t27b::type_text;\n\nfn a() -> []const u8 {\n return \"x\\ny\";\n}\n\n")); + assert(st[NOK] == 1); +} + +test a_backslash_before_a_newline_joins_the_string { + var out : [1024]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + const n : usize = body("fn a() -> &'static str { \"one \\\n two\\\n\\\n three\" }", &out, &st); + assert(out_is(n, &out, 0, "use tri::t27b::type_text;\n\nfn a() -> []const u8 {\n return \"one twothree\";\n}\n\n")); + assert(st[NOK] == 1); +} + +test a_continuation_with_crlf_skips_the_carriage_return_too { + var out : [1024]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + var buf : [64]u8 = [0] ** 64; + var bs : [1024]usize = [0] ** 1024; + puts(&bs, &buf, "fn a() -> &'static str { \"p \\"); + put(&bs, &buf, B_CR); + put(&bs, &buf, B_NL); + puts(&bs, &buf, " q\" }"); + const n : usize = body(buf[0 .. bs[W]], &out, &st); + assert(out_is(n, &out, 0, "use tri::t27b::type_text;\n\nfn a() -> []const u8 {\n return \"p q\";\n}\n\n")); + assert(st[NOK] == 1); +} + +test negative_control_only_a_backslash_right_before_a_newline_is_a_continuation { + var out : [1024]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + const n : usize = body("fn a() -> &'static str { \"x\\ \n y\" }\nfn b() -> &'static str { \"x\\\\\ny\" }\nfn c() -> &'static str { \"x\\ry\" }", &out, &st); + assert(out_is(n, &out, 0, "// t27o: refused fn a (line 1): literal `\\ ` at line 1\n\nfn a() -> []const u8 {\n}\n\n// t27o: refused fn b (line 3): literal `\"x\\\\?y\"` at line 3\n\nfn b() -> []const u8 {\n}\n\n// t27o: refused fn c (line 5): literal `\\r` at line 5\n\nfn c() -> []const u8 {\n}\n\n")); + assert(st[NOK] == 0); +} + +test a_for_over_a_borrowed_array_literal_is_a_for_over_the_literal { + var out : [1024]u8 = undefined; + var out2 : [1024]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + var st2 : [1024]usize = [0] ** 1024; + const n : usize = body("fn t() {\n for bad in &[\"9bad\", \"has space\", \"\"] {\n assert!(bad.len() < 20);\n }\n for &x in &[1u8, 2] { assert!(x > 0); }\n}", &out, &st); + assert(out_is(n, &out, 0, "fn t() -> void {\n const t27o_a2 : [3][]const u8 = [\"9bad\", \"has space\", \"\"];\n for bad in t27o_a2 {\n assert(bad.len < 20);\n }\n const t27o_a5 : [2]u8 = [@as(u8, 1), 2];\n for x in t27o_a5 {\n assert(x > 0);\n }\n}\n\n")); + assert(st[NOK] == 1); + // the very text of the loops over the literals themselves, borrowed or not + const n2 : usize = body("fn t() {\n for bad in [\"9bad\", \"has space\", \"\"] {\n assert!(bad.len() < 20);\n }\n for &x in [1u8, 2] { assert!(x > 0); }\n}", &out2, &st2); + assert(out_is(n, &out, 0, out2[0 .. n2])); +} + +test negative_control_a_mutable_or_unsuffixed_or_iterated_array_literal_loop_stays_refused { + var out : [1024]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + const n : usize = body("fn t() {\n for y in &mut [1u8, 2] { assert!(*y > 0); }\n}\nfn u() { for k in &[1, 2] { assert!(*k > 0); } }\nfn w() { for k in &[1u8, 2].iter() { assert!(*k > 0); } }", &out, &st); + assert(out_is(n, &out, 0, "// t27o: refused fn t (line 1): &mut of a value `[` at line 2\n\nfn t() -> void {\n}\n\n// t27o: refused fn u (line 4): for over an iterator `[` at line 4\n\nfn u() -> void {\n}\n\n// t27o: refused fn w (line 5): for over an iterator `[` at line 5\n\nfn w() -> void {\n}\n\n")); + assert(st[NOK] == 0); +} + +test ok_and_err_comparisons_in_a_test_read_a_result_as_an_optional { + var out : [4096]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + const n : usize = body("struct Reg { n: u32 }\nimpl Reg {\n fn unregister(&mut self, h: u32) -> Result { if h > self.n { Err(1) } else { Ok(h) } }\n}\nfn parse(x: u32) -> Result { if x > 1 { Ok(x) } else { Err(1) } }\nfn name(x: u32) -> Result<&'static str, u8> { if x > 1 { Ok(\"big\") } else { Err(0) } }\nfn check(x: u32) -> Result<(), u8> { if x > 1 { Ok(()) } else { Err(1) } }\n#[test]\nfn t() {\n let mut reg = Reg { n: 0 };\n assert_eq!(parse(2), Ok(2));\n assert_eq!(parse(0), Err(1));\n assert_ne!(parse(0), Ok(3), \"m\");\n assert_ne!(parse(2), Err(7));\n assert_eq!(name(2), Ok(\"big\"));\n assert_eq!(check(2), Ok(()));\n assert_eq!(check(0), Err(1));\n assert_eq!(\n reg.unregister(9),\n Err(1)\n );\n let r = parse(5);\n assert_eq!(r, Ok(5));\n}", &out, &st); + assert(out_is(n, &out, 0, "use tri::t27b::type_text;\n\nconst Reg = struct {\n n : u32,\n};\n\nfn Reg_unregister(self: *Reg, h: u32) -> ?u32 {\n if (h > self.n) {\n return null;\n } else {\n return h;\n }\n}\n\nfn parse(x: u32) -> ?u32 {\n if (x > 1) {\n return x;\n } else {\n return null;\n }\n}\n\nfn name(x: u32) -> ?[]const u8 {\n if (x > 1) {\n return \"big\";\n } else {\n return null;\n }\n}\n\nfn check(x: u32) -> bool {\n if (x > 1) {\n return true;\n } else {\n return false;\n }\n}\n\ntest t {\n var reg = Reg{ .n = 0 };\n const t27o_r446 = parse(2);\n assert(t27o_r446 != null);\n assert(t27o_r446.? == 2);\n // t27o: Err payload dropped (Result is ?T)\n assert(parse(0) == null);\n const t27o_r513 = parse(0);\n assert(t27o_r513 == null or t27o_r513.? != 3);\n // t27o: Err payload dropped (Result is ?T)\n assert(parse(2) != null);\n const t27o_r584 = name(2);\n assert(t27o_r584 != null);\n assert(type_text::same(t27o_r584.?, \"big\"));\n assert((check(2)) == true);\n // t27o: Err payload dropped (Result<(), E> is a bool)\n assert((check(0)) == false);\n // t27o: Err payload dropped (Result is ?T)\n assert(Reg_unregister(®, 9) == null);\n const r = parse(5);\n assert(r != null);\n assert(r.? == 5);\n}\n\n")); + assert(st[NTEST] == 1); + assert(st[NOK] == 5); +} + +test negative_control_ok_unit_of_a_local_and_a_struct_payload_stay_refused { + var out : [4096]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + const n : usize = body("struct Pt { x: u32 }\nfn mk(x: u32) -> Result { if x > 1 { Ok(Pt { x }) } else { Err(1) } }\nfn check(x: u32) -> Result<(), u8> { if x > 1 { Ok(()) } else { Err(1) } }\n#[test]\nfn a() { let r = check(2); assert_eq!(r, Ok(())); }\n#[test]\nfn b() { assert_eq!(mk(2), Ok(Pt { x: 2 })); }\n#[test]\nfn c() { assert_eq!(Ok(2u32), mk(2).map(|p| p.x)); }", &out, &st); + assert(out_is(n, &out, 0, "const Pt = struct {\n x : u32,\n};\n\nfn mk(x: u32) -> ?Pt {\n if (x > 1) {\n return Pt{ .x = x };\n } else {\n return null;\n }\n}\n\nfn check(x: u32) -> bool {\n if (x > 1) {\n return true;\n } else {\n return false;\n }\n}\n\n// t27o: refused fn a (line 5): type `Ok` at line 5\n\n// t27o: refused fn b (line 7): == of structs `Pt` at line 7\n\n// t27o: refused fn c (line 9): type `Ok` at line 9\n\n")); + assert(st[NTEST] == 0); +} + +test an_empty_slice_argument_takes_its_parameters_element_type { + var out : [4096]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + const n : usize = body("struct Behavior { n: u32 }\nfn build(name: &str, bs: &[Behavior]) -> usize { bs.len() }\nfn total(v: &[u32]) -> u32 { v.len() as u32 }\nfn bytes(a: u32, v: &[u8]) -> usize { v.len() + a as usize }\nstruct Rd { n: u32 }\nimpl Rd {\n fn new(data: &[u8]) -> Rd { Rd { n: data.len() as u32 } }\n fn feed(&mut self, cmd: u8, args: &[u8]) -> u32 { self.n + args.len() as u32 + cmd as u32 }\n}\n#[test]\nfn a() {\n assert_eq!(build(\"m\", &[]), 0);\n assert_eq!(total(&[]), 0);\n assert_eq!(bytes(1, &[]), 1);\n let mut r = Rd::new(&[]);\n assert_eq!(r.feed(3, &[]), 3);\n let r2 = Rd::new(&[]);\n let e: [f64; 0] = [];\n let mut vm: [u32; 0] = [];\n}\n#[test]\nfn b() { assert_eq!(unknown(&[]), 0); }\n#[test]\nfn d() { assert_eq!(total(&[1, 2]), 2); }\nfn unknown(v: &[u8]) -> usize { v.len() }", &out, &st); + assert(out_is(n, &out, 0, "const Behavior = struct {\n n : u32,\n};\n\nfn build(name: []const u8, bs: []const Behavior) -> usize {\n _ = name;\n return bs.len;\n}\n\nfn total(v: []const u32) -> u32 {\n return v.len as u32;\n}\n\nfn bytes(a: u32, v: []const u8) -> usize {\n return v.len + (a as usize);\n}\n\nconst Rd = struct {\n n : u32,\n};\n\nfn Rd_new(data: []const u8) -> Rd {\n return Rd{ .n = data.len as u32 };\n}\n\nfn Rd_feed(self: *Rd, cmd: u8, args: []const u8) -> u32 {\n return self.n + (args.len as u32) + (cmd as u32);\n}\n\ntest a {\n assert(build(\"m\", [0]Behavior{}) == 0);\n assert(total([0]u32{}) == 0);\n assert(bytes(1, [0]u8{}) == 1);\n var r = Rd_new([0]u8{});\n assert(Rd_feed(&r, 3, [0]u8{}) == 3);\n const r2 = Rd_new([0]u8{});\n const e : [0]f64 = [];\n var vm : [0]u32 = [];\n}\n\ntest b {\n assert(unknown([0]u8{}) == 0);\n}\n\ntest d {\n assert(total([1, 2]) == 2);\n}\n\nfn unknown(v: []const u8) -> usize {\n return v.len;\n}\n\n")); + assert(st[NTEST] == 3); +} + +test negative_control_an_empty_slice_with_no_known_element_type_is_refused_and_says_so { + var out : [4096]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + const n : usize = body("struct A { n: u32 }\nstruct B { n: u32 }\nimpl A { fn f(v: &[u8]) -> u32 { v.len() as u32 } }\nimpl B { fn f(v: &[u16]) -> u32 { v.len() as u32 } }\nfn s(v: &[&str]) -> usize { v.len() }\nfn m(v: &mut [u8]) -> usize { v.len() }\nfn a() -> usize { other(&[]) }\nfn b() -> u32 { A::f(&[]) }\nfn c() -> usize { s(&[]) }\nfn d() { let v = []; }\nfn e() -> usize { let mut x = [1u8]; m(&mut []) }", &out, &st); + assert(out_is(n, &out, 0, "const A = struct {\n n : u32,\n};\n\nconst B = struct {\n n : u32,\n};\n\nfn A_f(v: []const u8) -> u32 {\n return v.len as u32;\n}\n\nfn B_f(v: []const u16) -> u32 {\n return v.len as u32;\n}\n\nfn s(v: [][]const u8) -> usize {\n return v.len;\n}\n\nfn m(v: []u8) -> usize {\n return v.len;\n}\n\n// t27o: refused fn a (line 7): empty slice with no known element type `&[]` at line 7\n\nfn a() -> usize {\n}\n\n// t27o: refused fn b (line 8): empty slice with no known element type `&[]` at line 8\n\nfn b() -> u32 {\n}\n\n// t27o: refused fn c (line 9): empty slice with no known element type `&[]` at line 9\n\nfn c() -> usize {\n}\n\n// t27o: refused fn d (line 10): empty slice with no known element type `[]` at line 10\n\nfn d() -> void {\n}\n\n// t27o: refused fn e (line 11): empty slice with no known element type `[]` at line 11\n\nfn e() -> usize {\n}\n\n")); +} + +test a_line_continuation_in_a_format_string_is_joined_and_a_last_argument_may_end_a_line { + var out : [4096]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + const n : usize = body("fn spec(stmt: &str) -> String {\n format!(\n \"module m;\\n\\\n pub const A : u32 = 4;\\n\\\n test t {{\\n {}\\n}}\\n\",\n stmt\n )\n}\nfn two(n: u32) -> String {\n format!(\"a \\\n b {} c\\\n\\\n d\", n)\n}", &out, &st); + assert(out_is(n, &out, 0, "use tools::t27o::rust_std;\nuse tri::t27b::type_text;\n\nfn spec_(stmt: []const u8, out: []u8) -> usize {\n var t27o_w : usize = 0;\n t27o_w = rust_std::append_text(out, t27o_w, \"module m;\\npub const A : u32 = 4;\\ntest t \");\n t27o_w = rust_std::append_text(out, t27o_w, \"{\");\n t27o_w = rust_std::append_text(out, t27o_w, \"\\n \");\n t27o_w = rust_std::append_text(out, t27o_w, stmt);\n t27o_w = rust_std::append_text(out, t27o_w, \"\\n\");\n t27o_w = rust_std::append_text(out, t27o_w, \"}\");\n t27o_w = rust_std::append_text(out, t27o_w, \"\\n\");\n return t27o_w;\n}\n\nfn two(n: u32, out: []u8) -> usize {\n var t27o_w : usize = 0;\n t27o_w = rust_std::append_text(out, t27o_w, \"a b \");\n t27o_w = rust_std::append_uint(out, t27o_w, n as u64);\n t27o_w = rust_std::append_text(out, t27o_w, \" cd\");\n return t27o_w;\n}\n\n")); + assert(st[NOK] == 2); +}