@@ -31,7 +31,6 @@ enum States {
31
31
/// Types of parameters a capability can use
32
32
pub enum Param {
33
33
String ( ~str ) ,
34
- Char ( char ) ,
35
34
Number ( int )
36
35
}
37
36
@@ -64,13 +63,10 @@ impl Variables {
64
63
pub fn expand ( cap : & [ u8 ] , params : & [ Param ] , vars : & mut Variables )
65
64
-> Result < ~[ u8 ] , ~str > {
66
65
let mut state = Nothing ;
67
- let mut i = 0 ;
68
66
69
67
// expanded cap will only rarely be larger than the cap itself
70
68
let mut output = vec:: with_capacity ( cap. len ( ) ) ;
71
69
72
- let mut cur;
73
-
74
70
let mut stack: ~[ Param ] = ~[ ] ;
75
71
76
72
let mut intstate = ~[ ] ;
@@ -81,92 +77,123 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
81
77
* dst = src;
82
78
}
83
79
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;
86
82
let mut old_state = state;
87
83
match state {
88
84
Nothing => {
89
85
if cur == '%' {
90
86
state = Percent ;
91
87
} else {
92
- output. push ( cap [ i ] ) ;
88
+ output. push( c ) ;
93
89
}
94
90
} ,
95
91
Percent => {
96
92
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")
110
99
}
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") } ,
113
116
'p' => state = PushParam ,
114
117
'P' => state = SetVar ,
115
118
'g' => state = GetVar ,
116
119
'\'' => state = CharConstant ,
117
120
'{' => 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") } ,
170
197
'i' => match ( copy mparams[ 0 ] , copy mparams[ 1 ] ) {
171
198
( Number ( ref mut x) , Number ( ref mut y) ) => {
172
199
* x += 1 ;
@@ -180,15 +207,22 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
180
207
},
181
208
PushParam => {
182
209
// 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
+ } ] ) ;
184
214
} ,
185
215
SetVar => {
186
216
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") }
189
221
} 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") }
192
226
} else {
193
227
return Err ( ~"bad variable name in %P ") ;
194
228
}
@@ -205,7 +239,7 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
205
239
}
206
240
} ,
207
241
CharConstant => {
208
- stack. push ( Char ( cur ) ) ;
242
+ stack. push ( Number ( c as int ) ) ;
209
243
state = CharClose ;
210
244
} ,
211
245
CharClose => {
@@ -215,28 +249,71 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
215
249
} ,
216
250
IntConstant => {
217
251
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 ( ) ;
219
257
state = Nothing ;
258
+ } else {
259
+ intstate. push ( cur as u8 ) ;
260
+ old_state = Nothing ;
220
261
}
221
- intstate. push ( cur as u8 ) ;
222
- old_state = Nothing ;
223
262
}
224
263
_ => return Err ( ~"unimplemented state")
225
264
}
226
265
if state == old_state {
227
266
state = Nothing ;
228
267
}
229
- i += 1 ;
230
268
}
231
269
Ok ( output)
232
270
}
233
271
234
272
#[ cfg( test) ]
235
273
mod test {
236
274
use super :: * ;
275
+
237
276
#[ test]
238
277
fn test_basic_setabf ( ) {
239
278
let s = bytes ! ( "\\ E[48;5;%p1%dm" ) ;
240
279
assert_eq ! ( expand( s, [ Number ( 1 ) ] , & mut Variables :: new( ) ) . unwrap( ) , bytes!( "\\ E[48;5;1m" ) . to_owned( ) ) ;
241
280
}
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
+ }
242
319
}
0 commit comments