Tag v3-strcat. Suite 128/128; fixpoint OK
(bootstrap/fixpoint.sh, image 2,080,607 bytes).
+SymTab.IsStrType(t) (exported): a string literal (ClStr) or a
1-D ARRAY OF CHAR.SimExpr action gained an OpAdd string branch before the
arithmetic path: a + b where both are string-compatible, or where
one is a string and the other a CHAR (the char becomes a
one-character string literal via QbeGen.DeclCharStr). The result
type is NewStr() (a string literal value).QbeGen.StrCat emits %t =l call $m2strcat(l a, l b); the shim
concatenates into a static buffer and returns its descriptor
address. The value is valid until the next concatenation, so it
must be consumed (assigned/compared/passed) before another one.shim.c: m2strcat.Previously a := "…" did a whole-array copy with a runtime count
check, so any length mismatch trapped (the old
t_trap_count/t_trap_emptystr tests). It now copies content:
QbeGen.StrAssign emits call $m2strassign(l dst, l src).shim.c: m2strassign copies up to cap characters (a CHAR array
has cap content slots plus a reserved terminator at index cap),
truncates to fit, and NUL-terminates.a := "abcd" into ARRAY [0..2] OF CHAR now yields "abc"
(truncated); a := "" clears the destination.+ concatenation (result
type ClStr).LEN / LENGTH on string literalsLEN("hello") / LENGTH("abc") now fold to the content length at
compile time; the non-literal forms were already supported (LEN of
an array is its capacity, LEN of an open array reads the count).
t_strcat.mod (exit 63): array + array, literal + CHAR,
string assignment, LEN(array)/LEN("hello")/LENGTH("abc").t_strtrunc.mod (exit 42): "abcd" into capacity 3 → "abc".t_strempty.mod (exit 42): "" clears the destination.t_trap_count.mod / t_trap_emptystr.mod removed (obsolete).expect_run/expect_trap now link shim.c (string assignment calls
into it)."x" + "y" is accepted through the CHAR rule (single-character
literals are CHAR, as in classic Modula-2, so they are not
ClStr).("a" + "b") + "c" is fine: each result is
consumed by the next + immediately).LEN of an array remains the capacity, not the current
NUL-terminated length (use the shim-backed Strings.Length/LEN
of the string for that).compiler/src/M2.atg (string +, string-assign branches, LEN
literal), compiler/src/QbeGen.def/.mod (StrCat/StrAssign/
StrLen), compiler/src/SymTab.def (IsStrType),
runtime/syslib/shim.c (m2strcat/m2strassign/m2strlen),
compiler/run_tests.sh, compiler/tests/{t_strcat,t_strtrunc,
t_strempty}.mod, docs/features.md.