# Step: string concatenation + string assignment/LEN polish Tag `v3-strcat`. Suite **128/128**; fixpoint **OK** (`bootstrap/fixpoint.sh`, image **2,080,607 bytes**). ## String concatenation `+` - `SymTab.IsStrType(t)` (exported): a string literal (`ClStr`) or a 1-D `ARRAY OF CHAR`. - The `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`. ## String assignment is now a content copy 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. - Assignment also works when the RHS is a `+` concatenation (result type `ClStr`). ## `LEN` / `LENGTH` on string literals `LEN("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). ## Tests - `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). ## Notes / caveats - `"x" + "y"` is accepted through the CHAR rule (single-character literals are `CHAR`, as in classic Modula-2, so they are not `ClStr`). - The concat buffer is a single static 8 KiB; nested/interleaved concatenations must be consumed in order. This is sufficient for ordinary expressions (`("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). ## Files `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`.