Skip to content

Commit 3378cef

Browse files
fsmike64jknack
authored andcommitted
Create only one antlr token per text instead of separate tokens for newlines and spaces.
Adjusted whitespace control accordingly.
1 parent ec23dc2 commit 3378cef

11 files changed

Lines changed: 683 additions & 191 deletions

File tree

handlebars/src/main/antlr4/com/github/jknack/handlebars/internal/HbsLexer.g4

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,37 @@ lexer grammar HbsLexer;
1515
this.end = end;
1616
}
1717

18-
private boolean isWhite(int ch) {
19-
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n';
20-
}
21-
2218
private boolean consumeUntil(final String token) {
23-
int offset = 0;
24-
while(!isEOF(offset) && !(ahead("\\" + token, offset) || ahead(token, offset)) &&
25-
!isWhite(_input.LA(offset + 1))) {
26-
offset+=1;
19+
int tokenOffset = 0;
20+
int colOffset = 0;
21+
int lineOffset = 0;
22+
boolean inNewline = false;
23+
boolean resetLine = false;
24+
while(!isEOF(tokenOffset) && !(ahead("\\" + token, tokenOffset) || ahead(token, tokenOffset))) {
25+
if (resetLine) {
26+
colOffset = 0;
27+
lineOffset+=1;
28+
resetLine = false;
29+
}
30+
tokenOffset+=1;
31+
colOffset+=1;
32+
int chr = _input.LA(tokenOffset);
33+
if (!inNewline && '\n' == chr) {
34+
resetLine = true;
35+
}
36+
inNewline = false;
37+
if ('\r' == chr) {
38+
inNewline = true;
39+
resetLine = true;
40+
}
2741
}
28-
if (offset == 0) {
42+
if (tokenOffset == 0) {
2943
return false;
3044
}
3145
// Since we found the text, increase the CharStream's index.
32-
_input.seek(_input.index() + offset - 1);
33-
getInterpreter().setCharPositionInLine(_tokenStartCharPositionInLine + offset - 1);
46+
_input.seek(_input.index() + tokenOffset - 1);
47+
getInterpreter().setCharPositionInLine(_tokenStartCharPositionInLine + colOffset - 1);
48+
getInterpreter().setLine(_tokenStartLine + lineOffset);
3449
return true;
3550
}
3651

@@ -205,17 +220,6 @@ START
205220
: {startToken(start)}? . -> pushMode(VAR)
206221
;
207222

208-
SPACE
209-
:
210-
[ \t]+
211-
;
212-
213-
NL
214-
:
215-
'\r'? '\n'
216-
| '\r'
217-
;
218-
219223
mode SET_DELIMS;
220224

221225
END_DELIM

handlebars/src/main/antlr4/com/github/jknack/handlebars/internal/HbsParser.g4

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,7 @@ body
4646

4747
statement
4848
:
49-
spaces
50-
| newline
51-
| text
49+
text
5250
| block
5351
| var
5452
| tvar
@@ -72,14 +70,6 @@ text
7270
TEXT
7371
;
7472

75-
spaces
76-
: SPACE
77-
;
78-
79-
newline
80-
: NL
81-
;
82-
8373
block
8474
:
8575
startToken = START_BLOCK DECORATOR? sexpr blockParams? END

handlebars/src/main/java/com/github/jknack/handlebars/internal/BaseTemplate.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,6 @@ abstract class BaseTemplate implements Template {
7676
*/
7777
protected String filename;
7878

79-
/**
80-
* A Handlebars.js lock.
81-
*/
82-
private final Object jsLock = new Object();
83-
8479
/**
8580
* A pre-compiled JavaScript function.
8681
*/

handlebars/src/main/java/com/github/jknack/handlebars/internal/HbsParserFactory.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,10 @@ public Template parse(final TemplateSource source) throws IOException {
9090
logger.debug("Applying Mustache spec");
9191
new ParseTreeWalker().walk(new MustacheSpec(), tree);
9292
}
93-
93+
9494
if (lexer.whiteSpaceControl) {
9595
logger.debug("Applying white spaces control");
96-
new ParseTreeWalker().walk(new WhiteSpaceControl(), tree);
96+
new ParseTreeWalker().walk(new WhiteSpaceControl((CommonTokenStream)parser.getTokenStream()), tree);
9797
}
9898

9999
/**

handlebars/src/main/java/com/github/jknack/handlebars/internal/MustacheSpec.java

Lines changed: 90 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,13 @@
2222

2323
import org.antlr.v4.runtime.CommonToken;
2424
import org.antlr.v4.runtime.Token;
25+
import org.apache.commons.lang3.StringUtils;
2526

2627
import com.github.jknack.handlebars.internal.HbsParser.AmpvarContext;
2728
import com.github.jknack.handlebars.internal.HbsParser.BlockContext;
2829
import com.github.jknack.handlebars.internal.HbsParser.CommentContext;
2930
import com.github.jknack.handlebars.internal.HbsParser.DelimitersContext;
30-
import com.github.jknack.handlebars.internal.HbsParser.NewlineContext;
3131
import com.github.jknack.handlebars.internal.HbsParser.PartialContext;
32-
import com.github.jknack.handlebars.internal.HbsParser.SpacesContext;
3332
import com.github.jknack.handlebars.internal.HbsParser.TemplateContext;
3433
import com.github.jknack.handlebars.internal.HbsParser.TextContext;
3534
import com.github.jknack.handlebars.internal.HbsParser.TvarContext;
@@ -45,65 +44,51 @@
4544
public class MustacheSpec extends HbsParserBaseListener {
4645

4746
/**
48-
* Track if the current line has real text (not spaces).
47+
* Tracks if the current line should be treated as stand-alone.
4948
*/
50-
private boolean nonSpace = false;
49+
private Boolean standAlone;
5150

5251
/**
53-
* Track if the current line has mustache instruction.
52+
* Tracks text tokens for future whitespace removal.
5453
*/
55-
private Boolean hasTag;
54+
private List<CommonToken> textTokens = new ArrayList<>();
5655

5756
/**
58-
* Track the current line.
57+
* Channel for tokens that need their last line removed.
5958
*/
60-
protected StringBuilder line = new StringBuilder();
61-
62-
/**
63-
* Track the spaces/lines that need to be excluded.
64-
*/
65-
private List<CommonToken> spaces = new ArrayList<>();
66-
67-
@Override
68-
public void enterSpaces(final SpacesContext ctx) {
69-
CommonToken space = (CommonToken) ctx.SPACE().getSymbol();
70-
line.append(space.getText());
71-
spaces.add(space);
72-
}
73-
74-
@Override
75-
public void enterNewline(final NewlineContext ctx) {
76-
CommonToken newline = (CommonToken) ctx.NL().getSymbol();
77-
spaces.add(newline);
78-
stripSpaces();
79-
}
59+
public static final int REMOVE_LAST_LINE_CHANNEL = Token.MIN_USER_CHANNEL_VALUE;
8060

8161
@Override
8262
public void exitTemplate(final TemplateContext ctx) {
83-
stripSpaces();
63+
removeWhitespace();
64+
this.textTokens.clear();
65+
this.standAlone = null;
8466
}
8567

86-
/**
87-
* Move tokens to the hidden channel if necessary.
88-
*/
89-
private void stripSpaces() {
90-
boolean hasTag = this.hasTag == null ? false : this.hasTag.booleanValue();
91-
if (hasTag && !nonSpace) {
92-
for (CommonToken space : spaces) {
93-
space.setChannel(Token.HIDDEN_CHANNEL);
68+
@Override
69+
public void enterText(final TextContext ctx) {
70+
CommonToken currentToken = (CommonToken) ctx.getStart();
71+
String currentText = currentToken.getText();
72+
Integer secondLineIndex = MustacheStringUtils.indexOfSecondLine(currentText);
73+
if (secondLineIndex == null) {
74+
// Non-whitespace was found. Clear list and start again with the current token
75+
this.textTokens.clear();
76+
this.standAlone = null;
77+
} else if (secondLineIndex >= 0) {
78+
// Try to remove whitespace from the previously saved text tokens
79+
boolean canRemoveWhitespace = removeWhitespace();
80+
if (canRemoveWhitespace) {
81+
// Remove the first line of this text as well
82+
String newText = StringUtils.substring(currentText, secondLineIndex);
83+
currentToken.setText(newText);
9484
}
95-
} else {
96-
spaces.clear();
85+
86+
// Clear list and start again with the current token
87+
this.textTokens.clear();
88+
this.standAlone = null;
9789
}
9890

99-
this.hasTag = null;
100-
nonSpace = false;
101-
line.setLength(0);
102-
}
103-
104-
@Override
105-
public void enterText(final TextContext ctx) {
106-
nonSpace = true;
91+
this.textTokens.add(currentToken);
10792
}
10893

10994
@Override
@@ -136,7 +121,8 @@ public void enterUnless(final UnlessContext ctx) {
136121
hasTag(true);
137122
}
138123

139-
@Override public void enterElseBlock(final HbsParser.ElseBlockContext ctx) {
124+
@Override
125+
public void enterElseBlock(final HbsParser.ElseBlockContext ctx) {
140126
hasTag(true);
141127
}
142128

@@ -166,8 +152,63 @@ public void enterVar(final VarContext ctx) {
166152
* @param hasTag True, to indicate there is a mustache instruction.
167153
*/
168154
private void hasTag(final boolean hasTag) {
169-
if (this.hasTag != Boolean.FALSE) {
170-
this.hasTag = hasTag;
155+
if (this.standAlone != Boolean.FALSE) {
156+
this.standAlone = hasTag;
171157
}
172158
}
159+
160+
/**
161+
* Remove whitespace from previously saved text tokens.
162+
*
163+
* @return True if whitespace could be removed. False otherwise.
164+
*/
165+
private boolean removeWhitespace() {
166+
boolean canRemoveWhitespace = this.standAlone == null ? false : this.standAlone.booleanValue();
167+
if (!canRemoveWhitespace) {
168+
return false;
169+
}
170+
171+
if (textTokens.isEmpty()) {
172+
return true;
173+
}
174+
175+
// Try to remove whitespace from the last line
176+
CommonToken lastToken = textTokens.get(textTokens.size() - 1);
177+
String lastText = lastToken.getText();
178+
int newlineIndex = StringUtils.lastIndexOfAny(lastText, "\r", "\n");
179+
if (newlineIndex >= 0) {
180+
String lastLine = lastText.substring(newlineIndex + 1);
181+
if (!StringUtils.isWhitespace(lastLine)) {
182+
// Cannot remove anything since line contains non-whitespace
183+
return false;
184+
}
185+
186+
// Mark the last line for removal
187+
lastToken.setChannel(REMOVE_LAST_LINE_CHANNEL);
188+
} else {
189+
// Check for non-whitespace
190+
int maxIndex = textTokens.size() - 1;
191+
for (int i = maxIndex; i >= 0; i--) {
192+
CommonToken loopToken = textTokens.get(i);
193+
String loopText = loopToken.getText();
194+
if (!StringUtils.isWhitespace(loopText)) {
195+
// Cannot remove anything since line contains non-whitespace
196+
return false;
197+
}
198+
if (i == 0) {
199+
// Mark tokens already checked for removal
200+
int j = 0;
201+
while (j <= maxIndex) {
202+
CommonToken whiteSpaceToken = textTokens.get(j);
203+
whiteSpaceToken.setChannel(Token.HIDDEN_CHANNEL);
204+
j++;
205+
}
206+
207+
break;
208+
}
209+
}
210+
}
211+
212+
return true;
213+
}
173214
}

0 commit comments

Comments
 (0)