-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Free variable analysis on fixed structure
- Loading branch information
1 parent
2d9ee95
commit 863a61d
Showing
5 changed files
with
151 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
>>> from dsn.form_analysis.structure import MalformedForm, ValueForm, VariableForm, QuoteForm, DefineForm, IfForm, LambdaForm, SequenceForm, ApplicationForm | ||
>>> from dsn.form_analysis.structure import FormList, Symbol, SymbolList | ||
|
||
>>> from dsn.form_analysis.free_variables import free_variables | ||
>>> from dsn.form_analysis.constants import VT_INTEGER | ||
|
||
>>> free_variables(ValueForm(VT_INTEGER, 32)) | ||
set() | ||
|
||
>>> free_variables(VariableForm(Symbol("a"))) | ||
{'a'} | ||
|
||
>>> sorted(free_variables(SequenceForm(FormList([ | ||
... VariableForm(Symbol("a")), | ||
... VariableForm(Symbol("b")), | ||
... ])))) | ||
['a', 'b'] | ||
|
||
>>> free_variables(LambdaForm( | ||
... SymbolList([Symbol("bound_by_param")]), | ||
... FormList([ | ||
... DefineForm(Symbol("bound_by_define"), VariableForm(Symbol("a"))), | ||
... VariableForm(Symbol("bound_by_param")), | ||
... VariableForm(Symbol("bound_by_define")), | ||
... ]))) | ||
{'a'} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
from dsn.form_analysis.structure import ( | ||
VariableForm, | ||
LambdaForm, | ||
IfForm, | ||
DefineForm, | ||
ApplicationForm, | ||
SequenceForm, | ||
) | ||
|
||
from dsn.form_analysis.somewhere import collect_definitions | ||
|
||
|
||
def free_variables(form): | ||
# return type? let's start with python-lists of symbols. | ||
# Or rather: a set! | ||
|
||
if isinstance(form, VariableForm): | ||
return set([form.symbol.symbol]) | ||
|
||
if isinstance(form, LambdaForm): | ||
a = set.union(*[free_variables(f) for f in form.body]) | ||
b = set([d.symbol.symbol for d in collect_definitions(form)]) | ||
c = set([p.symbol for p in form.parameters]) | ||
return a - b - c | ||
|
||
# When considering a DefineForm, we might ask whether the form.symbol should be excluded from the result or not. | ||
# Arguments could be made for both decisions; which is unfortunate. | ||
|
||
# The confusion stems from the fact that choice of symbol influences the surrounding scope, but not the value of the | ||
# DefineForm itself (the value resulting from evaluating Define differs from implementation to implementation, | ||
# indicating that it's a non-central concept in the first place) | ||
|
||
# Consider e.g. this definition: "A variable, V, is 'free in an expression', E, if the meaning of E is _changed_ by | ||
# the uniform replacement of a variable, W, not occurring in E, for every occurrence of V in E."; note that the | ||
# definition doesn't help us very much, because it hinges on what the _meaning_ of the expression is... in the case | ||
# under consideration the expression will evaluate the same way when replacing the definition's symbol... but the | ||
# effects on the surrounding scope are _not_ the same. | ||
|
||
# Given that observation, it could be argued that the problem is simply to allow for "Define" as a separate form, | ||
# rather than as a property of a lambda or let. I'll take this as a datapoint for now, rather than banning define. | ||
|
||
# Rephrasing the question "is the variable free" as "is the variable bound to an enclosing scope" makes it easier: | ||
# in that case the answer is "form.symbol should not be excluded from the result", because if the symbol occurs on | ||
# the RHS it's bound to an enclosing scope (even though it's bound to that scope precisely by the LHS). | ||
|
||
# Another approach could be to consider "why are you interested in free variables". The answer is "to determine how | ||
# information from "the world" flows into the expression. Same answer, for the same reason. | ||
# Counterpoint: when doing an analysis for unused definitions, you actually want to ignore things that are defined | ||
# in terms of themselves (because that fact alone does not make it so that the defintion is used elsewhere). | ||
|
||
# In any case, I'm settling on "no special treatment of Define's LHS" for now. | ||
|
||
return general_means_of_collection(form, free_variables, lambda l: set.union(*l), set()) | ||
|
||
|
||
def general_means_of_collection(form, f, combine, identity): | ||
# Apply thing_to_do on all this form's child (but no lower descendants) forms. | ||
|
||
# The behavior for MalformedForm is TBD; in any case it has no child forms | ||
# VariableForm, ValueForm & QuoteForm have no child-forms | ||
|
||
if isinstance(form, IfForm): | ||
return combine([f(form.predicate), f(form.consequent), f(form.alternative)]) | ||
|
||
if isinstance(form, DefineForm): | ||
return f(form.definition) | ||
|
||
if isinstance(form, LambdaForm): | ||
return combine([f(child) for child in form.body]) | ||
|
||
if isinstance(form, ApplicationForm): | ||
return combine([f(form.procedure)] + [f(child) for child in form.arguments]) | ||
|
||
if isinstance(form, SequenceForm): | ||
return combine([f(child) for child in form.sequence]) | ||
|
||
return identity |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# `somewhere.py` means: I don't know how to organize these functions into modules yet. | ||
|
||
# ## Define - restrictions on where it can appear: | ||
|
||
# With respect to MIT Scheme, we restrict the locations where "define" is a legal form; in particular: | ||
# | ||
# * Define must be a direct child (not just any descendant) of a lambda. | ||
# * Redefinitions are illegal. | ||
# * Definitions must be at the start of a lambda's body. (not done yet) | ||
# * Definitions must not rely on definitions from the same scope to be evaluated (not done yet) | ||
# | ||
# (As it stands, these restrictions are not yet checked; but the rest of the implementation already relies on it) | ||
# | ||
# I believe that the above restrictions are useful in the sense that they lead to less errors; they also happen to make | ||
# implementation easier. | ||
# | ||
# In SICP, I could find the following notes that hint at the fact that the above restrictions are useful (although they | ||
# are not part of the SICP implementation): | ||
# | ||
# * p. 241, footnote: "If there is already a binding for the variable in the current frame, then the binding is changed. | ||
# [..] some people prefer redefinitions of existing symbols to signal errors" * p. 388, "4.1.6. Internal definitions" | ||
# talks about the problems that arise when not having your definitions at the start of the lambda; especially when the | ||
# definitions use other variables from the present scope, and when the value of the definitions is calculated before all | ||
# variables of the scope are defined. | ||
# | ||
# W.R.T. non-top-level definitions I could not find an explicit discussion of the topic, however. | ||
# | ||
# However, it seems that I'm not the first one to think of the above restriction, e.g. from Racket (a Scheme)'s | ||
# specification: | ||
# | ||
# "11.2 Definitions - Definitionsmay appear within a <top-level body>, at the top of a <library body>, or at the top of | ||
# a <body>" | ||
# | ||
# A similar question (as of yet unresolved) is: will we allow define to redefine parameters from the same scope (i.e. | ||
# name-collissions between paramters and defines in the same scope). In Racket, this is legal: | ||
# ((lambda (foo) (define foo 1) foo) 2) | ||
|
||
|
||
from dsn.form_analysis.structure import DefineForm | ||
|
||
|
||
def collect_definitions(lambda_form): | ||
# TODO: determine type (list of forms, FormList, something else?) | ||
return [f for f in lambda_form.body if isinstance(f, DefineForm)] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters