Adding support for including ^ and $ in the middle of an expression
Closes #29
This commit is contained in:
parent
851bc32141
commit
2ceb94fc42
20
spec/parser/javascript/anchor_spec.js
Normal file
20
spec/parser/javascript/anchor_spec.js
Normal 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));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -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();
|
||||||
|
15
src/js/parser/javascript/anchor.js
Normal file
15
src/js/parser/javascript/anchor.js
Normal 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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -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>
|
||||||
|
@ -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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 };
|
||||||
|
Loading…
Reference in New Issue
Block a user