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

View File

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

View File

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

View File

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

View File

@ -45,5 +45,24 @@
) )
) )
(call $export/ns.two) (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 { import {
add, add,
sub as sub, sub as sub,
@ -11,3 +13,11 @@ import {
add(a, b) + sub(b, c) + mul(c, a); add(a, b) + sub(b, c) + mul(c, a);
renamed_ns.two(); 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) (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)
) )
) )