Commit 331840f5 authored by Rob Pike's avatar Rob Pike

exp/template: allow declaration of variables only inside control structures.

In simple pipelines the declaration has no scope.
Also document the scope.

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/4761044
parent e07c6e6e
...@@ -18,6 +18,9 @@ The input text for a template is UTF-8-encoded text in any format. ...@@ -18,6 +18,9 @@ The input text for a template is UTF-8-encoded text in any format.
"{{" and "}}"; all text outside actions is copied to the output unchanged. "{{" and "}}"; all text outside actions is copied to the output unchanged.
Actions may not span newlines. Actions may not span newlines.
Once constructed, templates and template sets can be executed safely in
parallel.
Actions Actions
Here is the list of actions. "Arguments" and "pipelines" are evaluations of Here is the list of actions. "Arguments" and "pipelines" are evaluations of
...@@ -150,24 +153,27 @@ Execute. ...@@ -150,24 +153,27 @@ Execute.
Variables Variables
A pipeline may initialize a single variable to capture the result. The A pipeline inside an "if" or "with" action may initialize a variable to capture
initialization has syntax the result. The initialization has syntax
$variable := pipeline $variable := pipeline
where $variable is the name of the variable. where $variable is the name of the variable.
The one exception is a pipeline in a range action; in ranges, the variable is If a "range" action initializes a variable, the variable is set to the
set to the successive elements of the iteration. Also, a range may declare two successive elements of the iteration. Also, a "range" may declare two
variables, separated by a comma: variables, separated by a comma:
$index, $element := pipeline $index, $element := pipeline
In this case $index and $element are set to the successive values of the in which case $index and $element are set to the successive values of the
array/slice index or map key and element, respectively. Note that if there is array/slice index or map key and element, respectively. Note that if there is
only one variable, it is assigned the element; this is opposite to the only one variable, it is assigned the element; this is opposite to the
convention in Go range clauses. convention in Go range clauses.
A variable's scope extends to the "end" action of the control structure
declaring it.
When execution begins, $ is set to the data argument passed to Execute, that is, When execution begins, $ is set to the data argument passed to Execute, that is,
to the starting value of dot. to the starting value of dot.
......
...@@ -696,13 +696,13 @@ func (t *Template) action() (n node) { ...@@ -696,13 +696,13 @@ func (t *Template) action() (n node) {
} }
t.backup() t.backup()
defer t.popVars(len(t.vars)) defer t.popVars(len(t.vars))
return newAction(t.lex.lineNumber(), t.pipeline("command")) return newAction(t.lex.lineNumber(), t.pipeline("command", false))
} }
// Pipeline: // Pipeline:
// field or command // field or command
// pipeline "|" pipeline // pipeline "|" pipeline
func (t *Template) pipeline(context string) (pipe *pipeNode) { func (t *Template) pipeline(context string, allowDecls bool) (pipe *pipeNode) {
var decl []*variableNode var decl []*variableNode
// Are there declarations? // Are there declarations?
for { for {
...@@ -714,6 +714,9 @@ func (t *Template) pipeline(context string) (pipe *pipeNode) { ...@@ -714,6 +714,9 @@ func (t *Template) pipeline(context string) (pipe *pipeNode) {
if len(variable.ident) != 1 { if len(variable.ident) != 1 {
t.errorf("illegal variable in declaration: %s", v.val) t.errorf("illegal variable in declaration: %s", v.val)
} }
if !allowDecls {
t.errorf("variable %q declared but cannot be referenced", v.val)
}
decl = append(decl, variable) decl = append(decl, variable)
t.vars = append(t.vars, v.val) t.vars = append(t.vars, v.val)
if next.typ == itemChar && next.val == "," { if next.typ == itemChar && next.val == "," {
...@@ -750,7 +753,7 @@ func (t *Template) pipeline(context string) (pipe *pipeNode) { ...@@ -750,7 +753,7 @@ func (t *Template) pipeline(context string) (pipe *pipeNode) {
func (t *Template) parseControl(context string) (lineNum int, pipe *pipeNode, list, elseList *listNode) { func (t *Template) parseControl(context string) (lineNum int, pipe *pipeNode, list, elseList *listNode) {
lineNum = t.lex.lineNumber() lineNum = t.lex.lineNumber()
defer t.popVars(len(t.vars)) defer t.popVars(len(t.vars))
pipe = t.pipeline(context) pipe = t.pipeline(context, true)
var next node var next node
list, next = t.itemList(false) list, next = t.itemList(false)
switch next.typ() { switch next.typ() {
...@@ -825,7 +828,7 @@ func (t *Template) templateControl() node { ...@@ -825,7 +828,7 @@ func (t *Template) templateControl() node {
if t.next().typ != itemRightDelim { if t.next().typ != itemRightDelim {
t.backup() t.backup()
defer t.popVars(len(t.vars)) defer t.popVars(len(t.vars))
pipe = t.pipeline("template") pipe = t.pipeline("template", false)
} }
return newTemplate(t.lex.lineNumber(), name, pipe) return newTemplate(t.lex.lineNumber(), name, pipe)
} }
......
...@@ -181,8 +181,6 @@ var parseTests = []parseTest{ ...@@ -181,8 +181,6 @@ var parseTests = []parseTest{
"[(action: [(command: [I=printf S=`%d` N=23])])]"}, "[(action: [(command: [I=printf S=`%d` N=23])])]"},
{"pipeline", "{{.X|.Y}}", noError, {"pipeline", "{{.X|.Y}}", noError,
`[(action: [(command: [F=[X]]) (command: [F=[Y]])])]`}, `[(action: [(command: [F=[X]]) (command: [F=[Y]])])]`},
{"pipeline with decl", "{{$x := .X|.Y}}", noError,
`[(action: [V=[$x]] := [(command: [F=[X]]) (command: [F=[Y]])])]`},
{"declaration", "{{.X|.Y}}", noError, {"declaration", "{{.X|.Y}}", noError,
`[(action: [(command: [F=[X]]) (command: [F=[Y]])])]`}, `[(action: [(command: [F=[X]]) (command: [F=[Y]])])]`},
{"simple if", "{{if .X}}hello{{end}}", noError, {"simple if", "{{if .X}}hello{{end}}", noError,
...@@ -226,6 +224,7 @@ var parseTests = []parseTest{ ...@@ -226,6 +224,7 @@ var parseTests = []parseTest{
{"invalid punctuation", "{{printf 3, 4}}", hasError, ""}, {"invalid punctuation", "{{printf 3, 4}}", hasError, ""},
{"multidecl outside range", "{{with $v, $u := 3}}{{end}}", hasError, ""}, {"multidecl outside range", "{{with $v, $u := 3}}{{end}}", hasError, ""},
{"too many decls in range", "{{range $u, $v, $w := 3}}{{end}}", hasError, ""}, {"too many decls in range", "{{range $u, $v, $w := 3}}{{end}}", hasError, ""},
{"useless declaration", "{{$x := .X|.Y}}", hasError, ""},
} }
func TestParse(t *testing.T) { func TestParse(t *testing.T) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment