diff options
Diffstat (limited to 'vendor/github.com/xo/terminfo/param.go')
-rw-r--r-- | vendor/github.com/xo/terminfo/param.go | 490 |
1 files changed, 490 insertions, 0 deletions
diff --git a/vendor/github.com/xo/terminfo/param.go b/vendor/github.com/xo/terminfo/param.go new file mode 100644 index 000000000..e6b8a1bc0 --- /dev/null +++ b/vendor/github.com/xo/terminfo/param.go @@ -0,0 +1,490 @@ +package terminfo + +import ( + "bytes" + "fmt" + "io" + "strconv" + "strings" + "sync" +) + +// parametizer represents the a scan state for a parameterized string. +type parametizer struct { + // z is the string to parameterize + z []byte + + // pos is the current position in s. + pos int + + // nest is the current nest level. + nest int + + // s is the variable stack. + s stack + + // skipElse keeps the state of skipping else. + skipElse bool + + // buf is the result buffer. + buf *bytes.Buffer + + // params are the parameters to interpolate. + params [9]interface{} + + // vars are dynamic variables. + vars [26]interface{} +} + +// staticVars are the static, global variables. +var staticVars = struct { + vars [26]interface{} + sync.Mutex +}{} + +var parametizerPool = sync.Pool{ + New: func() interface{} { + p := new(parametizer) + p.buf = bytes.NewBuffer(make([]byte, 0, 45)) + return p + }, +} + +// newParametizer returns a new initialized parametizer from the pool. +func newParametizer(z []byte) *parametizer { + p := parametizerPool.Get().(*parametizer) + p.z = z + + return p +} + +// reset resets the parametizer. +func (p *parametizer) reset() { + p.pos, p.nest = 0, 0 + + p.s.reset() + p.buf.Reset() + + p.params, p.vars = [9]interface{}{}, [26]interface{}{} + + parametizerPool.Put(p) +} + +// stateFn represents the state of the scanner as a function that returns the +// next state. +type stateFn func() stateFn + +// exec executes the parameterizer, interpolating the supplied parameters. +func (p *parametizer) exec() string { + for state := p.scanTextFn; state != nil; { + state = state() + } + return p.buf.String() +} + +// peek returns the next byte. +func (p *parametizer) peek() (byte, error) { + if p.pos >= len(p.z) { + return 0, io.EOF + } + return p.z[p.pos], nil +} + +// writeFrom writes the characters from ppos to pos to the buffer. +func (p *parametizer) writeFrom(ppos int) { + if p.pos > ppos { + // append remaining characters. + p.buf.Write(p.z[ppos:p.pos]) + } +} + +func (p *parametizer) scanTextFn() stateFn { + ppos := p.pos + for { + ch, err := p.peek() + if err != nil { + p.writeFrom(ppos) + return nil + } + + if ch == '%' { + p.writeFrom(ppos) + p.pos++ + return p.scanCodeFn + } + + p.pos++ + } +} + +func (p *parametizer) scanCodeFn() stateFn { + ch, err := p.peek() + if err != nil { + return nil + } + + switch ch { + case '%': + p.buf.WriteByte('%') + + case ':': + // this character is used to avoid interpreting "%-" and "%+" as operators. + // the next character is where the format really begins. + p.pos++ + _, err = p.peek() + if err != nil { + return nil + } + return p.scanFormatFn + + case '#', ' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.': + return p.scanFormatFn + + case 'o': + p.buf.WriteString(strconv.FormatInt(int64(p.s.popInt()), 8)) + + case 'd': + p.buf.WriteString(strconv.Itoa(p.s.popInt())) + + case 'x': + p.buf.WriteString(strconv.FormatInt(int64(p.s.popInt()), 16)) + + case 'X': + p.buf.WriteString(strings.ToUpper(strconv.FormatInt(int64(p.s.popInt()), 16))) + + case 's': + p.buf.WriteString(p.s.popString()) + + case 'c': + p.buf.WriteByte(p.s.popByte()) + + case 'p': + p.pos++ + return p.pushParamFn + + case 'P': + p.pos++ + return p.setDsVarFn + + case 'g': + p.pos++ + return p.getDsVarFn + + case '\'': + p.pos++ + ch, err = p.peek() + if err != nil { + return nil + } + + p.s.push(ch) + + // skip the '\'' + p.pos++ + + case '{': + p.pos++ + return p.pushIntfn + + case 'l': + p.s.push(len(p.s.popString())) + + case '+': + bi, ai := p.s.popInt(), p.s.popInt() + p.s.push(ai + bi) + + case '-': + bi, ai := p.s.popInt(), p.s.popInt() + p.s.push(ai - bi) + + case '*': + bi, ai := p.s.popInt(), p.s.popInt() + p.s.push(ai * bi) + + case '/': + bi, ai := p.s.popInt(), p.s.popInt() + if bi != 0 { + p.s.push(ai / bi) + } else { + p.s.push(0) + } + + case 'm': + bi, ai := p.s.popInt(), p.s.popInt() + if bi != 0 { + p.s.push(ai % bi) + } else { + p.s.push(0) + } + + case '&': + bi, ai := p.s.popInt(), p.s.popInt() + p.s.push(ai & bi) + + case '|': + bi, ai := p.s.popInt(), p.s.popInt() + p.s.push(ai | bi) + + case '^': + bi, ai := p.s.popInt(), p.s.popInt() + p.s.push(ai ^ bi) + + case '=': + bi, ai := p.s.popInt(), p.s.popInt() + p.s.push(ai == bi) + + case '>': + bi, ai := p.s.popInt(), p.s.popInt() + p.s.push(ai > bi) + + case '<': + bi, ai := p.s.popInt(), p.s.popInt() + p.s.push(ai < bi) + + case 'A': + bi, ai := p.s.popBool(), p.s.popBool() + p.s.push(ai && bi) + + case 'O': + bi, ai := p.s.popBool(), p.s.popBool() + p.s.push(ai || bi) + + case '!': + p.s.push(!p.s.popBool()) + + case '~': + p.s.push(^p.s.popInt()) + + case 'i': + for i := range p.params[:2] { + if n, ok := p.params[i].(int); ok { + p.params[i] = n + 1 + } + } + + case '?', ';': + + case 't': + return p.scanThenFn + + case 'e': + p.skipElse = true + return p.skipTextFn + } + + p.pos++ + + return p.scanTextFn +} + +func (p *parametizer) scanFormatFn() stateFn { + // the character was already read, so no need to check the error. + ch, _ := p.peek() + + // 6 should be the maximum length of a format string, for example "%:-9.9d". + f := []byte{'%', ch, 0, 0, 0, 0} + + var err error + + for { + p.pos++ + ch, err = p.peek() + if err != nil { + return nil + } + + f = append(f, ch) + switch ch { + case 'o', 'd', 'x', 'X': + fmt.Fprintf(p.buf, string(f), p.s.popInt()) + break + + case 's': + fmt.Fprintf(p.buf, string(f), p.s.popString()) + break + + case 'c': + fmt.Fprintf(p.buf, string(f), p.s.popByte()) + break + } + } + + p.pos++ + + return p.scanTextFn +} + +func (p *parametizer) pushParamFn() stateFn { + ch, err := p.peek() + if err != nil { + return nil + } + + if ai := int(ch - '1'); ai >= 0 && ai < len(p.params) { + p.s.push(p.params[ai]) + } else { + p.s.push(0) + } + + // skip the '}' + p.pos++ + + return p.scanTextFn +} + +func (p *parametizer) setDsVarFn() stateFn { + ch, err := p.peek() + if err != nil { + return nil + } + + if ch >= 'A' && ch <= 'Z' { + staticVars.Lock() + staticVars.vars[int(ch-'A')] = p.s.pop() + staticVars.Unlock() + } else if ch >= 'a' && ch <= 'z' { + p.vars[int(ch-'a')] = p.s.pop() + } + + p.pos++ + return p.scanTextFn +} + +func (p *parametizer) getDsVarFn() stateFn { + ch, err := p.peek() + if err != nil { + return nil + } + + var a byte + if ch >= 'A' && ch <= 'Z' { + a = 'A' + } else if ch >= 'a' && ch <= 'z' { + a = 'a' + } + + staticVars.Lock() + p.s.push(staticVars.vars[int(ch-a)]) + staticVars.Unlock() + + p.pos++ + + return p.scanTextFn +} + +func (p *parametizer) pushIntfn() stateFn { + var ai int + for { + ch, err := p.peek() + if err != nil { + return nil + } + + p.pos++ + if ch < '0' || ch > '9' { + p.s.push(ai) + return p.scanTextFn + } + + ai = (ai * 10) + int(ch-'0') + } +} + +func (p *parametizer) scanThenFn() stateFn { + p.pos++ + + if p.s.popBool() { + return p.scanTextFn + } + + p.skipElse = false + + return p.skipTextFn +} + +func (p *parametizer) skipTextFn() stateFn { + for { + ch, err := p.peek() + if err != nil { + return nil + } + + p.pos++ + if ch == '%' { + break + } + } + + if p.skipElse { + return p.skipElseFn + } + + return p.skipThenFn +} + +func (p *parametizer) skipThenFn() stateFn { + ch, err := p.peek() + if err != nil { + return nil + } + + p.pos++ + switch ch { + case ';': + if p.nest == 0 { + return p.scanTextFn + } + p.nest-- + + case '?': + p.nest++ + + case 'e': + if p.nest == 0 { + return p.scanTextFn + } + } + + return p.skipTextFn +} + +func (p *parametizer) skipElseFn() stateFn { + ch, err := p.peek() + if err != nil { + return nil + } + + p.pos++ + switch ch { + case ';': + if p.nest == 0 { + return p.scanTextFn + } + p.nest-- + + case '?': + p.nest++ + } + + return p.skipTextFn +} + +// Printf evaluates a parameterized terminfo value z, interpolating params. +func Printf(z []byte, params ...interface{}) string { + p := newParametizer(z) + defer p.reset() + + // make sure we always have 9 parameters -- makes it easier + // later to skip checks and its faster + for i := 0; i < len(p.params) && i < len(params); i++ { + p.params[i] = params[i] + } + + return p.exec() +} + +// Fprintf evaluates a parameterized terminfo value z, interpolating params and +// writing to w. +func Fprintf(w io.Writer, z []byte, params ...interface{}) { + w.Write([]byte(Printf(z, params...))) +} |