Support 'import * as' directives, see #27

This commit is contained in:
dcodeIO 2018-06-12 00:45:19 +02:00
parent f2eb64c0fd
commit 25b433dca9
9 changed files with 213 additions and 51 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -602,7 +602,7 @@ export abstract class Node {
}
static createExportStatement(
members: ExportMember[],
members: ExportMember[] | null,
path: StringLiteralExpression | null,
flags: CommonFlags,
range: Range
@ -610,7 +610,7 @@ export abstract class Node {
var stmt = new ExportStatement();
stmt.range = range;
stmt.flags = flags;
stmt.members = members; setParent(members, stmt);
stmt.members = members; if (members) setParent(members, stmt);
stmt.path = path;
if (path) {
let normalizedPath = normalizePath(path.value);
@ -1627,8 +1627,8 @@ export class ExportMember extends Node {
export class ExportStatement extends Statement {
kind = NodeKind.EXPORT;
/** Array of members. */
members: ExportMember[];
/** Array of members if a set of named exports, or `null` if a filespace export. */
members: ExportMember[] | null;
/** Path being exported from, if applicable. */
path: StringLiteralExpression | null;
/** Normalized path, if `path` is set. */

View File

@ -1097,8 +1097,9 @@ export class Compiler extends DiagnosticEmitter {
compileExportStatement(statement: ExportStatement): void {
var module = this.module;
var exports = this.program.fileLevelExports;
var fileLevelExports = this.program.fileLevelExports;
var members = statement.members;
if (!members) return; // filespace
for (let i = 0, k = members.length; i < k; ++i) {
let member = members[i];
let internalExportName = (
@ -1106,7 +1107,7 @@ export class Compiler extends DiagnosticEmitter {
PATH_DELIMITER +
member.externalName.text
);
let element = exports.get(internalExportName);
let element = fileLevelExports.get(internalExportName);
if (!element) continue; // reported in Program#initialize
switch (element.kind) {
case ElementKind.CLASS_PROTOTYPE: {

View File

@ -1915,6 +1915,7 @@ export class Parser extends DiagnosticEmitter {
// at 'export': '{' ExportMember (',' ExportMember)* }' ('from' StringLiteral)? ';'?
var path: StringLiteralExpression | null = null;
if (tn.skip(Token.OPENBRACE)) {
let members = new Array<ExportMember>();
while (!tn.skip(Token.CLOSEBRACE)) {
@ -1933,7 +1934,6 @@ export class Parser extends DiagnosticEmitter {
}
}
}
let path: StringLiteralExpression | null = null;
if (tn.skip(Token.FROM)) {
if (tn.skip(Token.STRINGLITERAL)) {
path = Node.createStringLiteralExpression(tn.readString(), tn.range());
@ -1953,6 +1953,30 @@ export class Parser extends DiagnosticEmitter {
}
tn.skip(Token.SEMICOLON);
return ret;
} else if (tn.skip(Token.ASTERISK)) {
if (tn.skip(Token.FROM)) {
if (tn.skip(Token.STRINGLITERAL)) {
path = Node.createStringLiteralExpression(tn.readString(), tn.range());
let ret = Node.createExportStatement(null, path, flags, tn.range(startPos, tn.pos));
let internalPath = ret.internalPath;
if (internalPath !== null && !this.seenlog.has(internalPath)) {
this.backlog.push(internalPath);
this.seenlog.add(internalPath);
}
tn.skip(Token.SEMICOLON);
return ret;
} else {
this.error(
DiagnosticCode.String_literal_expected,
tn.range()
);
}
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), "from"
);
}
} else {
this.error(
DiagnosticCode._0_expected,
@ -2805,8 +2829,8 @@ export class Parser extends DiagnosticEmitter {
return Node.createFalseExpression(tn.range());
}
var p = determinePrecedenceStart(token);
if (p != Precedence.INVALID) {
var precedence = determinePrecedenceStart(token);
if (precedence != Precedence.INVALID) {
let operand: Expression | null;
// TODO: SpreadExpression, YieldExpression (currently become unsupported UnaryPrefixExpressions)
@ -2830,7 +2854,7 @@ export class Parser extends DiagnosticEmitter {
}
return null;
} else {
operand = this.parseExpression(tn, p);
operand = this.parseExpression(tn, precedence);
if (!operand) return null;
}
@ -3328,7 +3352,7 @@ export const enum Precedence {
}
/** Determines the precedence of a starting token. */
function determinePrecedenceStart(kind: Token): i32 {
function determinePrecedenceStart(kind: Token): Precedence {
switch (kind) {
case Token.DOT_DOT_DOT: return Precedence.SPREAD;
case Token.YIELD: return Precedence.YIELD;
@ -3347,7 +3371,7 @@ function determinePrecedenceStart(kind: Token): i32 {
}
/** Determines the precende of a non-starting token. */
function determinePrecedence(kind: Token): i32 {
function determinePrecedence(kind: Token): Precedence {
switch (kind) {
case Token.COMMA: return Precedence.COMMA;
case Token.EQUALS:

View File

@ -120,6 +120,8 @@ export const INNER_DELIMITER = "~";
export const LIBRARY_SUBST = "~lib";
/** Library directory prefix. */
export const LIBRARY_PREFIX = LIBRARY_SUBST + PATH_DELIMITER;
/** Prefix used to indicate a filespace element. */
export const FILESPACE_PREFIX = "file:";
/** Represents a yet unresolved export. */
class QueuedExport {
@ -133,7 +135,7 @@ class QueuedImport {
internalName: string;
referencedName: string;
referencedNameAlt: string;
declaration: ImportDeclaration;
declaration: ImportDeclaration | null; // not set if a filespace
}
/** Represents a type alias. */
@ -338,6 +340,8 @@ export class Program extends DiagnosticEmitter {
resolvedThisExpression: Expression | null = null;
/** Element expression of the previously resolved element access. */
resolvedElementExpression : Expression | null = null;
/** Currently processing filespace. */
currentFilespace: Filespace;
/** Constructs a new program, optionally inheriting parser diagnostics. */
constructor(diagnostics: DiagnosticMessage[] | null = null) {
@ -357,11 +361,12 @@ export class Program extends DiagnosticEmitter {
/** Looks up the source for the specified possibly ambiguous path. */
lookupSourceByPath(normalizedPathWithoutExtension: string): Source | null {
var tmp: string;
return (
this.getSource(normalizedPathWithoutExtension + ".ts") ||
this.getSource(normalizedPathWithoutExtension + "/index.ts") ||
this.getSource(LIBRARY_PREFIX + normalizedPathWithoutExtension + ".ts") ||
this.getSource(LIBRARY_PREFIX + normalizedPathWithoutExtension + "/index.ts")
this.getSource((tmp = LIBRARY_PREFIX + normalizedPathWithoutExtension) + ".ts") ||
this.getSource( tmp + "/index.ts")
);
}
@ -395,6 +400,13 @@ export class Program extends DiagnosticEmitter {
// build initial lookup maps of internal names to declarations
for (let i = 0, k = this.sources.length; i < k; ++i) {
let source = this.sources[i];
// create one filespace per source
let filespace = new Filespace(this, source);
this.elementsLookup.set(filespace.internalName, filespace);
this.currentFilespace = filespace;
// process this source's statements
let statements = source.statements;
for (let j = 0, l = statements.length; j < l; ++j) {
let statement = statements[j];
@ -442,22 +454,39 @@ export class Program extends DiagnosticEmitter {
// queued imports should be resolvable now through traversing exports and queued exports
for (let i = 0; i < queuedImports.length;) {
let queuedImport = queuedImports[i];
let element = this.tryResolveImport(queuedImport.referencedName, queuedExports);
if (element) {
this.elementsLookup.set(queuedImport.internalName, element);
queuedImports.splice(i, 1);
} else {
if (element = this.tryResolveImport(queuedImport.referencedNameAlt, queuedExports)) {
let declaration = queuedImport.declaration;
if (declaration) { // named
let element = this.tryResolveImport(queuedImport.referencedName, queuedExports);
if (element) {
this.elementsLookup.set(queuedImport.internalName, element);
queuedImports.splice(i, 1);
} else {
this.error(
DiagnosticCode.Module_0_has_no_exported_member_1,
queuedImport.declaration.range,
(<ImportStatement>queuedImport.declaration.parent).path.value,
queuedImport.declaration.externalName.text
);
++i;
if (element = this.tryResolveImport(queuedImport.referencedNameAlt, queuedExports)) {
this.elementsLookup.set(queuedImport.internalName, element);
queuedImports.splice(i, 1);
} else {
this.error(
DiagnosticCode.Module_0_has_no_exported_member_1,
declaration.range,
(<ImportStatement>declaration.parent).path.value,
declaration.externalName.text
);
++i;
}
}
} else { // filespace
let element = this.elementsLookup.get(queuedImport.referencedName);
if (element) {
this.elementsLookup.set(queuedImport.internalName, element);
queuedImports.splice(i, 1);
} else {
if (element = this.elementsLookup.get(queuedImport.referencedNameAlt)) {
this.elementsLookup.set(queuedImport.internalName, element);
queuedImports.splice(i, 1);
} else {
assert(false); // already reported by the parser not finding the file
++i;
}
}
}
}
@ -732,6 +761,7 @@ export class Program extends DiagnosticEmitter {
return;
}
this.fileLevelExports.set(internalName, prototype);
this.currentFilespace.members.set(simpleName, prototype);
if (prototype.is(CommonFlags.EXPORT) && declaration.range.source.isEntry) {
if (this.moduleLevelExports.has(internalName)) {
this.error(
@ -1165,6 +1195,7 @@ export class Program extends DiagnosticEmitter {
return;
}
this.fileLevelExports.set(internalName, element);
this.currentFilespace.members.set(simpleName, element);
if (declaration.range.source.isEntry) {
if (this.moduleLevelExports.has(internalName)) {
this.error(
@ -1215,26 +1246,45 @@ export class Program extends DiagnosticEmitter {
queuedExports: Map<string,QueuedExport>
): void {
var members = statement.members;
for (let i = 0, k = members.length; i < k; ++i) {
this.initializeExport(members[i], statement.internalPath, queuedExports);
if (members) { // named
for (let i = 0, k = members.length; i < k; ++i) {
this.initializeExport(members[i], statement.internalPath, queuedExports);
}
} else { // TODO: filespace
this.error(
DiagnosticCode.Operation_not_supported,
statement.range
);
}
}
private setExportAndCheckLibrary(
name: string,
internalName: string,
element: Element,
identifier: IdentifierExpression
): void {
this.fileLevelExports.set(name, element);
if (identifier.range.source.isLibrary) { // add global alias
if (this.elementsLookup.has(identifier.text)) {
// add to file-level exports
this.fileLevelExports.set(internalName, element);
// add to filespace
var internalPath = identifier.range.source.internalPath;
var prefix = FILESPACE_PREFIX + internalPath;
var filespace = this.elementsLookup.get(prefix);
if (!filespace) filespace = assert(this.elementsLookup.get(prefix + PATH_DELIMITER + "index"));
assert(filespace.kind == ElementKind.FILESPACE);
var simpleName = identifier.text;
(<Filespace>filespace).members.set(simpleName, element);
// add global alias if from a library file
if (identifier.range.source.isLibrary) {
if (this.elementsLookup.has(simpleName)) {
this.error(
DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0,
identifier.range, identifier.text
identifier.range, simpleName
);
} else {
element.internalName = identifier.text;
this.elementsLookup.set(identifier.text, element);
element.internalName = simpleName;
this.elementsLookup.set(simpleName, element);
}
}
}
@ -1401,6 +1451,7 @@ export class Program extends DiagnosticEmitter {
return;
}
this.fileLevelExports.set(internalName, prototype);
this.currentFilespace.members.set(simpleName, prototype);
if (declaration.range.source.isEntry) {
if (this.moduleLevelExports.has(internalName)) {
this.error(
@ -1446,10 +1497,22 @@ export class Program extends DiagnosticEmitter {
);
return;
}
this.error( // TODO
DiagnosticCode.Operation_not_supported,
statement.range
);
// resolve right away if the exact filespace exists
let filespace = this.elementsLookup.get(statement.internalPath);
if (filespace) {
this.elementsLookup.set(internalName, filespace);
return;
}
// otherwise queue it
let queuedImport = new QueuedImport();
queuedImport.internalName = internalName;
let prefix = FILESPACE_PREFIX + statement.internalPath;
queuedImport.referencedName = prefix;
queuedImport.referencedNameAlt = prefix + PATH_DELIMITER + "index";
queuedImport.declaration = null;
queuedImports.push(queuedImport);
}
}
@ -1511,9 +1574,10 @@ export class Program extends DiagnosticEmitter {
}
var decorators = declaration.decorators;
var simpleName = declaration.name.text;
var prototype = new InterfacePrototype(
this,
declaration.name.text,
simpleName,
internalName,
declaration,
decorators
@ -1548,6 +1612,7 @@ export class Program extends DiagnosticEmitter {
return;
}
this.fileLevelExports.set(internalName, prototype);
this.currentFilespace.members.set(simpleName, prototype);
if (declaration.range.source.isEntry) {
if (this.moduleLevelExports.has(internalName)) {
this.error(
@ -1632,6 +1697,7 @@ export class Program extends DiagnosticEmitter {
} else {
this.fileLevelExports.set(internalName, namespace);
}
this.currentFilespace.members.set(simpleName, namespace);
if (declaration.range.source.isEntry) {
if (this.moduleLevelExports.has(internalName)) {
this.error(
@ -1759,6 +1825,7 @@ export class Program extends DiagnosticEmitter {
} else {
this.fileLevelExports.set(internalName, global);
}
this.currentFilespace.members.set(simpleName, global);
if (declaration.range.source.isEntry) {
if (this.moduleLevelExports.has(internalName)) {
this.error(
@ -2144,11 +2211,13 @@ export class Program extends DiagnosticEmitter {
}
default: { // enums or other namespace-like elements
let members = target.members;
let member: Element | null;
if (members && (member = members.get(propertyName))) {
this.resolvedThisExpression = targetExpression;
this.resolvedElementExpression = null;
return member; // static ENUMVALUE, static GLOBAL, static FUNCTION_PROTOTYPE...
if (members) {
let member = members.get(propertyName);
if (member) {
this.resolvedThisExpression = targetExpression;
this.resolvedElementExpression = null;
return member; // static ENUMVALUE, static GLOBAL, static FUNCTION_PROTOTYPE...
}
}
break;
}
@ -2373,7 +2442,9 @@ export enum ElementKind {
/** A {@link Property}. */
PROPERTY,
/** A {@link Namespace}. */
NAMESPACE
NAMESPACE,
/** A {@link Filespace}. */
FILESPACE,
}
/** Indicates traits of a {@link Node} or {@link Element}. */
@ -2515,7 +2586,25 @@ export abstract class Element {
hasDecorator(flag: DecoratorFlags): bool { return (this.decoratorFlags & flag) == flag; }
}
/** A namespace. */
/** A filespace representing the implicit top-level namespace of a source. */
export class Filespace extends Element {
kind = ElementKind.FILESPACE;
/** File members (externally visible only). */
members: Map<string,Element>; // more specific
/** Constructs a new filespace. */
constructor(
program: Program,
source: Source
) {
super(program, source.internalPath, FILESPACE_PREFIX + source.internalPath);
this.members = new Map();
}
}
/** A namespace that differs from a filespace in being user-declared with a name. */
export class Namespace extends Element {
// All elements have namespace semantics. This is an explicitly declared one.

View File

@ -45,5 +45,24 @@
)
)
(call $export/ns.two)
(drop
(i32.add
(i32.add
(call $export/add
(i32.const 1)
(i32.const 2)
)
(call $export/sub
(i32.const 2)
(i32.const 3)
)
)
(call $export/mul
(i32.const 3)
(i32.const 1)
)
)
)
(call $export/ns.two)
)
)

View File

@ -1,3 +1,5 @@
/* tslint:disable:no-duplicate-imports */
import {
add,
sub as sub,
@ -11,3 +13,11 @@ import {
add(a, b) + sub(b, c) + mul(c, a);
renamed_ns.two();
import * as other from "./export";
other.add(other.a, other.b) +
other.sub(other.b, other.renamed_c) +
other.renamed_mul(other.renamed_c, other.a);
other.ns.two();

View File

@ -55,5 +55,24 @@
)
)
(call $export/ns.two)
(drop
(i32.add
(i32.add
(call $export/add
(i32.const 1)
(i32.const 2)
)
(call $export/sub
(i32.const 2)
(i32.const 3)
)
)
(call $export/mul
(i32.const 3)
(i32.const 1)
)
)
)
(call $export/ns.two)
)
)