Writing CX when your instinct writes C — the conversion pass, in priority order
If you are a language model writing CX, read this before you write a line. It is a CONVERSION PASS, not a style guide, and it exists because the instinct that fires when you are asked for CX is the instinct that writes C. That is not a fault — it is the same reflex a human C programmer brings, and the same easier memory. It just produces C in CX costume, which runs and teaches nothing. PRIORITY ORDER IS LOAD ORDER: apply rule 1 before rule 2 exists for you. This is the checklist the CX+AI project holds its own AI contributors to, published unchanged so that the code an AI writes for you and the code an AI writes for us are held to one standard.
You know C. That reflex writes C in CX costume. Convert it. The one question that governs every line: "How would I write this if the data had to be json, and if I wanted to read and modify the behaviour as text while the program runs?" Partition what might change from what never will; what might change must be reachable at runtime as structured text.
setCode/replaceCode). The look as theme documents. Fixed code only for what never changes. And AUTHOR the text AS text: a known-shape json document is written as its LITERAL document text — the document IS the source — never assembled set-call by set-call; a string is built as the text it is, not as concatenation plumbing. Call-by-call assembly is for shapes decided at runtime; if you can read the finished document in the source, write the finished document in the source. Use _json and _rules to do it — see Authoring a document at the foot.on* doors, onerror).p = new point; — no malloc, no sizeof, no sizes anywhere. Construction-on-demand is the law.->. d->count, d->type, d->format, d->valid, node->key. There is no call form: a container is declared as what it is, and a name like jsonSize(d) is a syntax error.array a[n] resizes (also inside a callee, onto the caller's array — that is the point). redim is retired; the compiler redirects you..i .s .f .v — omitting still means int, and always will; that default is not going anywhere. What changed in v3.3.007.0 is that int now holds you to it. Returning a float from an unsuffixed function is CX-E1123, and finishing without returning a value — either by falling off the end or by writing a bare return; — is CX-E1124. Both were silent before, and the float one disagreed across backends on the same source (2.000 native, 2.500 on the VM). So: .v on anything that just does a thing, .f on anything that computes a fraction._C{} for genuine C needs, stated honestly. Never to dodge a CX spelling that exists. All of C remains yours — the failure mode is reflex, not use.s[n] is the byte at n; substr(s, start, len) and strstr() count from 0, like arrays. strstr answers -1 when the needle is absent, so the test is >= 0 — a > 0 test silently rejects a match at the first character. A negative start counts from the end: substr(s, -3) is the last three. stringsplit(s, sep, n) reads field n, also from 0. insertstring(s, t, where) is 0-based, negative counts from the end, and out of range clamps — the same answer substr gives. The 1-indexed PureBasic forms are GONE, not deprecated: writing one is a syntax error.12a. The length is a NOUN: s->len. Not len(s), not length(s) — both are gone, and len(...) on a typed handle is CX-E1110. strlen() is a macro over the same field, so prefer the arrow. Same for containers: xs->count, d->count. 12b. A cast is the conversion. (int)s and (float)s read a number out of text; str(n) writes one in, and strf(f) / strf(f, dp) control the decimals. val vali valf atoi atof stri itoa ltoa are all GONE — CX-E1028, like a name that never existed. And there is no implicit parse to fall back on: assigning a string into a numeric sink is CX-E1074, because text has no lossless numeric reading. This is the rule most likely to be wrong from memory: every other language in your training data converts here silently or has a named function for it.
println proof lines are the test suite's dialect.args, a json document — not an argc()/argv(i) loop. args[0] is the first thing typed, args->count how many; a flag line is one call, json p = SplitParameters("-,--", "=", "--node");, and then p is read like any document. while (i < argc()) { … i = i + 1; } is C's convention leaking through; argc()/argv(n) exist for a C-identical read, not for parsing. (His finding 2026-09-08: the KwaaiNet demo parsed its --node with the C loop.)Before you ship the draft, ask once: could I change every changeable part of this while it runs? Each "no" is a rule above, unapplied.
Rule 1 asks you to write a known-shape document as its literal text. CX gives you two literal BLOCKS for that, and reaching for a quoted string first is the C reflex this page exists to convert.
_json — a document written as itselfstring sName; int nCrew;
sName = "Nostromo"; nCrew = 7;
_json fleet {
{
"fleet": "Outer Reach",
"ships": [
{ "name": sName, "crew": nCrew, "hull": 0.82 }
]
}
}
No escaped quotes, because there is no string. An unquoted identifier in VALUE position splices a CX variable in by its declared type — the float enters as a number instead of round-tripping through text. The block is parsed at BUILD time and lowered to builder calls, so a malformed document is an error on that .cx line rather than a bad handle at run time, and a literal inside a hot loop never re-parses. Keys stay literal; dynamic keys are a runtime jsonSet. Strict JSON inside the braces — a // comment there is a compile error, exactly as it would be in a .json file.
_rules — a rule written as the C it is#pragma rules c
_rules RETREAT {
{
float hull = e["hull"];
if (hull < 20) { e["speed"] = max(e["speed"] - 2, 3); }
}
}
_rules declares an ordinary string — ruleAdd, ruleExec and ruleAuthor take it unchanged and it can still be reassigned at run time, so the rule is still DATA. What the block buys is that the text is run through the rules frontend WHEN THE PROGRAM IS COMPILED. Compare the alternative: a typo inside a quoted rule is not a compile error, so at run time ruleExec prints a complaint and the program carries on with the rule silently never firing — which reads like a balance problem rather than a bug, and is the hardest failure of all to attribute.
When the document or the rule genuinely has to BE a runtime string — you are about to send it, store it, hand it to parseDoc, or show it — CX concatenates ADJACENT STRING LITERALS the way C does, so it is still written as the text it is, with no + plumbing between the lines:
string policy = "{ if (e[\"total\"] >= 50) { e[\"ship\"] = 0; } "
"else { e[\"ship\"] = 4.95; } }";
That is the FALLBACK. If you find yourself escaping quotes to build something the program itself will use, you have reached for it too early.