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}