> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lionlang.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# How Lion evaluates expressions

> Lion evaluates JSON through a three-stage pipeline: schema decoding, expression-type dispatch via Effect Match, and environment-backed execution.

Lion evaluation is a three-stage pipeline: the raw input is decoded against the `LionExpressionSchema`, dispatched to a handler based on its runtime type, and executed inside an Effect computation that carries the current environment as a service. Every step is modelled with [Effect](https://effect.website), giving Lion safe error propagation, dependency injection, and composable async execution with no boilerplate.

## The `evaluate` function

`evaluate` is the central dispatcher. It receives an already-decoded `LionExpressionType` and uses `Match` to route to one of three base-form handlers:

```typescript theme={null}
export const evaluate = (expression: LionExpressionType): EvaluateResult =>
  pipe(
    Match.value(expression),
    Match.when(Schema.is(LionArrayExpressionSchema), (_) => evaluateArray(_)),
    Match.when(Schema.is(LionRecordExpressionSchema), (_) => evaluateRecord(_)),
    Match.when(Schema.is(JsonPrimitiveSchema), (_) => evaluatePrimitive(_)),
    Match.exhaustive
  );
```

`Match.exhaustive` ensures the TypeScript compiler guarantees all three expression types are handled — no runtime fall-through is possible.

## The `run` entry point

`run` is the public API. It accepts raw unknown input and a plain JavaScript environment object, then wires the pipeline together:

```typescript theme={null}
export const run = (
  expression: unknown,
  environment: Record<string, unknown>
) =>
  pipe(
    Schema.decodeUnknown(LionExpressionSchema)(expression),
    Effect.flatMap(evaluate),
    Effect.provideServiceEffect(
      LionEnvironmentService,
      makeEnvironment(environment)
    )
  );
```

Three things happen in order:

1. **Decode** — `Schema.decodeUnknown` validates the raw value and narrows it to `LionExpressionType`. Invalid input fails the Effect here without reaching the evaluator.
2. **Evaluate** — `evaluate` is `flatMap`-ed onto the decoded expression, producing an `EvaluateResult` Effect.
3. **Provide environment** — `makeEnvironment` wraps the caller-supplied bindings in a `Ref`-backed environment and provides it to `LionEnvironmentService` so all nested calls can access it.

## Array dispatch and special forms

Array expressions go through a second dispatch layer inside `evaluateArray`:

```typescript theme={null}
export const evaluateArray = (expression: LionArrayExpressionType) =>
  pipe(
    Match.value(expression),
    Match.when(Arr.isEmptyReadonlyArray, Effect.succeed),
    Match.when(Schema.is(EvalFormSchema),     (_) => evaluateEval(_)),
    Match.when(Schema.is(QuoteFormSchema),    (_) => evaluateQuote(_)),
    Match.when(Schema.is(BeginFormSchema),    (_) => evaluateBegin(_)),
    Match.when(Schema.is(DefineFormSchema),   (_) => evaluateDefine(_)),
    Match.when(Schema.is(LambdaFormSchema),   (_) => evaluateLambda(_)),
    Match.when(Schema.is(CondFormSchema),     (_) => evaluateCond(_)),
    Match.when(Schema.is(MatchFormSchema),    (_) => evaluateMatch(_)),
    Match.when(Schema.is(FunctionCallFormSchema), (_) => evaluateFunctionCall(_)),
    Match.orElse(() => Effect.fail(new InvalidFunctionCallError({ expression })))
  );
```

Special forms are checked **before** the generic `FunctionCallFormSchema`. That ordering is what gives keywords like `"define"` and `"lambda"` their privileged status — they match a schema that requires a specific literal string as the first element, so they take priority over any function the environment might have bound to the same name.

## Primitive evaluation

Strings are the only primitive type with non-trivial behaviour. `evaluatePrimitive` checks whether the value is a string; if so it delegates to `evaluateReference`:

```typescript theme={null}
export const evaluateReference = (name: string) =>
  pipe(
    getService(LionEnvironmentService),
    Effect.flatMap((environment) => getBinding(environment, name)),
    Effect.map(Option.getOrElse(() => name))
  );
```

`getBinding` walks the environment chain and returns `Option.some(value)` if found. `Option.getOrElse(() => name)` ensures an unbound string falls back to itself rather than failing — making bare string literals safe anywhere in an expression.

## Record evaluation

Record evaluation is the simplest case — map `evaluate` over every value and collect results:

```typescript theme={null}
export const evaluateRecord = (expression: LionRecordExpressionType) =>
  pipe(expression, Record.map(evaluate), Effect.all);
```

`Effect.all` runs all value Effects in parallel (subject to Effect's concurrency model) and returns a new record with the same keys.

## Running an expression

```typescript theme={null}
import { Effect } from "effect";
import { run } from "@lionlang/core/evaluation/evaluate";
import { stdlib } from "@lionlang/core/modules";

const result = await Effect.runPromise(
  run(["number/add", 1, 2], stdlib)
);
// => 3
```

<Tip>
  `Effect.runPromise` is the standard way to execute an Effect in an async context. For synchronous testing use `Effect.runSync` when the computation contains no async steps.
</Tip>
