param.go

  1package terminfo
  2
  3import (
  4	"bytes"
  5	"fmt"
  6	"io"
  7	"strconv"
  8	"strings"
  9	"sync"
 10)
 11
 12// parametizer represents the a scan state for a parameterized string.
 13type parametizer struct {
 14	// z is the string to parameterize
 15	z []byte
 16	// pos is the current position in s.
 17	pos int
 18	// nest is the current nest level.
 19	nest int
 20	// s is the variable stack.
 21	s stack
 22	// skipElse keeps the state of skipping else.
 23	skipElse bool
 24	// buf is the result buffer.
 25	buf *bytes.Buffer
 26	// params are the parameters to interpolate.
 27	params [9]interface{}
 28	// vars are dynamic variables.
 29	vars [26]interface{}
 30}
 31
 32// staticVars are the static, global variables.
 33var staticVars = struct {
 34	vars [26]interface{}
 35	sync.Mutex
 36}{}
 37
 38var parametizerPool = sync.Pool{
 39	New: func() interface{} {
 40		p := new(parametizer)
 41		p.buf = bytes.NewBuffer(make([]byte, 0, 45))
 42		return p
 43	},
 44}
 45
 46// newParametizer returns a new initialized parametizer from the pool.
 47func newParametizer(z []byte) *parametizer {
 48	p := parametizerPool.Get().(*parametizer)
 49	p.z = z
 50	return p
 51}
 52
 53// reset resets the parametizer.
 54func (p *parametizer) reset() {
 55	p.pos, p.nest = 0, 0
 56	p.s.reset()
 57	p.buf.Reset()
 58	p.params, p.vars = [9]interface{}{}, [26]interface{}{}
 59	parametizerPool.Put(p)
 60}
 61
 62// stateFn represents the state of the scanner as a function that returns the
 63// next state.
 64type stateFn func() stateFn
 65
 66// exec executes the parameterizer, interpolating the supplied parameters.
 67func (p *parametizer) exec() string {
 68	for state := p.scanTextFn; state != nil; {
 69		state = state()
 70	}
 71	return p.buf.String()
 72}
 73
 74// peek returns the next byte.
 75func (p *parametizer) peek() (byte, error) {
 76	if p.pos >= len(p.z) {
 77		return 0, io.EOF
 78	}
 79	return p.z[p.pos], nil
 80}
 81
 82// writeFrom writes the characters from ppos to pos to the buffer.
 83func (p *parametizer) writeFrom(ppos int) {
 84	if p.pos > ppos {
 85		// append remaining characters.
 86		p.buf.Write(p.z[ppos:p.pos])
 87	}
 88}
 89
 90func (p *parametizer) scanTextFn() stateFn {
 91	ppos := p.pos
 92	for {
 93		ch, err := p.peek()
 94		if err != nil {
 95			p.writeFrom(ppos)
 96			return nil
 97		}
 98		if ch == '%' {
 99			p.writeFrom(ppos)
100			p.pos++
101			return p.scanCodeFn
102		}
103		p.pos++
104	}
105}
106
107func (p *parametizer) scanCodeFn() stateFn {
108	ch, err := p.peek()
109	if err != nil {
110		return nil
111	}
112	switch ch {
113	case '%':
114		p.buf.WriteByte('%')
115	case ':':
116		// this character is used to avoid interpreting "%-" and "%+" as operators.
117		// the next character is where the format really begins.
118		p.pos++
119		_, err = p.peek()
120		if err != nil {
121			return nil
122		}
123		return p.scanFormatFn
124	case '#', ' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.':
125		return p.scanFormatFn
126	case 'o':
127		p.buf.WriteString(strconv.FormatInt(int64(p.s.popInt()), 8))
128	case 'd':
129		p.buf.WriteString(strconv.Itoa(p.s.popInt()))
130	case 'x':
131		p.buf.WriteString(strconv.FormatInt(int64(p.s.popInt()), 16))
132	case 'X':
133		p.buf.WriteString(strings.ToUpper(strconv.FormatInt(int64(p.s.popInt()), 16)))
134	case 's':
135		p.buf.WriteString(p.s.popString())
136	case 'c':
137		p.buf.WriteByte(p.s.popByte())
138	case 'p':
139		p.pos++
140		return p.pushParamFn
141	case 'P':
142		p.pos++
143		return p.setDsVarFn
144	case 'g':
145		p.pos++
146		return p.getDsVarFn
147	case '\'':
148		p.pos++
149		ch, err = p.peek()
150		if err != nil {
151			return nil
152		}
153		p.s.push(ch)
154		// skip the '\''
155		p.pos++
156	case '{':
157		p.pos++
158		return p.pushIntfn
159	case 'l':
160		p.s.push(len(p.s.popString()))
161	case '+':
162		bi, ai := p.s.popInt(), p.s.popInt()
163		p.s.push(ai + bi)
164	case '-':
165		bi, ai := p.s.popInt(), p.s.popInt()
166		p.s.push(ai - bi)
167	case '*':
168		bi, ai := p.s.popInt(), p.s.popInt()
169		p.s.push(ai * bi)
170	case '/':
171		bi, ai := p.s.popInt(), p.s.popInt()
172		if bi != 0 {
173			p.s.push(ai / bi)
174		} else {
175			p.s.push(0)
176		}
177	case 'm':
178		bi, ai := p.s.popInt(), p.s.popInt()
179		if bi != 0 {
180			p.s.push(ai % bi)
181		} else {
182			p.s.push(0)
183		}
184	case '&':
185		bi, ai := p.s.popInt(), p.s.popInt()
186		p.s.push(ai & bi)
187	case '|':
188		bi, ai := p.s.popInt(), p.s.popInt()
189		p.s.push(ai | bi)
190	case '^':
191		bi, ai := p.s.popInt(), p.s.popInt()
192		p.s.push(ai ^ bi)
193	case '=':
194		bi, ai := p.s.popInt(), p.s.popInt()
195		p.s.push(ai == bi)
196	case '>':
197		bi, ai := p.s.popInt(), p.s.popInt()
198		p.s.push(ai > bi)
199	case '<':
200		bi, ai := p.s.popInt(), p.s.popInt()
201		p.s.push(ai < bi)
202	case 'A':
203		bi, ai := p.s.popBool(), p.s.popBool()
204		p.s.push(ai && bi)
205	case 'O':
206		bi, ai := p.s.popBool(), p.s.popBool()
207		p.s.push(ai || bi)
208	case '!':
209		p.s.push(!p.s.popBool())
210	case '~':
211		p.s.push(^p.s.popInt())
212	case 'i':
213		for i := range p.params[:2] {
214			if n, ok := p.params[i].(int); ok {
215				p.params[i] = n + 1
216			}
217		}
218	case '?', ';':
219	case 't':
220		return p.scanThenFn
221	case 'e':
222		p.skipElse = true
223		return p.skipTextFn
224	}
225	p.pos++
226	return p.scanTextFn
227}
228
229func (p *parametizer) scanFormatFn() stateFn {
230	// the character was already read, so no need to check the error.
231	ch, _ := p.peek()
232	// 6 should be the maximum length of a format string, for example "%:-9.9d".
233	f := []byte{'%', ch, 0, 0, 0, 0}
234	var err error
235	for {
236		p.pos++
237		ch, err = p.peek()
238		if err != nil {
239			return nil
240		}
241		f = append(f, ch)
242		switch ch {
243		case 'o', 'd', 'x', 'X':
244			fmt.Fprintf(p.buf, string(f), p.s.popInt())
245			break
246		case 's':
247			fmt.Fprintf(p.buf, string(f), p.s.popString())
248			break
249		case 'c':
250			fmt.Fprintf(p.buf, string(f), p.s.popByte())
251			break
252		}
253	}
254	p.pos++
255	return p.scanTextFn
256}
257
258func (p *parametizer) pushParamFn() stateFn {
259	ch, err := p.peek()
260	if err != nil {
261		return nil
262	}
263	if ai := int(ch - '1'); ai >= 0 && ai < len(p.params) {
264		p.s.push(p.params[ai])
265	} else {
266		p.s.push(0)
267	}
268	// skip the '}'
269	p.pos++
270	return p.scanTextFn
271}
272
273func (p *parametizer) setDsVarFn() stateFn {
274	ch, err := p.peek()
275	if err != nil {
276		return nil
277	}
278	if ch >= 'A' && ch <= 'Z' {
279		staticVars.Lock()
280		staticVars.vars[int(ch-'A')] = p.s.pop()
281		staticVars.Unlock()
282	} else if ch >= 'a' && ch <= 'z' {
283		p.vars[int(ch-'a')] = p.s.pop()
284	}
285	p.pos++
286	return p.scanTextFn
287}
288
289func (p *parametizer) getDsVarFn() stateFn {
290	ch, err := p.peek()
291	if err != nil {
292		return nil
293	}
294	var a byte
295	if ch >= 'A' && ch <= 'Z' {
296		a = 'A'
297	} else if ch >= 'a' && ch <= 'z' {
298		a = 'a'
299	}
300	staticVars.Lock()
301	p.s.push(staticVars.vars[int(ch-a)])
302	staticVars.Unlock()
303	p.pos++
304	return p.scanTextFn
305}
306
307func (p *parametizer) pushIntfn() stateFn {
308	var ai int
309	for {
310		ch, err := p.peek()
311		if err != nil {
312			return nil
313		}
314		p.pos++
315		if ch < '0' || ch > '9' {
316			p.s.push(ai)
317			return p.scanTextFn
318		}
319		ai = (ai * 10) + int(ch-'0')
320	}
321}
322
323func (p *parametizer) scanThenFn() stateFn {
324	p.pos++
325	if p.s.popBool() {
326		return p.scanTextFn
327	}
328	p.skipElse = false
329	return p.skipTextFn
330}
331
332func (p *parametizer) skipTextFn() stateFn {
333	for {
334		ch, err := p.peek()
335		if err != nil {
336			return nil
337		}
338		p.pos++
339		if ch == '%' {
340			break
341		}
342	}
343	if p.skipElse {
344		return p.skipElseFn
345	}
346	return p.skipThenFn
347}
348
349func (p *parametizer) skipThenFn() stateFn {
350	ch, err := p.peek()
351	if err != nil {
352		return nil
353	}
354	p.pos++
355	switch ch {
356	case ';':
357		if p.nest == 0 {
358			return p.scanTextFn
359		}
360		p.nest--
361	case '?':
362		p.nest++
363	case 'e':
364		if p.nest == 0 {
365			return p.scanTextFn
366		}
367	}
368	return p.skipTextFn
369}
370
371func (p *parametizer) skipElseFn() stateFn {
372	ch, err := p.peek()
373	if err != nil {
374		return nil
375	}
376	p.pos++
377	switch ch {
378	case ';':
379		if p.nest == 0 {
380			return p.scanTextFn
381		}
382		p.nest--
383	case '?':
384		p.nest++
385	}
386	return p.skipTextFn
387}
388
389// Printf evaluates a parameterized terminfo value z, interpolating params.
390func Printf(z []byte, params ...interface{}) string {
391	p := newParametizer(z)
392	defer p.reset()
393	// make sure we always have 9 parameters -- makes it easier
394	// later to skip checks and its faster
395	for i := 0; i < len(p.params) && i < len(params); i++ {
396		p.params[i] = params[i]
397	}
398	return p.exec()
399}
400
401// Fprintf evaluates a parameterized terminfo value z, interpolating params and
402// writing to w.
403func Fprintf(w io.Writer, z []byte, params ...interface{}) {
404	w.Write([]byte(Printf(z, params...)))
405}