Commit 4797228524e785f9c30759855da3b4adabc58078

Updated ELoaderAuthor based on feedback from Kevin Reid

- safeScope no longer contains the extra bindings.
- Construct the new environments without using Scope.fromState.
- Use "env" instead of "scope" in a few places.
- Added nestOuter call, so that bindings can be overridden.

- <this> is not added automatically. Pass it explicitly if desired.
- getRoot() has been removed. Extra methods can be added to <this>
  by extending it and passing the new object as <this>.
  
1111myObject in its safeScope:
1212
1313? def myObject := 42
14? def <loader> := makeELoader(<file:.>, [ => myObject ], "updoc$")
14? def <this> := makeELoader(<file:.>, [ => myObject, => <this> ], "updoc$")
1515# value: <loader>
1616
17Use the loader to load a test file:
17Use the this to load a test file. myObject is in the file's top-level environment:
1818
19? def obj := <loader:test/A>
19? def obj := <this:test/A>
2020# stdout: updoc$test/A: myObject == 42
21# updoc$test/A: safeScope.myObject == 42
2221#
2322
23However, myObject is not in A's safeScope:
24
25? obj.getSafeScope()["myObject"]
26# problem: <NullPointerException: Internal: Variable definition not found: myObject>
27
28safeScope does contain traceln:
29
30? obj.getSafeScope()["traceln"]("Hi")
31# stdout: updoc$test/A: Hi
32#
33
2434Check the loaded object has the correct type:
2535
2636? obj.__getAllegedType().getFQName()
4040
4141? def b := obj.loadB()
4242# stdout: updoc$test/B: B's myObject == 42
43# updoc$test/A: loaded B
4344#
4445
4546# value: <objB>
  
55pragma.syntax("0.9")
66
77def eParser := <elang:syntax.makeEParser>
8def makeOuterNounExpr := <elang:evm.makeOuterNounExpr>
9def makeFinalPattern := <elang:evm.makeFinalPattern>
810
911# These loaders are similar to the normal E <import> mechanism, except that
1012# they load code from a given directory rather than from classpath.
1113#
12# You can specify extra bindings to be added to the scope of each imported file.
13# This is useful for testing and for doing dependency injection. In addition,
14# every loaded emaker file gets an extra <this> in scope, which is the loader
15# itself. This allows it to import other files from the same module.
14# You can specify extra bindings to be added to the environment (scope) of each
15# imported file. This is useful for testing and for doing dependency injection.
1616#
17# Notes:
17# By convention, these bindings include the loader itself as <this>, allowing a
18# file to import other files from the same module.
1819#
19# - The loader will freely give out (a readonly version of) sourceDir to
20# any emaker it loads that asks for it. This allows programs to find
21# resource files (e.g. icons) easily.
22#
23# - Every file gets a scope that extends a single shared scope. The shared scope
24# (which is the loader's own safeScope, plus scopeExtras) must not therefore
25# contain objects that break confinement or confer authority, but this is not
26# enforced.
20# envExtras should not contain objects that break confinement or confer
21# authority, but this is not enforced. Typically, they are all E loaders
22# themselves, giving modules access to their dependencies.
2723def loaderAuthor(makeScope, makeTraceln) {
28 return def makeLoader(var sourceDir, scopeExtras :Map, fqnPrefix :String) {
24 return def makeLoader(sourceDir, envExtras :Map, fqnPrefix :String) {
2925 #traceln(`creating loader for $fqnPrefix from $sourceDir`)
3026
31 sourceDir := sourceDir.deepReadOnly()
27 # There are several scope layouts:
28 # - our safeScope's layout
29 # - each loaded file's safeScope layout (with the file's FQName)
30 # - defaultScopeLayout (containing envExtras's names)
31 # - each loaded file's top-level scope layout (defaultScopeLayout + FQName)
3232
33 def loader
34 def realScopeExtras := scopeExtras.with("this__uriGetter", loader)
35 def theScope := makeScope.fromState(safeScope.getState() | realScopeExtras, fqnPrefix)
33 # There are also several environments (scopes):
34 # - our safeScope (from <import>; contains our private traceln)
35 # - each loaded file's safeScope (contains the file's traceln)
36 # - each loaded file's top-level scope (the file's safeScope + envExtras' values)
3637
37 # Create an extension of SafeScope with the right FQName, so __getAllegedType works.
38 # Create a ScopeLayout describing baseScope + envExtras
39 def makeScopeLayout(baseScope, FQName) {
40 var scopeLayout := baseScope.getScopeLayout()
41
42 var i := scopeLayout.getOuterCount()
43 require(i >= 0)
44 for name => value in envExtras {
45 def nounExpr := makeOuterNounExpr(null, name, i, null)
46 def nounPattern := makeFinalPattern(null, nounExpr, null, true, null)
47 scopeLayout with= (name, nounPattern)
48 i += 1
49 }
50
51 return scopeLayout.nestOuter().withPrefix(FQName)
52 }
53
54 def getEvalContext(scope) {
55 # (passing 0 here is special; it returns the current context)
56 # See: http://www.eros-os.org/pipermail/e-lang/2010-February/013448.html
57 return scope.newContext(0)
58 }
59
60 # Create an extension of baseScope with the extended layout,
61 # and the right FQName (so __getAllegedType works).
3862 def makeFileScope(baseScope, suffix) {
3963 def fileFQName := fqnPrefix + suffix
40 def scopeLayout := baseScope.getScopeLayout().withPrefix(fileFQName + "$")
64 def fileTraceln := makeTraceln(fileFQName)
4165
42 def fileScope := baseScope.update(scopeLayout)
43 def evalContext := fileScope.newContext(0)
66 # Create the file's safeScope object
67 def safeOuters := getEvalContext(safeScope).outers()
68 def fileSafeScopeLayout := safeScope.getScopeLayout().withPrefix(fileFQName + "$")
69 def fileSafeScope := makeScope.outer(fileSafeScopeLayout, safeOuters)
4470
45 # We need to override traceln in each new file's scope.
46 def safeScopeNoun := scopeLayout.getNoun("safeScope")
47 def tracelnNoun := scopeLayout.getNoun("traceln")
48 tracelnNoun.initFinal(evalContext, makeTraceln(fileFQName))
49 safeScopeNoun.initFinal(evalContext, fileScope)
71 # Override traceln in the new safeScope
72 def tracelnNoun := fileSafeScopeLayout.getNoun("traceln")
73 tracelnNoun.initFinal(getEvalContext(fileSafeScope), fileTraceln)
5074
75 # Make fileSafeScope.safeScope point to itself
76 def safeScopeNoun := fileSafeScopeLayout.getNoun("safeScope")
77 safeScopeNoun.initFinal(getEvalContext(fileSafeScope), fileSafeScope)
78
79 # Create the file's top-level environment
80 def fileScopeLayout := makeScopeLayout(baseScope, fileFQName + "$")
81 def fileScope := baseScope.update(fileScopeLayout)
82 def fileEvalContext := getEvalContext(fileScope)
83
84 # Override traceln in the new fileScope
85 def fileTracelnNoun := fileScopeLayout.getNoun("traceln")
86 fileTracelnNoun.initFinal(fileEvalContext, fileTraceln)
87
88 # Make fileSafeScope.safeScope point to the file's safeScope
89 def fileScopeNoun := fileScopeLayout.getNoun("safeScope")
90 fileScopeNoun.initFinal(fileEvalContext, fileSafeScope)
91
92 # Fill in the new slots with the values from envExtras
93 for name => value in envExtras {
94 def noun := fileScopeLayout.getNoun(name)
95 noun.initFinal(fileEvalContext, value)
96 }
97
5198 return fileScope
5299 }
53100
54 return bind loader {
101 return def loader {
55102 to get(name) {
56103 def src := sourceDir[`$name.emaker`].getTwine()
57 return eParser(src).eval(makeFileScope(theScope, name))
104 return eParser(src).eval(makeFileScope(safeScope, name))
58105 }
59106
60107 to getWithBase(pathName, baseScope) {
61108 def i := pathName.lastIndexOf1('.')
62109 def name := pathName(0, i)
63110 def src := sourceDir[pathName].getTwine()
64 def scope := makeScope.fromState(baseScope | realScopeExtras, fqnPrefix)
65 return eParser(src).eval(makeFileScope(scope, name))
66 }
67
68 to getRoot() {
69 return sourceDir
111 return eParser(src).eval(makeFileScope(baseScope, name))
70112 }
71113 }
72114 }
  
11traceln(`myObject == $myObject`)
22
3safeScope["traceln"](`safeScope.myObject == ${safeScope["myObject"]}`)
4
53def obj {
64 to loadB() {
7 return <this:test/B>
5 def b := <this:test/B>
6 traceln("loaded B")
7 return b
8 }
9
10 to getSafeScope() {
11 return safeScope
812 }
913}