From 2ceb94fc420c0d9eea46b4d834ec10b5bc98cf71 Mon Sep 17 00:00:00 2001 From: Jeff Avallone Date: Tue, 26 Jul 2016 22:27:17 -0400 Subject: [PATCH] Adding support for including ^ and $ in the middle of an expression Closes #29 --- spec/parser/javascript/anchor_spec.js | 20 +++++ spec/parser/javascript/match_spec.js | 105 +------------------------- src/js/parser/javascript/anchor.js | 15 ++++ src/js/parser/javascript/grammar.peg | 7 +- src/js/parser/javascript/match.js | 37 ++------- src/js/parser/javascript/parser.js | 2 + 6 files changed, 50 insertions(+), 136 deletions(-) create mode 100644 spec/parser/javascript/anchor_spec.js create mode 100644 src/js/parser/javascript/anchor.js diff --git a/spec/parser/javascript/anchor_spec.js b/spec/parser/javascript/anchor_spec.js new file mode 100644 index 0000000..2129ecf --- /dev/null +++ b/spec/parser/javascript/anchor_spec.js @@ -0,0 +1,20 @@ +import javascript from '../../../src/js/parser/javascript/parser.js'; +import _ from 'lodash'; + +describe('parser/javascript/anchor.js', function() { + + _.forIn({ + '^': { + label: 'Start of line' + }, + '$': { + label: 'End of line' + } + }, (content, str) => { + it(`parses "${str}" as an Anchor`, function() { + var parser = new javascript.Parser(str); + expect(parser.__consume__anchor()).toEqual(jasmine.objectContaining(content)); + }); + }); + +}); diff --git a/spec/parser/javascript/match_spec.js b/spec/parser/javascript/match_spec.js index c914288..0e0e75c 100644 --- a/spec/parser/javascript/match_spec.js +++ b/spec/parser/javascript/match_spec.js @@ -7,8 +7,6 @@ describe('parser/javascript/match.js', function() { _.forIn({ 'example': { - anchorStart: false, - anchorEnd: false, parts: [ jasmine.objectContaining({ content: jasmine.objectContaining({ literal: 'example' }) @@ -18,27 +16,7 @@ describe('parser/javascript/match.js', function() { content: jasmine.objectContaining({ literal: 'example' }) }) }, - '^example': { - anchorStart: true, - anchorEnd: false, - parts: [ - jasmine.objectContaining({ - content: jasmine.objectContaining({ literal: 'example' }) - }) - ] - }, - 'example$': { - anchorStart: false, - anchorEnd: true, - parts: [ - jasmine.objectContaining({ - content: jasmine.objectContaining({ literal: 'example' }) - }) - ] - }, 'example*': { - anchorStart: false, - anchorEnd: false, parts: [ jasmine.objectContaining({ content: jasmine.objectContaining({ literal: 'exampl' }) @@ -49,8 +27,6 @@ describe('parser/javascript/match.js', function() { ] }, '': { - anchorStart: false, - anchorEnd: false, parts: [] } }, (content, str) => { @@ -99,9 +75,6 @@ describe('parser/javascript/match.js', function() { beforeEach(function() { this.node = new javascript.Parser('a').__consume__match(); - this.node.anchorStart = true; - this.node.anchorEnd = true; - this.node.container = jasmine.createSpyObj('container', [ 'addClass', 'group', @@ -110,14 +83,6 @@ describe('parser/javascript/match.js', function() { ]); this.node.container.group.and.returnValue('example group'); - this.labelDeferreds = { - 'Start of line': this.testablePromise(), - 'End of line': this.testablePromise() - }; - spyOn(this.node, 'renderLabel').and.callFake(label => { - return this.labelDeferreds[label].promise; - }); - this.node.parts = [ jasmine.createSpyObj('part 0', ['render']), jasmine.createSpyObj('part 1', ['render']), @@ -135,58 +100,6 @@ describe('parser/javascript/match.js', function() { this.node.parts[2].render.and.returnValue(this.partDeferreds[2].promise); }); - describe('when there is no start anchor', function() { - - beforeEach(function() { - this.node.anchorStart = false; - }); - - it('does not render a start anchor label', function() { - this.node._render(); - expect(this.node.renderLabel).not.toHaveBeenCalledWith('Start of line'); - }); - - }); - - describe('when there is a start anchor', function() { - - beforeEach(function() { - this.node.anchorStart = true; - }); - - it('renders a start anchor label', function() { - this.node._render(); - expect(this.node.renderLabel).toHaveBeenCalledWith('Start of line'); - }); - - }); - - describe('when there is no end anchor', function() { - - beforeEach(function() { - this.node.anchorEnd = false; - }); - - it('does not render an end anchor label', function() { - this.node._render(); - expect(this.node.renderLabel).not.toHaveBeenCalledWith('End of line'); - }); - - }); - - describe('when there is an end anchor', function() { - - beforeEach(function() { - this.node.anchorEnd = true; - }); - - it('renders an end anchor label', function() { - this.node._render(); - expect(this.node.renderLabel).toHaveBeenCalledWith('End of line'); - }); - - }); - it('renders each part', function() { this.node._render(); expect(this.node.parts[0].render).toHaveBeenCalledWith('example group'); @@ -197,12 +110,6 @@ describe('parser/javascript/match.js', function() { describe('positioning of items', function() { beforeEach(function() { - this.startLabel = jasmine.createSpyObj('start', ['addClass']); - this.startLabel.addClass.and.returnValue('start label'); - this.endLabel = jasmine.createSpyObj('end', ['addClass']); - this.endLabel.addClass.and.returnValue('end label'); - this.labelDeferreds['Start of line'].resolve(this.startLabel); - this.labelDeferreds['End of line'].resolve(this.endLabel); this.partDeferreds[0].resolve('part 0'); this.partDeferreds[1].resolve('part 1'); this.partDeferreds[2].resolve('part 2'); @@ -214,8 +121,8 @@ describe('parser/javascript/match.js', function() { it('sets the start and end properties', function(done) { this.node._render() .then(() => { - expect(this.node.start).toEqual('start label'); - expect(this.node.end).toEqual('end label'); + expect(this.node.start).toEqual('part 0'); + expect(this.node.end).toEqual('part 2'); done(); }); }); @@ -224,11 +131,9 @@ describe('parser/javascript/match.js', function() { this.node._render() .then(() => { expect(util.spaceHorizontally).toHaveBeenCalledWith([ - 'start label', 'part 0', 'part 1', - 'part 2', - 'end label' + 'part 2' ], { padding: 10 }); done(); }); @@ -238,11 +143,9 @@ describe('parser/javascript/match.js', function() { this.node._render() .then(() => { expect(this.node.connectorPaths).toHaveBeenCalledWith([ - 'start label', 'part 0', 'part 1', - 'part 2', - 'end label' + 'part 2' ]); expect(this.node.container.path).toHaveBeenCalledWith('connector paths'); done(); diff --git a/src/js/parser/javascript/anchor.js b/src/js/parser/javascript/anchor.js new file mode 100644 index 0000000..d59cfde --- /dev/null +++ b/src/js/parser/javascript/anchor.js @@ -0,0 +1,15 @@ +export default { + _render() { + return this.renderLabel(this.label).then(label => { + return label.addClass('anchor'); + }); + }, + + setup() { + if (this.textValue === '^') { + this.label = 'Start of line'; + } else { + this.label = 'End of line'; + } + } +}; diff --git a/src/js/parser/javascript/grammar.peg b/src/js/parser/javascript/grammar.peg index b05babe..37ef9d5 100644 --- a/src/js/parser/javascript/grammar.peg +++ b/src/js/parser/javascript/grammar.peg @@ -1,10 +1,9 @@ grammar JavascriptRegexp root <- ( ( "/" regexp "/" flags:[igm]* ) / regexp flags:""? ) regexp <- match:match alternates:( "|" match )* - match <- anchor_start:"^"? - (!repeat) parts:match_fragment* - anchor_end:"$"? - match_fragment <- content:( subexp / charset / terminal ) repeat:repeat? + match <- (!repeat) parts:match_fragment* + anchor <- ( "^" / "$" ) + match_fragment <- content:( anchor / subexp / charset / terminal ) repeat:repeat? repeat <- spec:( repeat_any / repeat_required / repeat_optional / repeat_spec ) greedy:"?"? repeat_any <- "*" repeat_required <- "+" diff --git a/src/js/parser/javascript/match.js b/src/js/parser/javascript/match.js index 476042f..c01ed60 100644 --- a/src/js/parser/javascript/match.js +++ b/src/js/parser/javascript/match.js @@ -29,39 +29,19 @@ export default { // Renders the match into the currently set container. _render() { - var start, end, - partPromises, + var partPromises, items; - // A `^` at the beginning of the match leads to the "Start of line" - // indicator being rendered. - if (this.anchorStart) { - start = this.renderLabel('Start of line') - .then(label => { - return label.addClass('anchor'); - }); - } - - // A `$` at the end of the match leads to the "End of line" indicator - // being rendered. - if (this.anchorEnd) { - end = this.renderLabel('End of line') - .then(label => { - return label.addClass('anchor'); - }); - } - // Render each of the match fragments. partPromises = _.map(this.parts, part => { return part.render(this.container.group()); }); - items = _([start, partPromises, end]).flatten().compact().value(); + items = _(partPromises).compact().value(); // Handle the situation where a regular expression of `()` is rendered. - // This leads to a Match node with no fragments, no start indicator, and - // no end indicator. Something must be rendered so that the anchor can be - // calculated based on it. + // This leads to a Match node with no fragments. Something must be rendered + // so that the anchor can be calculated based on it. // // Furthermore, the content rendered must have height and width or else the // anchor calculations fail. @@ -120,13 +100,8 @@ export default { return result; }, []); - // Indicates if the expression starts with a `^`. - this.anchorStart = (this.properties.anchor_start.textValue !== ''); - // Indicates if the expression ends with a `$`. - this.anchorEnd = (this.properties.anchor_end.textValue !== ''); - - // When there are no anchors and only one part, then proxy to the part. - if (!this.anchorStart && !this.anchorEnd && this.parts.length === 1) { + // When there is only one part, then proxy to the part. + if (this.parts.length === 1) { this.proxy = this.parts[0]; } } diff --git a/src/js/parser/javascript/parser.js b/src/js/parser/javascript/parser.js index 1ae217c..25f7814 100644 --- a/src/js/parser/javascript/parser.js +++ b/src/js/parser/javascript/parser.js @@ -9,6 +9,7 @@ import Root from './root.js'; import Regexp from './regexp.js'; import Match from './match.js'; import MatchFragment from './match_fragment.js'; +import Anchor from './anchor.js'; import Subexp from './subexp.js'; import Charset from './charset.js'; import CharsetEscape from './charset_escape.js'; @@ -35,6 +36,7 @@ parser.Parser.Root = { module: Root }; parser.Parser.Regexp = { module: Regexp }; parser.Parser.Match = { module: Match }; parser.Parser.MatchFragment = { module: MatchFragment }; +parser.Parser.Anchor = { module: Anchor }; parser.Parser.Subexp = { module: Subexp }; parser.Parser.Charset = { module: Charset }; parser.Parser.CharsetEscape = { module: CharsetEscape };