Evaluate a Scoped Lisp Expression
AttentiveSoftware Engineer · OnsiteLast updated
Implement a recursive descent or token-based interpreter to evaluate a scoped, Lisp-like arithmetic expression supporting addition, multiplication, and sequential variable bindings with shadowing.
Problem Statement
Implement an evaluator for a mini Lisp-like language. You are given a string expression representing a syntactically valid expression. Evaluate the expression and return its integer result.
An expression can take one of the following forms:
- Integer Literal: A sequence of digits, optionally prefixed with
-(e.g.,42,-7). - Variable: A sequence of lowercase English letters (e.g.,
x,foo). Its value is looked up in the current scope. - Addition:
(add e1 e2)evaluates expressione1and expressione2, returning their sum. - Multiplication:
(mult e1 e2)evaluates expressione1and expressione2, returning their product. - Let Binding:
(let v1 e1 v2 e2 ... vn en expr)binds variablev1to the evaluation ofe1, thenv2to the evaluation ofe2, and so forth sequentially. Finally, it evaluates and returnsexpr. Variable bindings inside aletblock are local to that block and shadow any bindings with the same name in enclosing scopes.
Function Signature
def evaluate(expression: str) -> int:
pass
Constraints
1 <= len(expression) <= 2000expressionis syntactically valid and balanced with spaces separating tokens, except possibly immediately adjacent to parentheses (e.g.,(add 1 2)).- Variable names consist only of lowercase English letters.
- The result of any arithmetic operation and intermediate computation fits within a standard 32-bit signed integer.
- All variables referenced are guaranteed to be bound in the current or an outer scope.
Examples
Example 1:
Input: expression = "(let x 2 (mult x 5))"
Output: 10
Explanation:
x is assigned 2. The final expression evaluates (mult 2 5) = 10.
Example 2:
Input: expression = "(let x 3 (let x 4 (add x 5)))"
Output: 9
Explanation:
The inner let shadows the outer x with the value 4. (add 4 5) evaluates to 9.
Example 3:
Input: expression = "(let a (add 1 2) b (mult a 2) (add a b))"
Output: 9
Explanation:
First, a is bound to 3. Then, b is bound to (mult 3 2) = 6. Finally, (add 3 6) evaluates to 9.
Hints
Hint 1
Write a helper function to parse top-level sub-expressions or tokens inside a parenthesized list without accidentally splitting nested parentheses.
Hint 2
Maintain an environment mapping variables to values; when entering a `let` expression, create a child scope (e.g., a copy or a scope chain) to isolate new bindings.
Hint 3
Evaluate `let` bindings from left to right, immediately adding each binding to the current scope so that subsequent bindings can refer to it.
Follow-up questions
- How would you extend this interpreter to support user-defined functions or lambda expressions?
- How would you modify the evaluator to produce descriptive error messages for unbound variables or syntax errors rather than assuming valid input?
- If expressions are nested thousands of levels deep, how would you avoid Python's call-stack limit?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.