| |   |
| 5 | 5 | pragma.syntax("0.9") |
| 6 | 6 | |
| 7 | 7 | def eParser := <elang:syntax.makeEParser> |
| def makeOuterNounExpr := <elang:evm.makeOuterNounExpr> |
| def makeFinalPattern := <elang:evm.makeFinalPattern> |
| 8 | 10 | |
| 9 | 11 | # These loaders are similar to the normal E <import> mechanism, except that |
| 10 | 12 | # they load code from a given directory rather than from classpath. |
| 11 | 13 | # |
| # You can specify extra bindings to be added to the scope of each imported file. |
| # This is useful for testing and for doing dependency injection. In addition, |
| # every loaded emaker file gets an extra <this> in scope, which is the loader |
| # itself. This allows it to import other files from the same module. |
| # You can specify extra bindings to be added to the environment (scope) of each |
| # imported file. This is useful for testing and for doing dependency injection. |
| 16 | 16 | # |
| # Notes: |
| # By convention, these bindings include the loader itself as <this>, allowing a |
| # file to import other files from the same module. |
| 18 | 19 | # |
| # - The loader will freely give out (a readonly version of) sourceDir to |
| # any emaker it loads that asks for it. This allows programs to find |
| # resource files (e.g. icons) easily. |
| # |
| # - Every file gets a scope that extends a single shared scope. The shared scope |
| # (which is the loader's own safeScope, plus scopeExtras) must not therefore |
| # contain objects that break confinement or confer authority, but this is not |
| # enforced. |
| # envExtras should not contain objects that break confinement or confer |
| # authority, but this is not enforced. Typically, they are all E loaders |
| # themselves, giving modules access to their dependencies. |
| 27 | 23 | def loaderAuthor(makeScope, makeTraceln) { |
| return def makeLoader(var sourceDir, scopeExtras :Map, fqnPrefix :String) { |
| return def makeLoader(sourceDir, envExtras :Map, fqnPrefix :String) { |
| 29 | 25 | #traceln(`creating loader for $fqnPrefix from $sourceDir`) |
| 30 | 26 | |
| sourceDir := sourceDir.deepReadOnly() |
| # There are several scope layouts: |
| # - our safeScope's layout |
| # - each loaded file's safeScope layout (with the file's FQName) |
| # - defaultScopeLayout (containing envExtras's names) |
| # - each loaded file's top-level scope layout (defaultScopeLayout + FQName) |
| 32 | 32 | |
| def loader |
| def realScopeExtras := scopeExtras.with("this__uriGetter", loader) |
| def theScope := makeScope.fromState(safeScope.getState() | realScopeExtras, fqnPrefix) |
| # There are also several environments (scopes): |
| # - our safeScope (from <import>; contains our private traceln) |
| # - each loaded file's safeScope (contains the file's traceln) |
| # - each loaded file's top-level scope (the file's safeScope + envExtras' values) |
| 36 | 37 | |
| # Create an extension of SafeScope with the right FQName, so __getAllegedType works. |
| # Create a ScopeLayout describing baseScope + envExtras |
| def makeScopeLayout(baseScope, FQName) { |
| var scopeLayout := baseScope.getScopeLayout() |
|
| var i := scopeLayout.getOuterCount() |
| require(i >= 0) |
| for name => value in envExtras { |
| def nounExpr := makeOuterNounExpr(null, name, i, null) |
| def nounPattern := makeFinalPattern(null, nounExpr, null, true, null) |
| scopeLayout with= (name, nounPattern) |
| i += 1 |
| } |
|
| return scopeLayout.nestOuter().withPrefix(FQName) |
| } |
|
| def getEvalContext(scope) { |
| # (passing 0 here is special; it returns the current context) |
| # See: http://www.eros-os.org/pipermail/e-lang/2010-February/013448.html |
| return scope.newContext(0) |
| } |
|
| # Create an extension of baseScope with the extended layout, |
| # and the right FQName (so __getAllegedType works). |
| 38 | 62 | def makeFileScope(baseScope, suffix) { |
| 39 | 63 | def fileFQName := fqnPrefix + suffix |
| def scopeLayout := baseScope.getScopeLayout().withPrefix(fileFQName + "$") |
| def fileTraceln := makeTraceln(fileFQName) |
| 41 | 65 | |
| def fileScope := baseScope.update(scopeLayout) |
| def evalContext := fileScope.newContext(0) |
| # Create the file's safeScope object |
| def safeOuters := getEvalContext(safeScope).outers() |
| def fileSafeScopeLayout := safeScope.getScopeLayout().withPrefix(fileFQName + "$") |
| def fileSafeScope := makeScope.outer(fileSafeScopeLayout, safeOuters) |
| 44 | 70 | |
| # We need to override traceln in each new file's scope. |
| def safeScopeNoun := scopeLayout.getNoun("safeScope") |
| def tracelnNoun := scopeLayout.getNoun("traceln") |
| tracelnNoun.initFinal(evalContext, makeTraceln(fileFQName)) |
| safeScopeNoun.initFinal(evalContext, fileScope) |
| # Override traceln in the new safeScope |
| def tracelnNoun := fileSafeScopeLayout.getNoun("traceln") |
| tracelnNoun.initFinal(getEvalContext(fileSafeScope), fileTraceln) |
| 50 | 74 | |
| # Make fileSafeScope.safeScope point to itself |
| def safeScopeNoun := fileSafeScopeLayout.getNoun("safeScope") |
| safeScopeNoun.initFinal(getEvalContext(fileSafeScope), fileSafeScope) |
|
| # Create the file's top-level environment |
| def fileScopeLayout := makeScopeLayout(baseScope, fileFQName + "$") |
| def fileScope := baseScope.update(fileScopeLayout) |
| def fileEvalContext := getEvalContext(fileScope) |
|
| # Override traceln in the new fileScope |
| def fileTracelnNoun := fileScopeLayout.getNoun("traceln") |
| fileTracelnNoun.initFinal(fileEvalContext, fileTraceln) |
|
| # Make fileSafeScope.safeScope point to the file's safeScope |
| def fileScopeNoun := fileScopeLayout.getNoun("safeScope") |
| fileScopeNoun.initFinal(fileEvalContext, fileSafeScope) |
|
| # Fill in the new slots with the values from envExtras |
| for name => value in envExtras { |
| def noun := fileScopeLayout.getNoun(name) |
| noun.initFinal(fileEvalContext, value) |
| } |
|
| 51 | 98 | return fileScope |
| 52 | 99 | } |
| 53 | 100 | |
| return bind loader { |
| return def loader { |
| 55 | 102 | to get(name) { |
| 56 | 103 | def src := sourceDir[`$name.emaker`].getTwine() |
| return eParser(src).eval(makeFileScope(theScope, name)) |
| return eParser(src).eval(makeFileScope(safeScope, name)) |
| 58 | 105 | } |
| 59 | 106 | |
| 60 | 107 | to getWithBase(pathName, baseScope) { |
| 61 | 108 | def i := pathName.lastIndexOf1('.') |
| 62 | 109 | def name := pathName(0, i) |
| 63 | 110 | def src := sourceDir[pathName].getTwine() |
| def scope := makeScope.fromState(baseScope | realScopeExtras, fqnPrefix) |
| return eParser(src).eval(makeFileScope(scope, name)) |
| } |
|
| to getRoot() { |
| return sourceDir |
| return eParser(src).eval(makeFileScope(baseScope, name)) |
| 70 | 112 | } |
| 71 | 113 | } |
| 72 | 114 | } |