diff options
author | Fumitoshi Ukai <fumitoshi.ukai@gmail.com> | 2015-05-07 12:56:12 +0900 |
---|---|---|
committer | Fumitoshi Ukai <fumitoshi.ukai@gmail.com> | 2015-05-07 15:12:44 +0900 |
commit | b06cd9d54075f942445f0030da9993368a1f49ab (patch) | |
tree | 6a99750aeed9863b8040c6f6d3d8d2402a4c74de /func.go | |
parent | 5ca0ecded9bc653fd9de59a2fad414d6f2da3bcf (diff) | |
download | kati-b06cd9d54075f942445f0030da9993368a1f49ab.tar.gz |
use sync.Pool for temporal bytes.Buffer.
remove ev.Value and ev.Values
before:
scanblock: 8.22s (14.83%)
runtime.MScan_Sweep 4.99s(9.00%)
after:
scanblock: 7.42s (12.95%)
runtime.MScan_Sweep 4.57s(7.98%)
Diffstat (limited to 'func.go')
-rw-r--r-- | func.go | 250 |
1 files changed, 183 insertions, 67 deletions
@@ -100,13 +100,12 @@ func (sw *ssvWriter) WriteString(s string) { sw.Write([]byte(s)) } -func numericValueForFunc(ev *Evaluator, v Value, funcName string, nth string) int { - a := bytes.TrimSpace(ev.Value(v)) - n, err := strconv.Atoi(string(a)) +func numericValueForFunc(v string) (int, bool) { + n, err := strconv.Atoi(v) if err != nil || n < 0 { - Error(ev.filename, ev.lineno, `*** non-numeric %s argument to "%s" function: "%s".`, nth, funcName, a) + return n, false } - return n + return n, true } type fclosure struct { @@ -137,7 +136,7 @@ func (c *fclosure) String() string { return fmt.Sprintf("$%s %s%c", arg0, strings.Join(args, ","), cp) } -func (c *fclosure) Serialize() SerializableVar{ +func (c *fclosure) Serialize() SerializableVar { r := SerializableVar{Type: "func"} for _, a := range c.args { r.Children = append(r.Children, a.Serialize()) @@ -151,11 +150,14 @@ type funcSubst struct{ fclosure } func (f *funcSubst) Arity() int { return 3 } func (f *funcSubst) Eval(w io.Writer, ev *Evaluator) { assertArity("subst", 3, len(f.args)) - from := ev.Value(f.args[1]) - to := ev.Value(f.args[2]) - text := ev.Value(f.args[3]) + abuf := newBuf() + fargs := ev.args(abuf, f.args[1:]...) + from := fargs[0] + to := fargs[1] + text := fargs[2] Log("subst from:%q to:%q text:%q", from, to, text) w.Write(bytes.Replace(text, from, to, -1)) + freeBuf(abuf) } type funcPatsubst struct{ fclosure } @@ -163,14 +165,17 @@ type funcPatsubst struct{ fclosure } func (f *funcPatsubst) Arity() int { return 3 } func (f *funcPatsubst) Eval(w io.Writer, ev *Evaluator) { assertArity("patsubst", 3, len(f.args)) - pat := ev.Value(f.args[1]) - repl := ev.Value(f.args[2]) - texts := splitSpacesBytes(ev.Value(f.args[3])) + abuf := newBuf() + fargs := ev.args(abuf, f.args[1:]...) + pat := fargs[0] + repl := fargs[1] + texts := splitSpacesBytes(fargs[2]) sw := ssvWriter{w: w} for _, text := range texts { t := substPatternBytes(pat, repl, text) sw.Write(t) } + freeBuf(abuf) } type funcStrip struct{ fclosure } @@ -178,8 +183,10 @@ type funcStrip struct{ fclosure } func (f *funcStrip) Arity() int { return 1 } func (f *funcStrip) Eval(w io.Writer, ev *Evaluator) { assertArity("strip", 1, len(f.args)) - text := ev.Value(f.args[1]) - w.Write(bytes.TrimSpace(text)) + abuf := newBuf() + f.args[1].Eval(abuf, ev) + w.Write(trimSpaceBytes(abuf.Bytes())) + freeBuf(abuf) } type funcFindstring struct{ fclosure } @@ -187,11 +194,14 @@ type funcFindstring struct{ fclosure } func (f *funcFindstring) Arity() int { return 2 } func (f *funcFindstring) Eval(w io.Writer, ev *Evaluator) { assertArity("findstring", 2, len(f.args)) - find := ev.Value(f.args[1]) - text := ev.Value(f.args[2]) + abuf := newBuf() + fargs := ev.args(abuf, f.args[1:]...) + find := fargs[0] + text := fargs[1] if bytes.Index(text, find) >= 0 { w.Write(find) } + freeBuf(abuf) } type funcFilter struct{ fclosure } @@ -199,8 +209,10 @@ type funcFilter struct{ fclosure } func (f *funcFilter) Arity() int { return 2 } func (f *funcFilter) Eval(w io.Writer, ev *Evaluator) { assertArity("filter", 2, len(f.args)) - patterns := splitSpacesBytes(ev.Value(f.args[1])) - texts := splitSpacesBytes(ev.Value(f.args[2])) + abuf := newBuf() + fargs := ev.args(abuf, f.args[1:]...) + patterns := splitSpacesBytes(fargs[0]) + texts := splitSpacesBytes(fargs[1]) sw := ssvWriter{w: w} for _, text := range texts { for _, pat := range patterns { @@ -209,6 +221,7 @@ func (f *funcFilter) Eval(w io.Writer, ev *Evaluator) { } } } + freeBuf(abuf) } type funcFilterOut struct{ fclosure } @@ -216,8 +229,10 @@ type funcFilterOut struct{ fclosure } func (f *funcFilterOut) Arity() int { return 2 } func (f *funcFilterOut) Eval(w io.Writer, ev *Evaluator) { assertArity("filter-out", 2, len(f.args)) - patterns := splitSpacesBytes(ev.Value(f.args[1])) - texts := splitSpacesBytes(ev.Value(f.args[2])) + abuf := newBuf() + fargs := ev.args(abuf, f.args[1:]...) + patterns := splitSpacesBytes(fargs[0]) + texts := splitSpacesBytes(fargs[1]) sw := ssvWriter{w: w} Loop: for _, text := range texts { @@ -228,6 +243,7 @@ Loop: } sw.Write(text) } + freeBuf(abuf) } type funcSort struct{ fclosure } @@ -235,7 +251,10 @@ type funcSort struct{ fclosure } func (f *funcSort) Arity() int { return 1 } func (f *funcSort) Eval(w io.Writer, ev *Evaluator) { assertArity("sort", 1, len(f.args)) - toks := splitSpaces(string(ev.Value(f.args[1]))) + abuf := newBuf() + f.args[1].Eval(abuf, ev) + toks := splitSpaces(abuf.String()) + freeBuf(abuf) sort.Strings(toks) // Remove duplicate words. @@ -254,15 +273,23 @@ type funcWord struct{ fclosure } func (f *funcWord) Arity() int { return 2 } func (f *funcWord) Eval(w io.Writer, ev *Evaluator) { assertArity("word", 2, len(f.args)) - index := numericValueForFunc(ev, f.args[1], "word", "first") + abuf := newBuf() + fargs := ev.args(abuf, f.args[1:]...) + v := string(trimSpaceBytes(fargs[0])) + index, ok := numericValueForFunc(v) + if !ok { + Error(ev.filename, ev.lineno, `*** non-numeric first argument to "word" function: %q.`, v) + } if index == 0 { Error(ev.filename, ev.lineno, `*** first argument to "word" function must be greater than 0.`) } - toks := splitSpacesBytes(ev.Value(f.args[2])) + toks := splitSpacesBytes(fargs[1]) if index-1 >= len(toks) { + freeBuf(abuf) return } w.Write(toks[index-1]) + freeBuf(abuf) } type funcWordlist struct{ fclosure } @@ -270,17 +297,28 @@ type funcWordlist struct{ fclosure } func (f *funcWordlist) Arity() int { return 3 } func (f *funcWordlist) Eval(w io.Writer, ev *Evaluator) { assertArity("wordlist", 3, len(f.args)) - si := numericValueForFunc(ev, f.args[1], "wordlist", "first") + abuf := newBuf() + fargs := ev.args(abuf, f.args[1:]...) + v := string(trimSpaceBytes(fargs[0])) + si, ok := numericValueForFunc(v) + if !ok { + Error(ev.filename, ev.lineno, `*** non-numeric first argument to "wordlist" function: %q.`, v) + } if si == 0 { Error(ev.filename, ev.lineno, `*** invalid first argument to "wordlist" function: %s`, f.args[1]) } - ei := numericValueForFunc(ev, f.args[2], "wordlist", "second") + v = string(trimSpaceBytes(fargs[1])) + ei, ok := numericValueForFunc(v) + if !ok { + Error(ev.filename, ev.lineno, `*** non-numeric second argument to "wordlist" function: %q.`, v) + } if ei == 0 { Error(ev.filename, ev.lineno, `*** invalid second argument to "wordlist" function: %s`, f.args[2]) } - toks := splitSpacesBytes(ev.Value(f.args[3])) + toks := splitSpacesBytes(fargs[2]) if si-1 >= len(toks) { + freeBuf(abuf) return } if ei-1 >= len(toks) { @@ -291,6 +329,7 @@ func (f *funcWordlist) Eval(w io.Writer, ev *Evaluator) { for _, t := range toks[si-1 : ei] { sw.Write(t) } + freeBuf(abuf) } type funcWords struct{ fclosure } @@ -298,8 +337,11 @@ type funcWords struct{ fclosure } func (f *funcWords) Arity() int { return 1 } func (f *funcWords) Eval(w io.Writer, ev *Evaluator) { assertArity("words", 1, len(f.args)) - toks := splitSpacesBytes(ev.Value(f.args[1])) + abuf := newBuf() + f.args[1].Eval(abuf, ev) + toks := splitSpacesBytes(abuf.Bytes()) w.Write([]byte(strconv.Itoa(len(toks)))) + freeBuf(abuf) } type funcFirstword struct{ fclosure } @@ -307,11 +349,15 @@ type funcFirstword struct{ fclosure } func (f *funcFirstword) Arity() int { return 1 } func (f *funcFirstword) Eval(w io.Writer, ev *Evaluator) { assertArity("firstword", 1, len(f.args)) - toks := splitSpacesBytes(ev.Value(f.args[1])) + abuf := newBuf() + f.args[1].Eval(abuf, ev) + toks := splitSpacesBytes(abuf.Bytes()) if len(toks) == 0 { + freeBuf(abuf) return } w.Write(toks[0]) + freeBuf(abuf) } type funcLastword struct{ fclosure } @@ -319,11 +365,15 @@ type funcLastword struct{ fclosure } func (f *funcLastword) Arity() int { return 1 } func (f *funcLastword) Eval(w io.Writer, ev *Evaluator) { assertArity("lastword", 1, len(f.args)) - toks := splitSpacesBytes(ev.Value(f.args[1])) + abuf := newBuf() + f.args[1].Eval(abuf, ev) + toks := splitSpacesBytes(abuf.Bytes()) if len(toks) == 0 { + freeBuf(abuf) return } w.Write(toks[len(toks)-1]) + freeBuf(abuf) } // https://www.gnu.org/software/make/manual/html_node/File-Name-Functions.html#File-Name-Functions @@ -333,8 +383,10 @@ type funcJoin struct{ fclosure } func (f *funcJoin) Arity() int { return 2 } func (f *funcJoin) Eval(w io.Writer, ev *Evaluator) { assertArity("join", 2, len(f.args)) - list1 := splitSpacesBytes(ev.Value(f.args[1])) - list2 := splitSpacesBytes(ev.Value(f.args[2])) + abuf := newBuf() + fargs := ev.args(abuf, f.args[1:]...) + list1 := splitSpacesBytes(fargs[0]) + list2 := splitSpacesBytes(fargs[1]) sw := ssvWriter{w: w} for i, v := range list1 { if i < len(list2) { @@ -350,6 +402,7 @@ func (f *funcJoin) Eval(w io.Writer, ev *Evaluator) { sw.Write(v) } } + freeBuf(abuf) } type funcWildcard struct{ fclosure } @@ -361,8 +414,12 @@ func (f *funcWildcard) Eval(w io.Writer, ev *Evaluator) { ev.hasIO = true return } + abuf := newBuf() + f.args[1].Eval(abuf, ev) + patterns := splitSpaces(abuf.String()) + freeBuf(abuf) sw := ssvWriter{w: w} - for _, pattern := range splitSpaces(string(ev.Value(f.args[1]))) { + for _, pattern := range patterns { files, err := filepath.Glob(pattern) if err != nil { panic(err) @@ -382,7 +439,10 @@ func (f *funcDir) Eval(w io.Writer, ev *Evaluator) { ev.hasIO = true return } - names := splitSpaces(string(ev.Value(f.args[1]))) + abuf := newBuf() + f.args[1].Eval(abuf, ev) + names := splitSpaces(abuf.String()) + freeBuf(abuf) sw := ssvWriter{w: w} for _, name := range names { if name == "/" { @@ -402,7 +462,10 @@ func (f *funcNotdir) Eval(w io.Writer, ev *Evaluator) { ev.hasIO = true return } - names := splitSpaces(string(ev.Value(f.args[1]))) + abuf := newBuf() + f.args[1].Eval(abuf, ev) + names := splitSpaces(abuf.String()) + freeBuf(abuf) sw := ssvWriter{w: w} for _, name := range names { if name == string(filepath.Separator) { @@ -418,7 +481,10 @@ type funcSuffix struct{ fclosure } func (f *funcSuffix) Arity() int { return 1 } func (f *funcSuffix) Eval(w io.Writer, ev *Evaluator) { assertArity("suffix", 1, len(f.args)) - toks := splitSpaces(string(ev.Value(f.args[1]))) + abuf := newBuf() + f.args[1].Eval(abuf, ev) + toks := splitSpaces(abuf.String()) + freeBuf(abuf) sw := ssvWriter{w: w} for _, tok := range toks { e := filepath.Ext(tok) @@ -433,7 +499,10 @@ type funcBasename struct{ fclosure } func (f *funcBasename) Arity() int { return 1 } func (f *funcBasename) Eval(w io.Writer, ev *Evaluator) { assertArity("basename", 1, len(f.args)) - toks := splitSpaces(string(ev.Value(f.args[1]))) + abuf := newBuf() + f.args[1].Eval(abuf, ev) + toks := splitSpaces(abuf.String()) + freeBuf(abuf) sw := ssvWriter{w: w} for _, tok := range toks { e := stripExt(tok) @@ -446,14 +515,17 @@ type funcAddsuffix struct{ fclosure } func (f *funcAddsuffix) Arity() int { return 2 } func (f *funcAddsuffix) Eval(w io.Writer, ev *Evaluator) { assertArity("addsuffix", 2, len(f.args)) - suf := ev.Value(f.args[1]) - toks := splitSpacesBytes(ev.Value(f.args[2])) + abuf := newBuf() + fargs := ev.args(abuf, f.args[1:]...) + suf := fargs[0] + toks := splitSpacesBytes(fargs[1]) sw := ssvWriter{w: w} for _, tok := range toks { sw.Write(tok) // Use |w| not to append extra ' '. w.Write(suf) } + freeBuf(abuf) } type funcAddprefix struct{ fclosure } @@ -461,14 +533,17 @@ type funcAddprefix struct{ fclosure } func (f *funcAddprefix) Arity() int { return 2 } func (f *funcAddprefix) Eval(w io.Writer, ev *Evaluator) { assertArity("addprefix", 2, len(f.args)) - pre := ev.Value(f.args[1]) - toks := splitSpacesBytes(ev.Value(f.args[2])) + abuf := newBuf() + fargs := ev.args(abuf, f.args[1:]...) + pre := fargs[0] + toks := splitSpacesBytes(fargs[1]) sw := ssvWriter{w: w} for _, tok := range toks { sw.Write(pre) // Use |w| not to append extra ' '. w.Write(tok) } + freeBuf(abuf) } type funcRealpath struct{ fclosure } @@ -480,7 +555,10 @@ func (f *funcRealpath) Eval(w io.Writer, ev *Evaluator) { ev.hasIO = true return } - names := splitSpaces(string(ev.Value(f.args[1]))) + abuf := newBuf() + f.args[1].Eval(abuf, ev) + names := splitSpaces(abuf.String()) + freeBuf(abuf) sw := ssvWriter{w: w} for _, name := range names { name, err := filepath.Abs(name) @@ -506,7 +584,10 @@ func (f *funcAbspath) Eval(w io.Writer, ev *Evaluator) { ev.hasIO = true return } - names := splitSpaces(string(ev.Value(f.args[1]))) + abuf := newBuf() + f.args[1].Eval(abuf, ev) + names := splitSpaces(abuf.String()) + freeBuf(abuf) sw := ssvWriter{w: w} for _, name := range names { name, err := filepath.Abs(name) @@ -524,11 +605,14 @@ type funcIf struct{ fclosure } func (f *funcIf) Arity() int { return 3 } func (f *funcIf) Eval(w io.Writer, ev *Evaluator) { assertArity("if", 2, len(f.args)) - cond := ev.Value(f.args[1]) - if len(cond) != 0 { + abuf := newBuf() + f.args[1].Eval(abuf, ev) + if len(abuf.Bytes()) != 0 { + freeBuf(abuf) f.args[2].Eval(w, ev) return } + freeBuf(abuf) if len(f.args) > 3 { f.args[3].Eval(w, ev) } @@ -539,14 +623,19 @@ type funcAnd struct{ fclosure } func (f *funcAnd) Arity() int { return 0 } func (f *funcAnd) Eval(w io.Writer, ev *Evaluator) { assertArity("and", 0, len(f.args)) + abuf := newBuf() var cond []byte for _, arg := range f.args[1:] { - cond = ev.Value(arg) + abuf.Reset() + arg.Eval(abuf, ev) + cond = abuf.Bytes() if len(cond) == 0 { + freeBuf(abuf) return } } w.Write(cond) + freeBuf(abuf) } type funcOr struct{ fclosure } @@ -554,13 +643,18 @@ type funcOr struct{ fclosure } func (f *funcOr) Arity() int { return 0 } func (f *funcOr) Eval(w io.Writer, ev *Evaluator) { assertArity("or", 0, len(f.args)) + abuf := newBuf() for _, arg := range f.args[1:] { - cond := ev.Value(arg) + abuf.Reset() + arg.Eval(abuf, ev) + cond := abuf.Bytes() if len(cond) != 0 { w.Write(cond) + freeBuf(abuf) return } } + freeBuf(abuf) } // http://www.gnu.org/software/make/manual/make.html#Shell-Function @@ -574,10 +668,13 @@ func (f *funcShell) Eval(w io.Writer, ev *Evaluator) { ev.hasIO = true return } - arg := ev.Value(f.args[1]) + abuf := newBuf() + f.args[1].Eval(abuf, ev) + arg := abuf.String() + freeBuf(abuf) shellVar := ev.LookupVar("SHELL") // TODO: Should be Eval, not String. - cmdline := []string{shellVar.String(), "-c", string(arg)} + cmdline := []string{shellVar.String(), "-c", arg} cmd := exec.Cmd{ Path: cmdline[0], Args: cmdline, @@ -599,7 +696,9 @@ type funcCall struct{ fclosure } func (f *funcCall) Arity() int { return 0 } func (f *funcCall) Eval(w io.Writer, ev *Evaluator) { - variable := ev.Value(f.args[1]) + abuf := newBuf() + fargs := ev.args(abuf, f.args[1:]...) + variable := fargs[0] Log("call %q variable %q", f.args[1], variable) v := ev.LookupVar(string(variable)) // Evalualte all arguments first before we modify the table. @@ -610,10 +709,10 @@ func (f *funcCall) Eval(w io.Writer, ev *Evaluator) { // the built-in function is always invoked (even if a make variable // by that name also exists). - for i, arg := range f.args[2:] { - // f.args[2] will be $1. - args = append(args, tmpval(ev.Value(arg))) - Log("call $%d: %q=>%q", i+1, arg, args[i+1]) + for i, arg := range fargs[1:] { + // f.args[2]=>args[1] will be $1. + args = append(args, tmpval(arg)) + Log("call $%d: %q=>%q", i+1, arg, fargs[i+1]) } oldParams := ev.paramVars ev.paramVars = args @@ -638,6 +737,7 @@ func (f *funcCall) Eval(w io.Writer, ev *Evaluator) { } ev.paramVars = oldParams Log("call %q variable %q return %q", f.args[1], variable, buf.Bytes()) + freeBuf(abuf) } // http://www.gnu.org/software/make/manual/make.html#Value-Function @@ -656,8 +756,10 @@ type funcEval struct{ fclosure } func (f *funcEval) Arity() int { return 1 } func (f *funcEval) Eval(w io.Writer, ev *Evaluator) { assertArity("eval", 1, len(f.args)) - s := ev.Value(f.args[1]) - Log("eval %q at %s:%d", string(s), ev.filename, ev.lineno) + abuf := newBuf() + f.args[1].Eval(abuf, ev) + s := abuf.Bytes() + Log("eval %q at %s:%d", s, ev.filename, ev.lineno) mk, err := ParseMakefileBytes(s, ev.filename, ev.lineno) if err != nil { panic(err) @@ -666,6 +768,7 @@ func (f *funcEval) Eval(w io.Writer, ev *Evaluator) { for _, stmt := range mk.stmts { ev.eval(stmt) } + freeBuf(abuf) } func (f *funcEval) Compact() Value { @@ -738,7 +841,7 @@ func (f *funcNop) Eval(io.Writer, *Evaluator) {} func (f *funcNop) Serialize() SerializableVar { return SerializableVar{ Type: "funcNop", - V: f.expr, + V: f.expr, } } @@ -775,7 +878,9 @@ func (f *funcEvalAssign) String() string { } func (f *funcEvalAssign) Eval(w io.Writer, ev *Evaluator) { - rhs := trimLeftSpaceBytes(ev.Value(f.rhs)) + var abuf bytes.Buffer + f.rhs.Eval(&abuf, ev) + rhs := trimLeftSpaceBytes(abuf.Bytes()) var rvalue Var switch f.op { case ":=": @@ -785,7 +890,9 @@ func (f *funcEvalAssign) Eval(w io.Writer, ev *Evaluator) { if err != nil { panic(fmt.Sprintf("eval assign error: %q: %v", f.String(), err)) } - rvalue = SimpleVar{value: tmpval(ev.Value(expr)), origin: "file"} + abuf.Reset() + expr.Eval(&abuf, ev) + rvalue = SimpleVar{value: tmpval(abuf.Bytes()), origin: "file"} case "=": rvalue = RecursiveVar{expr: tmpval(rhs), origin: "file"} case "+=": @@ -847,8 +954,10 @@ func (f *funcInfo) Eval(w io.Writer, ev *Evaluator) { ev.hasIO = true return } - arg := ev.Value(f.args[1]) - fmt.Printf("%s\n", arg) + abuf := newBuf() + f.args[1].Eval(abuf, ev) + fmt.Printf("%s\n", abuf.String()) + freeBuf(abuf) } type funcWarning struct{ fclosure } @@ -860,8 +969,10 @@ func (f *funcWarning) Eval(w io.Writer, ev *Evaluator) { ev.hasIO = true return } - arg := ev.Value(f.args[1]) - fmt.Printf("%s:%d: %s\n", ev.filename, ev.lineno, arg) + abuf := newBuf() + f.args[1].Eval(abuf, ev) + fmt.Printf("%s:%d: %s\n", ev.filename, ev.lineno, abuf.String()) + freeBuf(abuf) } type funcError struct{ fclosure } @@ -873,8 +984,10 @@ func (f *funcError) Eval(w io.Writer, ev *Evaluator) { ev.hasIO = true return } - arg := ev.Value(f.args[1]) - Error(ev.filename, ev.lineno, "*** %s.", arg) + abuf := newBuf() + f.args[1].Eval(abuf, ev) + Error(ev.filename, ev.lineno, "*** %s.", abuf.String()) + freeBuf(abuf) } // http://www.gnu.org/software/make/manual/make.html#Foreach-Function @@ -884,8 +997,10 @@ func (f *funcForeach) Arity() int { return 3 } func (f *funcForeach) Eval(w io.Writer, ev *Evaluator) { assertArity("foreach", 3, len(f.args)) - varname := string(ev.Value(f.args[1])) - list := ev.Values(f.args[2]) + abuf := newBuf() + fargs := ev.args(abuf, f.args[1], f.args[2]) + varname := string(fargs[0]) + list := splitSpacesBytes(fargs[1]) text := f.args[3] restore := ev.outVars.save(varname) defer restore() @@ -893,7 +1008,7 @@ func (f *funcForeach) Eval(w io.Writer, ev *Evaluator) { for _, word := range list { ev.outVars.Assign(varname, SimpleVar{ - value: word, + value: tmpval(word), origin: "automatic", }) if space { @@ -902,4 +1017,5 @@ func (f *funcForeach) Eval(w io.Writer, ev *Evaluator) { text.Eval(w, ev) space = true } + freeBuf(abuf) } |