package coffee.liz.lambda.eval; import coffee.liz.lambda.ast.Expression; import coffee.liz.lambda.ast.Macro; import coffee.liz.lambda.bind.ExternalBinding; import jakarta.annotation.Nullable; import lombok.RequiredArgsConstructor; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; /** * Runtime environment for variable bindings, macros, and external bindings. */ @RequiredArgsConstructor public final class Environment { /** Named expansions */ private final Map macros; /** "FFI" */ private final Map externalBindings; /** Variable name bound at this scope level. Null for root. */ @Nullable private final String boundName; /** Lazily-evaluated value for boundName, or null for root. */ @Nullable private final Supplier boundValue; /** Enclosing scope, or null for root. Forms a linked list of bindings. */ @Nullable private final Environment parent; /** * Creates an environment from macro and external binding lists. * * @param macros * program macro definitions * @param externalBindings * external Java bindings for FFI * @return the new environment */ public static Environment from(final List macros, final List externalBindings) { return new Environment(macros.stream().collect(Collectors.toMap(Macro::name, Macro::expression)), externalBindings.stream().collect(Collectors.toMap(ExternalBinding::getName, Function.identity())), null, null, null); } /** * Creates a child scope. * * @param name * the variable name * @param value * the value supplier (thunk) * @return a new environment with the binding added */ public Environment extend(final String name, final Supplier value) { return new Environment(macros, externalBindings, name, value, this); } /** * Looks up a name, checking bindings, then macros, then external bindings. * * @param name * the name to look up * @return the lookup result, or empty if not found */ public Optional lookup(final String name) { for (Environment env = this; env != null; env = env.parent) { if (!name.equals(env.boundName)) { continue; } return Optional.of(new LookupResult.Binding(env.boundValue)); } final Expression macro = macros.get(name); if (macro != null) { return Optional.of(new LookupResult.Macro(macro)); } final ExternalBinding external = externalBindings.get(name); if (external != null) { return Optional.of(new LookupResult.External(external)); } return Optional.empty(); } /** * Result of looking up a name in the environment. */ public sealed interface LookupResult { /** A local variable binding. */ record Binding(Supplier value) implements LookupResult { } /** A macro definition. */ record Macro(Expression expression) implements LookupResult { } /** An external Java binding. */ record External(ExternalBinding binding) implements LookupResult { } } }