forked from monte-language/typhon
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathloader.mt
228 lines (199 loc) · 9.96 KB
/
loader.mt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
object moduleGraphUnsatisfiedExit as DeepFrozen:
"An unsatisfied exit point on a module configuration graph."
object moduleGraphLiveExit as DeepFrozen:
"A live (non-`DeepFrozen`) exit point on a module configuration graph."
interface Config :DeepFrozen {}
def makeModuleConfiguration(module :DeepFrozen,
knownDependencies :Map[Str, DeepFrozen]) as DeepFrozen:
return object moduleConfiguration as DeepFrozen implements Config:
"Information about the metadata and state of a module."
to _printOn(out):
out.print(`moduleConfiguration($knownDependencies)`)
to getModule() :DeepFrozen:
return module
to dependencyNames() :List[Str]:
return module.dependencies()
to dependencyMap() :Map[Str, DeepFrozen]:
return knownDependencies
to withDependency(petname :Str, dep :DeepFrozen) :DeepFrozen:
return makeModuleConfiguration(module,
knownDependencies.with(petname, dep))
to withLiveDependency(petname :Str) :DeepFrozen:
return makeModuleConfiguration(module,
knownDependencies.with(petname, moduleGraphLiveExit))
to withMissingDependency(petname :Str) :DeepFrozen:
return makeModuleConfiguration(module,
knownDependencies.with(petname, moduleGraphUnsatisfiedExit))
to run(loader):
return module(loader)
def ModuleStructure :DeepFrozen := Pair[Map[Str, Map[Str, Any]], NullOk[Config]]
traceln(`Defining main`)
def loaderMain():
def collectedTests := [].diverge()
def collectedBenches := [].diverge()
object testCollector:
to get(locus):
return def testBucket(tests):
for t in (tests):
collectedTests.push([locus, t])
object benchCollector:
to get(locus):
return def benchBucket(aBench, name :Str):
collectedBenches.push([`$locus: $name`, aBench])
def makeLoader(imports :Map):
return object loader:
to "import"(name):
return imports[name]
def makeModuleAndConfiguration(modname,
=> collectTests := false,
=> collectBenchmarks := false):
def depMap := [].asMap().diverge()
def subload(modname :Str):
if (modname == "unittest"):
if (collectTests):
trace(`test collector invoked`)
return [["unittest" => ["unittest" => testCollector[modname]]], null]
else:
return [["unittest" => ["unittest" => fn _ {null}]], null]
if (modname == "bench"):
if (collectBenchmarks):
return [["bench" => ["bench" => benchCollector[modname]]], null]
else:
return [["bench" => ["bench" => fn _, _ {null}]], null]
def fname := _findTyphonFile(modname)
if (fname == null):
throw(`Unable to locate $modname`)
def loadModuleFile():
def code := makeFileResource(fname).getContents()
return when (code) ->
try:
def modObj := astEval(code, safeScope)
depMap[modname] := makeModuleConfiguration(modObj, [].asMap())
catch problem:
traceln(`Unable to eval file ${M.toQuote(fname)}`)
traceln.exception(problem)
throw(problem)
var config := depMap.fetch(modname, loadModuleFile)
return when (config) ->
def deps := promiseAllFulfilled([for d in (config.dependencyNames())
subload(d)])
when (deps) ->
# Fill in the module configuration.
def depNames := config.dependencyNames()
for depName in (depNames):
if (depMap.contains(depName)):
def dep := depMap[depName]
# If the dependency is DF, then add it to the map.
# Otherwise, put in the stub.
if (dep =~ frozenDep :DeepFrozen):
config withDependency= (depName, frozenDep)
else:
config withLiveDependency= (depName)
else:
config withMissingDependency= (depName)
# Update the dependency map with the latest config.
depMap[modname] := config
def pre := collectedTests.size()
var imports := [].asMap()
for [importable, _] in (deps :List[ModuleStructure]):
imports |= importable
def module := config(makeLoader(imports))
if (collectTests):
traceln(`collected ${collectedTests.size() - pre} tests`)
[[modname => module], config]
def moduleAndConfig := subload(modname)
return when (moduleAndConfig) ->
def [[(modname) => module], config] := (moduleAndConfig :ModuleStructure)
[module, config]
def args := currentProcess.getArguments().slice(2)
traceln(`Loader args: $args`)
def usage := "Usage: loader run <modname> <args> | loader test <modname>"
if (args.size() < 1):
throw(usage)
return switch (args):
match [=="run", modname] + subargs:
traceln(`Loading $modname`)
def exps := makeModuleAndConfiguration(modname)
when (exps) ->
traceln(`Loaded $modname as $exps`)
def [module, _] := exps
def excludes := ["typhonEval", "_findTyphonFile", "bench"]
# Leave out loader-only objects.
def unsafeScopeValues := [for `&&@n` => &&v in (unsafeScope)
? (!excludes.contains(n))
n => v]
# We don't care about config or anything that isn't the
# entrypoint named `main`.
def [=> main] | _ := module
M.call(main, "run", [subargs], unsafeScopeValues)
match [=="dot", modname] + subargs:
def tubes := makeModuleAndConfiguration("lib/tubes")
when (tubes) ->
# An unconventional import statement, to be sure.
def [[=> makeUTF8EncodePump,
=> makePumpTube,
] | _, _] := tubes
def stdout := makePumpTube(makeUTF8EncodePump())
stdout<-flowTo(makeStdOut())
def exps := makeModuleAndConfiguration(modname)
when (exps) ->
# We only care about the config.
def [_, topConfig] := exps
stdout.receive(`digraph "$modname" {$\n`)
# Iteration order doesn't really matter.
def stack := [[modname, topConfig]].diverge()
while (stack.size() != 0):
def [name, config] := stack.pop()
for depName => depConfig in (config.dependencyMap()):
stdout.receive(` "$name" -> "$depName";$\n`)
if (depConfig != moduleGraphUnsatisfiedExit &&
depConfig != moduleGraphLiveExit):
stack.push([depName, depConfig])
stdout.receive(`}$\n`)
# Success!
0
match [=="test"] + modnames:
def someMods := promiseAllFulfilled(
[for modname in (modnames)
makeModuleAndConfiguration(modname,
"collectTests" => true)] +
[(def testRunner := makeModuleAndConfiguration("testRunner")),
(def tubes := makeModuleAndConfiguration("lib/tubes"))])
when (someMods) ->
def [[=> makeIterFount,
=> makeUTF8EncodePump,
=> makePumpTube
] | _, _] := tubes
def [[=> makeAsserter,
=> makeTestDrain,
=> runTests
] | _, _] := testRunner
def stdout := makePumpTube(makeUTF8EncodePump())
stdout <- flowTo(makeStdOut())
def asserter := makeAsserter()
def testDrain := makeTestDrain(stdout, unsealException, asserter)
when (runTests(collectedTests, testDrain, makeIterFount)) ->
def fails := asserter.fails()
stdout.receive(`${asserter.total()} tests run, $fails failures$\n`)
# Exit code: Only returns 0 if there were 0 failures.
for loc => errors in (asserter.errors()):
stdout.receive(`In $loc:$\n`)
for error in (errors):
stdout.receive(`~ $error$\n`)
fails.min(1)
match [=="bench"] + modnames:
def someMods := promiseAllFulfilled(
[for modname in (modnames)
makeModuleAndConfiguration(modname,
"collectBenchmarks" => true)] +
[(def benchRunner := makeModuleAndConfiguration("benchRunner"))])
return when (someMods) ->
def [[=> runBenchmarks] | _, _] := benchRunner
when (runBenchmarks(collectedBenches, bench,
makeFileResource("bench.html"))) ->
traceln(`Benchmark report written to bench.html.`)
0
match _:
throw(usage)
traceln(`Calling main`)
Ref.whenBroken(loaderMain(), fn x {traceln.exception(Ref.optProblem(x))})