From 821a962febc3261e97e31174dcb8cf142678bb4b Mon Sep 17 00:00:00 2001 From: Corey Richardson Date: Thu, 13 Jun 2013 21:42:49 -0400 Subject: [PATCH 1/7] Various terminfo parameterization changes --- src/libextra/terminfo/parm.rs | 38 +++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/libextra/terminfo/parm.rs b/src/libextra/terminfo/parm.rs index 40191c899257..a92ab5ef5085 100644 --- a/src/libextra/terminfo/parm.rs +++ b/src/libextra/terminfo/parm.rs @@ -68,6 +68,7 @@ pub fn expand(cap: &[u8], params: &mut [Param], sta: &mut [Param], dyn: &mut [Pa while i < cap.len() { cur = cap[i] as char; + debug!("current char: %c", cur); let mut old_state = state; match state { Nothing => { @@ -132,9 +133,36 @@ pub fn expand(cap: &[u8], params: &mut [Param], sta: &mut [Param], dyn: &mut [Pa (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"), + 'A' => match (stack.pop(), stack.pop()) { + (Number(x), Number(y)) => { + if x == 1 && y == 1 { + stack.push(Number(1)); + } else { + stack.push(Number(0)); + } + }, + (_, _) => return Err(~"non-numbers on stack with logical and") + }, + 'O' => match (stack.pop(), stack.pop()) { + (Number(x), Number(y)) => { + if x == 1 && y == 1 { + stack.push(Number(1)); + } else { + stack.push(Number(0)); + } + }, + (_, _) => return Err(~"non-numbers on stack with logical or") + }, + '!' => match stack.pop() { + Number(x) => { + if x == 1 { + stack.push(Number(0)) + } else { + stack.push(Number(1)) + } + }, + _ => return Err(~"non-number on stack with logical not") + }, '~' => match stack.pop() { Number(x) => stack.push(Number(!x)), _ => return Err(~"non-number on stack with %~") @@ -181,7 +209,9 @@ pub fn expand(cap: &[u8], params: &mut [Param], sta: &mut [Param], dyn: &mut [Pa state = CharClose; }, CharClose => { - assert!(cur == '\'', "malformed character constant"); + if cur != '\'' { + return Err(~"malformed character constant"); + } }, IntConstant => { if cur == '}' { From 9f9e50540581881a3c3a7304eaa2577afda59048 Mon Sep 17 00:00:00 2001 From: Kevin Ballard Date: Thu, 13 Jun 2013 19:36:45 -0700 Subject: [PATCH 2/7] Tweak new terminfo logical operator support --- src/libextra/terminfo/parm.rs | 39 +++++++++++------------------------ 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/src/libextra/terminfo/parm.rs b/src/libextra/terminfo/parm.rs index a92ab5ef5085..4c6aea9ea431 100644 --- a/src/libextra/terminfo/parm.rs +++ b/src/libextra/terminfo/parm.rs @@ -68,7 +68,6 @@ pub fn expand(cap: &[u8], params: &mut [Param], sta: &mut [Param], dyn: &mut [Pa while i < cap.len() { cur = cap[i] as char; - debug!("current char: %c", cur); let mut old_state = state; match state { Nothing => { @@ -134,33 +133,19 @@ pub fn expand(cap: &[u8], params: &mut [Param], sta: &mut [Param], dyn: &mut [Pa (_, _) => return Err(~"non-numbers on stack with |") }, 'A' => match (stack.pop(), stack.pop()) { - (Number(x), Number(y)) => { - if x == 1 && y == 1 { - stack.push(Number(1)); - } else { - stack.push(Number(0)); - } - }, - (_, _) => return Err(~"non-numbers on stack with logical and") + (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") }, 'O' => match (stack.pop(), stack.pop()) { - (Number(x), Number(y)) => { - if x == 1 && y == 1 { - stack.push(Number(1)); - } else { - stack.push(Number(0)); - } - }, - (_, _) => return Err(~"non-numbers on stack with logical or") + (Number(0), Number(0)) => stack.push(Number(0)), + (Number(_), Number(_)) => stack.push(Number(1)), + _ => return Err(~"non-numbers on stack with logical or") }, '!' => match stack.pop() { - Number(x) => { - if x == 1 { - stack.push(Number(0)) - } else { - stack.push(Number(1)) - } - }, + Number(0) => stack.push(Number(1)), + Number(_) => stack.push(Number(0)), _ => return Err(~"non-number on stack with logical not") }, '~' => match stack.pop() { @@ -168,9 +153,9 @@ pub fn expand(cap: &[u8], params: &mut [Param], sta: &mut [Param], dyn: &mut [Pa _ => 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); + (Number(ref mut x), Number(ref mut y)) => { + *x += 1; + *y += 1; }, (_, _) => return Err(~"first two params not numbers with %i") }, From c9e234a1ae7b631ca058f2331a3986630b5d1b64 Mon Sep 17 00:00:00 2001 From: Kevin Ballard Date: Thu, 13 Jun 2013 18:16:25 -0700 Subject: [PATCH 3/7] Tweak terminfo::parm::expand function signature Take a new struct Variables instead of two &mut [] vectors for static and dynamic variables. --- src/libextra/term.rs | 8 ++++---- src/libextra/terminfo/parm.rs | 35 +++++++++++++++++++++++------------ 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/libextra/term.rs b/src/libextra/term.rs index 455cc0b74507..f09c00ccce2e 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,7 @@ impl Terminal { } pub fn reset(&self) { if self.color_supported { - let s = expand(*self.ti.strings.find_equiv(&("op")).unwrap(), [], [], []); + let s = expand(*self.ti.strings.find_equiv(&("op")).unwrap(), [], &mut Variables::new()); 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 4c6aea9ea431..c6bb4e60628b 100644 --- a/src/libextra/terminfo/parm.rs +++ b/src/libextra/terminfo/parm.rs @@ -34,26 +34,37 @@ pub enum Param { 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: &mut [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; @@ -170,10 +181,10 @@ pub fn expand(cap: &[u8], params: &mut [Param], sta: &mut [Param], dyn: &mut [Pa SetVar => { if cur >= 'A' && cur <= 'Z' { let idx = (cur as u8) - ('A' as u8); - sta[idx] = stack.pop(); + vars.sta[idx] = stack.pop(); } else if cur >= 'a' && cur <= 'z' { let idx = (cur as u8) - ('a' as u8); - dyn[idx] = stack.pop(); + vars.dyn[idx] = stack.pop(); } else { return Err(~"bad variable name in %P"); } @@ -181,10 +192,10 @@ 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"); } @@ -222,6 +233,6 @@ mod test { #[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()); } } From e990239a3ae9bafc22e1fa363110ec125dc0be09 Mon Sep 17 00:00:00 2001 From: Kevin Ballard Date: Thu, 13 Jun 2013 19:37:20 -0700 Subject: [PATCH 4/7] Don't require &mut [Param] with terminfo::parm::expand() --- src/libextra/terminfo/parm.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/libextra/terminfo/parm.rs b/src/libextra/terminfo/parm.rs index c6bb4e60628b..61a31bdd12a2 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 { @@ -60,15 +61,12 @@ impl Variables { 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], vars: &mut Variables) +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"); - 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; @@ -77,6 +75,12 @@ pub fn expand(cap: &[u8], params: &mut [Param], vars: &mut Variables) let mut intstate = ~[]; + // 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; + } + while i < cap.len() { cur = cap[i] as char; let mut old_state = state; @@ -163,7 +167,7 @@ pub fn expand(cap: &[u8], params: &mut [Param], vars: &mut Variables) Number(x) => stack.push(Number(!x)), _ => return Err(~"non-number on stack with %~") }, - 'i' => match (copy params[0], copy params[1]) { + 'i' => match (copy mparams[0], copy mparams[1]) { (Number(ref mut x), Number(ref mut y)) => { *x += 1; *y += 1; @@ -176,7 +180,7 @@ pub fn expand(cap: &[u8], params: &mut [Param], vars: &mut Variables) }, PushParam => { // params are 1-indexed - stack.push(copy params[char::to_digit(cur, 10).expect("bad param number") - 1]); + stack.push(copy mparams[char::to_digit(cur, 10).expect("bad param number") - 1]); }, SetVar => { if cur >= 'A' && cur <= 'Z' { From 6423548818e7dd6940a95889641fbd327749ebd4 Mon Sep 17 00:00:00 2001 From: Kevin Ballard Date: Fri, 14 Jun 2013 00:22:52 -0700 Subject: [PATCH 5/7] Fix a bunch of failure cases in terminfo Replace all potentially-failing operations with Err returns and add tests. Remove the Char parameter type; characters are represented as Numbers. Fix integer constants to work properly when there are multiple constants in the same capability string. Tweak loop to use iterators instead of indexing into cap. --- src/libextra/terminfo/parm.rs | 245 ++++++++++++++++++++++------------ 1 file changed, 161 insertions(+), 84 deletions(-) diff --git a/src/libextra/terminfo/parm.rs b/src/libextra/terminfo/parm.rs index 61a31bdd12a2..ace6ad3ebb0f 100644 --- a/src/libextra/terminfo/parm.rs +++ b/src/libextra/terminfo/parm.rs @@ -31,7 +31,6 @@ enum States { /// Types of parameters a capability can use pub enum Param { String(~str), - Char(char), Number(int) } @@ -64,13 +63,10 @@ impl Variables { pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) -> Result<~[u8], ~str> { let mut state = Nothing; - let mut i = 0; // 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 = ~[]; @@ -81,92 +77,123 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) *dst = src; } - while i < cap.len() { - cur = cap[i] as char; + 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' => 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") - }, - 'O' => 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") - }, - '!' => match stack.pop() { - Number(0) => stack.push(Number(1)), - Number(_) => stack.push(Number(0)), - _ => return Err(~"non-number on stack with logical not") - }, - '~' => match stack.pop() { - Number(x) => stack.push(Number(!x)), - _ => return Err(~"non-number on stack with %~") - }, + '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(x), Number(y)) => 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(x), Number(y)) => 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(x), Number(y)) => 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(x), Number(y)) => 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(x), Number(y)) => 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(x), Number(y)) => 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(x), Number(y)) => stack.push(Number(x | y)), + (_, _) => 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; @@ -180,15 +207,22 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) }, PushParam => { // params are 1-indexed - stack.push(copy mparams[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); - vars.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); - vars.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"); } @@ -205,7 +239,7 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) } }, CharConstant => { - stack.push(Char(cur)); + stack.push(Number(c as int)); state = CharClose; }, CharClose => { @@ -215,18 +249,22 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) }, 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; } - intstate.push(cur as u8); - old_state = Nothing; } _ => return Err(~"unimplemented state") } if state == old_state { state = Nothing; } - i += 1; } Ok(output) } @@ -234,9 +272,48 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) #[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)], &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()); + } } From f31767df66b9468cea81a3aceb34633fad213d67 Mon Sep 17 00:00:00 2001 From: Kevin Ballard Date: Fri, 14 Jun 2013 01:32:34 -0700 Subject: [PATCH 6/7] Implement terminfo param conditionals Implement the %?, %t, %e, and %; operators. Also implement the %<, %=, %> operators, without which conditionals aren't very useful. Fix the order of parameters for the arithmetic operators. Implement the missing %^ operator. --- src/libextra/terminfo/parm.rs | 147 +++++++++++++++++++++++++++++----- 1 file changed, 129 insertions(+), 18 deletions(-) diff --git a/src/libextra/terminfo/parm.rs b/src/libextra/terminfo/parm.rs index ace6ad3ebb0f..59d34c7317b8 100644 --- a/src/libextra/terminfo/parm.rs +++ b/src/libextra/terminfo/parm.rs @@ -24,8 +24,10 @@ enum States { CharConstant, CharClose, IntConstant, - IfCond, - IfBody + SeekIfElse(int), + SeekIfElsePercent(int), + SeekIfEnd(int), + SeekIfEndPercent(int) } /// Types of parameters a capability can use @@ -126,44 +128,68 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) } else { return Err(~"stack is empty") }, '+' => if stack.len() > 1 { match (stack.pop(), stack.pop()) { - (Number(x), Number(y)) => stack.push(Number(x + y)), - (_, _) => return Err(~"non-numbers on stack with +") + (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(x), Number(y)) => stack.push(Number(x - y)), - (_, _) => return Err(~"non-numbers on stack with -") + (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(x), Number(y)) => stack.push(Number(x * y)), - (_, _) => return Err(~"non-numbers on stack with *") + (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(x), Number(y)) => stack.push(Number(x / y)), - (_, _) => return Err(~"non-numbers on stack with /") + (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(x), Number(y)) => stack.push(Number(x % y)), - (_, _) => return Err(~"non-numbers on stack with %") + (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(x), Number(y)) => stack.push(Number(x & y)), - (_, _) => return Err(~"non-numbers on stack with &") + (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(x), Number(y)) => stack.push(Number(x | y)), - (_, _) => return Err(~"non-numbers on stack with |") + (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 { @@ -201,7 +227,19 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) }, (_, _) => 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)) } }, @@ -260,7 +298,46 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) old_state = Nothing; } } - _ => return Err(~"unimplemented state") + 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); + } + old_state = Nothing; + } + 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; @@ -316,4 +393,38 @@ mod 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()); + } } From da4e614742ea67677ed122985c1730590748d788 Mon Sep 17 00:00:00 2001 From: Kevin Ballard Date: Fri, 14 Jun 2013 13:02:24 -0700 Subject: [PATCH 7/7] Fix line lengths in terminfo --- src/libextra/term.rs | 3 ++- src/libextra/terminfo/parm.rs | 15 ++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/libextra/term.rs b/src/libextra/term.rs index f09c00ccce2e..17d80ded47f8 100644 --- a/src/libextra/term.rs +++ b/src/libextra/term.rs @@ -105,7 +105,8 @@ impl Terminal { } pub fn reset(&self) { if self.color_supported { - let s = expand(*self.ti.strings.find_equiv(&("op")).unwrap(), [], &mut Variables::new()); + 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 59d34c7317b8..c395b57219c2 100644 --- a/src/libextra/terminfo/parm.rs +++ b/src/libextra/terminfo/parm.rs @@ -176,19 +176,22 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) } 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 })), + (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 })), + (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 })), + (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") }, @@ -353,12 +356,14 @@ mod test { #[test] fn test_basic_setabf() { let s = bytes!("\\E[48;5;%p1%dm"); - assert_eq!(expand(s, [Number(1)], &mut Variables::new()).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()); + assert_eq!(expand(bytes!("%{1}%{2}%d%d"), [], &mut Variables::new()).unwrap(), + bytes!("21").to_owned()); } #[test]