// Copyright 2012 Neal van Veen. All rights reserved. // Usage of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package gotty import ( "bytes" "errors" "fmt" "regexp" "strconv" "strings" ) var exp = [...]string{ "%%", "%c", "%s", "%p(\\d)", "%P([A-z])", "%g([A-z])", "%'(.)'", "%{([0-9]+)}", "%l", "%\\+|%-|%\\*|%/|%m", "%&|%\\||%\\^", "%=|%>|%<", "%A|%O", "%!|%~", "%i", "%(:[\\ #\\-\\+]{0,4})?(\\d+\\.\\d+|\\d+)?[doxXs]", "%\\?(.*?);", } var regex *regexp.Regexp var staticVar map[byte]stacker // Parses the attribute that is received with name attr and parameters params. func (term *TermInfo) Parse(attr string, params ...interface{}) (string, error) { // Get the attribute name first. iface, err := term.GetAttribute(attr) str, ok := iface.(string) if err != nil { return "", err } if !ok { return str, errors.New("Only string capabilities can be parsed.") } // Construct the hidden parser struct so we can use a recursive stack based // parser. ps := &parser{} // Dynamic variables only exist in this context. ps.dynamicVar = make(map[byte]stacker, 26) ps.parameters = make([]stacker, len(params)) // Convert the parameters to insert them into the parser struct. for i, x := range params { ps.parameters[i] = x } // Recursively walk and return. result, err := ps.walk(str) return result, err } // Parses the attribute that is received with name attr and parameters params. // Only works on full name of a capability that is given, which it uses to // search for the termcap name. func (term *TermInfo) ParseName(attr string, params ...interface{}) (string, error) { tc := GetTermcapName(attr) return term.Parse(tc, params) } // Identify each token in a stack based manner and do the actual parsing. func (ps *parser) walk(attr string) (string, error) { // We use a buffer to get the modified string. var buf bytes.Buffer // Next, find and identify all tokens by their indices and strings. tokens := regex.FindAllStringSubmatch(attr, -1) if len(tokens) == 0 { return attr, nil } indices := regex.FindAllStringIndex(attr, -1) q := 0 // q counts the matches of one token // Iterate through the string per character. for i := 0; i < len(attr); i++ { // If the current position is an identified token, execute the following // steps. if q < len(indices) && i >= indices[q][0] && i < indices[q][1] { // Switch on token. switch { case tokens[q][0][:2] == "%%": // Literal percentage character. buf.WriteByte('%') case tokens[q][0][:2] == "%c": // Pop a character. c, err := ps.st.pop() if err != nil { return buf.String(), err } buf.WriteByte(c.(byte)) case tokens[q][0][:2] == "%s": // Pop a string. str, err := ps.st.pop() if err != nil { return buf.String(), err } if _, ok := str.(string); !ok { return buf.String(), errors.New("Stack head is not a string") } buf.WriteString(str.(string)) case tokens[q][0][:2] == "%p": // Push a parameter on the stack. index, err := strconv.ParseInt(tokens[q][1], 10, 8) index-- if err != nil { return buf.String(), err } if int(index) >= len(ps.parameters) { return buf.String(), errors.New("Parameters index out of bound") } ps.st.push(ps.parameters[index]) case tokens[q][0][:2] == "%P": // Pop a variable from the stack as a dynamic or static variable. val, err := ps.st.pop() if err != nil { return buf.String(), err } index := tokens[q][2] if len(index) > 1 { errorStr := fmt.Sprintf("%s is not a valid dynamic variables index", index) return buf.String(), errors.New(errorStr) } // Specify either dynamic or static. if index[0] >= 'a' && index[0] <= 'z' { ps.dynamicVar[index[0]] = val } else if index[0] >= 'A' && index[0] <= 'Z' { staticVar[index[0]] = val } case tokens[q][0][:2] == "%g": // Push a variable from the stack as a dynamic or static variable. index := tokens[q][3] if len(index) > 1 { errorStr := fmt.Sprintf("%s is not a valid static variables index", index) return buf.String(), errors.New(errorStr) } var val stacker if index[0] >= 'a' && index[0] <= 'z' { val = ps.dynamicVar[index[0]] } else if index[0] >= 'A' && index[0] <= 'Z' { val = staticVar[index[0]] } ps.st.push(val) case tokens[q][0][:2] == "%'": // Push a character constant. con := tokens[q][4] if len(con) > 1 { errorStr := fmt.Sprintf("%s is not a valid character constant", con) return buf.String(), errors.New(errorStr) } ps.st.push(con[0]) case tokens[q][0][:2] == "%{": // Push an integer constant. con, err := strconv.ParseInt(tokens[q][5], 10, 32) if err != nil { return buf.String(), err } ps.st.push(con) case tokens[q][0][:2] == "%l": // Push the length of the string that is popped from the stack. popStr, err := ps.st.pop() if err != nil { return buf.String(), err } if _, ok := popStr.(string); !ok { errStr := fmt.Sprintf("Stack head is not a string") return buf.String(), errors.New(errStr) } ps.st.push(len(popStr.(string))) case tokens[q][0][:2] == "%?": // If-then-else construct. First, the whole string is identified and // then inside this substring, we can specify which parts to switch on. ifReg, _ := regexp.Compile("%\\?(.*)%t(.*)%e(.*);|%\\?(.*)%t(.*);") ifTokens := ifReg.FindStringSubmatch(tokens[q][0]) var ( ifStr string err error ) // Parse the if-part to determine if-else. if len(ifTokens[1]) > 0 { ifStr, err = ps.walk(ifTokens[1]) } else { // else ifStr, err = ps.walk(ifTokens[4]) } // Return any errors if err != nil { return buf.String(), err } else if len(ifStr) > 0 { // Self-defined limitation, not sure if this is correct, but didn't // seem like it. return buf.String(), errors.New("If-clause cannot print statements") } var thenStr string // Pop the first value that is set by parsing the if-clause. choose, err := ps.st.pop() if err != nil { return buf.String(), err } // Switch to if or else. if choose.(int) == 0 && len(ifTokens[1]) > 0 { thenStr, err = ps.walk(ifTokens[3]) } else if choose.(int) != 0 { if len(ifTokens[1]) > 0 { thenStr, err = ps.walk(ifTokens[2]) } else { thenStr, err = ps.walk(ifTokens[5]) } } if err != nil { return buf.String(), err } buf.WriteString(thenStr) case tokens[q][0][len(tokens[q][0])-1] == 'd': // Fallthrough for printing fallthrough case tokens[q][0][len(tokens[q][0])-1] == 'o': // digits. fallthrough case tokens[q][0][len(tokens[q][0])-1] == 'x': fallthrough case tokens[q][0][len(tokens[q][0])-1] == 'X': fallthrough case tokens[q][0][len(tokens[q][0])-1] == 's': token := tokens[q][0] // Remove the : that comes before a flag. if token[1] == ':' { token = token[:1] + token[2:] } digit, err := ps.st.pop() if err != nil { return buf.String(), err } // The rest is determined like the normal formatted prints. digitStr := fmt.Sprintf(token, digit.(int)) buf.WriteString(digitStr) case tokens[q][0][:2] == "%i": // Increment the parameters by one. if len(ps.parameters) < 2 { return buf.String(), errors.New("Not enough parameters to increment.") } val1, val2 := ps.parameters[0].(int), ps.parameters[1].(int) val1++ val2++ ps.parameters[0], ps.parameters[1] = val1, val2 default: // The rest of the tokens is a special case, where two values are // popped and then operated on by the token that comes after them. op1, err := ps.st.pop() if err != nil { return buf.String(), err } op2, err := ps.st.pop() if err != nil { return buf.String(), err } var result stacker switch tokens[q][0][:2] { case "%+": // Addition result = op2.(int) + op1.(int) case "%-": // Subtraction result = op2.(int) - op1.(int) case "%*": // Multiplication result = op2.(int) * op1.(int) case "%/": // Division result = op2.(int) / op1.(int) case "%m": // Modulo result = op2.(int) % op1.(int) case "%&": // Bitwise AND result = op2.(int) & op1.(int) case "%|": // Bitwise OR result = op2.(int) | op1.(int) case "%^": // Bitwise XOR result = op2.(int) ^ op1.(int) case "%=": // Equals result = op2 == op1 case "%>": // Greater-than result = op2.(int) > op1.(int) case "%<": // Lesser-than result = op2.(int) < op1.(int) case "%A": // Logical AND result = op2.(bool) && op1.(bool) case "%O": // Logical OR result = op2.(bool) || op1.(bool) case "%!": // Logical complement result = !op1.(bool) case "%~": // Bitwise complement result = ^(op1.(int)) } ps.st.push(result) } i = indices[q][1] - 1 q++ } else { // We are not "inside" a token, so just skip until the end or the next // token, and add all characters to the buffer. j := i if q != len(indices) { for !(j >= indices[q][0] && j < indices[q][1]) { j++ } } else { j = len(attr) } buf.WriteString(string(attr[i:j])) i = j } } // Return the buffer as a string. return buf.String(), nil } // Push a stacker-value onto the stack. func (st *stack) push(s stacker) { *st = append(*st, s) } // Pop a stacker-value from the stack. func (st *stack) pop() (stacker, error) { if len(*st) == 0 { return nil, errors.New("Stack is empty.") } newStack := make(stack, len(*st)-1) val := (*st)[len(*st)-1] copy(newStack, (*st)[:len(*st)-1]) *st = newStack return val, nil } // Initialize regexes and the static vars (that don't get changed between // calls. func init() { // Initialize the main regex. expStr := strings.Join(exp[:], "|") regex, _ = regexp.Compile(expStr) // Initialize the static variables. staticVar = make(map[byte]stacker, 26) }