Talking to the outside world
Chiya can compute, but it can't print, and it can't work with lists. Real languages ship built-in functions: functions written in the host language (here, JavaScript) that the interpreter offers to every program. Today you'll add arrays and indexing to the evaluator, a handful of built-ins, and run(source), which turns any program into { output, value, error }.
let sum = fn(arr) {
if (len(arr) == 0) { return 0; }
first(arr) + sum(rest(arr))
};
print("total:", sum([1, 2, 3, 4]));Built-ins live in an environment
Built-ins are just values stored under names, so they belong in an Env. Create a base environment holding { kind: "builtin", name, fn } objects, and make the program's global environment an inner Env of it. call() gets one more case: a builtin is called by running its JavaScript fn with the arguments. Programs can even shadow a built-in with their own let len = ….
function builtins(output) {
const env = new Env();
const def = (name, fn) => env.define(name, { kind: "builtin", name, fn });
def("len", (x) => {
if (typeof x === "string" || Array.isArray(x)) return x.length;
throw new Error(`len() needs a string or array, not ${typeOf(x)}`);
});
def("print", (...values) => { output.push(values.map(display).join(" ")); return null; });
return env;
}push returns a new array instead of changing the old one. With no assignment in Chiya yet, immutable lists keep programs predictable.