Skip to content

Commit

Permalink
Document CFG and associated methods
Browse files Browse the repository at this point in the history
Also, pass each rule provided to the constructor through the `rule()`
method, instead of simply pushing it onto the array. This allows
stringy rules to be passed into the constructor.
  • Loading branch information
patgrasso committed Aug 4, 2016
1 parent 1f249d5 commit 7b6e594
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 1 deletion.
59 changes: 58 additions & 1 deletion lib/cfg.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,72 @@
/**
* Defines the CFG class, a container for production rules that essentially
* amounts to an array with some extra methods
*
* @module lib/cfg
*/

'use strict';

const Rule = require('./rules').Rule;
const Sym = require('./rules').Sym;

/**
* Regex that matches bare JS regex expressions like /^\d+$/g
* @constant {RegExp} REGEX_REGEX
*/
const REGEX_REGEX = new RegExp('^\/(.*)\/([gimy]*)$');

/**
* Regex that matches bare JS string expressions like "hello" and 'hello'
* @constant {RegExp} STR_REGEX
*/
const STR_REGEX = new RegExp('^\'(.*)\'|"(.*)"$');


/**
* Constructs a new context-free grammar, which is just a container for
* production rules
*
* @class CFG
* @extends Array
* @constructor
* @param {rules=} - Optional array of {@link module:lib/rules.Rule|Rules} to
* initialize the grammar with
*/
function CFG(rules) {
let arr = [];

arr.push.apply(arr, rules);
arr.__proto__ = CFG.prototype;
if (Array.isArray(rules)) {
rules.forEach(arr.rule.bind(arr));
}

return arr;
}
Object.setPrototypeOf(CFG.prototype, Array.prototype);


/**
* Adds a rule to the grammar. The rule can be either an instance of
* {@link Rule}, or a string of the form
* <pre>
* A -> B C D
* </pre>
* Symbols in a stringy rule (such as 'A', 'B', 'C', and 'D' above) will be
* searched for by name in existing rules and replaced if a match is found, or
* if no match is found a new Sym will be created.
*
* Terminal symbols such as "a", 'b' will be treated as terminal strings and not
* converted into Syms, and likewise bare regexps such as /\d+/ will be
* converted into RegExp objects
*
* @method rule
* @param {string|Rule} theRule - Rule object or string describing a production
* @return {Rule} The rule that was either passed in, or created from the string
* passed in, and subsequently added to the grammar
* @throws {SyntaxError} If a stringy rule is passed in that doesn't have a ->
* separator
*/
CFG.prototype.rule = function (theRule) {
var rhs, lhs, syms, match;

Expand Down Expand Up @@ -57,6 +107,13 @@ CFG.prototype.rule = function (theRule) {
};


/**
* Retrieve all unique Sym objects within the grammar by searching each rule
*
* @return {object} Object whose keys are the names of each symbol, and whose
* values are the actual Sym object pointers
* @throws {Error} If multiple symbols are found with the same name
*/
CFG.prototype.getSymbols = function () {
let syms = {};

Expand Down
18 changes: 18 additions & 0 deletions spec/cfg-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,24 @@ describe('CFG', () => {
expect(cfg).toEqual(jasmine.arrayContaining(rules));
});

it('accepts stringy rules in the optional array argument', () => {
let rules = [
'S -> NP VP',
'NP -> Det N',
'VP -> V NP'
];
cfg = new CFG(rules);
expect(cfg).toContain(Rule(s, [np, vp]));
expect(cfg).toContain(Rule(np, [Sym('Det'), Sym('N')]));
expect(cfg).toContain(Rule(vp, [Sym('V'), np]));
expect(cfg).not.toContain(Rule(vp, [Sym('V'), vp]));
});

it('ignores non-array things passed in', () => {
cfg = new CFG('peanut butter');
expect(cfg.length).toBe(0);
});

it('initializes fine with no rules provided', () => {
cfg = new CFG();
expect(cfg.length).toBe(0);
Expand Down

0 comments on commit 7b6e594

Please sign in to comment.