Adding support for including ^ and $ in the middle of an expression

Closes #29
This commit is contained in:
Jeff Avallone 2016-07-26 22:27:17 -04:00
parent 851bc32141
commit 2ceb94fc42
6 changed files with 50 additions and 136 deletions

View File

@ -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));
});
});
});

View File

@ -7,8 +7,6 @@ describe('parser/javascript/match.js', function() {
_.forIn({ _.forIn({
'example': { 'example': {
anchorStart: false,
anchorEnd: false,
parts: [ parts: [
jasmine.objectContaining({ jasmine.objectContaining({
content: jasmine.objectContaining({ literal: 'example' }) content: jasmine.objectContaining({ literal: 'example' })
@ -18,27 +16,7 @@ describe('parser/javascript/match.js', function() {
content: jasmine.objectContaining({ literal: 'example' }) 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*': { 'example*': {
anchorStart: false,
anchorEnd: false,
parts: [ parts: [
jasmine.objectContaining({ jasmine.objectContaining({
content: jasmine.objectContaining({ literal: 'exampl' }) content: jasmine.objectContaining({ literal: 'exampl' })
@ -49,8 +27,6 @@ describe('parser/javascript/match.js', function() {
] ]
}, },
'': { '': {
anchorStart: false,
anchorEnd: false,
parts: [] parts: []
} }
}, (content, str) => { }, (content, str) => {
@ -99,9 +75,6 @@ describe('parser/javascript/match.js', function() {
beforeEach(function() { beforeEach(function() {
this.node = new javascript.Parser('a').__consume__match(); this.node = new javascript.Parser('a').__consume__match();
this.node.anchorStart = true;
this.node.anchorEnd = true;
this.node.container = jasmine.createSpyObj('container', [ this.node.container = jasmine.createSpyObj('container', [
'addClass', 'addClass',
'group', 'group',
@ -110,14 +83,6 @@ describe('parser/javascript/match.js', function() {
]); ]);
this.node.container.group.and.returnValue('example group'); 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 = [ this.node.parts = [
jasmine.createSpyObj('part 0', ['render']), jasmine.createSpyObj('part 0', ['render']),
jasmine.createSpyObj('part 1', ['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); 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() { it('renders each part', function() {
this.node._render(); this.node._render();
expect(this.node.parts[0].render).toHaveBeenCalledWith('example group'); expect(this.node.parts[0].render).toHaveBeenCalledWith('example group');
@ -197,12 +110,6 @@ describe('parser/javascript/match.js', function() {
describe('positioning of items', function() { describe('positioning of items', function() {
beforeEach(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[0].resolve('part 0');
this.partDeferreds[1].resolve('part 1'); this.partDeferreds[1].resolve('part 1');
this.partDeferreds[2].resolve('part 2'); 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) { it('sets the start and end properties', function(done) {
this.node._render() this.node._render()
.then(() => { .then(() => {
expect(this.node.start).toEqual('start label'); expect(this.node.start).toEqual('part 0');
expect(this.node.end).toEqual('end label'); expect(this.node.end).toEqual('part 2');
done(); done();
}); });
}); });
@ -224,11 +131,9 @@ describe('parser/javascript/match.js', function() {
this.node._render() this.node._render()
.then(() => { .then(() => {
expect(util.spaceHorizontally).toHaveBeenCalledWith([ expect(util.spaceHorizontally).toHaveBeenCalledWith([
'start label',
'part 0', 'part 0',
'part 1', 'part 1',
'part 2', 'part 2'
'end label'
], { padding: 10 }); ], { padding: 10 });
done(); done();
}); });
@ -238,11 +143,9 @@ describe('parser/javascript/match.js', function() {
this.node._render() this.node._render()
.then(() => { .then(() => {
expect(this.node.connectorPaths).toHaveBeenCalledWith([ expect(this.node.connectorPaths).toHaveBeenCalledWith([
'start label',
'part 0', 'part 0',
'part 1', 'part 1',
'part 2', 'part 2'
'end label'
]); ]);
expect(this.node.container.path).toHaveBeenCalledWith('connector paths'); expect(this.node.container.path).toHaveBeenCalledWith('connector paths');
done(); done();

View File

@ -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';
}
}
};

View File

@ -1,10 +1,9 @@
grammar JavascriptRegexp grammar JavascriptRegexp
root <- ( ( "/" regexp "/" flags:[igm]* ) / regexp flags:""? ) <Root> root <- ( ( "/" regexp "/" flags:[igm]* ) / regexp flags:""? ) <Root>
regexp <- match:match alternates:( "|" match )* <Regexp> regexp <- match:match alternates:( "|" match )* <Regexp>
match <- anchor_start:"^"? match <- (!repeat) parts:match_fragment* <Match>
(!repeat) parts:match_fragment* anchor <- ( "^" / "$" ) <Anchor>
anchor_end:"$"? <Match> match_fragment <- content:( anchor / subexp / charset / terminal ) repeat:repeat? <MatchFragment>
match_fragment <- content:( subexp / charset / terminal ) repeat:repeat? <MatchFragment>
repeat <- spec:( repeat_any / repeat_required / repeat_optional / repeat_spec ) greedy:"?"? <Repeat> repeat <- spec:( repeat_any / repeat_required / repeat_optional / repeat_spec ) greedy:"?"? <Repeat>
repeat_any <- "*" <RepeatAny> repeat_any <- "*" <RepeatAny>
repeat_required <- "+" <RepeatRequired> repeat_required <- "+" <RepeatRequired>

View File

@ -29,39 +29,19 @@ export default {
// Renders the match into the currently set container. // Renders the match into the currently set container.
_render() { _render() {
var start, end, var partPromises,
partPromises,
items; 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. // Render each of the match fragments.
partPromises = _.map(this.parts, part => { partPromises = _.map(this.parts, part => {
return part.render(this.container.group()); 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. // Handle the situation where a regular expression of `()` is rendered.
// This leads to a Match node with no fragments, no start indicator, and // This leads to a Match node with no fragments. Something must be rendered
// no end indicator. Something must be rendered so that the anchor can be // so that the anchor can be calculated based on it.
// calculated based on it.
// //
// Furthermore, the content rendered must have height and width or else the // Furthermore, the content rendered must have height and width or else the
// anchor calculations fail. // anchor calculations fail.
@ -120,13 +100,8 @@ export default {
return result; return result;
}, []); }, []);
// Indicates if the expression starts with a `^`. // When there is only one part, then proxy to the part.
this.anchorStart = (this.properties.anchor_start.textValue !== ''); if (this.parts.length === 1) {
// 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) {
this.proxy = this.parts[0]; this.proxy = this.parts[0];
} }
} }

View File

@ -9,6 +9,7 @@ import Root from './root.js';
import Regexp from './regexp.js'; import Regexp from './regexp.js';
import Match from './match.js'; import Match from './match.js';
import MatchFragment from './match_fragment.js'; import MatchFragment from './match_fragment.js';
import Anchor from './anchor.js';
import Subexp from './subexp.js'; import Subexp from './subexp.js';
import Charset from './charset.js'; import Charset from './charset.js';
import CharsetEscape from './charset_escape.js'; import CharsetEscape from './charset_escape.js';
@ -35,6 +36,7 @@ parser.Parser.Root = { module: Root };
parser.Parser.Regexp = { module: Regexp }; parser.Parser.Regexp = { module: Regexp };
parser.Parser.Match = { module: Match }; parser.Parser.Match = { module: Match };
parser.Parser.MatchFragment = { module: MatchFragment }; parser.Parser.MatchFragment = { module: MatchFragment };
parser.Parser.Anchor = { module: Anchor };
parser.Parser.Subexp = { module: Subexp }; parser.Parser.Subexp = { module: Subexp };
parser.Parser.Charset = { module: Charset }; parser.Parser.Charset = { module: Charset };
parser.Parser.CharsetEscape = { module: CharsetEscape }; parser.Parser.CharsetEscape = { module: CharsetEscape };