150 lines
3.2 KiB
JavaScript
150 lines
3.2 KiB
JavaScript
import * as assert from 'assert';
|
|
|
|
export default class Scope {
|
|
static global(schemas) {
|
|
const global = new Scope(null, null);
|
|
|
|
for (const schema of schemas) {
|
|
global.addDefinition(schema, false);
|
|
}
|
|
|
|
return global;
|
|
}
|
|
|
|
constructor(parent, module) {
|
|
this.parent = parent;
|
|
this.module = module;
|
|
this.scopeId = module && module.generateScopeId();
|
|
this.entries = new Map;
|
|
}
|
|
|
|
get namespace() {
|
|
assert.ok(this.module);
|
|
|
|
let namespace = this.module.namespace;
|
|
|
|
// Nested scopes.
|
|
if (this.scopeId > 0) {
|
|
namespace += '._' + this.scopeId;
|
|
}
|
|
|
|
return namespace;
|
|
}
|
|
|
|
extend(module = null) {
|
|
return new Scope(this, module || this.module);
|
|
}
|
|
|
|
addDeclaration(name, node, params) {
|
|
assert.ok(!this.entries.has(name));
|
|
|
|
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.ok(template);
|
|
assert.equal(template.type, 'template');
|
|
|
|
template.instances.push({params, schema});
|
|
}
|
|
|
|
addDefinition(schema, declared) {
|
|
const decl = this.entries.get(schema.name);
|
|
|
|
if (declared) {
|
|
assert.ok(decl);
|
|
assert.equal(decl.type, 'declaration');
|
|
} else {
|
|
assert.ok(!decl);
|
|
}
|
|
|
|
this.entries.set(schema.name, {
|
|
type: 'definition',
|
|
schema,
|
|
scope: this,
|
|
});
|
|
}
|
|
|
|
addImport(info) {
|
|
assert.ok(!this.entries.has(info.local));
|
|
|
|
this.entries.set(info.local, {
|
|
type: 'external',
|
|
info,
|
|
scope: this,
|
|
});
|
|
}
|
|
|
|
addExport(name, reference) {
|
|
assert.ok(this.module);
|
|
|
|
this.module.addExport(name, this, reference);
|
|
}
|
|
|
|
resolve(path) {
|
|
assert.ok(this.module);
|
|
|
|
return this.module.resolve(path);
|
|
}
|
|
|
|
query(name, params) {
|
|
const entry = this.entries.get(name);
|
|
|
|
if (entry && entry.type === 'template') {
|
|
assert.ok(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) {
|
|
return this.parent.query(name);
|
|
}
|
|
|
|
return {
|
|
type: 'unknown',
|
|
};
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|