4 --------------------------------------------------------------------------------
7 -- * H.alpha is the `Local{ } (or `Set{ }) statement which will
8 -- receive the alpha-conversions required to restore the free
9 -- variables of the transformed term. For instance,
10 -- H+{print(1)} will be transformed into +{.1.X.print(1)},
11 -- and alpha will contain +{local -{`Id '.1.X.print} = print }.
12 -- alpha is reused and augmented by successive calls to H().
14 -- * H.side contains 'inside', 'outside', 'both' or nil (equivalent to
15 -- 'both'). It indicates the kind of hygienization that's to be
18 -- * H.keep contain a set of free variable names which must not be
21 -- * H.kind is the kind of walker that must be used ('expr', 'stat',
22 -- 'block'...) and defaults to 'guess'.
24 -- * H:set (field, val) sets a field in H and returns H, so that calls
25 -- can be chained, e.g.:
26 -- > H:set(keep, {'print'}):set('side', outside)+{print(x)}
28 -- * H:reset(field) sets a field to nil, and returns the value of that
29 -- field prior to nilification.
30 --------------------------------------------------------------------------------
32 H = { } --setmetatable(H, H)
34 H.template = { alpha = { } }
36 --------------------------------------------------------------------------------
38 --------------------------------------------------------------------------------
40 local instance = table.deep_copy(self.template)
41 if x then instance <- x end
42 setmetatable(instance, self)
46 --------------------------------------------------------------------------------
48 --------------------------------------------------------------------------------
49 function H:__call (ast)
50 assert (type(ast)=='table', "H expects an AST")
52 local local_renames -- only set if inside hygienization's required
54 -----------------------------------------------------------------------------
55 -- kind of hygienization(s) to perform: h_inseide and/or h_outside
56 -----------------------------------------------------------------------------
57 local h_inside, h_outside do
58 local side = self.side or 'both'
59 h_inside = side=='inside' or side=='both'
60 h_outside = side=='outside' or side=='both'
63 -----------------------------------------------------------------------------
64 -- Initialize self.keep:
65 -- self.keep is a dictionary of free var names to be protected from capture
66 -----------------------------------------------------------------------------
69 -- If there's no self.keep, that's an empty dictionary
70 if not k then k = { }; self.keep = k
71 -- If it's a string, consider it as a single-element dictionary
72 elseif type(k)=='string' then k = { [k] = true }; self.keep=k
73 -- If there's a list-part in self.keep, transpose it:
74 else for i, v in ipairs(k) do k[v], k[i] = true, nil end end
77 -----------------------------------------------------------------------------
78 -- Config skeleton for the id walker
79 -----------------------------------------------------------------------------
80 local cfg = { expr = { }, stat = { }, id = { } }
82 -----------------------------------------------------------------------------
83 -- Outside hygienization: all free variables are renamed to fresh ones,
84 -- and self.alpha is updated to contain the assignments required to keep
85 -- the AST's semantics.
86 -----------------------------------------------------------------------------
88 local alpha = self.alpha
90 -- free_vars is an old_name -> new_name dictionary computed from alpha:
91 -- self.alpha is not an efficient representation for searching.
92 if not alpha then alpha = { }; self.alpha = alpha end
93 -- FIXME: alpha should only be overridden when there actually are some
95 if #alpha==0 then alpha <- `Local{ { }, { } } end
96 local new, old = unpack(alpha)
99 assert (#new==#old, "Invalid alpha list")
101 assert (old[i].tag=='Id' and #old[i]==1, "Invalid lhs in alpha list")
102 assert (new[i].tag=='Id' and #new[i]==1, "Invalid rhs in alpha list")
103 free_vars[old[i][1]] = new[i][1]
106 -- Rename free variables that are not supposed to be captured.
107 function cfg.id.free (id)
108 local old_name = id[1]
109 if self.keep[old_name] then return end
110 local new_name = free_vars[old_name]
112 new_name = mlp.gensym('X.'..old_name)[1]
113 free_vars[old_name] = new_name
114 table.insert(alpha[1], `Id{new_name})
115 table.insert(alpha[2], `Id{old_name})
121 -----------------------------------------------------------------------------
122 -- Inside hygienization: rename all local variables and their ocurrences.
123 -----------------------------------------------------------------------------
126 ----------------------------------------------------------------
127 -- Renamings can't performed on-the-spot, as it would
128 -- transiently break the link between binders and bound vars,
129 -- thus preventing the algo to work. They're therefore stored
130 -- in local_renames, and performed after the whole tree has been
132 ----------------------------------------------------------------
134 local_renames = { } -- `Id{ old_name } -> new_name
135 local bound_vars = { } -- binding statement -> old_name -> new_name
137 ----------------------------------------------------------------
138 -- Give a new name to newly created local vars, store it in
140 ----------------------------------------------------------------
141 function cfg.binder (id, binder)
142 if id.h_boundary then return end
143 local old_name = id[1]
144 local binder_table = bound_vars[binder]
145 if not binder_table then
147 bound_vars[binder] = binder_table
149 local new_name = mlp.gensym('L.'..old_name)[1]
150 binder_table[old_name] = new_name
151 local_renames[id] = new_name
154 ----------------------------------------------------------------
155 -- List a bound var for renaming. The new name has already been
156 -- chosen and put in bound_vars by cfg.binder().
157 ----------------------------------------------------------------
158 function cfg.id.bound (id, binder)
159 if id.h_boundary then return end
160 local old_name = id[1]
161 local new_name = bound_vars[binder][old_name]
162 --.log(bound_vars[binder])
163 assert(new_name, "no alpha conversion for a bound var?!")
164 local_renames[id] = new_name
168 -----------------------------------------------------------------------------
169 -- Don't traverse subtrees marked by '!'
170 -----------------------------------------------------------------------------
171 local cut_boundaries = |x| x.h_boundary and 'break' or nil
172 cfg.stat.down, cfg.expr.down = cut_boundaries, cut_boundaries
174 -----------------------------------------------------------------------------
175 -- The walker's config is ready, let's go.
176 -- After that, ids are renamed in ast, free_vars and bound_vars are set.
177 -----------------------------------------------------------------------------
178 walk_id [self.kind or 'guess'] (cfg, ast)
180 if h_inside then -- Apply local name changes
181 for id, new_name in pairs(local_renames) do id[1] = new_name end
187 --------------------------------------------------------------------------------
188 -- Return H to allow call chainings
189 --------------------------------------------------------------------------------
190 function H:set(field, val)
191 local t = type(field)
192 if t=='string' then self[field]=val
193 elseif t=='table' then self <- field
194 else error("Can't set H, field arg can't be of type "..t) end
198 --------------------------------------------------------------------------------
199 -- Return the value before reset
200 --------------------------------------------------------------------------------
201 function H:reset(field)
202 if type(field) ~= 'string' then error "Can only reset H string fields" end
208 -- local function commit_locals_to_chunk(x)
209 -- local alpha = H:reset 'alpha'
210 -- --$log ('commit locals', x, alpha, 'nohash')
211 -- if not alpha or not alpha[1][1] then return end
212 -- if not x then return alpha end
213 -- table.insert(x, 1, alpha)
216 -- mlp.chunk.transformers:add (commit_locals_to_chunk)