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