Skip to content

Commit f2aaaa7

Browse files
committed
refactored up to (define...)
1 parent 1574d10 commit f2aaaa7

File tree

4 files changed

+65
-28
lines changed

4 files changed

+65
-28
lines changed

mylis/mylis_3/environ.py

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,31 @@
66

77
def core_env() -> Environment:
88
"An environment for an s-expression calculator."
9-
# sin, cos, sqrt, pi, ...
10-
env: Environment = {Symbol(name): obj for name, obj in vars(math).items()}
9+
env: Environment = vars(math) # sin, cos, sqrt, pi, ...
1110
env.update(
1211
{
13-
Symbol('+'): op.add,
14-
Symbol('-'): op.sub,
15-
Symbol('*'): op.mul,
16-
Symbol('/'): op.truediv,
17-
Symbol('quotient'): op.floordiv,
18-
Symbol('>'): op.gt,
19-
Symbol('<'): op.lt,
20-
Symbol('>='): op.ge,
21-
Symbol('<='): op.le,
22-
Symbol('='): op.eq,
23-
Symbol('abs'): abs,
24-
Symbol('begin'): lambda *x: x[-1],
25-
Symbol('eq?'): op.is_,
26-
Symbol('equal?'): op.eq,
27-
Symbol('max'): max,
28-
Symbol('min'): min,
29-
Symbol('not'): op.not_,
30-
Symbol('number?'): lambda x: isinstance(x, (int, float)),
31-
Symbol('procedure?'): callable,
32-
Symbol('modulo'): op.mod,
33-
Symbol('round'): round,
34-
Symbol('symbol?'): lambda x: isinstance(x, Symbol),
12+
'+': op.add,
13+
'-': op.sub,
14+
'*': op.mul,
15+
'/': op.truediv,
16+
'quotient': op.floordiv,
17+
'>': op.gt,
18+
'<': op.lt,
19+
'>=': op.ge,
20+
'<=': op.le,
21+
'=': op.eq,
22+
'abs': abs,
23+
'begin': lambda *x: x[-1],
24+
'eq?': op.is_,
25+
'equal?': op.eq,
26+
'max': max,
27+
'min': min,
28+
'not': op.not_,
29+
'number?': lambda x: isinstance(x, (int, float)),
30+
'procedure?': callable,
31+
'modulo': op.mod,
32+
'round': round,
33+
'symbol?': lambda x: isinstance(x, Symbol),
3534
}
3635
)
3736
return env

mylis/mylis_3/evaluator.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
from .parser import s_expr
44
from .mytypes import Environment, Expression, InvalidSyntax, Symbol, UndefinedSymbol
55

6+
KEYWORDS = 'define'.split()
7+
8+
# Quantifiers in s-expression syntax comments:
9+
# * : 0 or more
10+
# + : 1 or more
11+
# ? : 0 or 1
612

713
def evaluate(exp: Expression, env: Environment) -> Any:
814
"Evaluate an expression in an environment."
@@ -16,7 +22,7 @@ def evaluate(exp: Expression, env: Environment) -> Any:
1622
raise UndefinedSymbol(var) from exc
1723
case ['define', Symbol(var), value_exp]: # (define var exp)
1824
env[var] = evaluate(value_exp, env)
19-
case [op, *args]: # (op arg1...)
25+
case [op, *args] if op not in KEYWORDS: # (op exp*)
2026
proc = evaluate(op, env)
2127
values = [evaluate(arg, env) for arg in args]
2228
return proc(*values)

mylis/mylis_3/evaluator_test.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import ast
12
from typing import Any
23

34
from .environ import core_env
4-
from .evaluator import evaluate
5+
from .evaluator import evaluate, KEYWORDS
56
from .parser import parse
67
from .mytypes import Environment, Symbol
78

@@ -47,3 +48,35 @@ def test_define_variable(std_env: Environment) -> None:
4748
got = evaluate(parse(source), std_env)
4849
assert got is None
4950
assert std_env[Symbol('answer')] == 42
51+
52+
53+
# TODO: consider moving test below as a self test in evaluator.py
54+
55+
def test_declared_keywords():
56+
""" Check that the set of KEYWORDS is the same as
57+
the set of string constants in the first position of
58+
sequence patterns in the match/case inside evaluate()
59+
"""
60+
with open('evaluator.py') as source:
61+
tree = ast.parse(source.read())
62+
63+
cases = None
64+
for node in ast.walk(tree):
65+
match node:
66+
# find FunctionDef named 'evaluate'
67+
case ast.FunctionDef(name='evaluate', body=[_,
68+
ast.Match(cases=cases)
69+
]):
70+
break
71+
assert cases is not None
72+
73+
# collect string constants in the first position of sequence patterns
74+
found_keywords = set()
75+
for case in cases:
76+
match case.pattern:
77+
case ast.MatchSequence(
78+
patterns=[ast.MatchValue(ast.Constant(value=str(kw))), *_]):
79+
found_keywords.add(kw)
80+
81+
found_keywords = sorted(found_keywords)
82+
assert found_keywords == sorted(KEYWORDS)

mylis/mylis_3/mytypes.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44

55
# cannot use NewType because we need isinstance()
66
# to support (symbol? x) in environ.py
7-
class Symbol(str):
8-
pass
7+
Symbol: TypeAlias = str
98
Number: TypeAlias = int | float
109
Atom: TypeAlias = int | float | Symbol
1110
Expression: TypeAlias = Atom | list # type: ignore

0 commit comments

Comments
 (0)