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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user