Skip to content
This repository has been archived by the owner on Mar 21, 2021. It is now read-only.

Commit

Permalink
WIP Ast Builder.
Browse files Browse the repository at this point in the history
  • Loading branch information
Shahar Soel committed Sep 8, 2017
1 parent 0c27008 commit 6df5348
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 11 deletions.
100 changes: 100 additions & 0 deletions lib/dsl/poc/ast_builder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
const JDLParser = require('./parser').JDLParser;
const _ = require('lodash');


function buildAst(cst) {
// eslint-disable-next-line no-use-before-define
const astBuilderVisitor = new JDLAstBuilderVisitor();
const ast = astBuilderVisitor.visit(cst);
return ast;
}

const BaseJDLCSTVisitor = new JDLParser().getBaseCstVisitorConstructor();

class JDLAstBuilderVisitor extends BaseJDLCSTVisitor {
constructor() {
super();
this.validateVisitor();
}

prog(ctx) {
return {
entities: _.map(ctx.entityDecl, elm => this.visit(elm)),
constants: _.map(ctx.constantDecl, elm => this.visit(elm))
};
}

constantDecl(ctx) {
return {
name: ctx.NAME[0].image,
value: parseInt(ctx.VALUE[0], 10)
};
}

entityDecl(ctx) {
return {
name: ctx.NAME[0].image,
// ctx.entityTableNameDecl is optional which means
// either an empty array or an array of a single element
// the "this.visit" API will handle this transparently and return
// undefined in the case of empty array.
tableName: this.visit(ctx.entityTableNameDecl),
fields: this.visit(ctx.entityBody)
};
}

entityTableNameDecl(ctx) {
return ctx.NAME[0].image;
}

entityBody(ctx) {
return _.map(ctx.fieldDec, elm => this.visit(elm));
}

fieldDec(ctx) {
return {
name: ctx.NAME[0].image,
// ctx.type is an array with a single item.
// in that case:
// this.visit(ctx.type) is equivalent to this.visit(ctx.type[0])
type: this.visit(ctx.type),
validations: _.map(ctx.validation, elm => this.visit(elm))
};
}

type(ctx) {
return ctx.NAME[0].image;
}

validation(ctx) {
// only one of these alternatives can exist at the same time.
if (!_.isEmpty(ctx.REQUIRED)) {
return {
validationType: 'required'
};
} else if (!_.isEmpty(ctx.minMaxValidation)) {
return this.visit(ctx.minMaxValidation);
}
return this.visit(ctx.pattern);
}

minMaxValidation(ctx) {
return {
validationType: ctx.MIN_MAX_KEYWORD.image,
limit: _.isEmpty(ctx.NAME) ?
parseInt(ctx.INTEGER[0], 10) :
ctx.NAME[0].image
};
}

pattern(ctx) {
return {
validationType: 'pattern',
pattern: ctx.REGEX[0].image
};
}
}

module.exports = {
buildAst
};
29 changes: 18 additions & 11 deletions lib/dsl/poc/lexer.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ const Lexer = chevrotain.Lexer;
const tokens = {};

function createToken(config) {
const isKeyword = _.isString(config.pattern) && /\w+/.test(config.pattern);
if (isKeyword) {

}
tokens[config.name] = chevrotain.createToken(config);
}


createToken({
name: 'WhiteSpace',
// TODO: uncertain why the original grammar disallowed newlines
Expand Down Expand Up @@ -39,6 +44,19 @@ createToken({
line_breaks: true
});

// All the names tokens of the pegjs implementation have been merged into
// a single token type. This is because they would cause ambiguities
// when the lexing stage is separated from the parsing stage.
// They restrictions on the names should be implemented as semantic checks
// That approach could also provide a better experience in an Editor
// As semantic checks don't require fault tolerance and recovery like
// syntax errors do.
// TODO: looks like the parenthesis should not be part of the name, but a suffix, eg: "maxlength(25)"
// TODO: because if it is part of the name than this is also valid "max))((Length()1)))"...
createToken({ name: 'NAME', pattern: /[a-zA-Z_][a-zA-Z_\d()]*/ });

createToken({ name: 'KEYWORD', pattern: Lexer.NA, longer_alt: tokens.NAME });

// Constants
// Application constants
createToken({ name: 'APPLICATION', pattern: 'application' });
Expand Down Expand Up @@ -118,17 +136,6 @@ createToken({ name: 'PATTERN', pattern: 'pattern' });
createToken({ name: 'REGEX', pattern: /\/[^\n\r/]*\// });
createToken({ name: 'INTEGER', pattern: /-?\d+/ });

// All the names tokens of the pegjs implementation have been merged into
// a single token type. This is because they would cause ambiguities
// when the lexing stage is separated from the parsing stage.
// They restrictions on the names should be implemented as semantic checks
// That approach could also provide a better experience in an Editor
// As semantic checks don't require fault tolerance and recovery like
// syntax errors do.
// TODO: looks like the parenthesis should not be part of the name, but a suffix, eg: "maxlength(25)"
// TODO: because if it is part of the name than this is also valid "max))((Length()1)))"...
createToken({ name: 'NAME', pattern: /[a-zA-Z_][a-zA-Z_\d()]*/ });

// punctuation
createToken({ name: 'LPAREN', pattern: '(' });
createToken({ name: 'RPAREN', pattern: ')' });
Expand Down
70 changes: 70 additions & 0 deletions test/spec/grammar/ast_builder_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/* eslint-disable key-spacing, no-unused-expressions */
const expect = require('chai').expect;
const parse = require('../../../lib/dsl/poc/api').parse;
const buildAst = require('../../../lib/dsl/poc/ast_builder').buildAst;


describe('JDL AST Builder poc', () => {
it('can build an ast for a simple entity', () => {
const input = 'entity Person { name string }';
const parseResult = parse(input);
expect(parseResult.lexErrors).to.be.empty;
expect(parseResult.parseErrors).to.be.empty;

const ast = buildAst(parseResult.cst);
expect(ast).to.deep.equal({
constants: [],
entities: [
{
fields: [
{
name: 'name',
type: 'string',
validations: []
}
],
name: 'Person',
tableName: undefined
}
]
});
});

it('can build an ast for a complex JDL', () => {
const input = `
entity Person
{
name string,
age number max(max_age)
}
max_age = 120
entity Job
{
address string required
}
`;
const parseResult = parse(input);
expect(parseResult.lexErrors).to.be.empty;
expect(parseResult.parseErrors).to.be.empty;

const ast = buildAst(parseResult.cst);
expect(ast).to.deep.equal({
constants: [],
entities: [
{
fields: [
{
name: 'name',
type: 'string',
validations: []
}
],
name: 'Person',
tableName: undefined
}
]
});
});
});

0 comments on commit 6df5348

Please sign in to comment.