This commit is contained in:
Brian Terlson 2021-03-29 18:00:11 -07:00 коммит произвёл GitHub
Родитель 5f8b5c3ca9
Коммит 3d5410354f
7 изменённых файлов: 138 добавлений и 56 удалений

Просмотреть файл

@ -7,7 +7,7 @@ import { spawnSync } from "child_process";
import { CompilerOptions } from "../compiler/options.js";
import { DiagnosticError, dumpError, logDiagnostics } from "./diagnostics.js";
import { adlVersion } from "./util.js";
import { readFile, mkdtemp, readdir, rmdir } from "fs/promises";
import { stat, readFile, mkdtemp, readdir, rmdir } from "fs/promises";
import os from "os";
import { CompilerHost } from "./types.js";
@ -83,6 +83,9 @@ const NodeHost: CompilerHost = {
const rootDir = this.getExecutionRoot();
return [join(rootDir, "lib"), join(rootDir, "dist/lib")];
},
stat(path: string) {
return stat(path);
},
};
async function compileInput(compilerOptions: CompilerOptions) {

Просмотреть файл

@ -37,6 +37,10 @@ export function parse(code: string | Types.SourceFile) {
throw error("Blockless namespaces can't follow other declarations");
}
seenBlocklessNs = true;
} else if (item.kind === Types.SyntaxKind.ImportStatement) {
if (seenDecl || seenBlocklessNs) {
throw error("Imports must come prior to namespaces or other declarations");
}
} else {
seenDecl = true;
}
@ -499,48 +503,18 @@ export function parse(code: string | Types.SourceFile) {
const pos = tokenPos();
parseExpected(Token.ImportKeyword);
const id = parseIdentifier();
let as: Array<Types.NamedImportNode> = [];
if (token() === Token.Identifier && tokenValue() === "as") {
parseExpected(Token.Identifier);
parseExpected(Token.OpenBrace);
if (token() !== Token.CloseBrace) {
as = parseNamedImports();
}
parseExpected(Token.CloseBrace);
}
const pathLiteral = parseStringLiteral();
const path = pathLiteral.value;
parseExpected(Token.Semicolon);
return finishNode(
{
kind: Types.SyntaxKind.ImportStatement,
as,
id,
path,
},
pos
);
}
function parseNamedImports(): Array<Types.NamedImportNode> {
const names: Array<Types.NamedImportNode> = [];
do {
const pos = tokenPos();
names.push(
finishNode(
{
kind: Types.SyntaxKind.NamedImport,
id: parseIdentifier(),
},
pos
)
);
} while (parseOptional(Token.Comma));
return names;
}
function parseDecoratorExpression(): Types.DecoratorExpressionNode {
const pos = tokenPos();
parseExpected(Token.At);
@ -824,7 +798,7 @@ export function visitChildren<T>(node: Types.Node, cb: NodeCb<T>): T | undefined
case Types.SyntaxKind.DecoratorExpression:
return visitNode(cb, node.target) || visitEach(cb, node.arguments);
case Types.SyntaxKind.ImportStatement:
return visitNode(cb, node.id) || visitEach(cb, node.as);
return;
case Types.SyntaxKind.OperationStatement:
return (
visitEach(cb, node.decorators) ||

Просмотреть файл

@ -1,4 +1,4 @@
import { resolve } from "path";
import { dirname, extname, isAbsolute, resolve } from "path";
import { lstat } from "fs/promises";
import { join } from "path";
import { createBinder, SymbolTable } from "./binder.js";
@ -15,7 +15,6 @@ import {
ModelType,
SyntaxKind,
Type,
SourceFile,
DecoratorSymbol,
CompilerHost,
NamespaceStatementNode,
@ -42,6 +41,7 @@ export async function createProgram(
): Promise<Program> {
const buildCbs: any = [];
const seenSourceFiles = new Set<string>();
const program: Program = {
compilerOptions: options || {},
globalNamespace: createGlobalNamespace(),
@ -149,33 +149,39 @@ export async function createProgram(
async function loadStandardLibrary(program: Program) {
for (const dir of host.getLibDirs()) {
await loadDirectory(program, dir);
await loadDirectory(dir);
}
}
async function loadDirectory(program: Program, rootDir: string) {
async function loadDirectory(rootDir: string) {
const dir = await host.readDir(rootDir);
for (const entry of dir) {
if (entry.isFile()) {
const path = join(rootDir, entry.name);
if (entry.name.endsWith(".js")) {
await loadJsFile(program, path);
await loadJsFile(path);
} else if (entry.name.endsWith(".adl")) {
await loadAdlFile(program, path);
await loadAdlFile(path);
}
}
}
}
async function loadAdlFile(program: Program, path: string) {
async function loadAdlFile(path: string) {
if (seenSourceFiles.has(path)) {
return;
}
seenSourceFiles.add(path);
const contents = await host.readFile(path);
if (!contents) {
throw new Error("Couldn't load ADL file " + path);
}
program.evalAdlScript(contents, path);
await evalAdlScript(contents, path);
}
async function loadJsFile(program: Program, path: string) {
async function loadJsFile(path: string) {
const exports = await host.getJsImport(path);
for (const match of Object.keys(exports)) {
@ -199,13 +205,45 @@ export async function createProgram(
// Evaluates an arbitrary line of ADL in the context of a
// specified file path. If no path is specified, use a
// virtual file path
function evalAdlScript(adlScript: string, filePath?: string): void {
async function evalAdlScript(adlScript: string, filePath?: string): Promise<void> {
filePath = filePath ?? `__virtual_file_${++virtualFileCount}`;
const unparsedFile = createSourceFile(adlScript, filePath);
const sourceFile = parse(unparsedFile);
program.sourceFiles.push(sourceFile);
binder.bindSourceFile(program, sourceFile);
await evalImports(sourceFile);
}
async function evalImports(file: ADLScriptNode) {
// collect imports
for (const stmt of file.statements) {
if (stmt.kind !== SyntaxKind.ImportStatement) break;
const path = stmt.path;
const ext = extname(path);
let target: string;
if (path.startsWith("./") || path.startsWith("../")) {
target = resolve(dirname(file.file.path), path);
} else if (isAbsolute(path)) {
target = path;
} else {
throwDiagnostic("Import paths must begin with ./ or ../ or be absolute", stmt);
}
if (ext === "") {
// look for a main.adl
await loadAdlFile(join(target, "main.adl"));
} else if (ext === ".js") {
await loadJsFile(target);
} else if (ext === ".adl") {
await loadAdlFile(target);
} else {
throwDiagnostic(
"Import paths must reference either a directory, a .adl file, or .js file",
stmt
);
}
}
}
async function loadMain(options: CompilerOptions) {
@ -215,12 +253,12 @@ export async function createProgram(
const mainPath = resolve(host.getCwd(), options.mainFile);
const mainStat = await lstat(mainPath);
const mainStat = await host.stat(mainPath);
if (mainStat.isDirectory()) {
await loadDirectory(program, mainPath);
await loadDirectory(mainPath);
} else {
await loadAdlFile(program, mainPath);
await loadAdlFile(mainPath);
}
}
}

Просмотреть файл

@ -219,8 +219,7 @@ export type ScopeNode = NamespaceStatementNode | ModelStatementNode | ADLScriptN
export interface ImportStatementNode extends BaseNode {
kind: SyntaxKind.ImportStatement;
id: IdentifierNode;
as: Array<NamedImportNode>;
path: string;
}
export interface IdentifierNode extends BaseNode {
@ -456,4 +455,7 @@ export interface CompilerHost {
// get the current working directory
getCwd(): string;
// get info about a path (presently just isDirectory())
stat(path: string): Promise<{ isDirectory(): boolean }>;
}

Просмотреть файл

@ -0,0 +1,46 @@
import { strictEqual, ok } from "assert";
import { ModelType, NamespaceType, Type } from "../../compiler/types.js";
import { createTestHost, TestHost } from "../test-host.js";
describe("loader", () => {
let testHost: TestHost;
beforeEach(async () => {
testHost = await createTestHost();
});
it.only("loads ADL and JS files", async () => {
testHost.addJsFile("blue.js", { blue() {} });
testHost.addAdlFile(
"a.adl",
`
import "./b.adl";
import "./blue.js";
@blue
model A extends B, C { }
`
);
testHost.addAdlFile(
"b.adl",
`
import "./test";
model B { }
`
);
testHost.addAdlFile(
"test/main.adl",
`
import "./c.adl";
`
);
testHost.addAdlFile(
"test/c.adl",
`
model C { }
`
);
await testHost.compile("a.adl");
});
});

Просмотреть файл

@ -57,6 +57,24 @@ export async function createTestHost(): Promise<TestHost> {
getCwd() {
return "/";
},
async stat(path: string) {
for (const fsPath of Object.keys(virtualFs)) {
if (fsPath.startsWith(path) && fsPath !== path) {
return {
isDirectory() {
return true;
},
};
}
}
return {
isDirectory() {
return false;
},
};
},
};
// load standard library into the vfs

Просмотреть файл

@ -4,11 +4,12 @@ import { SyntaxKind } from "../compiler/types.js";
describe("syntax", () => {
describe("import statements", () => {
parseEach([
"import x;",
"import x as { one };",
"import x as {};",
"import x as { one, two };",
parseEach(['import "x";']);
parseErrorEach([
'namespace Foo { import "x"; }',
'namespace Foo { } import "x";',
'model Foo { } import "x";',
]);
});