/
LibraryCompiler.java
312 lines (282 loc) · 11.6 KB
/
LibraryCompiler.java
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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
// Copyright (c) 2020-2021 Yinsen (Tesla) Zhang.
// Use of this source code is governed by the MIT license that can be found in the LICENSE.md file.
package org.aya.cli.library;
import kala.collection.SeqView;
import kala.collection.immutable.ImmutableSeq;
import kala.collection.mutable.DynamicLinkedSeq;
import kala.collection.mutable.DynamicSeq;
import kala.collection.mutable.MutableSet;
import kala.tuple.Unit;
import org.aya.api.error.CountingReporter;
import org.aya.api.error.Reporter;
import org.aya.api.error.SourceFileLocator;
import org.aya.cli.library.json.LibraryConfig;
import org.aya.cli.library.json.LibraryConfigData;
import org.aya.cli.library.json.LibraryDependency;
import org.aya.cli.single.CliReporter;
import org.aya.concrete.parse.AyaParsing;
import org.aya.concrete.resolve.ResolveInfo;
import org.aya.concrete.resolve.module.CachedModuleLoader;
import org.aya.concrete.resolve.module.FileModuleLoader;
import org.aya.concrete.resolve.module.ModuleLoader;
import org.aya.core.def.Def;
import org.aya.core.serde.Serializer;
import org.aya.util.MutableGraph;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.function.Function;
import java.util.stream.IntStream;
/**
* @author kiva
*/
public record LibraryCompiler(@NotNull Path buildRoot, boolean unicode) {
public static int compile(@NotNull Path libraryRoot, boolean unicode) throws IOException {
var config = LibraryConfigData.fromLibraryRoot(libraryRoot);
new LibraryCompiler(config.libraryBuildRoot(), unicode).make(config);
return 0;
}
private @Nullable LibraryConfig depConfig(@NotNull LibraryDependency dep) throws IOException {
// TODO: test only: dependency resolving should be done in package manager
if (dep instanceof LibraryDependency.DepFile file)
return LibraryConfigData.fromDependencyRoot(file.depRoot(), version -> depBuildRoot(dep.depName(), version));
return null;
}
private @NotNull Path depBuildRoot(@NotNull String depName, @NotNull String version) {
return buildRoot.resolve("deps").resolve(depName + "_" + version);
}
private @NotNull LibrarySource resolveImports(
@NotNull LibraryConfig owner,
@NotNull Reporter reporter,
@NotNull SourceFileLocator locator,
@NotNull ImportResolver.ImportLoader loader,
@NotNull Path path
) throws IOException {
var program = AyaParsing.program(locator, reporter, path);
var source = new LibrarySource(owner, path);
var finder = new ImportResolver(loader, source);
finder.resolveStmt(program);
return source;
}
private @NotNull MutableGraph<LibrarySource> resolveImports(
@NotNull LibraryConfig owner,
@NotNull Reporter reporter,
@NotNull SourceFileLocator locator,
@NotNull ImportResolver.ImportLoader loader,
@NotNull LibraryConfig config
) throws IOException {
System.out.println(" [Info] Collecting source files");
var graph = MutableGraph.<LibrarySource>create();
var sources = collectSource(config.librarySrcRoot());
System.out.println(" [Info] Resolving source file dependency");
for (var file : sources) {
var resolve = resolveImports(owner, reporter, locator, loader, file);
collectDep(graph, resolve);
}
return graph;
}
/**
* Incrementally compiles a library.
*
* @return whether the library is up-to-date.
* @apiNote The return value does not indicate whether the library is compiled successfully.
*/
private boolean make(@NotNull LibraryConfig config) throws IOException {
// TODO[kiva]: move to package manager
var thisModulePath = DynamicSeq.<Path>create();
var locatorPath = DynamicSeq.<Path>create();
var anyDepChanged = false;
for (var dep : config.deps()) {
var depConfig = depConfig(dep);
if (depConfig == null) {
System.out.println("Skipping " + dep.depName());
continue;
}
var upToDate = make(depConfig);
anyDepChanged = anyDepChanged || !upToDate;
thisModulePath.append(depConfig.libraryOutRoot());
locatorPath.append(depConfig.librarySrcRoot());
}
System.out.println("Compiling " + config.name());
// TODO: be incremental when dependencies changed
if (anyDepChanged) deleteRecursively(config.libraryOutRoot());
var srcRoot = config.librarySrcRoot();
var thisOutRoot = Files.createDirectories(config.libraryOutRoot());
thisModulePath.append(srcRoot);
locatorPath.prepend(srcRoot);
var reporter = CliReporter.stdio(unicode);
var locator = new SourceFileLocator.Module(locatorPath.view());
var timestamp = new Timestamp(locator, thisOutRoot);
var resolveLoader = new LibraryImportLoader(config, this, reporter, locator, locatorPath.view());
var depGraph = resolveImports(config, reporter, locator, resolveLoader, config);
var moduleLoader = new CachedModuleLoader(new LibraryModuleLoader(reporter, locator, thisModulePath.view(), thisOutRoot));
return make(reporter, moduleLoader, depGraph, timestamp);
}
/**
* @return whether the library is up-to-date.
*/
private boolean make(
@NotNull Reporter reporter,
@NotNull ModuleLoader moduleLoader,
@NotNull MutableGraph<LibrarySource> depGraph,
@NotNull Timestamp timestamp
) throws IOException {
var changed = MutableSet.<LibrarySource>create();
var usage = depGraph.transpose();
depGraph.E().keysView().forEach(s -> {
if (timestamp.sourceModified(s.file()))
collectChanged(usage, s, changed);
});
if (changed.isEmpty()) {
System.out.println(" [Info] No changes detected, no need to remake");
return true;
}
var changedDepGraph = MutableGraph.<LibrarySource>create();
changed.forEach(c -> collectDep(changedDepGraph, c));
var order = changedDepGraph.topologicalOrder().view()
.flatMap(Function.identity())
.map(LibrarySource::file)
.toImmutableSeq();
tyckLibrary(reporter, moduleLoader, timestamp.locator, timestamp, order);
return false;
}
/** Produces tyck order of modules in a library */
private void tyckLibrary(
@NotNull Reporter reporter,
@NotNull ModuleLoader moduleLoader,
@NotNull SourceFileLocator locator,
@NotNull Timestamp timestamp,
@NotNull ImmutableSeq<Path> order
) throws IOException {
var coreSaver = new CoreSaver(timestamp);
for (var f : order) Files.deleteIfExists(coreFile(locator, f, timestamp.outRoot));
order.forEach(file -> {
var mod = resolveModule(moduleLoader, locator, file);
if (mod.thisProgram().isEmpty()) {
System.out.println(" [Reuse] " + mod.thisModule().underlyingFile());
return;
}
System.out.println(" [Tyck] " + mod.thisModule().underlyingFile());
var counting = new CountingReporter(reporter);
FileModuleLoader.tyckResolvedModule(mod, counting,
(moduleResolve, stmts, defs) -> {
if (counting.noError()) coreSaver.saveCompiledCore(moduleResolve, defs);
},
null);
});
}
private @NotNull ResolveInfo resolveModule(
@NotNull ModuleLoader moduleLoader,
@NotNull SourceFileLocator locator,
@NotNull Path file
) {
var mod = moduleName(locator, file);
System.out.printf(" [Resolve] %s (%s)%n", mod.joinToString("::"), file);
var resolveInfo = moduleLoader.load(mod);
if (resolveInfo == null) throw new IllegalStateException("Unable to load module: " + mod);
return resolveInfo;
}
private static @NotNull Path coreFile(
@NotNull SourceFileLocator locator, @NotNull Path file, @NotNull Path outRoot
) throws IOException {
var raw = outRoot.resolve(locator.displayName(file));
var core = raw.resolveSibling(raw.getFileName().toString() + "c");
Files.createDirectories(core.getParent());
return core;
}
record Timestamp(@NotNull SourceFileLocator locator, @NotNull Path outRoot) {
public boolean sourceModified(@NotNull Path file) {
try {
var core = coreFile(locator, file, outRoot);
if (!Files.exists(core)) return true;
return Files.getLastModifiedTime(file)
.compareTo(Files.getLastModifiedTime(core)) > 0;
} catch (IOException ignore) {
return true;
}
}
public void update(@NotNull Path file) {
try {
var core = coreFile(locator, file, outRoot);
Files.setLastModifiedTime(core, Files.getLastModifiedTime(file));
} catch (IOException ignore) {
}
}
}
record CoreSaver(@NotNull Timestamp timestamp) {
private void saveCompiledCore(@NotNull ResolveInfo resolveInfo, @NotNull ImmutableSeq<Def> defs) {
var sourcePath = resolveInfo.canonicalPath();
try (var outputStream = openCompiledCore(sourcePath)) {
var serDefs = defs.map(def -> def.accept(new Serializer(new Serializer.State()), Unit.unit()));
var compiled = CompiledAya.from(resolveInfo, serDefs);
outputStream.writeObject(compiled);
timestamp.update(sourcePath);
} catch (IOException e) {
e.printStackTrace();
}
}
private @NotNull ObjectOutputStream openCompiledCore(@NotNull Path sourcePath) throws IOException {
return new ObjectOutputStream(Files.newOutputStream(
coreFile(timestamp.locator, sourcePath, timestamp.outRoot)));
}
}
private static @NotNull DynamicLinkedSeq<Path> collectSource(@NotNull Path srcRoot) throws IOException {
try (var walk = Files.walk(srcRoot)) {
return walk.filter(Files::isRegularFile)
.filter(path -> path.getFileName().toString().endsWith(".aya"))
.map(ResolveInfo::canonicalize)
.collect(DynamicLinkedSeq.factory());
}
}
private static void collectDep(@NotNull MutableGraph<LibrarySource> dep, @NotNull LibrarySource info) {
dep.suc(info).appendAll(info.imports());
info.imports().forEach(i -> {
collectDep(dep, i);
});
}
private static void collectChanged(
@NotNull MutableGraph<LibrarySource> usage,
@NotNull LibrarySource changed,
@NotNull MutableSet<LibrarySource> changedList
) {
if (changedList.contains(changed)) return;
changedList.add(changed);
usage.suc(changed).forEach(dep -> collectChanged(usage, dep, changedList));
}
private static @NotNull ImmutableSeq<String> moduleName(@NotNull SourceFileLocator locator, @NotNull Path file) {
var display = locator.displayName(file);
var displayNoExt = display.resolveSibling(display.getFileName().toString().replaceAll("\\.aya", ""));
return IntStream.range(0, displayNoExt.getNameCount())
.mapToObj(i -> displayNoExt.getName(i).toString())
.collect(ImmutableSeq.factory());
}
private static void deleteRecursively(@NotNull Path path) throws IOException {
if (!Files.exists(path)) return;
try (var walk = Files.walk(path)) {
walk.sorted(Comparator.reverseOrder())
.collect(ImmutableSeq.factory())
.forEachChecked(Files::deleteIfExists);
}
}
record LibraryImportLoader(
@NotNull LibraryConfig owner,
@NotNull LibraryCompiler compiler,
@NotNull Reporter reporter,
@NotNull SourceFileLocator locator,
@NotNull SeqView<Path> thisModulePath
) implements ImportResolver.ImportLoader {
@Override public @NotNull LibrarySource load(@NotNull ImmutableSeq<String> mod) {
var file = LibraryModuleLoader.resolveFile(thisModulePath, mod);
if (file == null) throw new IllegalArgumentException("incomplete module path");
try {
return compiler.resolveImports(owner, reporter, locator, this, file);
} catch (IOException e) {
throw new RuntimeException("Cannot load imported module " + mod, e);
}
}
}
}