@@ -31,7 +31,6 @@ enum States {
3131/// Types of parameters a capability can use
3232pub enum Param {
3333 String ( ~str ) ,
34- Char ( char ) ,
3534 Number ( int )
3635}
3736
@@ -64,13 +63,10 @@ impl Variables {
6463pub fn expand ( cap : & [ u8 ] , params : & [ Param ] , vars : & mut Variables )
6564 -> Result < ~[ u8 ] , ~str > {
6665 let mut state = Nothing ;
67- let mut i = 0 ;
6866
6967 // expanded cap will only rarely be larger than the cap itself
7068 let mut output = vec:: with_capacity ( cap. len ( ) ) ;
7169
72- let mut cur;
73-
7470 let mut stack: ~[ Param ] = ~[ ] ;
7571
7672 let mut intstate = ~[ ] ;
@@ -81,92 +77,123 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
8177 * dst = src;
8278 }
8379
84- while i < cap. len ( ) {
85- cur = cap [ i ] as char ;
80+ for cap. iter ( ) . transform ( | & x| x ) . advance |c| {
81+ let cur = c as char;
8682 let mut old_state = state;
8783 match state {
8884 Nothing => {
8985 if cur == '%' {
9086 state = Percent ;
9187 } else {
92- output. push ( cap [ i ] ) ;
88+ output. push( c ) ;
9389 }
9490 } ,
9591 Percent => {
9692 match cur {
97- '%' => { output. push ( cap[ i] ) ; state = Nothing } ,
98- 'c' => match stack. pop ( ) {
99- Char ( c) => output. push ( c as u8 ) ,
100- _ => return Err ( ~"a non-char was used with %c")
101- },
102- 's' => match stack.pop() {
103- String(s) => output.push_all(s.as_bytes()),
104- _ => return Err(~" a non-str was used with %s")
105- } ,
106- 'd' => match stack. pop ( ) {
107- Number ( x) => {
108- let s = x. to_str ( ) ;
109- output. push_all ( s. as_bytes ( ) )
93+ '%' => { output. push( c) ; state = Nothing } ,
94+ 'c' => if stack. len( ) > 0 {
95+ match stack. pop( ) {
96+ // if c is 0, use 0200 (128) for ncurses compatibility
97+ Number ( c) => output. push ( if c == 0 { 128 } else { c } as u8) ,
98+ _ => return Err ( ~"a non-char was used with %c")
11099 }
111- _ => return Err ( ~"a non-number was used with %d")
112- } ,
100+ } else { return Err(~" stack is empty") } ,
101+ 's' => if stack. len( ) > 0 {
102+ match stack. pop( ) {
103+ String ( s) => output. push_all( s. as_bytes( ) ) ,
104+ _ => return Err ( ~"a non-str was used with %s")
105+ }
106+ } else { return Err ( ~"stack is empty") } ,
107+ 'd' => if stack. len( ) > 0 {
108+ match stack. pop( ) {
109+ Number ( x) => {
110+ let s = x. to_str( ) ;
111+ output. push_all( s. as_bytes( ) )
112+ }
113+ _ => return Err ( ~"a non-number was used with %d")
114+ }
115+ } else { return Err ( ~"stack is empty") } ,
113116 'p' => state = PushParam ,
114117 'P' => state = SetVar ,
115118 'g' => state = GetVar ,
116119 '\'' => state = CharConstant ,
117120 '{' => state = IntConstant ,
118- 'l' => match stack. pop ( ) {
119- String ( s) => stack. push ( Number ( s. len ( ) as int ) ) ,
120- _ => return Err ( ~"a non-str was used with %l")
121- } ,
122- '+' => match ( stack. pop ( ) , stack. pop ( ) ) {
123- ( Number ( x) , Number ( y) ) => stack. push ( Number ( x + y) ) ,
124- ( _, _) => return Err ( ~"non-numbers on stack with +")
125- },
126- '-' => match (stack.pop(), stack.pop()) {
127- (Number(x), Number(y)) => stack.push(Number(x - y)),
128- (_, _) => return Err(~" non-numbers on stack with -")
129- },
130- '*' => match (stack.pop(), stack.pop()) {
131- (Number(x), Number(y)) => stack.push(Number(x * y)),
132- (_, _) => return Err(~" non-numbers on stack with * ")
133- },
134- '/' => match (stack.pop(), stack.pop()) {
135- (Number(x), Number(y)) => stack.push(Number(x / y)),
136- (_, _) => return Err(~" non-numbers on stack with /")
137- },
138- 'm' => match (stack.pop(), stack.pop()) {
139- (Number(x), Number(y)) => stack.push(Number(x % y)),
140- (_, _) => return Err(~" non-numbers on stack with %")
141- },
142- '&' => match (stack.pop(), stack.pop()) {
143- (Number(x), Number(y)) => stack.push(Number(x & y)),
144- (_, _) => return Err(~" non-numbers on stack with & ")
145- },
146- '|' => match (stack.pop(), stack.pop()) {
147- (Number(x), Number(y)) => stack.push(Number(x | y)),
148- (_, _) => return Err(~" non-numbers on stack with |")
149- },
150- 'A' => match (stack.pop(), stack.pop()) {
151- (Number(0), Number(_)) => stack.push(Number(0)),
152- (Number(_), Number(0)) => stack.push(Number(0)),
153- (Number(_), Number(_)) => stack.push(Number(1)),
154- _ => return Err(~" non-numbers on stack with logical and")
155- } ,
156- 'O' => match ( stack. pop ( ) , stack. pop ( ) ) {
157- ( Number ( 0 ) , Number ( 0 ) ) => stack. push ( Number ( 0 ) ) ,
158- ( Number ( _) , Number ( _) ) => stack. push ( Number ( 1 ) ) ,
159- _ => return Err ( ~"non-numbers on stack with logical or")
160- } ,
161- '!' => match stack. pop ( ) {
162- Number ( 0 ) => stack. push ( Number ( 1 ) ) ,
163- Number ( _) => stack. push ( Number ( 0 ) ) ,
164- _ => return Err ( ~"non-number on stack with logical not")
165- } ,
166- '~' => match stack. pop ( ) {
167- Number ( x) => stack. push ( Number ( !x) ) ,
168- _ => return Err ( ~"non-number on stack with %~")
169- } ,
121+ 'l' => if stack. len( ) > 0 {
122+ match stack. pop( ) {
123+ String ( s) => stack. push( Number ( s. len( ) as int) ) ,
124+ _ => return Err ( ~"a non-str was used with %l")
125+ }
126+ } else { return Err ( ~"stack is empty") } ,
127+ '+' => if stack. len( ) > 1 {
128+ match ( stack. pop( ) , stack. pop( ) ) {
129+ ( Number ( x) , Number ( y) ) => stack. push( Number ( x + y) ) ,
130+ ( _, _) => return Err ( ~"non-numbers on stack with +")
131+ }
132+ } else { return Err ( ~"stack is empty") } ,
133+ '-' => if stack. len( ) > 1 {
134+ match ( stack. pop( ) , stack. pop( ) ) {
135+ ( Number ( x) , Number ( y) ) => stack. push( Number ( x - y) ) ,
136+ ( _, _) => return Err ( ~"non-numbers on stack with -")
137+ }
138+ } else { return Err(~" stack is empty") } ,
139+ '*' => if stack. len( ) > 1 {
140+ match ( stack. pop( ) , stack. pop( ) ) {
141+ ( Number ( x) , Number ( y) ) => stack. push( Number ( x * y) ) ,
142+ ( _, _) => return Err ( ~"non-numbers on stack with * ")
143+ }
144+ } else { return Err(~" stack is empty") } ,
145+ '/' => if stack. len ( ) > 1 {
146+ match ( stack. pop ( ) , stack. pop ( ) ) {
147+ ( Number ( x) , Number ( y) ) => stack. push ( Number ( x / y) ) ,
148+ ( _, _) => return Err ( ~"non-numbers on stack with /")
149+ }
150+ } else { return Err(~" stack is empty") } ,
151+ 'm' => if stack. len ( ) > 1 {
152+ match ( stack. pop ( ) , stack. pop ( ) ) {
153+ ( Number ( x) , Number ( y) ) => stack. push ( Number ( x % y) ) ,
154+ ( _, _) => return Err ( ~"non-numbers on stack with %")
155+ }
156+ } else { return Err(~" stack is empty") } ,
157+ '&' => if stack. len ( ) > 1 {
158+ match ( stack. pop ( ) , stack. pop ( ) ) {
159+ ( Number ( x) , Number ( y) ) => stack. push ( Number ( x & y) ) ,
160+ ( _, _) => return Err ( ~"non-numbers on stack with & ")
161+ }
162+ } else { return Err(~" stack is empty") } ,
163+ '|' => if stack. len ( ) > 1 {
164+ match ( stack. pop ( ) , stack. pop ( ) ) {
165+ ( Number ( x) , Number ( y) ) => stack. push ( Number ( x | y) ) ,
166+ ( _, _) => return Err ( ~"non-numbers on stack with |")
167+ }
168+ } else { return Err(~" stack is empty") } ,
169+ 'A' => if stack. len ( ) > 1 {
170+ match ( stack. pop ( ) , stack. pop ( ) ) {
171+ ( Number ( 0 ) , Number ( _) ) => stack. push ( Number ( 0 ) ) ,
172+ ( Number ( _) , Number ( 0 ) ) => stack. push ( Number ( 0 ) ) ,
173+ ( Number ( _) , Number ( _) ) => stack. push ( Number ( 1 ) ) ,
174+ _ => return Err ( ~"non-numbers on stack with logical and")
175+ }
176+ } else { return Err ( ~"stack is empty") } ,
177+ 'O' => if stack. len ( ) > 1 {
178+ match ( stack. pop ( ) , stack. pop ( ) ) {
179+ ( Number ( 0 ) , Number ( 0 ) ) => stack. push ( Number ( 0 ) ) ,
180+ ( Number ( _) , Number ( _) ) => stack. push ( Number ( 1 ) ) ,
181+ _ => return Err ( ~"non-numbers on stack with logical or")
182+ }
183+ } else { return Err ( ~"stack is empty") } ,
184+ '!' => if stack. len ( ) > 0 {
185+ match stack. pop ( ) {
186+ Number ( 0 ) => stack. push ( Number ( 1 ) ) ,
187+ Number ( _) => stack. push ( Number ( 0 ) ) ,
188+ _ => return Err ( ~"non-number on stack with logical not")
189+ }
190+ } else { return Err ( ~"stack is empty") } ,
191+ '~' => if stack. len ( ) > 0 {
192+ match stack. pop ( ) {
193+ Number ( x) => stack. push ( Number ( !x) ) ,
194+ _ => return Err ( ~"non-number on stack with %~")
195+ }
196+ } else { return Err ( ~"stack is empty") } ,
170197 'i' => match ( copy mparams[ 0 ] , copy mparams[ 1 ] ) {
171198 ( Number ( ref mut x) , Number ( ref mut y) ) => {
172199 * x += 1 ;
@@ -180,15 +207,22 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
180207 },
181208 PushParam => {
182209 // params are 1-indexed
183- stack. push ( copy mparams[ char:: to_digit ( cur, 10 ) . expect ( "bad param number" ) - 1 ] ) ;
210+ stack.push(copy mparams[match char::to_digit(cur, 10) {
211+ Some(d) => d - 1,
212+ None => return Err(~" bad param number")
213+ } ] ) ;
184214 } ,
185215 SetVar => {
186216 if cur >= 'A' && cur <= 'Z' {
187- let idx = ( cur as u8 ) - ( 'A' as u8 ) ;
188- vars. sta [ idx] = stack. pop ( ) ;
217+ if stack. len ( ) > 0 {
218+ let idx = ( cur as u8 ) - ( 'A' as u8 ) ;
219+ vars. sta [ idx] = stack. pop ( ) ;
220+ } else { return Err ( ~"stack is empty") }
189221 } else if cur >= 'a' && cur <= 'z' {
190- let idx = ( cur as u8 ) - ( 'a' as u8 ) ;
191- vars. dyn [ idx] = stack. pop ( ) ;
222+ if stack. len ( ) > 0 {
223+ let idx = ( cur as u8 ) - ( 'a' as u8 ) ;
224+ vars. dyn [ idx] = stack. pop ( ) ;
225+ } else { return Err ( ~"stack is empty") }
192226 } else {
193227 return Err ( ~"bad variable name in %P ") ;
194228 }
@@ -205,7 +239,7 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
205239 }
206240 } ,
207241 CharConstant => {
208- stack. push ( Char ( cur ) ) ;
242+ stack. push ( Number ( c as int ) ) ;
209243 state = CharClose ;
210244 } ,
211245 CharClose => {
@@ -215,28 +249,71 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
215249 } ,
216250 IntConstant => {
217251 if cur == '}' {
218- stack. push ( Number ( int:: parse_bytes ( intstate, 10 ) . expect ( "bad int constant" ) ) ) ;
252+ stack. push ( match int:: parse_bytes ( intstate, 10 ) {
253+ Some ( n) => Number ( n) ,
254+ None => return Err ( ~"bad int constant")
255+ } ) ;
256+ intstate. clear ( ) ;
219257 state = Nothing ;
258+ } else {
259+ intstate. push ( cur as u8 ) ;
260+ old_state = Nothing ;
220261 }
221- intstate. push ( cur as u8 ) ;
222- old_state = Nothing ;
223262 }
224263 _ => return Err ( ~"unimplemented state")
225264 }
226265 if state == old_state {
227266 state = Nothing ;
228267 }
229- i += 1 ;
230268 }
231269 Ok ( output)
232270}
233271
234272#[ cfg( test) ]
235273mod test {
236274 use super :: * ;
275+
237276 #[ test]
238277 fn test_basic_setabf ( ) {
239278 let s = bytes ! ( "\\ E[48;5;%p1%dm" ) ;
240279 assert_eq ! ( expand( s, [ Number ( 1 ) ] , & mut Variables :: new( ) ) . unwrap( ) , bytes!( "\\ E[48;5;1m" ) . to_owned( ) ) ;
241280 }
281+
282+ #[ test]
283+ fn test_multiple_int_constants ( ) {
284+ assert_eq ! ( expand( bytes!( "%{1}%{2}%d%d" ) , [ ] , & mut Variables :: new( ) ) . unwrap( ) , bytes!( "21" ) . to_owned( ) ) ;
285+ }
286+
287+ #[ test]
288+ fn test_param_stack_failure_conditions( ) {
289+ let mut varstruct = Variables :: new ( ) ;
290+ let vars = & mut varstruct;
291+ let caps = [ "%d" , "%c" , "%s" , "%Pa" , "%l" , "%!" , "%~" ] ;
292+ for caps. iter( ) . advance |cap| {
293+ let res = expand( cap. as_bytes( ) , [ ] , vars) ;
294+ assert ! ( res. is_err( ) ,
295+ "Op %s succeeded incorrectly with 0 stack entries" , * cap) ;
296+ let p = if * cap == "%s" || * cap == "%l" { String ( ~"foo") } else { Number ( 97 ) } ;
297+ let res = expand( ( bytes ! ( "%p1" ) ) . to_owned( ) + cap. as_bytes( ) , [ p] , vars) ;
298+ assert ! ( res. is_ok( ) ,
299+ "Op %s failed with 1 stack entry: %s" , * cap, res. unwrap_err( ) ) ;
300+ }
301+ let caps = [ "%+" , "%-" , "%*" , "%/" , "%m" , "%&" , "%|" , "%A" , "%O" ] ;
302+ for caps. iter( ) . advance |cap| {
303+ let res = expand( cap. as_bytes( ) , [ ] , vars) ;
304+ assert ! ( res. is_err( ) ,
305+ "Binop %s succeeded incorrectly with 0 stack entries" , * cap) ;
306+ let res = expand( ( bytes ! ( "%{1}" ) ) . to_owned( ) + cap. as_bytes( ) , [ ] , vars) ;
307+ assert ! ( res. is_err( ) ,
308+ "Binop %s succeeded incorrectly with 1 stack entry" , * cap) ;
309+ let res = expand( ( bytes ! ( "%{1}%{2}" ) ) . to_owned( ) + cap. as_bytes( ) , [ ] , vars) ;
310+ assert ! ( res. is_ok( ) ,
311+ "Binop %s failed with 2 stack entries: %s" , * cap, res. unwrap_err( ) ) ;
312+ }
313+ }
314+
315+ #[ test]
316+ fn test_push_bad_param( ) {
317+ assert ! ( expand( bytes!( "%pa" ) , [ ] , & mut Variables :: new( ) ) . is_err( ) ) ;
318+ }
242319}
0 commit comments