Skip to content

Commit

Permalink
handlebars: Add support for block params
Browse files Browse the repository at this point in the history
  • Loading branch information
dmarcotte committed Jul 26, 2015
1 parent eb97e03 commit 28e2f9d
Show file tree
Hide file tree
Showing 11 changed files with 316 additions and 173 deletions.
345 changes: 180 additions & 165 deletions gen/com/dmarcotte/handlebars/parsing/_HbLexer.java

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions resources/messages/HbBundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ hb.parsing.element.expected.open_partial=Expected Open Partial "{{>"
hb.parsing.element.expected.open_unescaped=Expected Open Unescaped "{{{"
hb.parsing.element.expected.open_sexpr=Expected Open Subexpression "("
hb.parsing.element.expected.close_sexpr=Expected Close Subexpression ")"
hb.parsing.element.expected.open_block_params=Expected Open Block Param "as |"
hb.parsing.element.expected.close_block_params=Expected Close Block Param "|"
hb.parsing.element.expected.outer_element_type=Expected Handlebars Content
hb.parsing.element.expected.separator=Expected a Separator "/" or "."
hb.parsing.element.expected.string=Expected a String
Expand Down
35 changes: 32 additions & 3 deletions src/com/dmarcotte/handlebars/parsing/HbParsing.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

/**
* The parser is based directly on Handlebars.yy
* (taken from the following revision: https://github.com/wycats/handlebars.js/blob/eee2c4d4f29e233280907bc89a32556de66fe783/src/handlebars.yy)
* (taken from the following revision: https://github.com/wycats/handlebars.js/blob/b8a9f7264d3b6ac48514272bf35291736cedad00/src/handlebars.yy)
* <p/>
* Methods mapping to expression in the grammar are commented with the part of the grammar they map to.
* <p/>
Expand Down Expand Up @@ -295,7 +295,7 @@ private boolean parseOpenRawBlock(PsiBuilder builder) {

/**
* openBlock
* : OPEN_BLOCK sexpr CLOSE { $$ = new yy.MustacheNode($2[0], $2[1]); }
* : OPEN_BLOCK sexpr blockParams? CLOSE { $$ = new yy.MustacheNode($2[0], $2[1]); }
* ;
*/
private boolean parseOpenBlock(PsiBuilder builder) {
Expand All @@ -306,6 +306,7 @@ private boolean parseOpenBlock(PsiBuilder builder) {
}

if (parseSexpr(builder)) {
parseBlockParams(builder);
parseLeafTokenGreedy(builder, CLOSE);
}

Expand Down Expand Up @@ -337,7 +338,7 @@ private boolean parseOpenInverseChain(PsiBuilder builder) {

/**
* openInverse
* : OPEN_INVERSE sexpr CLOSE
* : OPEN_INVERSE sexpr blockParams? CLOSE
* ;
*/
private boolean parseOpenInverse(PsiBuilder builder) {
Expand All @@ -348,6 +349,7 @@ private boolean parseOpenInverse(PsiBuilder builder) {
}

if (parseSexpr(builder)) {
parseBlockParams(builder);
parseLeafTokenGreedy(builder, CLOSE);
}

Expand Down Expand Up @@ -773,6 +775,33 @@ private boolean parsePartialName(PsiBuilder builder) {
return false;
}

/**
* blockParams
* OPEN_BLOCK_PARAMS ID+ CLOSE_BLOCK_PARAMS
*/
private boolean parseBlockParams(PsiBuilder builder) {
PsiBuilder.Marker blockParamsMarker = builder.mark();
if (parseLeafToken(builder, OPEN_BLOCK_PARAMS)) {
blockParamsMarker.drop();
parseLeafToken(builder, ID);
// parse any additional IDs
while (true) {
PsiBuilder.Marker optionalIdMarker = builder.mark();
if (parseLeafToken(builder, ID)) {
optionalIdMarker.drop();
} else {
optionalIdMarker.rollbackTo();
break;
}
}
parseLeafToken(builder, CLOSE_BLOCK_PARAMS);
return true;
} else {
blockParamsMarker.rollbackTo();
return false;
}
}

/**
* dataName
* : DATA path
Expand Down
2 changes: 2 additions & 0 deletions src/com/dmarcotte/handlebars/parsing/HbTokenTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ private HbTokenTypes() {
public static final IElementType OPEN_UNESCAPED = new HbElementType("OPEN_UNESCAPED", "hb.parsing.element.expected.open_unescaped");
public static final IElementType OPEN_SEXPR = new HbElementType("OPEN_SEXPR", "hb.parsing.element.expected.open_sexpr");
public static final IElementType CLOSE_SEXPR = new HbElementType("CLOSE_SEXPR", "hb.parsing.element.expected.close_sexpr");
public static final IElementType OPEN_BLOCK_PARAMS = new HbElementType("OPEN_BLOCK_PARAMS", "hb.parsing.element.expected.open_block_params");
public static final IElementType CLOSE_BLOCK_PARAMS = new HbElementType("CLOSE_BLOCK_PARAMS", "hb.parsing.element.expected.close_block_params");
public static final IElementType OPEN_RAW_BLOCK = new HbElementType("OPEN_RAW_BLOCK", "hb.parsing.element.expected.open_raw_block");
public static final IElementType END_RAW_BLOCK = new HbElementType("END_RAW_BLOCK", "hb.parsing.element.expected.end_raw_block");
public static final IElementType CLOSE_RAW_BLOCK = new HbElementType("CLOSE_RAW_BLOCK", "hb.parsing.element.expected.close_raw_block");
Expand Down
8 changes: 5 additions & 3 deletions src/com/dmarcotte/handlebars/parsing/handlebars.flex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// We base our lexer directly on the official handlebars.l lexer definition,
// making some modifications to account for Jison/JFlex syntax and functionality differences
//
// Revision ported: https://github.com/wycats/handlebars.js/blob/14b7ef9066d107dc83deedc8e6791947811cc764/src/handlebars.l
// Revision ported: https://github.com/wycats/handlebars.js/blob/b8a9f7264d3b6ac48514272bf35291736cedad00/src/handlebars.l

package com.dmarcotte.handlebars.parsing;

Expand Down Expand Up @@ -150,7 +150,7 @@ WhiteSpace = {LineTerminator} | [ \t\f]

"{{"\~? { return HbTokenTypes.OPEN; }
"=" { return HbTokenTypes.EQUALS; }
"."/[\~\}\t \n\x0B\f\r] { return HbTokenTypes.ID; }
"."/[\~\}\t| \n\x0B\f\r] { return HbTokenTypes.ID; }
".." { return HbTokenTypes.ID; }
[\/.] { return HbTokenTypes.SEP; }
[\t \n\x0B\f\r]* { return HbTokenTypes.WHITE_SPACE; }
Expand All @@ -163,6 +163,8 @@ WhiteSpace = {LineTerminator} | [ \t\f]
"true"/[}\)\t \n\x0B\f\r] { return HbTokenTypes.BOOLEAN; }
"false"/[}\)\t \n\x0B\f\r] { return HbTokenTypes.BOOLEAN; }
\-?[0-9]+(\.[0-9]+)?/[}\)\t \n\x0B\f\r] { return HbTokenTypes.NUMBER; }
"as"[\t \n\x0B\f\r]+"|" { return HbTokenTypes.OPEN_BLOCK_PARAMS; }
"|" { return HbTokenTypes.CLOSE_BLOCK_PARAMS; }
/*
ID is the inverse of control characters.
Control characters ranges:
Expand All @@ -172,7 +174,7 @@ WhiteSpace = {LineTerminator} | [ \t\f]
[\[-\^`] [, \, ], ^, `, Exceptions in range: _
[\{-~] {, |, }, ~
*/
[^\t \n\x0B\f\r!\"#%-,\.\/;->@\[-\^`\{-~]+/[\~=}\)\t \n\x0B\f\r\/.] { return HbTokenTypes.ID; }
[^\t \n\x0B\f\r!\"#%-,\.\/;->@\[-\^`\{-~]+/[\~=}\)|\t \n\x0B\f\r\/.] { return HbTokenTypes.ID; }
// TODO handlesbars.l extracts the id from within the square brackets. Fix it to match handlebars.l?
"["[^\]]*"]" { return HbTokenTypes.ID; }
}
Expand Down
1 change: 1 addition & 0 deletions test/data/parser/BlockWithBlockParams.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{#foo as |bar baz|}}content{{/foo}}
33 changes: 33 additions & 0 deletions test/data/parser/BlockWithBlockParams.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
HbFile:BlockWithBlockParams.hbs
HbStatementsImpl(STATEMENTS)
HbBlockWrapperImpl(BLOCK_WRAPPER)
HbOpenBlockMustacheImpl(OPEN_BLOCK_STACHE)
HbPsiElementImpl([Hb] OPEN_BLOCK)
PsiElement([Hb] OPEN_BLOCK)('{{#')
HbMustacheNameImpl(MUSTACHE_NAME)
HbPathImpl(PATH)
HbPsiElementImpl([Hb] ID)
PsiElement([Hb] ID)('foo')
PsiWhiteSpace(' ')
HbPsiElementImpl([Hb] OPEN_BLOCK_PARAMS)
PsiElement([Hb] OPEN_BLOCK_PARAMS)('as |')
HbPsiElementImpl([Hb] ID)
PsiElement([Hb] ID)('bar')
PsiWhiteSpace(' ')
HbPsiElementImpl([Hb] ID)
PsiElement([Hb] ID)('baz')
HbPsiElementImpl([Hb] CLOSE_BLOCK_PARAMS)
PsiElement([Hb] CLOSE_BLOCK_PARAMS)('|')
HbPsiElementImpl([Hb] CLOSE)
PsiElement([Hb] CLOSE)('}}')
HbStatementsImpl(STATEMENTS)
PsiElement([Hb] CONTENT)('content')
HbCloseBlockMustacheImpl(CLOSE_BLOCK_STACHE)
HbPsiElementImpl([Hb] OPEN_ENDBLOCK)
PsiElement([Hb] OPEN_ENDBLOCK)('{{/')
HbMustacheNameImpl(MUSTACHE_NAME)
HbPathImpl(PATH)
HbPsiElementImpl([Hb] ID)
PsiElement([Hb] ID)('foo')
HbPsiElementImpl([Hb] CLOSE)
PsiElement([Hb] CLOSE)('}}')
1 change: 1 addition & 0 deletions test/data/parser/InverseBlockWithBlockParams.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{^foo as |bar baz|}}content{{/foo}}
33 changes: 33 additions & 0 deletions test/data/parser/InverseBlockWithBlockParams.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
HbFile:InverseBlockWithBlockParams.hbs
HbStatementsImpl(STATEMENTS)
HbBlockWrapperImpl(BLOCK_WRAPPER)
HbOpenInverseBlockMustacheImpl(OPEN_INVERSE_BLOCK_STACHE)
HbPsiElementImpl([Hb] OPEN_INVERSE)
PsiElement([Hb] OPEN_INVERSE)('{{^')
HbMustacheNameImpl(MUSTACHE_NAME)
HbPathImpl(PATH)
HbPsiElementImpl([Hb] ID)
PsiElement([Hb] ID)('foo')
PsiWhiteSpace(' ')
HbPsiElementImpl([Hb] OPEN_BLOCK_PARAMS)
PsiElement([Hb] OPEN_BLOCK_PARAMS)('as |')
HbPsiElementImpl([Hb] ID)
PsiElement([Hb] ID)('bar')
PsiWhiteSpace(' ')
HbPsiElementImpl([Hb] ID)
PsiElement([Hb] ID)('baz')
HbPsiElementImpl([Hb] CLOSE_BLOCK_PARAMS)
PsiElement([Hb] CLOSE_BLOCK_PARAMS)('|')
HbPsiElementImpl([Hb] CLOSE)
PsiElement([Hb] CLOSE)('}}')
HbStatementsImpl(STATEMENTS)
PsiElement([Hb] CONTENT)('content')
HbCloseBlockMustacheImpl(CLOSE_BLOCK_STACHE)
HbPsiElementImpl([Hb] OPEN_ENDBLOCK)
PsiElement([Hb] OPEN_ENDBLOCK)('{{/')
HbMustacheNameImpl(MUSTACHE_NAME)
HbPathImpl(PATH)
HbPsiElementImpl([Hb] ID)
PsiElement([Hb] ID)('foo')
HbPsiElementImpl([Hb] CLOSE)
PsiElement([Hb] CLOSE)('}}')
10 changes: 9 additions & 1 deletion test/src/com/dmarcotte/handlebars/parsing/HbParserSpecTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

/**
* Java representations of the validations in Handlebars spec/parser.js
* (Precise revision: https://github.com/wycats/handlebars.js/blob/4282668d47b90da0d00cf4c4a86977f18fc8cde4/spec/parser.js)
* (Precise revision: https://github.com/wycats/handlebars.js/blob/b8a9f7264d3b6ac48514272bf35291736cedad00/spec/parser.js)
* <p/>
* The tests here should map pretty clearly by name to the `it "does something"` validations in parser.js.
* <p/>
Expand Down Expand Up @@ -138,6 +138,14 @@ public void testOldStandaloneInverseSection() {
doTest(true);
}

public void testBlockWithBlockParams() {
doTest(true);
}

public void testInverseBlockWithBlockParams() {
doTest(true);
}

/**
* Note on the spec/parser.js porting: some tests at the end are omitted
* because they make no sense in the context of the plugin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
/**
* Java representation of the validations in the spec/tokenizer.js revision which corresponds
* to the revision of handlesbars.l that our lexer is based on
* (https://github.com/wycats/handlebars.js/blob/b09333db7946d20ba7dbc6d32d5496ab8295b8e1/spec/tokenizer.js)
* (https://github.com/wycats/handlebars.js/blob/b8a9f7264d3b6ac48514272bf35291736cedad00/spec/tokenizer.js)
* <p/>
* All the tests should be nearly identical except that we generate whitespace tokens to give IDEA a better picture
* of the text, vs. the actual Handlebars lexer which can just toss whitespace out
Expand Down Expand Up @@ -499,4 +499,21 @@ public void testTokenizesNestedSubexpressionLiterals() {
TokenizerResult result = tokenize("{{foo (bar (lol true) false) (baz 1) (blah 'b') (blorg \"c\")}}");
result.shouldMatchTokenTypes(OPEN, ID, WHITE_SPACE, OPEN_SEXPR, ID, WHITE_SPACE, OPEN_SEXPR, ID, WHITE_SPACE, BOOLEAN, CLOSE_SEXPR, WHITE_SPACE, BOOLEAN, CLOSE_SEXPR, WHITE_SPACE, OPEN_SEXPR, ID, WHITE_SPACE, NUMBER, CLOSE_SEXPR, WHITE_SPACE, OPEN_SEXPR, ID, WHITE_SPACE, STRING, CLOSE_SEXPR, WHITE_SPACE, OPEN_SEXPR, ID, WHITE_SPACE, STRING, CLOSE_SEXPR, CLOSE);
}

/**
* tokenizes block params
*/
public void testTokenizesBlockParams() {
TokenizerResult result = tokenize("{{#foo as |bar|}}");
result.shouldMatchTokenTypes(OPEN_BLOCK, ID, WHITE_SPACE, OPEN_BLOCK_PARAMS, ID, CLOSE_BLOCK_PARAMS, CLOSE);

result = tokenize("{{#foo as |bar baz|}}");
result.shouldMatchTokenTypes(OPEN_BLOCK, ID, WHITE_SPACE, OPEN_BLOCK_PARAMS, ID, WHITE_SPACE, ID, CLOSE_BLOCK_PARAMS, CLOSE);

result = tokenize("{{#foo as | bar baz |}}");
result.shouldMatchTokenTypes(OPEN_BLOCK, ID, WHITE_SPACE, OPEN_BLOCK_PARAMS, WHITE_SPACE, ID, WHITE_SPACE, ID, WHITE_SPACE, CLOSE_BLOCK_PARAMS, CLOSE);

result = tokenize("{{#foo as as | bar baz |}}");
result.shouldMatchTokenTypes(OPEN_BLOCK, ID, WHITE_SPACE, ID, WHITE_SPACE, OPEN_BLOCK_PARAMS, WHITE_SPACE, ID, WHITE_SPACE, ID, WHITE_SPACE, CLOSE_BLOCK_PARAMS, CLOSE);
}
}

0 comments on commit 28e2f9d

Please sign in to comment.