Skip to content

Commit

Permalink
Implement lexical scoping
Browse files Browse the repository at this point in the history
  • Loading branch information
WyattBlue committed Jul 25, 2023
1 parent 7bb559e commit 1f83e51
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 24 deletions.
51 changes: 27 additions & 24 deletions auto_editor/lang/palet.py
Original file line number Diff line number Diff line change
Expand Up @@ -738,16 +738,18 @@ def palet_assert(expr: object, msg: str | bool = False) -> None:
class UserProc(Proc):
"""A user-defined procedure."""

__slots__ = ("parms", "body", "name", "arity", "contracts")
__slots__ = ("env", "parms", "body", "name", "arity", "contracts")

def __init__(
self,
env: Env,
name: str,
parms: list,
body: list,
contracts: list[Any] | None = None,
eat_last: bool = False,
):
self.env = env
self.parms = [f"{p}" for p in parms]
self.body = body
self.name = name
Expand All @@ -760,23 +762,17 @@ def __init__(
self.contracts = contracts

def __call__(self, *args: Any) -> Any:
saved_env: Env = {}
for item in self.parms:
if item in env:
saved_env[item] = env[item]

if self.arity[1] is None:
largs = list(args)
args = tuple([largs[len(self.parms) - 1 :]])

env.update(zip(self.parms, args))
inner_env = self.env.copy()
inner_env.update(dict(zip(self.parms, args)))

for item in self.body[0:-1]:
my_eval(env, item)
result = my_eval(env, self.body[-1])
my_eval(inner_env, item)
result = my_eval(inner_env, self.body[-1])

for item in self.parms:
del env[item]
env.update(saved_env)
return result


Expand Down Expand Up @@ -823,7 +819,7 @@ def syn_lambda(env: Env, node: list) -> UserProc:
if not isinstance(node[1], list):
raise MyError(f"{node[0]}: bad syntax")

return UserProc("", node[1], node[2:]) # parms, body
return UserProc(env, "", node[1], node[2:]) # parms, body


def syn_define(env: Env, node: list) -> None:
Expand All @@ -844,7 +840,7 @@ def syn_define(env: Env, node: list) -> None:
parameters = node[1][1:]

body = node[2:]
env[n] = UserProc(n, parameters, body, eat_last=eat_last)
env[n] = UserProc(env, n, parameters, body, eat_last=eat_last)
return None
elif type(node[1]) is not Sym:
raise MyError(f"{node[0]}: must be an identifier")
Expand All @@ -862,7 +858,7 @@ def syn_define(env: Env, node: list) -> None:
):
parameters = node[2][1]
body = node[2][2:]
env[n] = UserProc(n, parameters, body)
env[n] = UserProc(env, n, parameters, body)
else:
for item in node[2:-1]:
my_eval(env, item)
Expand Down Expand Up @@ -898,7 +894,7 @@ def syn_definec(env: Env, node: list) -> None:

contracts.append(con)

env[n] = UserProc(n, parameters, node[2:], contracts)
env[n] = UserProc(env, n, parameters, node[2:], contracts)
return None


Expand Down Expand Up @@ -1156,12 +1152,13 @@ def my_eval(env: Env, node: object) -> Any:
check_args(oper.name, values, (1, 1), None)
else:
check_args(oper.name, values, oper.arity, oper.contracts)

return oper(*values)

return node


env: Env = {
env: Env = {}
env.update({
# constants
"true": True,
"false": False,
Expand Down Expand Up @@ -1221,13 +1218,19 @@ def my_eval(env: Env, node: object) -> Any:
"=": Proc("=", equal_num, (1, None), [is_num]),
"eq?": Proc("eq?", lambda a, b: a is b, (2, 2)),
"equal?": Proc("equal?", is_equal, (2, 2)),
"zero?": UserProc("zero?", ["z"], [[Sym("="), Sym("z"), 0]], [is_num]),
"positive?": UserProc("positive?", ["x"], [[Sym(">"), Sym("x"), 0]], [is_real]),
"negative?": UserProc("negative?", ["x"], [[Sym("<"), Sym("x"), 0]], [is_real]),
"zero?": UserProc(env, "zero?", ["z"], [[Sym("="), Sym("z"), 0]], [is_num]),
"positive?": UserProc(
env, "positive?", ["x"], [[Sym(">"), Sym("x"), 0]], [is_real]
),
"negative?": UserProc(
env, "negative?", ["x"], [[Sym("<"), Sym("x"), 0]], [is_real]
),
"even?": UserProc(
"even?", ["n"], [[Sym("zero?"), [Sym("mod"), Sym("n"), 2]]], [is_int]
env, "even?", ["n"], [[Sym("zero?"), [Sym("mod"), Sym("n"), 2]]], [is_int]
),
"odd?": UserProc(
env, "odd?", ["n"], [[Sym("not"), [Sym("even?"), Sym("n")]]], [is_int]
),
"odd?": UserProc("odd?", ["n"], [[Sym("not"), [Sym("even?"), Sym("n")]]], [is_int]),
">=/c": Proc(">=/c", gte_c, (1, 1), [is_real]),
">/c": Proc(">/c", gt_c, (1, 1), [is_real]),
"<=/c": Proc("<=/c", lte_c, (1, 1), [is_real]),
Expand Down Expand Up @@ -1333,7 +1336,7 @@ def my_eval(env: Env, node: object) -> Any:
"var-exists?": Proc("var-exists?", lambda sym: sym.val in env, (1, 1), [is_symbol]),
"rename": Syntax(syn_rename),
"delete": Syntax(syn_delete),
}
})


def interpret(env: Env, parser: Parser) -> list:
Expand Down
1 change: 1 addition & 0 deletions auto_editor/subcommands/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,7 @@ def cases(*cases: tuple[str, Any]) -> None:

def palet_scripts():
run.raw(["palet", "resources/scripts/maxcut.pt"])
run.raw(["palet", "resources/scripts/scope.pt"])

tests = []

Expand Down
21 changes: 21 additions & 0 deletions resources/scripts/scope.pt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env auto-editor palet

; Enforce lexical scoping

(define (f x) (lambda (y) (+ x y)))
(assert (equal? ((f 10) 12) 22))

; Test that variables do not leak scope

(define (outer a)
(define (inner1 b)
(define (inner2 c) c)
(inner2 b)
)
(inner1 a)
)

(outer 13)
(assert (not (var-exists? 'a)))
(assert (not (var-exists? 'b)))
(assert (not (var-exists? 'c)))

0 comments on commit 1f83e51

Please sign in to comment.