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({ | ||||
|     '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(); | ||||
|  | ||||
							
								
								
									
										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 | ||||
|   root <- ( ( "/" regexp "/" flags:[igm]* ) / regexp flags:""? ) <Root> | ||||
|   regexp <- match:match alternates:( "|" match )* <Regexp> | ||||
|   match <- anchor_start:"^"? | ||||
|            (!repeat) parts:match_fragment* | ||||
|            anchor_end:"$"? <Match> | ||||
|   match_fragment <- content:( subexp / charset / terminal ) repeat:repeat? <MatchFragment> | ||||
|   match <- (!repeat) parts:match_fragment* <Match> | ||||
|   anchor <- ( "^" / "$" ) <Anchor> | ||||
|   match_fragment <- content:( anchor / subexp / charset / terminal ) repeat:repeat? <MatchFragment> | ||||
|   repeat <- spec:( repeat_any / repeat_required / repeat_optional / repeat_spec ) greedy:"?"? <Repeat> | ||||
|   repeat_any <- "*" <RepeatAny> | ||||
|   repeat_required <- "+" <RepeatRequired> | ||||
|  | ||||
| @ -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]; | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @ -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 }; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user