[e-lang] Scope / EEnv split
Kevin Reid
kpreid at switchb.org
Thu Sep 9 03:46:25 PDT 2010
[Sorry, this message was delayed a couple days due to an outgoing mail
server screwup.]
On Sep 7, 2010, at 9:58, Thomas Leonard wrote:
> On Mon, 2010-05-24 at 09:50 -0400, Kevin Reid wrote:
>> On May 21, 2010, at 9:22, Thomas Leonard wrote:
>>
>>> Here, the Scope type fills both roles ("scope" and "newScope"). Some
>>> safej rules prevent E programs from accessing "internal" details
>>> (e.g.
>>> locals[]). This means you can't write the interpreter in E (using
>>> Scope), because E can't access these bits even when it needs them.
>>>
>>> If they were separate types ("Map env/Scope scope" rather than
>>> "Scope
>>> scope/Scope newScope"), the safej restrictions could go away and
>>> normal
>>> capability rules would apply (if you have a Scope, you can call all
>>> its
>>> methods, whether you're E or Java).
>>
>> It's perfectly all right for an interpreter to have a private
>> environment-with-extra-useful-bits type, but it should not be named
>> Scope. And the public environment type cannot be a Map because it has
>> additional metadata, as previously discussed.
>>
>> I agree that it would be useful not to require safej for what-is-
>> currently-known-as-Scope. The proper answer is to as you suggest make
>> it a separate type, but that should not be known as Scope. Perhaps
>> 'EvalEnv' would be a good name.
>
> OK, I've had a go at this. Here are the patches so far:
I'm not going to have time to take a serious look at this for a while
(I haven't looked at the source), but I'm liking the general direction
you describe.
This is a review of your comments, not of your changes. This DOES NOT
constitute approval to commit them.
> 1. Split Scope into Scope and EEnv, by making EEnv a copy of Scope
This is a good way to eliminate the "what the interpreter needs" vs
"what the exposed API should be" problem; insofar as it creates
efficiency issues, they would be raised by a compiling implementation
as well.
I am uncertain about whether the type should be named Env or EEnv.
> EExpr.eval (and EExpr.compile) now take an EEnv instead of a Scope.
> evalToPair still takes a Scope, because it's specific to the
> interpreter
> (a compiler probably wouldn't offer this?).
No. evalToPair, though poorly named, is the most-general interface and
eval should be merely a shortcut for evalToPair(...)[0]. evalToPair is
a highly useful tool for setting up environments and implementing
REPLs. Whether the implementation interprets or compiles is irrelevant.
Also, we want to move away from eval on E ASTs toward using the
elang.evm.Evaluator interface, because that allows "eval in other vat"
to be conveniently expressed. (That interface should be reviewed; the
first thing I note is its use of ConstMaps instead of Envs, which is
obsolete in the current world.)
> 2. Remove unnecessary EEnv/Scope methods
>
> e.g. there's no need for Scope to have a bindings/0 method, and no
> need
> for EEnv to have an EvalContext, etc
*nod*
> 3. Re-enabled LiteralSlotNounExpr and LiteralNounExpr optimisations
>
> These weren't used at all, but it appears that the intention was to
> use
> them for DeepFrozen Slots only. I don't understand what the plan was
> here, but I switched to using them for all slots in the environment.
> Will this cause problems? This means that EvalContext's outers only
> needs to contain values defined in the E code, not values from the
> environment.
A quick glance at the EoJ source suggests this is reasonable. E-on-CL
already does this type of optimization. Make sure that there are no
user-visible differences in the behavior of the object gotten from &foo.
> 4. Removed ScopeLayout from EEnv
>
> Now, an EEnv is just a map from names to Slots, plus a fqnPrefix.
It also must track which bindings were made in the innermost scope,
and discard that information upon nestOuter().
Remember, Envs must contain exactly the information such that it is
possible to write a correct but inefficient E interpreter with the
signature (for expressions):
eval(expr :EExpr, env :Env) :Tuple[any, Env]
> Note this causes a slight change:
>
> def x := 4
> e`x := 5`.eval(safeScope.withSlot("&x", &x))
>
> Before, this gave the run-time error "Final variables may not be
> changed"
> Now, it gives the compile-time error "Can't assign to final
> variable: x"
It's OK for this to change. Insofar as there is a problem here, it's
that the static detection exists at all; it should be a warning so
that E program semantics are not dependent on whether an entire
program or a fragment was evaluated.
> 5. Added ScopeLayoutEnv, to avoid creating lots of NounPatterns
>
> This layout just holds a reference to the EEnv and creates the
> Patterns
> from that on demand, rather than creating all the patterns at the
> start.
I'm not familiar enough with the EoJ evaluator to comment on this from
description.
> The overall effect of these changes is that safeScope is now an EEnv,
> whose state is simply:
>
> public class EEnv implements EIteratable {
> private final ConstMap myOuters;
> private final String myFqnPrefix;
>
> This means that EEnv's bindings are clearly immutable (unlike Scope,
> which is mutable but tries not to let E code see this, leading to the
> previously-reported security issue).
>
> It should therefore be safe to give safeScope to E code, without
> relying
> on taming.
Agreed. But note that (a) nestOuter must be supported; (b) safeScope
should be renamed to safeEnv at the same time as the rest of this
faint API upheaval.
Also, generally regarding Env, it should have provision for supporting
the bindings used for guard-based auditing; take a look at the methods
already present in E-on-CL for an example. (Though, it would be worth
going over what we need to support and designing a clean interface for
all implementations to support.)
> This should make the API easier to understand (e.g. it's now clear
> that
> you can't pass locals to EExpr.eval, because EEnv can't contain any).
I don't understand what you mean by "passing locals". This code must
succeed (even if the API changes, there must be some equivalent):
var x := 1
someExpr.eval(safeEnv.withSlot("x", &x))
> - Clean up EEnv API to avoid adding and stripping "&" all over the
> place?
Yes. String munging in environment interfaces is unsafe, messy, and
slow. If we decide that it's convenient to have interfaces that take
values ordinarily and slots optionally, it should be done using a non-
string type; e.g. instead of the current
makeScope.fromState([
"x" => x,
"&y" => &y,
], fqnp)
have
makeScope.fromState([
"x" => x,
slotname("y") => &y,
], fqnp)
or perhaps even:
makeScope.fromState([
epatt`x` => x,
epatt`&y` => &y,
], fqnp)
> Does this look like a reasonable approach?
See above for details :)
--
Kevin Reid <http://switchb.org/kpreid/>
More information about the e-lang
mailing list