# shen $ ./zinctest globals.csexp

Primitives

Type-checked safe wrappers

// shen/primitives.shen — 37 safe.X wrappers

Each C primitive is wrapped in a safe.X closure in shen/primitives.shen that validates its arguments and raises a catchable simple-error before the raw primitive is called. The C primitives themselves have no runtime type guards — ownership of catchable runtime type errors lives in the safe-wrapper layer.

Direct [prim X] dispatch bypasses the safe wrapper (it's the type-safe static path). [global X]safe.X fires on the dynamic path — a primitive used as a value, higher-order, or explicit (function X).

ZINC argument convention

// the #1 recurring bug pattern

ZINC evaluates arguments right-to-left: the rightmost arg is pushed first, the leftmost last (on top of the stack). When writing bytecode by hand, push args in right-to-left order:

(open "Makefile" in) → (s[2:s]in S[8:S]Makefile P[4:s]open)

Writing left-to-right works for commutative ops (+, =, cons-as-pair) but silently produces wrong results for non-commutative ops (-, /, trap-error, write-byte).

No push opcode

// standard ZINC auto-push semantics

All value-producing opcodes (number, string, symbol, boolean, access, global, cur, prim, apply, return) push their result to the stack AND set the accumulator — there is no push opcode. The compiler relies on auto-push. The pushmark (m) opcode remains and is emitted by zinc-c.