Initial support for generics
parent
16632df5ed
commit
1fc08b013e
|
@ -10,6 +10,7 @@ const Command = require('./commands');
|
||||||
const Module = require('./module');
|
const Module = require('./module');
|
||||||
const Scope = require('./scope');
|
const Scope = require('./scope');
|
||||||
const CircularList = require('./list');
|
const CircularList = require('./list');
|
||||||
|
const {isNode} = require('./utils');
|
||||||
|
|
||||||
class Collector {
|
class Collector {
|
||||||
constructor(parser, root = '.') {
|
constructor(parser, root = '.') {
|
||||||
|
@ -46,7 +47,7 @@ class Collector {
|
||||||
|
|
||||||
const scope = this.global.extend(module);
|
const scope = this.global.extend(module);
|
||||||
|
|
||||||
this._freestyle(extractors.declaration, ast.program, scope);
|
this._freestyle(extractors.declaration, ast.program, scope, null);
|
||||||
|
|
||||||
this.modules.set(path, module);
|
this.modules.set(path, module);
|
||||||
|
|
||||||
|
@ -69,7 +70,7 @@ class Collector {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
_freestyle(group, root, scope) {
|
_freestyle(group, root, scope, params) {
|
||||||
let stack;
|
let stack;
|
||||||
let parent;
|
let parent;
|
||||||
let keys = [];
|
let keys = [];
|
||||||
|
@ -91,7 +92,7 @@ class Collector {
|
||||||
|
|
||||||
if (isNode(node) && isAcceptableGroup(group, node)) {
|
if (isNode(node) && isAcceptableGroup(group, node)) {
|
||||||
if (!this.roots.has(node)) {
|
if (!this.roots.has(node)) {
|
||||||
const task = this._collect(group, node, scope);
|
const task = this._collect(group, node, scope, params);
|
||||||
this.roots.add(node);
|
this.roots.add(node);
|
||||||
this._spawn(task);
|
this._spawn(task);
|
||||||
}
|
}
|
||||||
|
@ -108,11 +109,11 @@ class Collector {
|
||||||
} while (stack);
|
} while (stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
* _collect(group, node, scope) {
|
* _collect(group, node, scope, params) {
|
||||||
const extractor = group[node.type];
|
const extractor = group[node.type];
|
||||||
|
|
||||||
if (!extractor) {
|
if (!extractor) {
|
||||||
this._freestyle(group, node, scope);
|
this._freestyle(group, node, scope, null);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,8 +139,19 @@ class Collector {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'define':
|
case 'define':
|
||||||
scope.addDefinition(...value.data);
|
const [schema, declared] = value.data;
|
||||||
this.schemas.push(value.data[0]);
|
|
||||||
|
if (declared && params) {
|
||||||
|
const name = schema.name;
|
||||||
|
|
||||||
|
schema.name = generateGenericName(name, params);
|
||||||
|
|
||||||
|
scope.addInstance(name, schema, params.map(p => p.value));
|
||||||
|
} else {
|
||||||
|
scope.addDefinition(...value.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.schemas.push(schema);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'external':
|
case 'external':
|
||||||
|
@ -151,7 +163,16 @@ class Collector {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'query':
|
case 'query':
|
||||||
result = yield* this._query(scope, value.data);
|
if (params) {
|
||||||
|
const param = params.find(p => p.name === value.data[0]);
|
||||||
|
|
||||||
|
if (param) {
|
||||||
|
result = param.value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = yield* this._query(scope, ...value.data);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'enter':
|
case 'enter':
|
||||||
|
@ -172,17 +193,17 @@ class Collector {
|
||||||
result = [];
|
result = [];
|
||||||
|
|
||||||
for (const val of value) {
|
for (const val of value) {
|
||||||
result.push(yield* this._collect(group, val, scope));
|
result.push(yield* this._collect(group, val, scope, params));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
assert(isNode(value));
|
assert(isNode(value));
|
||||||
result = yield* this._collect(group, value, scope);
|
result = yield* this._collect(group, value, scope, params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
* _query(scope, name) {
|
* _query(scope, name, params) {
|
||||||
let result = scope.query(name);
|
let result = scope.query(name, params);
|
||||||
|
|
||||||
// TODO: warning.
|
// TODO: warning.
|
||||||
assert.notEqual(result.type, 'unknown');
|
assert.notEqual(result.type, 'unknown');
|
||||||
|
@ -190,6 +211,9 @@ class Collector {
|
||||||
// Resulting scope is always the best choice for waiting.
|
// Resulting scope is always the best choice for waiting.
|
||||||
scope = result.scope;
|
scope = result.scope;
|
||||||
|
|
||||||
|
// It's only valid the sequence: E*[CT]?F,
|
||||||
|
// where E - external, C - declaration, T - template, F - definition.
|
||||||
|
|
||||||
switch (result.type) {
|
switch (result.type) {
|
||||||
case 'external':
|
case 'external':
|
||||||
const modulePath = scope.resolve(result.info.path);
|
const modulePath = scope.resolve(result.info.path);
|
||||||
|
@ -199,7 +223,7 @@ class Collector {
|
||||||
const module = this.modules.get(modulePath);
|
const module = this.modules.get(modulePath);
|
||||||
const {imported} = result.info;
|
const {imported} = result.info;
|
||||||
|
|
||||||
while ((result = module.query(imported)).type === 'unknown') {
|
while ((result = module.query(imported, params)).type === 'unknown') {
|
||||||
yield;
|
yield;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,16 +232,27 @@ class Collector {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: reexports.
|
// TODO: reexports.
|
||||||
assert.equal(result.type, 'declaration');
|
assert(result.type === 'declaration' || result.type === 'template');
|
||||||
|
|
||||||
scope = result.scope;
|
scope = result.scope;
|
||||||
name = result.name;
|
name = result.name;
|
||||||
|
|
||||||
// Fallthrough.
|
// Fallthrough.
|
||||||
case 'declaration':
|
case 'declaration':
|
||||||
this._freestyle(extractors.definition, result.node, scope);
|
case 'template':
|
||||||
|
let tmplParams = null;
|
||||||
|
|
||||||
while ((result = scope.query(name)).type === 'declaration') {
|
if (result.type === 'template') {
|
||||||
|
tmplParams = result.params.map((p, i) => ({
|
||||||
|
name: p.name,
|
||||||
|
value: params[i] || p.default,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
this._freestyle(extractors.definition, result.node, scope, tmplParams);
|
||||||
|
|
||||||
|
while ((result = scope.query(name, params)).type !== 'definition') {
|
||||||
|
assert.notEqual(result.type, 'external');
|
||||||
yield;
|
yield;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,7 +266,7 @@ class Collector {
|
||||||
|
|
||||||
* _grabExports(module) {
|
* _grabExports(module) {
|
||||||
for (const [scope, name] of module.exports()) {
|
for (const [scope, name] of module.exports()) {
|
||||||
yield* this._query(scope, name);
|
yield* this._query(scope, name, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,10 +303,6 @@ class Collector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNode(it) {
|
|
||||||
return it && typeof it === 'object' && it.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
function pathToNamespace(path) {
|
function pathToNamespace(path) {
|
||||||
const pathObj = pathlib.parse(path);
|
const pathObj = pathlib.parse(path);
|
||||||
|
|
||||||
|
@ -288,4 +319,15 @@ function isAcceptableGroup(group, node) {
|
||||||
return group.entries.includes(node.type);
|
return group.entries.includes(node.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generateGenericName(base, params) {
|
||||||
|
let name = base + '_';
|
||||||
|
|
||||||
|
for (const {value} of params) {
|
||||||
|
assert.equal(typeof value, 'string');
|
||||||
|
name += '_' + value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = Collector;
|
module.exports = Collector;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
class Command {
|
class Command {
|
||||||
static declare(name, node) {
|
static declare(name, node, params) {
|
||||||
return new Command('declare', [name, node]);
|
return new Command('declare', [name, node, params]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static define(schema, declared = true) {
|
static define(schema, declared = true) {
|
||||||
|
@ -17,8 +17,8 @@ class Command {
|
||||||
return new Command('provide', [name, reference]);
|
return new Command('provide', [name, reference]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static query(name) {
|
static query(name, params = null) {
|
||||||
return new Command('query', name);
|
return new Command('query', [name, params]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static enter() {
|
static enter() {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
|
|
||||||
const {declare, 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, isNode} = require('./utils');
|
||||||
|
|
||||||
const definition = {
|
const definition = {
|
||||||
entries: [
|
entries: [
|
||||||
|
@ -199,8 +199,13 @@ const definition = {
|
||||||
|
|
||||||
* GenericTypeAnnotation(node) {
|
* GenericTypeAnnotation(node) {
|
||||||
const name = yield node.id;
|
const name = yield node.id;
|
||||||
|
const params = node.typeParameters && (yield node.typeParameters);
|
||||||
|
|
||||||
const schema = yield query(name);
|
const schema = yield query(name, params);
|
||||||
|
|
||||||
|
if (typeof schema === 'string') {
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
if (schema.$unwrap) {
|
if (schema.$unwrap) {
|
||||||
return schema.type;
|
return schema.type;
|
||||||
|
@ -215,6 +220,10 @@ const definition = {
|
||||||
return makeFullname(schema);
|
return makeFullname(schema);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
* TypeParameterInstantiation(node) {
|
||||||
|
return yield node.params;
|
||||||
|
},
|
||||||
|
|
||||||
* FunctionTypeAnnotation(node) {
|
* FunctionTypeAnnotation(node) {
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
@ -385,32 +394,46 @@ const declaration = {
|
||||||
|
|
||||||
* TypeAlias(node) {
|
* TypeAlias(node) {
|
||||||
const name = yield node.id;
|
const name = yield node.id;
|
||||||
|
const params = node.typeParameters && (yield node.typeParameters);
|
||||||
|
|
||||||
yield declare(name, node);
|
yield declare(name, node, params);
|
||||||
|
|
||||||
return name;
|
return name;
|
||||||
},
|
},
|
||||||
|
|
||||||
* InterfaceDeclaration(node) {
|
* InterfaceDeclaration(node) {
|
||||||
const name = yield node.id;
|
const name = yield node.id;
|
||||||
|
const params = node.typeParameters && (yield node.typeParameters);
|
||||||
|
|
||||||
yield declare(name, node);
|
yield declare(name, node, params);
|
||||||
|
|
||||||
return name;
|
return name;
|
||||||
},
|
},
|
||||||
|
|
||||||
* ClassDeclaration(node) {
|
* ClassDeclaration(node) {
|
||||||
const name = yield node.id;
|
const name = yield node.id;
|
||||||
|
const params = node.typeParameters && (yield node.typeParameters);
|
||||||
|
|
||||||
// TODO: do it only for "all"-mode.
|
// TODO: do it only for "all"-mode.
|
||||||
const body = node.body;
|
const body = node.body;
|
||||||
yield body.body.filter(is('ClassMethod'));
|
yield body.body.filter(is('ClassMethod'));
|
||||||
|
|
||||||
yield declare(name, node);
|
yield declare(name, node, params);
|
||||||
|
|
||||||
return name;
|
return name;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
* TypeParameterDeclaration(node) {
|
||||||
|
return yield node.params;
|
||||||
|
},
|
||||||
|
|
||||||
|
* TypeParameter(node) {
|
||||||
|
return {
|
||||||
|
name: node.name,
|
||||||
|
default: node.default ? yield node.default : null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Utility.
|
* Utility.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -20,7 +20,7 @@ class Module {
|
||||||
this._exports.set(name, [scope, reference]);
|
this._exports.set(name, [scope, reference]);
|
||||||
}
|
}
|
||||||
|
|
||||||
query(name) {
|
query(name, params) {
|
||||||
const result = this._exports.get(name);
|
const result = this._exports.get(name);
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
@ -31,7 +31,7 @@ class Module {
|
||||||
|
|
||||||
const [scope, reference] = result;
|
const [scope, reference] = result;
|
||||||
|
|
||||||
return scope.query(reference);
|
return scope.query(reference, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(path) {
|
resolve(path) {
|
||||||
|
|
69
lib/scope.js
69
lib/scope.js
|
@ -37,22 +37,43 @@ class Scope {
|
||||||
return new Scope(this, module || this.module);
|
return new Scope(this, module || this.module);
|
||||||
}
|
}
|
||||||
|
|
||||||
addDeclaration(name, node) {
|
addDeclaration(name, node, params) {
|
||||||
assert(!this.entries.has(name));
|
assert(!this.entries.has(name));
|
||||||
|
|
||||||
this.entries.set(name, {
|
const isTemplate = Boolean(params);
|
||||||
type: 'declaration',
|
|
||||||
|
const entry = {
|
||||||
|
type: isTemplate ? 'template' : 'declaration',
|
||||||
name,
|
name,
|
||||||
node,
|
node,
|
||||||
scope: this,
|
scope: this,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (isTemplate) {
|
||||||
|
entry.params = params;
|
||||||
|
entry.instances = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.entries.set(name, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
addInstance(name, schema, params) {
|
||||||
|
const template = this.entries.get(name);
|
||||||
|
|
||||||
|
assert(template);
|
||||||
|
assert.equal(template.type, 'template');
|
||||||
|
|
||||||
|
template.instances.push({params, schema});
|
||||||
}
|
}
|
||||||
|
|
||||||
addDefinition(schema, declared) {
|
addDefinition(schema, declared) {
|
||||||
|
const decl = this.entries.get(schema.name);
|
||||||
|
|
||||||
if (declared) {
|
if (declared) {
|
||||||
assert.equal((this.entries.get(schema.name) || {}).type, 'declaration');
|
assert(decl);
|
||||||
|
assert.equal(decl.type, 'declaration');
|
||||||
} else {
|
} else {
|
||||||
assert(!this.entries.has(schema.name));
|
assert(!decl);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.entries.set(schema.name, {
|
this.entries.set(schema.name, {
|
||||||
|
@ -84,11 +105,26 @@ class Scope {
|
||||||
return this.module.resolve(path);
|
return this.module.resolve(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
query(name) {
|
query(name, params) {
|
||||||
const result = this.entries.get(name);
|
const entry = this.entries.get(name);
|
||||||
|
|
||||||
if (result) {
|
if (entry && entry.type === 'template') {
|
||||||
return result;
|
assert(params);
|
||||||
|
|
||||||
|
const augmented = entry.params.map((p, i) => params[i] || p.default);
|
||||||
|
const schema = findInstance(entry, augmented);
|
||||||
|
|
||||||
|
if (schema) {
|
||||||
|
return {
|
||||||
|
type: 'definition',
|
||||||
|
schema,
|
||||||
|
scope: entry.scope,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry) {
|
||||||
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.parent) {
|
if (this.parent) {
|
||||||
|
@ -101,4 +137,17 @@ class Scope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findInstance(template, queried) {
|
||||||
|
for (const {schema, params} of template.instances) {
|
||||||
|
// TODO: compare complex structures.
|
||||||
|
const same = params.every((p, i) => p === queried[i]);
|
||||||
|
|
||||||
|
if (same) {
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = Scope;
|
module.exports = Scope;
|
||||||
|
|
|
@ -11,6 +11,11 @@ function partition(iter, predicate) {
|
||||||
return [left, right];
|
return [left, right];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isNode(it) {
|
||||||
|
return it && typeof it === 'object' && it.type;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
partition,
|
partition,
|
||||||
|
isNode,
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
type A<T, K> = {
|
||||||
|
t: T,
|
||||||
|
k: K,
|
||||||
|
};
|
||||||
|
|
||||||
|
type X = {
|
||||||
|
a: A<string, number>,
|
||||||
|
};
|
||||||
|
|
||||||
|
export {X};
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"schemas": [
|
||||||
|
{
|
||||||
|
"type": "record",
|
||||||
|
"name": "A__string_double",
|
||||||
|
"namespace": "generics",
|
||||||
|
"fields": [
|
||||||
|
{"name": "t", "type": "string"},
|
||||||
|
{"name": "k", "type": "double"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "record",
|
||||||
|
"name": "X",
|
||||||
|
"namespace": "generics",
|
||||||
|
"fields": [{"name": "a", "type": "A__string_double"}]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"taskCount": 4
|
||||||
|
}
|
Loading…
Reference in New Issue