about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2013-06-15 03:07:05 -0700
committerbors <bors@rust-lang.org>2013-06-15 03:07:05 -0700
commitda42e6b7a0fcadcca819d221738894dcb6c4b76d (patch)
tree748b47c869b046a4918f734b4bced05d706ae950
parent83d44f87e5fe8935c1f8a5f26409a99286675650 (diff)
parentda4e614742ea67677ed122985c1730590748d788 (diff)
downloadrust-da42e6b7a0fcadcca819d221738894dcb6c4b76d.tar.gz
rust-da42e6b7a0fcadcca819d221738894dcb6c4b76d.zip
auto merge of #7133 : kballard/rust/terminfo-parm, r=thestinger
Implement conditional support in terminfo, along with a few other related operators.

Fix implementation of non-commutative arithmetic operators.

Remove all known cases of task failure from `terminfo::parm::expand`, and change the method signature.

Fix some other miscellaneous issues.
-rw-r--r--src/libextra/term.rs9
-rw-r--r--src/libextra/terminfo/parm.rs409
2 files changed, 321 insertions, 97 deletions
diff --git a/src/libextra/term.rs b/src/libextra/term.rs
index 455cc0b7450..17d80ded47f 100644
--- a/src/libextra/term.rs
+++ b/src/libextra/term.rs
@@ -20,7 +20,7 @@ use core::os;
 use terminfo::*;
 use terminfo::searcher::open;
 use terminfo::parser::compiled::parse;
-use terminfo::parm::{expand, Number};
+use terminfo::parm::{expand, Number, Variables};
 
 // FIXME (#2807): Windows support.
 
@@ -84,7 +84,7 @@ impl Terminal {
     pub fn fg(&self, color: u8) {
         if self.color_supported {
             let s = expand(*self.ti.strings.find_equiv(&("setaf")).unwrap(),
-                           [Number(color as int)], [], []);
+                           [Number(color as int)], &mut Variables::new());
             if s.is_ok() {
                 self.out.write(s.get());
             } else {
@@ -95,7 +95,7 @@ impl Terminal {
     pub fn bg(&self, color: u8) {
         if self.color_supported {
             let s = expand(*self.ti.strings.find_equiv(&("setab")).unwrap(),
-                           [Number(color as int)], [], []);
+                           [Number(color as int)], &mut Variables::new());
             if s.is_ok() {
                 self.out.write(s.get());
             } else {
@@ -105,7 +105,8 @@ impl Terminal {
     }
     pub fn reset(&self) {
         if self.color_supported {
-            let s = expand(*self.ti.strings.find_equiv(&("op")).unwrap(), [], [], []);
+            let mut vars = Variables::new();
+            let s = expand(*self.ti.strings.find_equiv(&("op")).unwrap(), [], &mut vars);
             if s.is_ok() {
                 self.out.write(s.get());
             } else {
diff --git a/src/libextra/terminfo/parm.rs b/src/libextra/terminfo/parm.rs
index 40191c89925..c395b57219c 100644
--- a/src/libextra/terminfo/parm.rs
+++ b/src/libextra/terminfo/parm.rs
@@ -12,6 +12,7 @@
 
 use core::prelude::*;
 use core::{char, int, vec};
+use core::iterator::IteratorUtil;
 
 #[deriving(Eq)]
 enum States {
@@ -23,144 +24,246 @@ enum States {
     CharConstant,
     CharClose,
     IntConstant,
-    IfCond,
-    IfBody
+    SeekIfElse(int),
+    SeekIfElsePercent(int),
+    SeekIfEnd(int),
+    SeekIfEndPercent(int)
 }
 
 /// Types of parameters a capability can use
 pub enum Param {
     String(~str),
-    Char(char),
     Number(int)
 }
 
+/// Container for static and dynamic variable arrays
+pub struct Variables {
+    /// Static variables A-Z
+    sta: [Param, ..26],
+    /// Dynamic variables a-z
+    dyn: [Param, ..26]
+}
+
+impl Variables {
+    /// Return a new zero-initialized Variables
+    pub fn new() -> Variables {
+        Variables{ sta: [Number(0), ..26], dyn: [Number(0), ..26] }
+    }
+}
+
 /**
   Expand a parameterized capability
 
   # Arguments
   * `cap`    - string to expand
   * `params` - vector of params for %p1 etc
-  * `sta`    - vector of params corresponding to static variables
-  * `dyn`    - vector of params corresponding to stativ variables
+  * `vars`   - Variables struct for %Pa etc
 
-  To be compatible with ncurses, `sta` and `dyn` should be the same between calls to `expand` for
+  To be compatible with ncurses, `vars` should be the same between calls to `expand` for
   multiple capabilities for the same terminal.
   */
-pub fn expand(cap: &[u8], params: &mut [Param], sta: &mut [Param], dyn: &mut [Param])
+pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
     -> Result<~[u8], ~str> {
-    assert!(cap.len() != 0, "expanding an empty capability makes no sense");
-    assert!(params.len() <= 9, "only 9 parameters are supported by capability strings");
-
-    assert!(sta.len() <= 26, "only 26 static vars are able to be used by capability strings");
-    assert!(dyn.len() <= 26, "only 26 dynamic vars are able to be used by capability strings");
-
     let mut state = Nothing;
-    let mut i = 0;
 
-    // expanded cap will only rarely be smaller than the cap itself
+    // expanded cap will only rarely be larger than the cap itself
     let mut output = vec::with_capacity(cap.len());
 
-    let mut cur;
-
     let mut stack: ~[Param] = ~[];
 
     let mut intstate = ~[];
 
-    while i < cap.len() {
-        cur = cap[i] as char;
+    // Copy parameters into a local vector for mutability
+    let mut mparams = [Number(0), ..9];
+    for mparams.mut_iter().zip(params.iter()).advance |(dst, &src)| {
+        *dst = src;
+    }
+
+    for cap.iter().transform(|&x| x).advance |c| {
+        let cur = c as char;
         let mut old_state = state;
         match state {
             Nothing => {
                 if cur == '%' {
                     state = Percent;
                 } else {
-                    output.push(cap[i]);
+                    output.push(c);
                 }
             },
             Percent => {
                 match cur {
-                    '%' => { output.push(cap[i]); state = Nothing },
-                    'c' => match stack.pop() {
-                        Char(c) => output.push(c as u8),
-                        _       => return Err(~"a non-char was used with %c")
-                    },
-                    's' => match stack.pop() {
-                        String(s) => output.push_all(s.as_bytes()),
-                        _         => return Err(~"a non-str was used with %s")
-                    },
-                    'd' => match stack.pop() {
-                        Number(x) => {
-                            let s = x.to_str();
-                            output.push_all(s.as_bytes())
+                    '%' => { output.push(c); state = Nothing },
+                    'c' => if stack.len() > 0 {
+                        match stack.pop() {
+                            // if c is 0, use 0200 (128) for ncurses compatibility
+                            Number(c) => output.push(if c == 0 { 128 } else { c } as u8),
+                            _       => return Err(~"a non-char was used with %c")
                         }
-                        _         => return Err(~"a non-number was used with %d")
-                    },
+                    } else { return Err(~"stack is empty") },
+                    's' => if stack.len() > 0 {
+                        match stack.pop() {
+                            String(s) => output.push_all(s.as_bytes()),
+                            _         => return Err(~"a non-str was used with %s")
+                        }
+                    } else { return Err(~"stack is empty") },
+                    'd' => if stack.len() > 0 {
+                        match stack.pop() {
+                            Number(x) => {
+                                let s = x.to_str();
+                                output.push_all(s.as_bytes())
+                            }
+                            _         => return Err(~"a non-number was used with %d")
+                        }
+                    } else { return Err(~"stack is empty") },
                     'p' => state = PushParam,
                     'P' => state = SetVar,
                     'g' => state = GetVar,
                     '\'' => state = CharConstant,
                     '{' => state = IntConstant,
-                    'l' => match stack.pop() {
-                        String(s) => stack.push(Number(s.len() as int)),
-                        _         => return Err(~"a non-str was used with %l")
-                    },
-                    '+' => match (stack.pop(), stack.pop()) {
-                        (Number(x), Number(y)) => stack.push(Number(x + y)),
-                        (_, _) => return Err(~"non-numbers on stack with +")
-                    },
-                    '-' => match (stack.pop(), stack.pop()) {
-                        (Number(x), Number(y)) => stack.push(Number(x - y)),
-                        (_, _) => return Err(~"non-numbers on stack with -")
-                    },
-                    '*' => match (stack.pop(), stack.pop()) {
-                        (Number(x), Number(y)) => stack.push(Number(x * y)),
-                        (_, _) => return Err(~"non-numbers on stack with *")
-                    },
-                    '/' => match (stack.pop(), stack.pop()) {
-                        (Number(x), Number(y)) => stack.push(Number(x / y)),
-                        (_, _) => return Err(~"non-numbers on stack with /")
-                    },
-                    'm' => match (stack.pop(), stack.pop()) {
-                        (Number(x), Number(y)) => stack.push(Number(x % y)),
-                        (_, _) => return Err(~"non-numbers on stack with %")
-                    },
-                    '&' => match (stack.pop(), stack.pop()) {
-                        (Number(x), Number(y)) => stack.push(Number(x & y)),
-                        (_, _) => return Err(~"non-numbers on stack with &")
-                    },
-                    '|' => match (stack.pop(), stack.pop()) {
-                        (Number(x), Number(y)) => stack.push(Number(x | y)),
-                        (_, _) => return Err(~"non-numbers on stack with |")
-                    },
-                    'A' => return Err(~"logical operations unimplemented"),
-                    'O' => return Err(~"logical operations unimplemented"),
-                    '!' => return Err(~"logical operations unimplemented"),
-                    '~' => match stack.pop() {
-                        Number(x) => stack.push(Number(!x)),
-                        _         => return Err(~"non-number on stack with %~")
-                    },
-                    'i' => match (copy params[0], copy params[1]) {
-                        (Number(x), Number(y)) => {
-                            params[0] = Number(x + 1);
-                            params[1] = Number(y + 1);
+                    'l' => if stack.len() > 0 {
+                        match stack.pop() {
+                            String(s) => stack.push(Number(s.len() as int)),
+                            _         => return Err(~"a non-str was used with %l")
+                        }
+                    } else { return Err(~"stack is empty") },
+                    '+' => if stack.len() > 1 {
+                        match (stack.pop(), stack.pop()) {
+                            (Number(y), Number(x)) => stack.push(Number(x + y)),
+                            _ => return Err(~"non-numbers on stack with +")
+                        }
+                    } else { return Err(~"stack is empty") },
+                    '-' => if stack.len() > 1 {
+                        match (stack.pop(), stack.pop()) {
+                            (Number(y), Number(x)) => stack.push(Number(x - y)),
+                            _ => return Err(~"non-numbers on stack with -")
+                        }
+                    } else { return Err(~"stack is empty") },
+                    '*' => if stack.len() > 1 {
+                        match (stack.pop(), stack.pop()) {
+                            (Number(y), Number(x)) => stack.push(Number(x * y)),
+                            _ => return Err(~"non-numbers on stack with *")
+                        }
+                    } else { return Err(~"stack is empty") },
+                    '/' => if stack.len() > 1 {
+                        match (stack.pop(), stack.pop()) {
+                            (Number(y), Number(x)) => stack.push(Number(x / y)),
+                            _ => return Err(~"non-numbers on stack with /")
+                        }
+                    } else { return Err(~"stack is empty") },
+                    'm' => if stack.len() > 1 {
+                        match (stack.pop(), stack.pop()) {
+                            (Number(y), Number(x)) => stack.push(Number(x % y)),
+                            _ => return Err(~"non-numbers on stack with %")
+                        }
+                    } else { return Err(~"stack is empty") },
+                    '&' => if stack.len() > 1 {
+                        match (stack.pop(), stack.pop()) {
+                            (Number(y), Number(x)) => stack.push(Number(x & y)),
+                            _ => return Err(~"non-numbers on stack with &")
+                        }
+                    } else { return Err(~"stack is empty") },
+                    '|' => if stack.len() > 1 {
+                        match (stack.pop(), stack.pop()) {
+                            (Number(y), Number(x)) => stack.push(Number(x | y)),
+                            _ => return Err(~"non-numbers on stack with |")
+                        }
+                    } else { return Err(~"stack is empty") },
+                    '^' => if stack.len() > 1 {
+                        match (stack.pop(), stack.pop()) {
+                            (Number(y), Number(x)) => stack.push(Number(x ^ y)),
+                            _ => return Err(~"non-numbers on stack with ^")
+                        }
+                    } else { return Err(~"stack is empty") },
+                    '=' => if stack.len() > 1 {
+                        match (stack.pop(), stack.pop()) {
+                            (Number(y), Number(x)) => stack.push(Number(if x == y { 1 }
+                                                                        else { 0 })),
+                            _ => return Err(~"non-numbers on stack with =")
+                        }
+                    } else { return Err(~"stack is empty") },
+                    '>' => if stack.len() > 1 {
+                        match (stack.pop(), stack.pop()) {
+                            (Number(y), Number(x)) => stack.push(Number(if x > y { 1 }
+                                                                        else { 0 })),
+                            _ => return Err(~"non-numbers on stack with >")
+                        }
+                    } else { return Err(~"stack is empty") },
+                    '<' => if stack.len() > 1 {
+                        match (stack.pop(), stack.pop()) {
+                            (Number(y), Number(x)) => stack.push(Number(if x < y { 1 }
+                                                                        else { 0 })),
+                            _ => return Err(~"non-numbers on stack with <")
+                        }
+                    } else { return Err(~"stack is empty") },
+                    'A' => if stack.len() > 1 {
+                        match (stack.pop(), stack.pop()) {
+                            (Number(0), Number(_)) => stack.push(Number(0)),
+                            (Number(_), Number(0)) => stack.push(Number(0)),
+                            (Number(_), Number(_)) => stack.push(Number(1)),
+                            _ => return Err(~"non-numbers on stack with logical and")
+                        }
+                    } else { return Err(~"stack is empty") },
+                    'O' => if stack.len() > 1 {
+                        match (stack.pop(), stack.pop()) {
+                            (Number(0), Number(0)) => stack.push(Number(0)),
+                            (Number(_), Number(_)) => stack.push(Number(1)),
+                            _ => return Err(~"non-numbers on stack with logical or")
+                        }
+                    } else { return Err(~"stack is empty") },
+                    '!' => if stack.len() > 0 {
+                        match stack.pop() {
+                            Number(0) => stack.push(Number(1)),
+                            Number(_) => stack.push(Number(0)),
+                            _ => return Err(~"non-number on stack with logical not")
+                        }
+                    } else { return Err(~"stack is empty") },
+                    '~' => if stack.len() > 0 {
+                        match stack.pop() {
+                            Number(x) => stack.push(Number(!x)),
+                            _         => return Err(~"non-number on stack with %~")
+                        }
+                    } else { return Err(~"stack is empty") },
+                    'i' => match (copy mparams[0], copy mparams[1]) {
+                        (Number(ref mut x), Number(ref mut y)) => {
+                            *x += 1;
+                            *y += 1;
                         },
                         (_, _) => return Err(~"first two params not numbers with %i")
                     },
-                    '?' => state = return Err(fmt!("if expressions unimplemented (%?)", cap)),
+
+                    // conditionals
+                    '?' => (),
+                    't' => if stack.len() > 0 {
+                        match stack.pop() {
+                            Number(0) => state = SeekIfElse(0),
+                            Number(_) => (),
+                            _         => return Err(~"non-number on stack with conditional")
+                        }
+                    } else { return Err(~"stack is empty") },
+                    'e' => state = SeekIfEnd(0),
+                    ';' => (),
+
                     _ => return Err(fmt!("unrecognized format option %c", cur))
                 }
             },
             PushParam => {
                 // params are 1-indexed
-                stack.push(copy params[char::to_digit(cur, 10).expect("bad param number") - 1]);
+                stack.push(copy mparams[match char::to_digit(cur, 10) {
+                    Some(d) => d - 1,
+                    None => return Err(~"bad param number")
+                }]);
             },
             SetVar => {
                 if cur >= 'A' && cur <= 'Z' {
-                    let idx = (cur as u8) - ('A' as u8);
-                    sta[idx] = stack.pop();
+                    if stack.len() > 0 {
+                        let idx = (cur as u8) - ('A' as u8);
+                        vars.sta[idx] = stack.pop();
+                    } else { return Err(~"stack is empty") }
                 } else if cur >= 'a' && cur <= 'z' {
-                    let idx = (cur as u8) - ('a' as u8);
-                    dyn[idx] = stack.pop();
+                    if stack.len() > 0 {
+                        let idx = (cur as u8) - ('a' as u8);
+                        vars.dyn[idx] = stack.pop();
+                    } else { return Err(~"stack is empty") }
                 } else {
                     return Err(~"bad variable name in %P");
                 }
@@ -168,35 +271,80 @@ pub fn expand(cap: &[u8], params: &mut [Param], sta: &mut [Param], dyn: &mut [Pa
             GetVar => {
                 if cur >= 'A' && cur <= 'Z' {
                     let idx = (cur as u8) - ('A' as u8);
-                    stack.push(copy sta[idx]);
+                    stack.push(copy vars.sta[idx]);
                 } else if cur >= 'a' && cur <= 'z' {
                     let idx = (cur as u8) - ('a' as u8);
-                    stack.push(copy dyn[idx]);
+                    stack.push(copy vars.dyn[idx]);
                 } else {
                     return Err(~"bad variable name in %g");
                 }
             },
             CharConstant => {
-                stack.push(Char(cur));
+                stack.push(Number(c as int));
                 state = CharClose;
             },
             CharClose => {
-                assert!(cur == '\'', "malformed character constant");
+                if cur != '\'' {
+                    return Err(~"malformed character constant");
+                }
             },
             IntConstant => {
                 if cur == '}' {
-                    stack.push(Number(int::parse_bytes(intstate, 10).expect("bad int constant")));
+                    stack.push(match int::parse_bytes(intstate, 10) {
+                        Some(n) => Number(n),
+                        None => return Err(~"bad int constant")
+                    });
+                    intstate.clear();
+                    state = Nothing;
+                } else {
+                    intstate.push(cur as u8);
+                    old_state = Nothing;
+                }
+            }
+            SeekIfElse(level) => {
+                if cur == '%' {
+                    state = SeekIfElsePercent(level);
+                }
+                old_state = Nothing;
+            }
+            SeekIfElsePercent(level) => {
+                if cur == ';' {
+                    if level == 0 {
+                        state = Nothing;
+                    } else {
+                        state = SeekIfElse(level-1);
+                    }
+                } else if cur == 'e' && level == 0 {
                     state = Nothing;
+                } else if cur == '?' {
+                    state = SeekIfElse(level+1);
+                } else {
+                    state = SeekIfElse(level);
+                }
+            }
+            SeekIfEnd(level) => {
+                if cur == '%' {
+                    state = SeekIfEndPercent(level);
                 }
-                intstate.push(cur as u8);
                 old_state = Nothing;
             }
-            _ => return Err(~"unimplemented state")
+            SeekIfEndPercent(level) => {
+                if cur == ';' {
+                    if level == 0 {
+                        state = Nothing;
+                    } else {
+                        state = SeekIfEnd(level-1);
+                    }
+                } else if cur == '?' {
+                    state = SeekIfEnd(level+1);
+                } else {
+                    state = SeekIfEnd(level);
+                }
+            }
         }
         if state == old_state {
             state = Nothing;
         }
-        i += 1;
     }
     Ok(output)
 }
@@ -204,9 +352,84 @@ pub fn expand(cap: &[u8], params: &mut [Param], sta: &mut [Param], dyn: &mut [Pa
 #[cfg(test)]
 mod test {
     use super::*;
+
     #[test]
     fn test_basic_setabf() {
         let s = bytes!("\\E[48;5;%p1%dm");
-        assert_eq!(expand(s, [Number(1)], [], []).unwrap(), bytes!("\\E[48;5;1m").to_owned());
+        assert_eq!(expand(s, [Number(1)], &mut Variables::new()).unwrap(),
+                   bytes!("\\E[48;5;1m").to_owned());
+    }
+
+    #[test]
+    fn test_multiple_int_constants() {
+        assert_eq!(expand(bytes!("%{1}%{2}%d%d"), [], &mut Variables::new()).unwrap(),
+                   bytes!("21").to_owned());
+    }
+
+    #[test]
+    fn test_param_stack_failure_conditions() {
+        let mut varstruct = Variables::new();
+        let vars = &mut varstruct;
+        let caps = ["%d", "%c", "%s", "%Pa", "%l", "%!", "%~"];
+        for caps.iter().advance |cap| {
+            let res = expand(cap.as_bytes(), [], vars);
+            assert!(res.is_err(),
+                    "Op %s succeeded incorrectly with 0 stack entries", *cap);
+            let p = if *cap == "%s" || *cap == "%l" { String(~"foo") } else { Number(97) };
+            let res = expand((bytes!("%p1")).to_owned() + cap.as_bytes(), [p], vars);
+            assert!(res.is_ok(),
+                    "Op %s failed with 1 stack entry: %s", *cap, res.unwrap_err());
+        }
+        let caps = ["%+", "%-", "%*", "%/", "%m", "%&", "%|", "%A", "%O"];
+        for caps.iter().advance |cap| {
+            let res = expand(cap.as_bytes(), [], vars);
+            assert!(res.is_err(),
+                    "Binop %s succeeded incorrectly with 0 stack entries", *cap);
+            let res = expand((bytes!("%{1}")).to_owned() + cap.as_bytes(), [], vars);
+            assert!(res.is_err(),
+                    "Binop %s succeeded incorrectly with 1 stack entry", *cap);
+            let res = expand((bytes!("%{1}%{2}")).to_owned() + cap.as_bytes(), [], vars);
+            assert!(res.is_ok(),
+                    "Binop %s failed with 2 stack entries: %s", *cap, res.unwrap_err());
+        }
+    }
+
+    #[test]
+    fn test_push_bad_param() {
+        assert!(expand(bytes!("%pa"), [], &mut Variables::new()).is_err());
+    }
+
+    #[test]
+    fn test_comparison_ops() {
+        let v = [('<', [1u8, 0u8, 0u8]), ('=', [0u8, 1u8, 0u8]), ('>', [0u8, 0u8, 1u8])];
+        for v.iter().advance |&(op, bs)| {
+            let s = fmt!("%%{1}%%{2}%%%c%%d", op);
+            let res = expand(s.as_bytes(), [], &mut Variables::new());
+            assert!(res.is_ok(), res.unwrap_err());
+            assert_eq!(res.unwrap(), ~['0' as u8 + bs[0]]);
+            let s = fmt!("%%{1}%%{1}%%%c%%d", op);
+            let res = expand(s.as_bytes(), [], &mut Variables::new());
+            assert!(res.is_ok(), res.unwrap_err());
+            assert_eq!(res.unwrap(), ~['0' as u8 + bs[1]]);
+            let s = fmt!("%%{2}%%{1}%%%c%%d", op);
+            let res = expand(s.as_bytes(), [], &mut Variables::new());
+            assert!(res.is_ok(), res.unwrap_err());
+            assert_eq!(res.unwrap(), ~['0' as u8 + bs[2]]);
+        }
+    }
+
+    #[test]
+    fn test_conditionals() {
+        let mut vars = Variables::new();
+        let s = bytes!("\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m");
+        let res = expand(s, [Number(1)], &mut vars);
+        assert!(res.is_ok(), res.unwrap_err());
+        assert_eq!(res.unwrap(), bytes!("\\E[31m").to_owned());
+        let res = expand(s, [Number(8)], &mut vars);
+        assert!(res.is_ok(), res.unwrap_err());
+        assert_eq!(res.unwrap(), bytes!("\\E[90m").to_owned());
+        let res = expand(s, [Number(42)], &mut vars);
+        assert!(res.is_ok(), res.unwrap_err());
+        assert_eq!(res.unwrap(), bytes!("\\E[38;5;42m").to_owned());
     }
 }