Make the definition process lazy

master
Paul Loyd 2017-11-09 12:10:21 +03:00
parent a72929ea17
commit dbd1927aab
39 changed files with 336 additions and 211 deletions

View File

@ -19,11 +19,12 @@ class Collector {
this.taskCount = 0; this.taskCount = 0;
this.active = true; this.active = true;
this.modules = new Map; this.modules = new Map;
this.roots = new Set;
this.global = Scope.global(globals); this.global = Scope.global(globals);
this.running = false; this.running = false;
} }
collect(path) { collect(path, internal = false) {
// TODO: follow symlinks. // TODO: follow symlinks.
path = pathlib.resolve(path); path = pathlib.resolve(path);
@ -44,22 +45,30 @@ class Collector {
const scope = this.global.extend(module); const scope = this.global.extend(module);
this._spawn(ast.program, scope); this._freestyle(extractors.declaration, ast.program, scope);
this.modules.set(path, module); this.modules.set(path, module);
if (!this.running) { if (this.running) {
try { return;
this.running = true; }
try {
this.running = true;
this._schedule();
if (!internal) {
const task = this._grabExports(module);
this._spawn(task);
this._schedule(); this._schedule();
} finally {
this.running = false;
} }
} finally {
this.running = false;
} }
} }
// Given the AST output of babylon parse, walk through in a depth-first order. // Given the AST output of babylon parse, walk through in a depth-first order.
_spawn(root, scope) { _freestyle(group, root, scope) {
let stack; let stack;
let parent; let parent;
let keys = []; let keys = [];
@ -79,20 +88,17 @@ class Collector {
const node = parent ? parent[keys[index]] : root; const node = parent ? parent[keys[index]] : root;
if (isNode(node)) { if (isNode(node) && isAcceptableGroup(group, node)) {
const {type} = node; if (!this.roots.has(node)) {
const group = type && getExtractorsGroup(type);
if (group) {
const task = this._collect(group, node, scope); const task = this._collect(group, node, scope);
this.roots.add(node);
this.tasks.add(task); this._spawn(task);
++this.taskCount;
continue;
} }
continue;
}
if (isNode(node) || node instanceof Array) {
stack = { parent, keys, index, prev: stack }; stack = { parent, keys, index, prev: stack };
parent = node; parent = node;
keys = Object.keys(node); keys = Object.keys(node);
@ -105,7 +111,7 @@ class Collector {
const extractor = group[node.type]; const extractor = group[node.type];
if (!extractor) { if (!extractor) {
assert.fail(`No extractor for "${node.type}" in group "${group.entries[0]}"`); this._freestyle(group, node, scope);
return null; return null;
} }
@ -126,13 +132,13 @@ class Collector {
if (value instanceof Command) { if (value instanceof Command) {
switch (value.name) { switch (value.name) {
case 'spawn': case 'declare':
this._spawn(value.data, scope); scope.addDeclaration(value.data[0], value.data[1]);
break; break;
case 'define': case 'define':
scope.addSchema(value.data); scope.addDefinition(value.data[0], value.data[1]);
this.schemas.push(value.data); this.schemas.push(value.data[0]);
break; break;
case 'external': case 'external':
@ -140,7 +146,7 @@ class Collector {
break; break;
case 'provide': case 'provide':
scope.addExport(value.data); scope.addExport(value.data[0], value.data[1]);
break; break;
case 'query': case 'query':
@ -175,34 +181,62 @@ class Collector {
} }
* _query(scope, name) { * _query(scope, name) {
let result; let result = scope.query(name);
// Wait any information about the reference. // TODO: warning.
while ((result = scope.query(name)).type === 'unknown') { assert.notEqual(result.type, 'unknown');
yield;
// Resulting scope is always the best choice for waiting.
scope = result.scope;
switch (result.type) {
case 'external':
const modulePath = scope.resolve(result.info.path);
this.collect(modulePath, true);
const module = this.modules.get(modulePath);
const {imported} = result.info;
while ((result = module.query(imported)).type === 'unknown') {
yield;
}
if (result.type === 'definition') {
return result.schema;
}
// TODO: reexports.
assert.equal(result.type, 'declaration');
scope = result.scope;
name = result.name;
// Fallthrough.
case 'declaration':
this._freestyle(extractors.definition, result.node, scope);
while ((result = scope.query(name)).type === 'declaration') {
yield;
}
assert.equal(result.type, 'definition');
// Fallthrough.
case 'definition':
return result.schema;
} }
}
if (result.type === 'local') { * _grabExports(module) {
return result.schema; for (const [scope, name] of module.exports.values()) {
yield* this._query(scope, name);
} }
}
assert.equal(result.type, 'external'); _spawn(task) {
this.tasks.add(task);
// TODO: reexports. ++this.taskCount;
const modulePath = scope.resolve(result.info.path);
this.collect(modulePath);
const module = this.modules.get(modulePath);
const {imported} = result.info;
let schema;
while (!(schema = module.query(imported))) {
yield;
}
return schema;
} }
_schedule() { _schedule() {
@ -234,7 +268,7 @@ class Collector {
} }
function isNode(it) { function isNode(it) {
return it && typeof it === 'object' && (it.type || it.length); return it && typeof it === 'object' && it.type;
} }
function pathToNamespace(path) { function pathToNamespace(path) {
@ -249,8 +283,8 @@ function pathToNamespace(path) {
.join('.'); .join('.');
} }
function getExtractorsGroup(type) { function isAcceptableGroup(group, node) {
return extractors.find(group => group.entries.includes(type)); return group.entries.includes(node.type);
} }
module.exports = Collector; module.exports = Collector;

View File

@ -1,20 +1,20 @@
'use strict'; 'use strict';
class Command { class Command {
static spawn(node) { static declare(name, node) {
return new Command('spawn', node); return new Command('declare', [name, node]);
} }
static define(schema) { static define(schema, declared = true) {
return new Command('define', schema); return new Command('define', [schema, declared]);
} }
static external(external) { static external(external) {
return new Command('external', external); return new Command('external', external);
} }
static provide(internal) { static provide(name, reference) {
return new Command('provide', internal); return new Command('provide', [name, reference]);
} }
static query(name) { static query(name) {

View File

@ -2,11 +2,15 @@
const assert = require('assert'); const assert = require('assert');
const {spawn, define, external, provide, query, enter, exit, namespace} = require('./commands'); const {declare, define, external, provide, query, enter, exit, namespace} = require('./commands');
const {partition} = require('./utils'); const {partition} = require('./utils');
const typedefsGroup = { const definition = {
entries: ['TypeAlias', 'InterfaceDeclaration', 'ClassDeclaration'], entries: [
'TypeAlias',
'InterfaceDeclaration',
'ClassDeclaration',
],
* TypeAlias(node) { * TypeAlias(node) {
let schema = yield node.right; let schema = yield node.right;
@ -83,9 +87,6 @@ const typedefsGroup = {
}, },
* ClassMethod(node) { * ClassMethod(node) {
// For cases, when the body has references to the class.
yield spawn(node.body);
return null; return null;
}, },
@ -235,25 +236,39 @@ const typedefsGroup = {
}, },
}; };
/* const declaration = {
* TODO: declarations. entries: [
*/ // Blocks.
const blocksGroup = { 'Program',
entries: ['BlockStatement'], 'BlockStatement',
// Imports.
'ImportDeclaration',
'VariableDeclarator',
// Exports.
'ExportNamedDeclaration',
'ExportDefaultDeclaration',
],
/*
* Blocks.
*/
* Program(node) {
yield node.body;
},
* BlockStatement(node) { * BlockStatement(node) {
yield enter(); yield enter();
yield spawn(node.body); yield node.body;
yield exit(); yield exit();
}, },
};
/* /*
* TODO: warning about "import typeof". * Imports.
* TODO: support form "import *". *
*/ * TODO: warning about "import typeof".
const importsGroup = { * TODO: support form "import *".
entries: ['ImportDeclaration', 'VariableDeclarator'], */
* ImportDeclaration(node) { * ImportDeclaration(node) {
const specifiers = yield node.specifiers; const specifiers = yield node.specifiers;
@ -325,6 +340,81 @@ const importsGroup = {
}; };
}, },
/*
* Exports.
*
* TODO: support "export from" form.
* TODO: support commonjs.
*/
* ExportDefaultDeclaration(node) {
const reference = yield node.declaration;
if (reference) {
yield provide(null, reference);
}
},
* ExportNamedDeclaration(node) {
if (!node.declaration) {
yield node.specifiers;
return;
}
const reference = yield node.declaration;
if (reference) {
yield provide(reference, reference);
}
},
* ExportSpecifier(node) {
const reference = yield node.local;
let name = yield node.exported;
if (name === 'default') {
name = null;
}
yield provide(name, reference);
},
/*
* Declarations.
*/
* TypeAlias(node) {
const name = yield node.id;
yield declare(name, node);
return name;
},
* InterfaceDeclaration(node) {
const name = yield node.id;
yield declare(name, node);
return name;
},
* ClassDeclaration(node) {
const name = yield node.id;
// TODO: do it only for "all"-mode.
const body = node.body;
yield body.body.filter(is('ClassMethod'));
yield declare(name, node);
return name;
},
/*
* Utility.
*/
* StringLiteral(node) { * StringLiteral(node) {
return node.value; return node.value;
}, },
@ -334,64 +424,6 @@ const importsGroup = {
}, },
}; };
/*
* TODO: support "export from" form.
* TODO: support commonjs.
*/
const exportsGroup = Object.assign({}, typedefsGroup, {
entries: ['ExportNamedDeclaration', 'ExportDefaultDeclaration'],
* ExportDefaultDeclaration(node) {
let schema = yield node.declaration;
if (!schema) {
return;
}
if (typeof schema === 'string') {
schema = yield query(schema);
}
yield provide({
schema,
exported: null,
});
},
* ExportNamedDeclaration(node) {
if (!node.declaration) {
yield node.specifiers;
return;
}
const schema = yield node.declaration;
if (!schema) {
return;
}
yield provide({
schema,
exported: schema.name,
});
},
* ExportSpecifier(node) {
let exported = yield node.exported;
if (exported === 'default') {
exported = null;
}
const name = yield node.local;
const schema = yield query(name);
yield provide({
schema,
exported,
});
},
});
function* extractLastPragma(comments) { function* extractLastPragma(comments) {
const pragmas = (yield comments).filter(Boolean); const pragmas = (yield comments).filter(Boolean);
@ -415,7 +447,7 @@ function* extractProperty(prop, value) {
if (type.type === 'record') { if (type.type === 'record') {
type.namespace = yield namespace(); type.namespace = yield namespace();
yield define(type); yield define(type, false);
type = type.name; type = type.name;
} }
@ -556,9 +588,11 @@ function mergeSchemas(schemas) {
}; };
} }
module.exports = [ function is(type) {
typedefsGroup, return node => Boolean(node) && node.type === type;
blocksGroup, }
importsGroup,
exportsGroup, module.exports = {
]; definition,
declaration,
};

View File

@ -10,7 +10,7 @@ class Scope {
const global = new Scope(null, null); const global = new Scope(null, null);
for (const schema of schemas) { for (const schema of schemas) {
global.addSchema(schema); global.addDefinition(schema, false);
} }
return global; return global;
@ -20,8 +20,7 @@ class Scope {
this.parent = parent; this.parent = parent;
this.module = module; this.module = module;
this.scopeId = module && module.generateScopeId(); this.scopeId = module && module.generateScopeId();
this.schemas = new Map; this.entries = new Map;
this.imports = new Map;
} }
get namespace() { get namespace() {
@ -41,22 +40,45 @@ class Scope {
return new Scope(this, module || this.module); return new Scope(this, module || this.module);
} }
addSchema(schema) { addDeclaration(name, node) {
assert(!this.schemas.has(schema.name)); assert(!this.entries.has(name));
this.schemas.set(schema.name, schema); this.entries.set(name, {
type: 'declaration',
name,
node,
scope: this,
});
}
addDefinition(schema, declared = true) {
if (declared) {
assert.equal((this.entries.get(schema.name) || {}).type, 'declaration');
} else {
assert(!this.entries.has(schema.name));
}
this.entries.set(schema.name, {
type: 'definition',
schema,
scope: this,
});
} }
addImport(info) { addImport(info) {
assert(!this.imports.has(info.local)); assert(!this.entries.has(info.local));
this.imports.set(info.local, info); this.entries.set(info.local, {
type: 'external',
info,
scope: this,
});
} }
addExport(info) { addExport(name, reference) {
assert(this.module); assert(this.module);
this.module.addExport(info.exported, info.schema); this.module.addExport(name, this, reference);
} }
resolve(path) { resolve(path) {
@ -66,7 +88,7 @@ class Scope {
} }
query(name) { query(name) {
const result = this._querySchema(name) || this._queryImport(name); const result = this.entries.get(name);
if (result) { if (result) {
return result; return result;
@ -80,24 +102,6 @@ class Scope {
type: 'unknown', type: 'unknown',
}; };
} }
_querySchema(name) {
const schema = this.schemas.get(name);
return schema && {
type: 'local',
schema,
};
}
_queryImport(name) {
const info = this.imports.get(name);
return info && {
type: 'external',
info,
};
}
} }
class Module { class Module {
@ -112,12 +116,22 @@ class Module {
return this.scopeCount++; return this.scopeCount++;
} }
addExport(name, schema) { addExport(name, scope, reference) {
this.exports.set(name, schema); this.exports.set(name, [scope, reference]);
} }
query(name) { query(name) {
return this.exports.get(name) || null; const result = this.exports.get(name);
if (!result) {
return {
type: 'unknown',
};
}
const [scope, reference] = result;
return scope.query(reference);
} }
resolve(path) { resolve(path) {

View File

@ -9,3 +9,5 @@ interface Interface {
class Class { class Class {
a: number[]; a: number[];
} }
export {Type, Interface, Class};

View File

@ -28,5 +28,5 @@
}] }]
} }
], ],
"taskCount": 3 "taskCount": 5
} }

View File

@ -7,3 +7,5 @@ type Y = {
}; };
type Z = string; type Z = string;
export {X};

View File

@ -18,5 +18,5 @@
"fields": [{"name": "y", "type": "Y"}] "fields": [{"name": "y", "type": "Y"}]
} }
], ],
"taskCount": 3 "taskCount": 5
} }

View File

@ -9,3 +9,5 @@ interface Interface {
class Class { class Class {
a: 'one' | 'two'; a: 'one' | 'two';
} }
export {Type, Interface, Class};

View File

@ -28,5 +28,5 @@
}] }]
} }
], ],
"taskCount": 3 "taskCount": 5
} }

View File

@ -21,3 +21,5 @@ type Y = {
k: T, k: T,
p: P, p: P,
}; };
export {X, Y};

View File

@ -71,5 +71,5 @@
] ]
} }
], ],
"taskCount": 18 "taskCount": 17
} }

View File

@ -1,8 +1,8 @@
export type N = { type N = {
n: boolean, n: boolean,
}; };
class M { export class M {
m: string; m: string;
} }
@ -10,8 +10,8 @@ interface KK {
k: number; k: number;
} }
class P { export default class P {
p: number; p: number;
} }
export {M, KK as K, P as default}; export {N, KK as K};

View File

@ -21,3 +21,5 @@ interface Y extends X {
interface Z extends Y { interface Z extends Y {
z: boolean; z: boolean;
} }
export {C, Z};

View File

@ -51,5 +51,5 @@
] ]
} }
], ],
"taskCount": 6 "taskCount": 8
} }

View File

@ -11,3 +11,5 @@ type Y = {
class Z { class Z {
z: A & C; z: A & C;
} }
export {X, Y, Z};

View File

@ -12,12 +12,6 @@
"namespace": "intersections", "namespace": "intersections",
"fields": [{"name": "b", "type": "string"}] "fields": [{"name": "b", "type": "string"}]
}, },
{
"type": "record",
"name": "C",
"namespace": "intersections",
"fields": [{"name": "c", "type": "boolean"}]
},
{ {
"type": "record", "type": "record",
"name": "X", "name": "X",
@ -27,6 +21,12 @@
{"name": "b", "type": "string"} {"name": "b", "type": "string"}
] ]
}, },
{
"type": "record",
"name": "C",
"namespace": "intersections",
"fields": [{"name": "c", "type": "boolean"}]
},
{ {
"type": "record", "type": "record",
"name": "_A_B_C", "name": "_A_B_C",
@ -63,5 +63,5 @@
] ]
} }
], ],
"taskCount": 6 "taskCount": 8
} }

View File

@ -5,3 +5,5 @@ type Type = {
interface Interface { interface Interface {
[string]: number; [string]: number;
} }
export {Type, Interface};

View File

@ -13,5 +13,5 @@
"values": "double" "values": "double"
} }
], ],
"taskCount": 2 "taskCount": 4
} }

View File

@ -36,3 +36,5 @@ class Class {
/* $avro fixed 10 */ /* $avro fixed 10 */
e: Buffer; e: Buffer;
} }
export {Type, Interface, Class};

View File

@ -37,5 +37,5 @@
] ]
} }
], ],
"taskCount": 3 "taskCount": 5
} }

View File

@ -21,3 +21,5 @@ class Class {
d: null; d: null;
e: Buffer; e: Buffer;
} }
export {Type, Interface, Class};

View File

@ -37,5 +37,5 @@
] ]
} }
], ],
"taskCount": 3 "taskCount": 5
} }

View File

@ -17,3 +17,5 @@ class Class {
b: A[]; b: A[];
c: A<number>; c: A<number>;
} }
export {Type, Interface, Class};

View File

@ -36,5 +36,5 @@
] ]
} }
], ],
"taskCount": 4 "taskCount": 6
} }

View File

@ -20,6 +20,9 @@ type Y = {
x: X, x: X,
z: Z, z: Z,
}; };
// TODO: replace with commonjs.
export {Y as Y2};
} }
class Test { class Test {
@ -30,6 +33,8 @@ type Y = {
x: X, x: X,
z: Z, z: Z,
}; };
export {Y as Y3};
} }
baz() { baz() {
@ -39,6 +44,12 @@ type Y = {
x: X, x: X,
z: Z, z: Z,
}; };
export {Y as Y4};
} }
} }
export {Y as Y1};
})(); })();
export {Y as Y0};

View File

@ -22,22 +22,16 @@
"namespace": "scopes._1", "namespace": "scopes._1",
"fields": [{"name": "x", "type": "X"}] "fields": [{"name": "x", "type": "X"}]
}, },
{
"type": "string",
"name": "Z",
"namespace": "scopes._1"
},
{
"type": "record",
"name": "Test",
"namespace": "scopes._1",
"fields": []
},
{ {
"type": "boolean", "type": "boolean",
"name": "X", "name": "X",
"namespace": "scopes._2" "namespace": "scopes._2"
}, },
{
"type": "string",
"name": "Z",
"namespace": "scopes._1"
},
{ {
"type": "record", "type": "record",
"name": "Y", "name": "Y",
@ -76,5 +70,5 @@
] ]
} }
], ],
"taskCount": 16 "taskCount": 17
} }

View File

@ -8,4 +8,9 @@ type X = {
type Y = { type Y = {
y: Buffer, y: Buffer,
}; };
// TODO: replace with commonjs.
export {Y};
})(); })();
export {X};

View File

@ -23,5 +23,5 @@
] ]
} }
], ],
"taskCount": 4 "taskCount": 6
} }

View File

@ -29,3 +29,5 @@ class Class {
baz: () => void; baz: () => void;
} }
export {Type, Interface, Class};

View File

@ -28,5 +28,5 @@
] ]
} }
], ],
"taskCount": 6 "taskCount": 8
} }

View File

@ -3,5 +3,10 @@ class Test {
type X = { type X = {
t: Test, t: Test,
}; };
// TODO: replace with commonjs.
export {X};
} }
} }
export {Test};

View File

@ -13,5 +13,5 @@
"fields": [{"name": "t", "type": "typeInMethod.Test"}] "fields": [{"name": "t", "type": "typeInMethod.Test"}]
} }
], ],
"taskCount": 3 "taskCount": 5
} }

View File

@ -12,3 +12,5 @@ class Class {
a: string | number; a: string | number;
b: ?string; b: ?string;
} }
export {Type, Interface, Class};

View File

@ -46,5 +46,5 @@
] ]
} }
], ],
"taskCount": 3 "taskCount": 5
} }

View File

@ -9,3 +9,5 @@ interface Interface {
class Class { class Class {
a: 'one' | 'two' | number; a: 'one' | 'two' | number;
} }
export {Type, Interface, Class};

View File

@ -37,5 +37,5 @@
}] }]
} }
], ],
"taskCount": 3 "taskCount": 5
} }

View File

@ -9,3 +9,5 @@ interface Interface {
class Class { class Class {
a: 'one'; a: 'one';
} }
export {Type, Interface, Class};

View File

@ -25,5 +25,5 @@
] ]
} }
], ],
"taskCount": 3 "taskCount": 5
} }