Made DollarGroovyParser.

This commit is contained in:
Jesse Brault 2023-01-23 12:42:00 +01:00
parent a4bfea92ef
commit 82bb229eb8
4 changed files with 204 additions and 21 deletions

View File

@ -94,6 +94,9 @@ class ComponentTokenizer {
on dollarOpen shiftTo State.DOLLAR_GROOVY exec { String s ->
tokens << new ComponentToken(Type.DOLLAR, s[0])
tokens << new ComponentToken(Type.CURLY_OPEN, s[1])
}
on DollarGroovyParser.&parse exec {
}
on percent shiftTo State.EXPRESSION_SCRIPTLET_GROOVY exec {
tokens << new ComponentToken(Type.PERCENT, it)
@ -131,27 +134,7 @@ class ComponentTokenizer {
}
whileIn(State.DOLLAR_GROOVY) {
on { String input ->
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 ->
on DollarGroovyParser.&parse shiftTo State.KEYS_AND_VALUES exec { String s ->
tokens << new ComponentToken(Type.GROOVY, s.substring(0, s.length() - 1))
tokens << new ComponentToken(Type.CURLY_CLOSE, '}')
}

View File

@ -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()
}
}

View File

@ -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 }')
)
}
}

View File

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<Head val=${ frontMatter.test } val=$test val="Hello!" val='Hello!'>
<meta val=${ binding.someVar } />
</Head>
</html>