Add the scope concept

master
Paul Loyd 2017-11-02 20:21:56 +03:00
parent 9706b554df
commit 492a9e9af8
6 changed files with 121 additions and 28 deletions

View File

@ -1,21 +1,26 @@
'use strict';
const assert = require('assert');
const fs = require('fs');
const extractors = require('./extractors');
const Command = require('./commands');
const Scope = require('./scope');
const {log} = require('./utils');
class Collector {
constructor(parser) {
this.parser = parser;
this.schemas = Object.create(null); // TODO: use `Map`.
this.scope = null;
}
collect(path) {
const code = fs.readFileSync(path, 'utf8');
const ast = this.parser.parse(code);
this.scope = new Scope(null);
this._visit(ast);
}
@ -43,12 +48,10 @@ class Collector {
if (isNode(node)) {
const {type} = node;
if (type) {
if (type in extractors) {
this._collect(node);
if (type && type in extractors) {
this._collect(node);
continue;
}
continue;
}
stack = { parent, keys, index, prev: stack };
@ -80,13 +83,32 @@ class Collector {
if (!value) {
result = null;
} else if (value instanceof Command && value.name === 'provide') {
const {schema} = value;
this.schemas[schema.name] = schema;
} else if (value instanceof Command) {
switch (value.name) {
case 'provide':
assert(this.scope);
this.scope.addSchema(value.data);
this.schemas[value.data.name] = value.data;
break;
case 'query':
const schema = this.scope.query(value.data);
assert(schema, value.data);
result = schema;
break;
case 'enter':
this.scope = new Scope(this.scope);
break;
case 'exit':
assert(this.scope);
this.scope = this.scope.parent;
break;
default:
assert(false);
}
} else if (Array.isArray(value)) {
result = value.map(val => this._collect(val));
} else {
assert(isNode(value));
result = this._collect(value);
}
}

View File

@ -2,13 +2,24 @@
class Command {
static provide(schema) {
return new Command('provide', {schema});
return new Command('provide', schema);
}
constructor(name, fields) {
this.name = name;
static query(name) {
return new Command('query', name);
}
Object.assign(this, fields);
static enter() {
return new Command('enter');
}
static exit() {
return new Command('exit');
}
constructor(name, data) {
this.name = name;
this.data = data;
}
}

View File

@ -2,7 +2,7 @@
const assert = require('assert');
const {provide} = require('./commands');
const {provide, query} = require('./commands');
const {partition, log} = require('./utils');
const extractors = {
@ -170,6 +170,8 @@ const extractors = {
return 'bytes';
}
yield query(name);
return name;
},

51
lib/scope.js Normal file
View File

@ -0,0 +1,51 @@
'use strict';
class Scope {
constructor(parent) {
this.schemas = new Map;
this.externals = new Map;
this.parent = parent;
}
addSchema(schema) {
this.schemas.set(schema.name, schema);
}
addExternal(external) {
this.externals.set(external.name, external);
}
query(name) {
const result = this._querySchema(name) || this._queryExternal(name);
if (result) {
return result;
}
if (this.parent) {
return this.parent.query(name);
}
return null;
}
_querySchema(name) {
const schema = this.schemas.get(name);
return schema ? {
type: 'local',
schema,
} : null;
}
_queryExternal(name) {
const info = this.externals.get(name);
return info ? {
type: 'external',
info,
} : null;
}
}
module.exports = Scope;

View File

@ -15,14 +15,14 @@ const list = fs.readdirSync(__dirname)
.map(fname => path.relative('.', path.join(__dirname, fname)));
for (const fpath of list) {
console.log(`${fpath}...`);
const code = fs.readFileSync(fpath, 'utf8');
const {schemas: actual} = collect(fpath);
const expected = eval(code.split('// ###')[1]);
const delta = jsondiffpatch.diff(actual, expected);
console.log(`${fpath}...`);
if (delta) {
jsondiffpatch.console.log(delta);
}

View File

@ -1,30 +1,37 @@
type A = {};
type Type = {
a: A,
b: B[],
c: C<number>,
b: A[],
c: A<number>,
};
interface Interface {
a: A;
b: B[];
c: C<number>;
b: A[];
c: A<number>;
}
class Class {
a: A;
b: B[];
c: C<number>;
b: A[];
c: A<number>;
}
// ###
({
A: {
type: 'record',
name: 'A',
fields: [],
},
Type: {
type: 'record',
name: 'Type',
fields: [
{name: 'a', type: 'A'},
{name: 'b', type: {type: 'array', items: 'B'}},
{name: 'c', type: 'C'},
{name: 'b', type: {type: 'array', items: 'A'}},
{name: 'c', type: 'A'},
],
},
Interface: {
@ -32,8 +39,8 @@ class Class {
name: 'Interface',
fields: [
{name: 'a', type: 'A'},
{name: 'b', type: {type: 'array', items: 'B'}},
{name: 'c', type: 'C'},
{name: 'b', type: {type: 'array', items: 'A'}},
{name: 'c', type: 'A'},
],
},
Class: {
@ -41,8 +48,8 @@ class Class {
name: 'Class',
fields: [
{name: 'a', type: 'A'},
{name: 'b', type: {type: 'array', items: 'B'}},
{name: 'c', type: 'C'},
{name: 'b', type: {type: 'array', items: 'A'}},
{name: 'c', type: 'A'},
],
},
});