Made DollarGroovyParser.
This commit is contained in:
		
							parent
							
								
									a4bfea92ef
								
							
						
					
					
						commit
						82bb229eb8
					
				| @ -94,6 +94,9 @@ class ComponentTokenizer { | |||||||
|                 on dollarOpen shiftTo State.DOLLAR_GROOVY exec { String s -> |                 on dollarOpen shiftTo State.DOLLAR_GROOVY exec { String s -> | ||||||
|                     tokens << new ComponentToken(Type.DOLLAR, s[0]) |                     tokens << new ComponentToken(Type.DOLLAR, s[0]) | ||||||
|                     tokens << new ComponentToken(Type.CURLY_OPEN, s[1]) |                     tokens << new ComponentToken(Type.CURLY_OPEN, s[1]) | ||||||
|  |                 } | ||||||
|  |                 on DollarGroovyParser.&parse exec { | ||||||
|  | 
 | ||||||
|                 } |                 } | ||||||
|                 on percent shiftTo State.EXPRESSION_SCRIPTLET_GROOVY exec { |                 on percent shiftTo State.EXPRESSION_SCRIPTLET_GROOVY exec { | ||||||
|                     tokens << new ComponentToken(Type.PERCENT, it) |                     tokens << new ComponentToken(Type.PERCENT, it) | ||||||
| @ -131,27 +134,7 @@ class ComponentTokenizer { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             whileIn(State.DOLLAR_GROOVY) { |             whileIn(State.DOLLAR_GROOVY) { | ||||||
|                 on { String input -> |                 on DollarGroovyParser.&parse shiftTo State.KEYS_AND_VALUES exec { String s -> | ||||||
|                     def b = new StringBuilder() |  | ||||||
|                     def openCurlyCount = 1 |  | ||||||
|                     def iterator = input.iterator() as Iterator<String> |  | ||||||
|                     while (iterator.hasNext()) { |  | ||||||
|                         def c0 = iterator.next() |  | ||||||
|                         if (c0 == '{') { |  | ||||||
|                             b << c0 |  | ||||||
|                             openCurlyCount++ |  | ||||||
|                         } else if (c0 == '}') { |  | ||||||
|                             b << c0 |  | ||||||
|                             openCurlyCount-- |  | ||||||
|                             if (openCurlyCount == 0) { |  | ||||||
|                                 break |  | ||||||
|                             } |  | ||||||
|                         } else { |  | ||||||
|                             b << c0 |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     b.toString() |  | ||||||
|                 } shiftTo State.KEYS_AND_VALUES exec { String s -> |  | ||||||
|                     tokens << new ComponentToken(Type.GROOVY, s.substring(0, s.length() - 1)) |                     tokens << new ComponentToken(Type.GROOVY, s.substring(0, s.length() - 1)) | ||||||
|                     tokens << new ComponentToken(Type.CURLY_CLOSE, '}') |                     tokens << new ComponentToken(Type.CURLY_CLOSE, '}') | ||||||
|                 } |                 } | ||||||
|  | |||||||
| @ -0,0 +1,129 @@ | |||||||
|  | package com.jessebrault.ssg.template.gspe.component | ||||||
|  | 
 | ||||||
|  | import org.slf4j.Logger | ||||||
|  | import org.slf4j.LoggerFactory | ||||||
|  | 
 | ||||||
|  | class DollarGroovyParser { | ||||||
|  | 
 | ||||||
|  |     private static Logger logger = LoggerFactory.getLogger(DollarGroovyParser) | ||||||
|  | 
 | ||||||
|  |     private enum State { | ||||||
|  |         NO_STRING, G_STRING, SINGLE_QUOTE_STRING | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static class Counter { | ||||||
|  | 
 | ||||||
|  |         int count = 0 | ||||||
|  | 
 | ||||||
|  |         void increment() { | ||||||
|  |             this.count++ | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void decrement() { | ||||||
|  |             this.count-- | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void next() { | ||||||
|  |             this.increment() | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void previous() { | ||||||
|  |             this.decrement() | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         boolean isZero() { | ||||||
|  |             this.count == 0 | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         String toString() { | ||||||
|  |             "Counter(${ this.count })" | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     static String parse(String input) { | ||||||
|  |         def acc = new StringBuilder() | ||||||
|  |         def stateStack = new LinkedList<State>([State.NO_STRING]) | ||||||
|  |         def counterStack = new LinkedList<Counter>([new Counter()]) | ||||||
|  |         def iterator = input.iterator() as Iterator<String> | ||||||
|  | 
 | ||||||
|  |         if (!iterator.hasNext() || iterator.next() != '$') { | ||||||
|  |             return null | ||||||
|  |         } else { | ||||||
|  |             acc << '$' | ||||||
|  |             counterStack.peek()++ | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (!iterator.hasNext() || iterator.next() != '{') { | ||||||
|  |             return null | ||||||
|  |         } else { | ||||||
|  |             acc << '{' | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         while (iterator.hasNext()) { | ||||||
|  |             assert counterStack.size() > 0 | ||||||
|  |             assert stateStack.size() > 0 | ||||||
|  | 
 | ||||||
|  |             def c0 = iterator.next() | ||||||
|  |             acc << c0 | ||||||
|  |             logger.debug('----') | ||||||
|  |             logger.debug('c0: {}', c0) | ||||||
|  |             logger.debug('acc: {}', acc) | ||||||
|  |             if (stateStack.peek() == State.NO_STRING) { | ||||||
|  |                 if (c0 == '{') { | ||||||
|  |                     counterStack.peek()++ | ||||||
|  |                 } else if (c0 == '}') { | ||||||
|  |                     counterStack.peek()-- | ||||||
|  |                     if (counterStack.peek().isZero()) { | ||||||
|  |                         if (counterStack.size() == 1) { | ||||||
|  |                             logger.debug('single Counter is zero; breaking while loop') | ||||||
|  |                             break // escape while loop | ||||||
|  |                         } else { | ||||||
|  |                             logger.debug('counterStack.size() is greater than zero and top Counter is zero; ' + | ||||||
|  |                                     'popping state and counter stacks') | ||||||
|  |                             counterStack.pop() | ||||||
|  |                             stateStack.pop() | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } else if (c0 == '"') { | ||||||
|  |                     stateStack.push(State.G_STRING) | ||||||
|  |                 } else if (c0 == "'") { | ||||||
|  |                     stateStack.push(State.SINGLE_QUOTE_STRING) | ||||||
|  |                 } | ||||||
|  |             } else if (stateStack.peek() == State.G_STRING) { | ||||||
|  |                 if (c0 == '\\') { | ||||||
|  |                     if (iterator.hasNext()) { | ||||||
|  |                         acc << iterator.next() | ||||||
|  |                     } else { | ||||||
|  |                         throw new IllegalArgumentException('Ill-formed dollar groovy') | ||||||
|  |                     } | ||||||
|  |                 } else if (c0 == '$') { | ||||||
|  |                     if (iterator.hasNext()) { | ||||||
|  |                         def c1 = iterator.next() | ||||||
|  |                         acc << c1 | ||||||
|  |                         if (c1 == '{') { | ||||||
|  |                             stateStack.push(State.NO_STRING) | ||||||
|  |                             counterStack.push(new Counter()) | ||||||
|  |                             counterStack.peek()++ | ||||||
|  |                         } | ||||||
|  |                     } else { | ||||||
|  |                         throw new IllegalArgumentException('Ill-formed dollar groovy') | ||||||
|  |                     } | ||||||
|  |                 } else if (c0 == '"') { | ||||||
|  |                     logger.debug('popping G_STRING state') | ||||||
|  |                     stateStack.pop() | ||||||
|  |                 } | ||||||
|  |             } else if (stateStack.peek() == State.SINGLE_QUOTE_STRING) { | ||||||
|  |                 if (c0 == "'") { | ||||||
|  |                     logger.debug('popping SINGLE_QUOTE_STRING state') | ||||||
|  |                     stateStack.pop() | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             logger.debug('stateStack: {}', stateStack) | ||||||
|  |             logger.debug('counterStack: {}', counterStack) | ||||||
|  |         } | ||||||
|  |         acc.toString() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,65 @@ | |||||||
|  | package com.jessebrault.ssg.template.gspe.component | ||||||
|  | 
 | ||||||
|  | import org.junit.jupiter.api.Test | ||||||
|  | 
 | ||||||
|  | import static com.jessebrault.ssg.template.gspe.component.DollarGroovyParser.parse | ||||||
|  | import static org.junit.jupiter.api.Assertions.assertEquals | ||||||
|  | 
 | ||||||
|  | class DollarGroovyParserTests { | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     void empty() { | ||||||
|  |         assertEquals('${}', parse('${}')) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     void simple() { | ||||||
|  |         assertEquals('${ 1 + 2 }', parse('${ 1 + 2 }')) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     void nestedString() { | ||||||
|  |         assertEquals('${ "myString" }', parse('${ "myString" }')) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     void nestedCurlyBraces() { | ||||||
|  |         assertEquals( | ||||||
|  |                 '${ [1, 2, 3].collect { it + 1 }.size() }', | ||||||
|  |                 parse('${ [1, 2, 3].collect { it + 1 }.size() }') | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     void nestedSingleQuoteString() { | ||||||
|  |         assertEquals( | ||||||
|  |                 '${ \'abc\' }', | ||||||
|  |                 parse('${ \'abc\' }') | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     void nestedGString() { | ||||||
|  |         assertEquals( | ||||||
|  |                 '${ "abc" }', | ||||||
|  |                 parse('${ "abc" }') | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     void nestedGStringWithClosure() { | ||||||
|  |         assertEquals( | ||||||
|  |                 '${ "abc${ it }" }', | ||||||
|  |                 parse('${ "abc${ it }" }') | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     void takesOnlyAsNeeded() { | ||||||
|  |         assertEquals( | ||||||
|  |                 '${ 1 + 2 }', | ||||||
|  |                 parse('${ 1 + 2 } someOther=${ 3 + 4 }') | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										6
									
								
								template/src/test/resources/test.gcp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								template/src/test/resources/test.gcp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html> | ||||||
|  |     <Head val=${ frontMatter.test } val=$test val="Hello!" val='Hello!'> | ||||||
|  |         <meta val=${ binding.someVar } /> | ||||||
|  |     </Head> | ||||||
|  | </html> | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Jesse Brault
						Jesse Brault