Skip to content

Commit 9761f65

Browse files
committed
detect '_' as special discard syntax in assignments
1 parent cc9978f commit 9761f65

File tree

8 files changed

+75
-21
lines changed

8 files changed

+75
-21
lines changed

constructors.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,3 +238,7 @@ func (t *Template) newNumber(pos Pos, text string, typ itemType) (*NumberNode, e
238238
func (t *Template) newIdentifier(ident string, pos Pos, line int) *IdentifierNode {
239239
return &IdentifierNode{NodeBase: NodeBase{TemplatePath: t.Name, NodeType: NodeIdentifier, Pos: pos, Line: line}, Ident: ident}
240240
}
241+
242+
func (t *Template) newDiscard(pos Pos, line int) *DiscardNode {
243+
return &DiscardNode{NodeBase: NodeBase{TemplatePath: t.Name, NodeType: NodeDiscard, Pos: pos, Line: line}}
244+
}

eval.go

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -284,34 +284,44 @@ RESTART:
284284
func (st *Runtime) executeSetList(set *SetNode) {
285285
if set.IndexExprGetLookup {
286286
value := st.evalPrimaryExpressionGroup(set.Right[0])
287-
st.executeSet(set.Left[0], value)
288-
if value.IsValid() {
289-
st.executeSet(set.Left[1], valueBoolTRUE)
290-
} else {
291-
st.executeSet(set.Left[1], valueBoolFALSE)
287+
if set.Left[0].Type() != NodeDiscard {
288+
st.executeSet(set.Left[0], value)
289+
}
290+
if set.Left[1].Type() != NodeDiscard {
291+
if value.IsValid() {
292+
st.executeSet(set.Left[1], valueBoolTRUE)
293+
} else {
294+
st.executeSet(set.Left[1], valueBoolFALSE)
295+
}
292296
}
293297
} else {
294298
for i := 0; i < len(set.Left); i++ {
295-
st.executeSet(set.Left[i], st.evalPrimaryExpressionGroup(set.Right[i]))
299+
value := st.evalPrimaryExpressionGroup(set.Right[i])
300+
if set.Left[i].Type() != NodeDiscard {
301+
st.executeSet(set.Left[i], value)
302+
}
296303
}
297304
}
298305
}
299306

300307
func (st *Runtime) executeLetList(set *SetNode) {
301308
if set.IndexExprGetLookup {
302309
value := st.evalPrimaryExpressionGroup(set.Right[0])
303-
304-
st.variables[set.Left[0].(*IdentifierNode).Ident] = value
305-
306-
if value.IsValid() {
307-
st.variables[set.Left[1].(*IdentifierNode).Ident] = valueBoolTRUE
308-
} else {
309-
st.variables[set.Left[1].(*IdentifierNode).Ident] = valueBoolFALSE
310+
if set.Left[0].Type() != NodeDiscard {
311+
st.variables[set.Left[0].(*IdentifierNode).Ident] = value
312+
}
313+
if set.Left[1].Type() != NodeDiscard {
314+
if value.IsValid() {
315+
st.variables[set.Left[1].(*IdentifierNode).Ident] = valueBoolTRUE
316+
} else {
317+
st.variables[set.Left[1].(*IdentifierNode).Ident] = valueBoolFALSE
318+
}
310319
}
311-
312320
} else {
313321
for i := 0; i < len(set.Left); i++ {
314-
st.variables[set.Left[i].(*IdentifierNode).Ident] = st.evalPrimaryExpressionGroup(set.Right[i])
322+
if set.Left[i].Type() != NodeDiscard {
323+
st.variables[set.Left[i].(*IdentifierNode).Ident] = st.evalPrimaryExpressionGroup(set.Right[i])
324+
}
315325
}
316326
}
317327
}

eval_test.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ func TestEvalActionNode(t *testing.T) {
184184
RunJetTest(t, data, nil, "actionNode_NumberNegative", `{{ -5 }}`, "-5")
185185
RunJetTest(t, data, nil, "actionNode_NumberNegative_1", `{{ 1 + -5 }}`, fmt.Sprint(1+-5))
186186

187-
//this is an error RunJetTest(t, data, nil, "actionNode_AddStringInt", `{{ "1"-2 }}`, "12")
187+
//this must be an error RunJetTest(t, data, nil, "actionNode_AddStringInt", `{{ "1"-2 }}`, "12")
188188

189189
RunJetTest(t, data, nil, "actionNode_Mult", `{{ 4*4 }}`, fmt.Sprint(4*4))
190190
RunJetTest(t, data, nil, "actionNode_MultAdd", `{{ 2+4*4 }}`, fmt.Sprint(2+4*4))
@@ -203,6 +203,21 @@ func TestEvalActionNode(t *testing.T) {
203203
RunJetTest(t, data, nil, "actionNode_NumericCmp", `{{ 5*5 > 2*12.5 }}`, fmt.Sprint(5*5 > 2*12.5))
204204
RunJetTest(t, data, nil, "actionNode_NumericCmp1", `{{ 5*5 >= 2*12.5 }}`, fmt.Sprint(5*5 >= 2*12.5))
205205
RunJetTest(t, data, nil, "actionNode_NumericCmp1", `{{ 5 * 5 > 2 * 12.5 == 5 * 5 > 2 * 12.5 }}`, fmt.Sprint((5*5 > 2*12.5) == (5*5 > 2*12.5)))
206+
207+
// test discard syntax in assignments
208+
called := false
209+
markCalled := func() { called = true }
210+
data.Set("foo", markCalled)
211+
data.Set("called", &called)
212+
RunJetTest(t, data, nil, "actionNode_assign_discard", `{{ _ = foo() ; called }}`, "true")
213+
if !called {
214+
t.Log("function whose value should be evaluated but discarded was never called!")
215+
t.Fail()
216+
}
217+
if _, ok := data["_"]; ok {
218+
t.Log("a variable with name '_' was set!")
219+
t.Fail()
220+
}
206221
}
207222

208223
func TestEvalIfNode(t *testing.T) {

lex.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ const (
8282
itemTernary
8383
itemLeftBrackets
8484
itemRightBrackets
85+
itemUnderscore
8586
// Keywords appear after all the rest.
8687
itemKeyword // used only to delimit the keywords
8788
itemExtends
@@ -346,7 +347,7 @@ func lexInsideAction(l *lexer) stateFn {
346347
if l.parenDepth == 0 {
347348
return lexRightDelim
348349
}
349-
return l.errorf("unclosed left paren")
350+
return l.errorf("unclosed left parenthesis")
350351
}
351352
switch r := l.next(); {
352353
case r == eof:
@@ -467,6 +468,12 @@ func lexInsideAction(l *lexer) stateFn {
467468
case '0' <= r && r <= '9':
468469
l.backup()
469470
return lexNumber
471+
case r == '_':
472+
if isSpace(l.peek()) {
473+
l.emit(itemUnderscore)
474+
return lexInsideAction
475+
}
476+
fallthrough // no space? must be the start of an identifier
470477
case isAlphaNumeric(r):
471478
l.backup()
472479
return lexIdentifier
@@ -485,7 +492,6 @@ func lexInsideAction(l *lexer) stateFn {
485492
}
486493
case r <= unicode.MaxASCII && unicode.IsPrint(r):
487494
l.emit(itemChar)
488-
return lexInsideAction
489495
default:
490496
return l.errorf("unrecognized character in action: %#U", r)
491497
}

lex_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ func TestLexer(t *testing.T) {
6565
lexerTestCase(t, `{{.Ex!1}}`, itemLeftDelim, itemField, itemNot, itemNumber, itemRightDelim)
6666
lexerTestCase(t, `{{.Ex==1}}`, itemLeftDelim, itemField, itemEquals, itemNumber, itemRightDelim)
6767
lexerTestCase(t, `{{.Ex&&1}}`, itemLeftDelim, itemField, itemAnd, itemNumber, itemRightDelim)
68+
lexerTestCase(t, `{{ _ = foo }}`, itemLeftDelim, itemUnderscore, itemAssign, itemIdentifier, itemRightDelim)
6869
}
6970

7071
func TestCustomDelimiters(t *testing.T) {

node.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ const (
7777
NodeCommand //An element of a pipeline.
7878
NodeField //A field or method name.
7979
NodeIdentifier //An identifier; always a function name.
80+
NodeDiscard //An underscore
8081
NodeList //A list of Nodes.
8182
NodePipe //A pipeline of commands.
8283
NodeSet
@@ -215,6 +216,15 @@ func (i *IdentifierNode) String() string {
215216
return i.Ident
216217
}
217218

219+
// DiscardNode signals to discard the corresponding right side of an assignment.
220+
type DiscardNode struct {
221+
NodeBase
222+
}
223+
224+
func (i *DiscardNode) String() string {
225+
return "_"
226+
}
227+
218228
// NilNode holds the special identifier 'nil' representing an untyped nil constant.
219229
type NilNode struct {
220230
NodeBase

parse.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ func (t *Template) assignmentOrExpression(context string) (operand Expression) {
619619
leftloop:
620620
for {
621621
switch operand.Type() {
622-
case NodeField, NodeChain, NodeIdentifier:
622+
case NodeField, NodeChain, NodeIdentifier, NodeDiscard:
623623
left = append(left, operand)
624624
default:
625625
t.errorf("unexpected node in assign")
@@ -638,7 +638,7 @@ func (t *Template) assignmentOrExpression(context string) (operand Expression) {
638638

639639
if isLet {
640640
for _, operand := range left {
641-
if operand.Type() != NodeIdentifier {
641+
if operand.Type() != NodeIdentifier && operand.Type() != NodeDiscard {
642642
t.errorf("unexpected node type %s in variable declaration", operand)
643643
}
644644
}
@@ -974,6 +974,8 @@ func (t *Template) term() Node {
974974
t.errorf("%s", token.val)
975975
case itemIdentifier:
976976
return t.newIdentifier(token.val, token.pos, t.lex.lineNumber())
977+
case itemUnderscore:
978+
return t.newDiscard(token.pos, t.lex.lineNumber())
977979
case itemNil:
978980
return t.newNil(token.pos)
979981
case itemField:

testData/assignment.jet

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22
{{ newName := name; safeHtml: newName, " ", "new name" }}
33
{{ newName,newValue := name,value }}
44
{{ value,found := name["key"] }}
5+
{{ _ := foo() }}
6+
{{ _ = foo() }}
7+
{{ _, _ = name["key"] }}
58
===
69
{{newURL:=url("", "").Method("");newURL | pipe}}
710
{{newName:=name;safeHtml(newName, " ", "new name")}}
811
{{newName, newValue:=name, value}}
9-
{{value, found:=name["key"]}}
12+
{{value, found:=name["key"]}}
13+
{{_:=foo()}}
14+
{{_=foo()}}
15+
{{_, _=name["key"]}}

0 commit comments

Comments
 (0)