1111//! Parameterized string expansion
1212
1313use core:: prelude:: * ;
14- use core:: { char, int, vec} ;
14+ use core:: { char, vec, util} ;
15+ use core:: num:: strconv:: { SignNone , SignNeg , SignAll , DigAll , to_str_bytes_common} ;
1516use core:: iterator:: IteratorUtil ;
1617
1718#[ deriving( Eq ) ]
@@ -23,13 +24,21 @@ enum States {
2324 PushParam ,
2425 CharConstant ,
2526 CharClose ,
26- IntConstant ,
27+ IntConstant ( int ) ,
28+ FormatPattern ( Flags , FormatState ) ,
2729 SeekIfElse ( int ) ,
2830 SeekIfElsePercent ( int ) ,
2931 SeekIfEnd ( int ) ,
3032 SeekIfEndPercent ( int )
3133}
3234
35+ #[ deriving( Eq ) ]
36+ enum FormatState {
37+ FormatStateFlags ,
38+ FormatStateWidth ,
39+ FormatStatePrecision
40+ }
41+
3342/// Types of parameters a capability can use
3443pub enum Param {
3544 String ( ~str ) ,
@@ -71,8 +80,6 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
7180
7281 let mut stack: ~[ Param ] = ~[ ] ;
7382
74- let mut intstate = ~[ ] ;
75-
7683 // Copy parameters into a local vector for mutability
7784 let mut mparams = [ Number ( 0 ) , ..9 ] ;
7885 for mparams. mut_iter( ) . zip( params. iter( ) ) . advance |( dst, & src) | {
@@ -100,26 +107,11 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
100107 _ => return Err ( ~"a non-char was used with %c")
101108 }
102109 } else { return Err(~" stack is empty") } ,
103- 's' => if stack. len( ) > 0 {
104- match stack. pop( ) {
105- String ( s) => output. push_all( s. as_bytes( ) ) ,
106- _ => return Err ( ~"a non-str was used with %s")
107- }
108- } else { return Err ( ~"stack is empty") } ,
109- 'd' => if stack. len( ) > 0 {
110- match stack. pop( ) {
111- Number ( x) => {
112- let s = x. to_str( ) ;
113- output. push_all( s. as_bytes( ) )
114- }
115- _ => return Err ( ~"a non-number was used with %d")
116- }
117- } else { return Err ( ~"stack is empty") } ,
118110 'p' => state = PushParam ,
119111 'P' => state = SetVar ,
120112 'g' => state = GetVar ,
121113 '\'' => state = CharConstant ,
122- '{' => state = IntConstant ,
114+ '{' => state = IntConstant ( 0 ) ,
123115 'l' => if stack. len( ) > 0 {
124116 match stack. pop( ) {
125117 String ( s) => stack. push( Number ( s. len( ) as int) ) ,
@@ -231,6 +223,30 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
231223 ( _, _) => return Err ( ~"first two params not numbers with %i")
232224 } ,
233225
226+ // printf-style support for %doxXs
227+ 'd' |'o' |'x' |'X' |'s' => if stack. len ( ) > 0 {
228+ let flags = Flags :: new ( ) ;
229+ let res = format ( stack. pop ( ) , FormatOp :: from_char ( cur) , flags) ;
230+ if res. is_err ( ) { return res }
231+ output. push_all ( res. unwrap ( ) )
232+ } else { return Err ( ~"stack is empty") } ,
233+ ':' |'#' |' ' |'.' |'0' ..'9' => {
234+ let mut flags = Flags :: new ( ) ;
235+ let mut fstate = FormatStateFlags ;
236+ match cur {
237+ ':' => ( ) ,
238+ '#' => flags. alternate = true ,
239+ ' ' => flags. space = true ,
240+ '.' => fstate = FormatStatePrecision ,
241+ '0' ..'9' => {
242+ flags. width = ( cur - '0' ) as uint ;
243+ fstate = FormatStateWidth ;
244+ }
245+ _ => util:: unreachable ( )
246+ }
247+ state = FormatPattern ( flags, fstate) ;
248+ }
249+
234250 // conditionals
235251 '?' => ( ) ,
236252 't' => if stack. len ( ) > 0 {
@@ -288,17 +304,61 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
288304 return Err ( ~"malformed character constant") ;
289305 }
290306 } ,
291- IntConstant => {
292- if cur == '}' {
293- stack. push ( match int:: parse_bytes ( intstate, 10 ) {
294- Some ( n) => Number ( n) ,
295- None => return Err ( ~"bad int constant")
296- } ) ;
297- intstate. clear ( ) ;
298- state = Nothing ;
299- } else {
300- intstate. push ( cur as u8 ) ;
301- old_state = Nothing ;
307+ IntConstant ( i) => {
308+ match cur {
309+ '}' => {
310+ stack. push ( Number ( i) ) ;
311+ state = Nothing ;
312+ }
313+ '0' ..'9' => {
314+ state = IntConstant ( i* 10 + ( ( cur - '0' ) as int ) ) ;
315+ old_state = Nothing ;
316+ }
317+ _ => return Err ( ~"bad int constant")
318+ }
319+ }
320+ FormatPattern ( ref mut flags, ref mut fstate) => {
321+ old_state = Nothing ;
322+ match ( * fstate, cur) {
323+ ( _, 'd' ) |( _, 'o' ) |( _, 'x' ) |( _, 'X' ) |( _, 's' ) => if stack. len ( ) > 0 {
324+ let res = format ( stack. pop ( ) , FormatOp :: from_char ( cur) , * flags) ;
325+ if res. is_err ( ) { return res }
326+ output. push_all ( res. unwrap ( ) ) ;
327+ old_state = state; // will cause state to go to Nothing
328+ } else { return Err ( ~"stack is empty") } ,
329+ ( FormatStateFlags , '#' ) => {
330+ flags. alternate = true ;
331+ }
332+ ( FormatStateFlags , '-' ) => {
333+ flags. left = true ;
334+ }
335+ ( FormatStateFlags , '+' ) => {
336+ flags. sign = true ;
337+ }
338+ ( FormatStateFlags , ' ' ) => {
339+ flags. space = true ;
340+ }
341+ ( FormatStateFlags , '0' ..'9' ) => {
342+ flags. width = ( cur - '0' ) as uint ;
343+ * fstate = FormatStateWidth ;
344+ }
345+ ( FormatStateFlags , '.' ) => {
346+ * fstate = FormatStatePrecision ;
347+ }
348+ ( FormatStateWidth , '0' ..'9' ) => {
349+ let old = flags. width ;
350+ flags. width = flags. width * 10 + ( ( cur - '0' ) as uint ) ;
351+ if flags. width < old { return Err ( ~"format width overflow") }
352+ }
353+ ( FormatStateWidth , '.' ) => {
354+ * fstate = FormatStatePrecision ;
355+ }
356+ ( FormatStatePrecision , '0' ..'9' ) => {
357+ let old = flags. precision ;
358+ flags. precision = flags. precision * 10 + ( ( cur - '0' ) as uint ) ;
359+ if flags. precision < old { return Err ( ~"format precision overflow") }
360+ }
361+ _ => return Err ( ~"invalid format specifier")
302362 }
303363 }
304364 SeekIfElse ( level) => {
@@ -349,6 +409,142 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
349409 Ok ( output)
350410}
351411
412+ #[ deriving ( Eq ) ]
413+ priv struct Flags {
414+ width : uint ,
415+ precision : uint ,
416+ alternate : bool ,
417+ left : bool ,
418+ sign : bool ,
419+ space : bool
420+ }
421+
422+ impl Flags {
423+ priv fn new ( ) -> Flags {
424+ Flags { width : 0 , precision : 0 , alternate : false ,
425+ left : false , sign : false , space : false }
426+ }
427+ }
428+
429+ priv enum FormatOp {
430+ FormatDigit ,
431+ FormatOctal ,
432+ FormatHex ,
433+ FormatHEX ,
434+ FormatString
435+ }
436+
437+ impl FormatOp {
438+ priv fn from_char ( c : char ) -> FormatOp {
439+ match c {
440+ 'd' => FormatDigit ,
441+ 'o' => FormatOctal ,
442+ 'x' => FormatHex ,
443+ 'X' => FormatHEX ,
444+ 's' => FormatString ,
445+ _ => fail ! ( "bad FormatOp char" )
446+ }
447+ }
448+ priv fn to_char ( self ) -> char {
449+ match self {
450+ FormatDigit => 'd' ,
451+ FormatOctal => 'o' ,
452+ FormatHex => 'x' ,
453+ FormatHEX => 'X' ,
454+ FormatString => 's'
455+ }
456+ }
457+ }
458+
459+ priv fn format ( val : Param , op : FormatOp , flags : Flags ) -> Result < ~[ u8 ] , ~str > {
460+ let mut s = match val {
461+ Number ( d) => {
462+ match op {
463+ FormatString => {
464+ return Err ( ~"non-number on stack with %s")
465+ }
466+ _ => {
467+ let radix = match op {
468+ FormatDigit => 10 ,
469+ FormatOctal => 8 ,
470+ FormatHex |FormatHEX => 16 ,
471+ FormatString => util:: unreachable ( )
472+ } ;
473+ let mut ( s, _) = match op {
474+ FormatDigit => {
475+ let sign = if flags. sign { SignAll } else { SignNeg } ;
476+ to_str_bytes_common ( & d, radix, false , sign, DigAll )
477+ }
478+ _ => to_str_bytes_common ( & ( d as uint ) , radix, false , SignNone , DigAll )
479+ } ;
480+ if flags. precision > s. len ( ) {
481+ let mut s_ = vec:: with_capacity ( flags. precision ) ;
482+ let n = flags. precision - s. len ( ) ;
483+ s_. grow ( n, & ( '0' as u8 ) ) ;
484+ s_. push_all_move ( s) ;
485+ s = s_;
486+ }
487+ assert ! ( !s. is_empty( ) , "string conversion produced empty result" ) ;
488+ match op {
489+ FormatDigit => {
490+ if flags. space && !( s[ 0 ] == '-' as u8 || s[ 0 ] == '+' as u8 ) {
491+ s. unshift ( ' ' as u8 ) ;
492+ }
493+ }
494+ FormatOctal => {
495+ if flags. alternate && s[ 0 ] != '0' as u8 {
496+ s. unshift ( '0' as u8 ) ;
497+ }
498+ }
499+ FormatHex => {
500+ if flags. alternate {
501+ let s_ = util:: replace ( & mut s, ~[ '0' as u8 , 'x' as u8 ] ) ;
502+ s. push_all_move ( s_) ;
503+ }
504+ }
505+ FormatHEX => {
506+ s = s. into_ascii ( ) . to_upper ( ) . into_bytes ( ) ;
507+ if flags. alternate {
508+ let s_ = util:: replace ( & mut s, ~[ '0' as u8 , 'X' as u8 ] ) ;
509+ s. push_all_move ( s_) ;
510+ }
511+ }
512+ FormatString => util:: unreachable ( )
513+ }
514+ s
515+ }
516+ }
517+ }
518+ String ( s) => {
519+ match op {
520+ FormatString => {
521+ let mut s = s. as_bytes_with_null_consume ( ) ;
522+ s. pop ( ) ; // remove the null
523+ if flags. precision > 0 && flags. precision < s. len ( ) {
524+ s. truncate ( flags. precision ) ;
525+ }
526+ s
527+ }
528+ _ => {
529+ return Err ( fmt ! ( "non-string on stack with %%%c" , op. to_char( ) ) )
530+ }
531+ }
532+ }
533+ } ;
534+ if flags. width > s. len ( ) {
535+ let n = flags. width - s. len ( ) ;
536+ if flags. left {
537+ s. grow ( n, & ( ' ' as u8 ) ) ;
538+ } else {
539+ let mut s_ = vec:: with_capacity ( flags. width ) ;
540+ s_. grow ( n, & ( ' ' as u8 ) ) ;
541+ s_. push_all_move ( s) ;
542+ s = s_;
543+ }
544+ }
545+ Ok ( s)
546+ }
547+
352548#[ cfg( test) ]
353549mod test {
354550 use super :: * ;
@@ -443,4 +639,20 @@ mod test {
443639 assert ! ( res. is_ok( ) , res. unwrap_err( ) ) ;
444640 assert_eq ! ( res. unwrap( ) , bytes!( "\\ E[38;5;42m" ) . to_owned( ) ) ;
445641 }
642+
643+ #[ test]
644+ fn test_format( ) {
645+ let mut varstruct = Variables :: new( ) ;
646+ let vars = & mut varstruct;
647+ assert_eq!( expand( bytes!( "%p1%s%p2%2 s%p3%2 s%p4%. 2 s") ,
648+ [ String ( ~"foo") , String ( ~"foo") , String ( ~"f") , String ( ~"foo") ] , vars) ,
649+ Ok ( bytes!( "foofoo ffo") . to_owned( ) ) ) ;
650+ assert_eq!( expand( bytes!( "%p1%: -4 . 2 s") , [ String ( ~"foo") ] , vars) ,
651+ Ok ( bytes!( "fo ") . to_owned( ) ) ) ;
652+
653+ assert_eq ! ( expand( bytes!( "%p1%d%p1%.3d%p1%5d%p1%:+d" ) , [ Number ( 1 ) ] , vars) ,
654+ Ok ( bytes!( "1001 1+1" ) . to_owned( ) ) ) ;
655+ assert_eq ! ( expand( bytes!( "%p1%o%p1%#o%p2%6.4x%p2%#6.4X" ) , [ Number ( 15 ) , Number ( 27 ) ] , vars) ,
656+ Ok ( bytes!( "17017 001b0X001B" ) . to_owned( ) ) ) ;
657+ }
446658}
0 commit comments