The sweet spot between pure mathematical logic, AI token efficiency, human
readability, and seamless execution is a Homoiconic Array Treeoften referred
to as "JSON-Lisp."
When designing an execution engine, patching lexer behaviors, or mapping
high-level logic to VM opcodes in a Node.js or JavaScript environment,
standard object-based ASTs become notoriously bloated. By representing
S-expressions as standard JSON arrays, you eliminate the need to write a lexer
entirely, while satisfying all of your constraints.
Here is how it looks in practice:
[
"def", "calculate_discount", ["price", "is_member"],
["if", "is_member",
["*", "price", 0.85],
"price"
]
]
Why this hits every constraint
* Most Minimal (Zero Parser Overhead): You do not need to write or maintain a
tokenizer or grammar definition. Any language can natively parse it using
JSON.parse(). It strips away the { "type": "BinaryExpression", "left": ... }
bloat found in standard ASTs.
* AI Understandable (Highly Token-Efficient): LLMs are uniquely trained on
vast amounts of JSON, but object-key repetition wastes their context window.
Array-based trees compress the logical intent into absolute minimum tokens,
allowing an AI to generate or analyze significantly larger program plans in a
single prompt.
* Most Optimal (Homoiconicity): The structure of the code is the data
structure. The first element of an array is always the operator/function, and
the subsequent elements are the operands. This allows for trivial
macro-expansion and meta-programming.
* Human Understandable: It forces functional purity. The indentation
naturally visualizes the execution flow and tree structure without requiring
the human to remember complex operator precedence rules.
* Self-Documenting: By strictly relying on recognizable, domain-specific
string literals as the first element (e.g., def, if, fetch, map), the schema
explains itself.
Designing the Interpreter
Because the grammar is universally solved by standard JSON parsers, the
interpreter logic only requires a single recursive evaluation loop. You can
write the core execution engine in under 20 lines of code.
Here is the blueprint for how this is evaluated:
function evaluate(ast, env = {}) {
// 1. Base cases: Primitives and Variables
if (!Array.isArray(ast)) {
// If it's a string, look it up in the environment. Otherwise, return the
literal.
return (typeof ast === 'string' && ast in env) ? env[ast] : ast;
}
// 2. Destructure the operation and its arguments
const [op, ...args] = ast;
// 3. Engine Built-ins / Special Forms (Lazy evaluation)
if (op === 'if') {
return evaluate(args[0], env) ? evaluate(args[1], env) : evaluate(args[2],
env);
}
if (op === 'def') {
// ["def", "func_name", ["arg1"], ["body"]]
const [name, params, body] = args;
env[name] = (...funcArgs) => {
const localEnv = { ...env };
params.forEach((param, i) => localEnv[param] = funcArgs[i]);
return evaluate(body, localEnv);
};
return `Defined: ${name}`;
}
// 4. Function Application (Eager evaluation for standard ops)
const resolvedOp = evaluate(op, env);
const resolvedArgs = args.map(arg => evaluate(arg, env));
if (typeof resolvedOp === 'function') {
return resolvedOp(...resolvedArgs);
}
// Map to standard operators or custom VM opcodes
switch (op) {
case '+': return resolvedArgs.reduce((a, b) => a + b, 0);
case '*': return resolvedArgs.reduce((a, b) => a * b, 1);
case '<': return resolvedArgs[0] < resolvedArgs[1];
default: throw new Error(`Unknown opcode: ${op}`);
}
}
Extending for the "Applicable Scenarios"
If this format needs to cross boundaries (e.g., streaming over a network to an
API gateway via ring buffers), it serializes to a highly compressed string. If
you need to compile it down to bytecode rather than interpreting it directly,
the evaluate function simply becomes an emit function that yields VM opcodes
(like OP_LOAD, OP_MUL) as it walks the tree.