aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/java/coffee/liz/lambda/eval/Environment.java
blob: 8854719e107680e2b81aafaff8f426820d655eb8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
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<String, Expression> macros;

	/** "FFI" */
	private final Map<String, ExternalBinding> 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<Value> 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<Macro> macros, final List<ExternalBinding> 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> 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<LookupResult> 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> value) implements LookupResult {
		}
		/** A macro definition. */
		record Macro(Expression expression) implements LookupResult {
		}
		/** An external Java binding. */
		record External(ExternalBinding binding) implements LookupResult {
		}
	}
}