# Copyright (c) 2025-2026 Chris Pressey, Cat's Eye Technologies.
# This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
# SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-Ebfer
import logging
from typing import Dict, List
from .term import Term, IntValue, Ctor, is_variable
from .ast import Development, Rule
logger = logging.getLogger(__name__)
Value = Term
def eval_term(t: Term, env: Dict[str, Value], functions: Dict[str, Rule]) -> Value:
if isinstance(t, IntValue):
return t
elif not t.subterms:
if t.symbol in env:
return env[t.symbol]
else:
return t # atom
elif t.symbol == 'add':
a = eval_term(t.subterms[0], env, functions)
b = eval_term(t.subterms[1], env, functions)
assert isinstance(a, IntValue) and isinstance(b, IntValue), \
f"Need 2 ints, not {a} and {b}"
return IntValue(a.value + b.value)
elif t.symbol == 'mul':
a = eval_term(t.subterms[0], env, functions)
b = eval_term(t.subterms[1], env, functions)
assert isinstance(a, IntValue) and isinstance(b, IntValue), \
f"Need 2 ints, not {a} and {b}"
return IntValue(a.value * b.value)
else:
if t.symbol not in functions:
raise ValueError(f"Unknown function: {t.symbol}")
fun = functions[t.symbol]
evaluated_args = [eval_term(arg, env, functions) for arg in t.subterms]
return eval_function(fun, evaluated_args, functions)
def eval_function(fun: Rule, args: List[Value], functions: Dict[str, Rule]) -> Value:
env = {}
for (formal, actual) in zip(fun.lhs.subterms, args):
assert is_variable(formal)
assert isinstance(formal, Ctor)
env[formal.symbol] = actual
return eval_term(fun.rhs, env, functions)
def eval_ebfer(development: Development) -> Value:
main_functions = [f for f in development.functions if f.lhs.symbol == "main"]
if len(main_functions) != 1:
raise AssertionError("No unique main function")
functions = {f.lhs.symbol: f for f in development.functions}
return eval_function(main_functions[0], [], functions)