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