printf: add (spare C99 hex floats)

This commit is contained in:
Nathan Ross
2015-12-24 01:11:00 -05:00
parent 1760f2937b
commit 0892ad3cde
25 changed files with 2887 additions and 0 deletions
Generated
+14
View File
@@ -48,6 +48,7 @@ dependencies = [
"paste 0.0.1",
"primal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"printenv 0.0.1",
"printf 0.0.1",
"ptx 0.0.1",
"pwd 0.0.1",
"rand 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -387,6 +388,11 @@ dependencies = [
"uucore 0.0.1",
]
[[package]]
name = "itertools"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "kernel32-sys"
version = "0.2.1"
@@ -615,6 +621,14 @@ dependencies = [
"uucore 0.0.1",
]
[[package]]
name = "printf"
version = "0.0.1"
dependencies = [
"itertools 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.1",
]
[[package]]
name = "ptx"
version = "0.0.1"
+2
View File
@@ -56,6 +56,7 @@ generic = [
"od",
"paste",
"printenv",
"printf",
"ptx",
"pwd",
"readlink",
@@ -129,6 +130,7 @@ nproc = { optional=true, path="src/nproc" }
od = { optional=true, path="src/od" }
paste = { optional=true, path="src/paste" }
printenv = { optional=true, path="src/printenv" }
printf = { optional=true, path="src/printf" }
ptx = { optional=true, path="src/ptx" }
pwd = { optional=true, path="src/pwd" }
readlink = { optional=true, path="src/readlink" }
+2
View File
@@ -65,6 +65,7 @@ PROGS := \
od \
paste \
printenv \
printf \
ptx \
pwd \
readlink \
@@ -149,6 +150,7 @@ TEST_PROGS := \
mv \
nl \
paste \
printf \
ptx \
pwd \
readlink \
+16
View File
@@ -0,0 +1,16 @@
[package]
name = "printf"
version = "0.0.1"
authors = ["Nathan Ross"]
[lib]
name = "uu_printf"
path = "printf.rs"
[dependencies]
"itertools" = "*"
uucore = { path="../uucore" }
[[bin]]
name = "printf"
path = "main.rs"
+34
View File
@@ -0,0 +1,34 @@
//! stdio convenience fns
#[allow(unused_must_use)]
use std::io::{stderr, stdout, Write};
use std::env;
pub static EXIT_OK: i32 = 0;
pub static EXIT_ERR: i32 = 1;
pub fn err_msg(msg:&str) {
let exe_path = match env::current_exe() {
Ok(p) => p.to_string_lossy().into_owned(),
_ => String::from("")
};
writeln!(&mut stderr(),"{}: {}", exe_path, msg).unwrap();
}
// by default stdout only flushes
// to console when a newline is passed.
#[allow(unused_must_use)]
pub fn flush_char(c: &char) {
print!("{}", c);
stdout().flush();
}
#[allow(unused_must_use)]
pub fn flush_str(s: &str) {
print!("{}", s);
stdout().flush();
}
#[allow(unused_must_use)]
pub fn flush_bytes(bslice: &[u8]) {
stdout().write(bslice);
stdout().flush();
}
+5
View File
@@ -0,0 +1,5 @@
extern crate uu_printf;
fn main() {
std::process::exit(uu_printf::uumain(std::env::args().collect()));
}
+84
View File
@@ -0,0 +1,84 @@
//! Memo runner of printf
//! Takes a format string and arguments
//! 1. tokenizes format string into tokens, consuming
//! any subst. arguments along the way.
//! 2. feeds remaining arguments into function
//! that prints tokens.
use std::iter::Peekable;
use std::slice::Iter;
use itertools::PutBackN;
use cli;
use tokenize::token::{Token, Tokenizer};
use tokenize::unescaped_text::UnescapedText;
use tokenize::sub::Sub;
pub struct Memo {
tokens: Vec<Box<Token>>,
}
fn warn_excess_args(first_arg : &str) {
cli::err_msg(&format!("warning: ignoring excess arguments, starting with '{}'",
first_arg));
}
impl Memo {
pub fn new(
pf_string: &String,
pf_args_it: &mut Peekable<Iter<String>>
) -> Memo {
let mut pm = Memo { tokens: Vec::new() };
let mut tmp_token : Option<Box<Token>>;
let mut it = PutBackN::new(pf_string.chars());
let mut has_sub = false;
loop {
tmp_token = UnescapedText::from_it(&mut it, pf_args_it);
match tmp_token {
Some(x) => pm.tokens.push(x),
None => {}
}
tmp_token = Sub::from_it(&mut it, pf_args_it);
match tmp_token {
Some(x) => {
if ! has_sub { has_sub = true; }
pm.tokens.push(x);
},
None => {}
}
if let Some(x) = it.next() {
it.put_back(x);
} else { break; }
}
if ! has_sub {
let mut drain= false;
if let Some(first_arg) = pf_args_it.peek() {
warn_excess_args(first_arg);
drain = true;
}
if drain {
loop {
//drain remaining args;
if pf_args_it.next().is_none() {
break;
}
}
}
}
pm
}
pub fn apply(&self, pf_args_it: &mut Peekable<Iter<String>>) {
for tkn in self.tokens.iter() {
tkn.print(pf_args_it);
}
}
pub fn run_all(pf_string: &String, pf_args: &[String]) {
let mut arg_it = pf_args.iter().peekable();
let pm = Memo::new(pf_string, &mut arg_it);
loop {
if arg_it.peek().is_none() {
break;
}
pm.apply(&mut arg_it);
}
}
}
+4
View File
@@ -0,0 +1,4 @@
mod cli;
mod memo;
mod tokenize;
+288
View File
@@ -0,0 +1,288 @@
#![crate_name = "uu_printf"]
#![allow(dead_code)]
extern crate itertools;
mod cli;
mod memo;
mod tokenize;
#[macro_use]
extern crate uucore;
static NAME: &'static str = "printf";
static VERSION: &'static str = "0.0.1";
static SHORT_USAGE: &'static str = "printf: usage: printf [-v var] format [arguments]";
static LONGHELP_LEAD: &'static str = "printf
USAGE: printf FORMATSTRING [ARGUMENT]...
basic anonymous string templating:
prints format string at least once, repeating as long as there are remaining arguments
output prints escaped literals in the format string as character literals
output replaces anonymous fields with the next unused argument, formatted according to the field.
Options:
--help display this help and exit
--version output version information and exit
";
static LONGHELP_BODY: &'static str = "
Prints the , replacing escaped character sequences with character literals
and substitution field sequences with passed arguments
literally, with the exception of the below
escaped character sequences, and the substitution sequences described further down.
ESCAPE SEQUENCES
The following escape sequences, organized here in alphabetical order,
will print the corresponding character literal:
\" double quote
\\\\ backslash
\\a alert (BEL)
\\b backspace
\\c End-of-Input
\\e escape
\\f form feed
\\n new line
\\r carriage return
\\t horizontal tab
\\v vertical tab
\\NNN byte with value expressed in octal value NNN (1 to 3 digits)
values greater than 256 will be treated
\\xHH byte with value expressed in hexadecimal value NN (1 to 2 digits)
\\uHHHH Unicode (IEC 10646) character with value expressed in hexadecimal value HHHH (4 digits)
\\uHHHH Unicode character with value expressed in hexadecimal value HHHH (8 digits)
%% a single %
SUBSTITUTIONS
SUBSTITUTION QUICK REFERENCE
Fields
%s - string
%b - string parsed for literals
second parameter is max length
%c - char
no second parameter
%i or %d - 64-bit integer
%u - 64 bit unsigned integer
%x or %X - 64-bit unsigned integer as hex
%o - 64-bit unsigned integer as octal
second parameter is min-width, integer
output below that width is padded with leading zeroes
%f or %F - decimal floating point value
%e or %E - scientific notation floating point value
%g or %G - shorter of specially interpreted decimal or SciNote floating point value.
second parameter is
-max places after decimal point for floating point output
-max number of significant digits for scientific notation output
parameterizing fields
examples:
printf '%4.3i' 7
has a first parameter of 4
and a second parameter of 3
will result in ' 007'
printf '%.1s' abcde
has no first parameter
and a second parameter of 1
will result in 'a'
printf '%4c' q
has a first parameter of 4
and no second parameter
will result in ' q'
The first parameter of a field is the minimum width to pad the output to
if the output is less than this absolute value of this width,
it will be padded with leading spaces, or, if the argument is negative,
with trailing spaces. the default is zero.
The second parameter of a field is particular to the output field type.
defaults can be found in the full substitution help below
special prefixes to numeric arguments
0 (e.g. 010) - interpret argument as octal (integer output fields only)
0x (e.g. 0xABC) - interpret argument as hex (numeric output fields only)
\' (e.g. \'a) - interpret argument as a character constant
HOW TO USE SUBSTITUTIONS
Substitutions are used to pass additional argument(s) into the FORMAT string, to be formatted a
particular way. E.g.
printf 'the letter %X comes before the letter %X' 10 11
will print
'the letter A comes before the letter B'
because the substitution field %X means
'take an integer argument and write it as a hexadecimal number'
Passing more arguments than are in the format string will cause the format string to be
repeated for the remaining substitutions
printf 'it is %i F in %s \n' 22 Portland 25 Boston 27 New York
will print
'it is 22 F in Portland
it is 25 F in Boston
it is 27 F in Boston
'
If a format string is printed but there are less arguments remaining
than there are substitution fields, substitution fields without
an argument will default to empty strings, or for numeric fields
the value 0
AVAILABLE SUBSTITUTIONS
This program, like GNU coreutils printf,
interprets a modified subset of the POSIX C printf spec,
a quick reference to substitutions is below.
STRING SUBSTITUTIONS
All string fields have a 'max width' parameter
%.3s means 'print no more than three characters of the original input'
%s - string
%b - escaped string - the string will be checked for any escaped literals from
the escaped literal list above, and translate them to literal charcters.
e.g. \\n will be transformed into a newline character.
One special rule about %b mode is that octal literals are intepreted differently
In arguments passed by %b, pass octal-interpreted literals must be in the form of \\0NNN instead of \\NNN
(Although, for legacy reasons, octal literals in the form of \\NNN will still be interpreted and not throw a warning, you will have problems if you use this for a literal whose code begins with zero, as it will be viewed as in \\0NNN form.)
CHAR SUBSTITUTIONS
The character field does not have a secondary parameter.
%c - a single character
INTEGER SUBSTITUTIONS
All integer fields have a 'pad with zero' parameter
%.4i means an integer which if it is less than 4 digits in length,
is padded with leading zeros until it is 4 digits in length.
%d or %i - 64-bit integer
%u - 64 bit unsigned integer
%x or %X - 64 bit unsigned integer printed in Hexadecimal (base 16)
%X instead of %x means to use uppercase letters for 'a' through 'f'
%o - 64 bit unsigned integer printed in octal (base 8)
FLOATING POINT SUBSTITUTIONS
All floating point fields have a 'max decimal places / max significant digits' parameter
%.10f means a decimal floating point with 7 decimal places past 0
%.10e means a scientific notation number with 10 significant digits
%.10g means the same behavior for decimal and Sci. Note, respectively, and provides the shorter of each's output.
Like with GNU coreutils, the value after the decimal point is these outputs is parsed as a double first before being rendered to text. For both implementations do not expect meaningful precision past the 18th decimal place. When using a number of decimal places that is 18 or higher, you can expect variation in output between GNU coreutils printf and this printf at the 18th decimal place of +/- 1
%f - floating point value presented in decimal, truncated and displayed to 6 decimal places by default.
There is not past-double behavior parity with Coreutils printf, values are not estimated or adjusted beyond input values.
%e or %E - floating point value presented in scientific notation
7 significant digits by default
%E means use to use uppercase E for the mantissa.
%g or %G - floating point value presented in the shorter of decimal and scientific notation
behaves differently from %f and %E, please see posix printf spec for full details,
some examples of different behavior:
Sci Note has 6 significant digits by default
Trailing zeroes are removed
Instead of being truncated, digit after last is rounded
Like other behavior in this utility, the design choices of floating point
behavior in this utility is selected to reproduce in exact
the behavior of GNU coreutils' printf from an inputs and outputs standpoint.
USING PARAMETERS
Most substitution fields can be parameterized using up to 2 numbers that can
be passed to the field, between the % sign and the field letter.
The 1st parameter always indicates the minimum width of output, it is useful for creating
columnar output. Any output that would be less than this minimum width is padded with
leading spaces
The 2nd parameter is proceeded by a dot.
You do not have to use parameters
SPECIAL FORMS OF INPUT
For numeric input, the following additional forms of input are accepted besides decimal:
Octal (only with integer): if the argument begins with a 0 the proceeding characters
will be interpreted as octal (base 8) for integer fields
Hexadecimal: if the argument begins with 0x the proceeding characters will be interpreted
will be interpreted as hex (base 16) for any numeric fields
for float fields, hexadecimal input results in a precision
limit (in converting input past the decimal point) of 10^-15
Character Constant: if the argument begins with a single quote character, the first byte
of the next character will be interpreted as an 8-bit unsigned integer. If there are
additional bytes, they will throw an error (unless the environment variable POSIXLY_CORRECt is set)
WRITTEN BY :
Nathan E. Ross, et al. for the uutils project
MORE INFO :
https://github.com/uutils/coreutils
COPYRIGHT :
Copyright 2015 uutils project.
Licensed under the MIT License, please see LICENSE file for details
";
pub fn uumain(args: Vec<String>) -> i32 {
let location = &args[0];
if args.len() <= 1 {
println!("{0}: missing operand\nTry '{0} --help' for more information.",
location);
return 1;
}
let ref formatstr = args[1];
if formatstr == "--help" {
print!("{} {}", LONGHELP_LEAD, LONGHELP_BODY);
} else if formatstr == "--version" {
println!("{} {}", NAME, VERSION);
} else {
let printf_args = &args[2..];
memo::Memo::run_all(formatstr, printf_args);
}
return 0;
}
+4
View File
@@ -0,0 +1,4 @@
pub mod token;
pub mod sub;
pub mod unescaped_text;
mod num_format;
@@ -0,0 +1,42 @@
//! Primitievs used by Sub Tokenizer
//! and num_format modules
#[derive(Clone)]
pub enum FieldType {
Strf,
Floatf,
Scif,
Decf,
Intf,
Charf,
}
/*
#[allow(non_camel_case_types)]
pub enum FChar {
d,
e,
E,
i,
f,
F,
g,
G,
u,
x,
X,
o
}
*/
// a Sub Tokens' fields are stored
// as a single object so they can be more simply
// passed by ref to num_format in a Sub method
#[derive(Clone)]
pub struct FormatField<'a> {
pub min_width: Option<isize>,
pub second_field: Option<u32>,
pub field_char: & 'a char,
pub field_type: & 'a FieldType,
pub orig : & 'a String
}
@@ -0,0 +1,73 @@
//! Primitives used by num_format and sub_modules.
//! never dealt with above (e.g. Sub Tokenizer never uses these)
use std::str::Chars;
use itertools::PutBackN;
use cli;
use super::format_field::FormatField;
// contains the rough ingredients to final
// output for a number, organized together
// to allow for easy generalization of output manipulation
// (e.g. max number of digits after decimal)
pub struct FormatPrimitive {
pub prefix: Option<String>,
pub pre_decimal: Option<String>,
pub post_decimal: Option<String>,
pub suffix: Option<String>
}
impl Default for FormatPrimitive {
fn default() -> FormatPrimitive {
FormatPrimitive {
prefix: None,
pre_decimal: None,
post_decimal: None,
suffix: None
}
}
}
#[derive(Clone)]
#[derive(PartialEq)]
pub enum Base {
Ten=10,
Hex=16,
Octal=8,
}
// information from the beginning of a numeric argument
// the precedes the beginning of a numeric value
pub struct InPrefix {
pub radix_in : Base,
pub sign : i8,
pub offset : usize
}
pub trait Formatter {
// return a FormatPrimitive for
// particular field char(s), given the argument
// string and prefix information (sign, radix)
fn get_primitive(
&self,
field: &FormatField,
inprefix: &InPrefix,
str_in: &str
) -> Option<FormatPrimitive>;
// return a string from a formatprimitive,
// given information about the field
fn primitive_to_str(
&self,
prim: &FormatPrimitive,
field: FormatField) -> String;
}
pub fn get_it_at(offset: usize,
str_in: &str) -> PutBackN<Chars> {
PutBackN::new(str_in[offset..].chars())
}
// TODO: put this somewhere better
pub fn warn_incomplete_conv(pf_arg: &str) {
//important: keep println here not print
cli::err_msg(&format!("{}: value not completely converted",
pf_arg))
}
@@ -0,0 +1,273 @@
pub fn arrnum_int_mult(
arrnum : &Vec<u8>,
basenum : u8,
base_ten_int_fact : u8
) -> Vec<u8> {
let mut carry : u16 = 0;
let mut rem : u16;
let mut new_amount : u16;
let fact : u16 = base_ten_int_fact as u16;
let base : u16 = basenum as u16;
let mut ret_rev : Vec<u8> = Vec::new();
let mut it = arrnum.iter().rev();
loop {
let i = it.next();
match i {
Some(u) => {
new_amount = ((u.clone() as u16)*fact) + carry;
rem = new_amount % base;
carry = (new_amount - rem) / base;
ret_rev.push(rem as u8)
},
None => {
while carry != 0 {
rem = carry % base;
carry = (carry - rem) / base;
ret_rev.push(rem as u8);
}
break;
}
}
}
let ret : Vec<u8> =
ret_rev.iter().rev().map(|x| x.clone()).collect();
ret
}
pub struct Remainder {
position : usize,
replace : Option<u8>
}
pub struct DivOut {
quotient : u8,
remainder: Remainder
}
pub fn arrnum_int_div(
arrnum : &Vec<u8>,
basenum : u8,
base_ten_int_divisor : u8,
rem_in : Remainder
) -> DivOut {
let mut rem_out = Remainder {
position: rem_in.position,
replace : None
};
let mut bufferval : u16 = 0;
let base : u16 = basenum as u16;
let divisor : u16 = base_ten_int_divisor as u16;
let mut quotient = 0;
let mut u_cur : Option<&u8> = Some(match rem_in.replace {
Some(ref u) => { u }
None => { &arrnum[rem_in.position] }
});
let str_f = &arrnum[rem_in.position+1..];
let mut it_f = str_f.iter();
loop {
match u_cur {
Some(u) => {
bufferval += u.clone() as u16;
if bufferval > divisor {
while bufferval >= divisor {
quotient+=1;
bufferval -= divisor;
}
if bufferval == 0 {
rem_out.position +=1;
} else {
rem_out.replace = Some(bufferval as u8);
}
break;
} else {
bufferval *= base;
}
},
None => {
break;
}
}
u_cur = it_f.next().clone();
rem_out.position+=1;
}
DivOut { quotient: quotient, remainder: rem_out }
}
pub fn arrnum_int_add(
arrnum : &Vec<u8>,
basenum : u8,
base_ten_int_term : u8
) -> Vec<u8> {
let mut carry : u16 = base_ten_int_term as u16;
let mut rem : u16;
let mut new_amount : u16;
let base : u16 = basenum as u16;
let mut ret_rev : Vec<u8> = Vec::new();
let mut it = arrnum.iter().rev();
loop {
let i = it.next();
match i {
Some(u) => {
new_amount = (u.clone() as u16) + carry;
rem = new_amount % base;
carry = (new_amount - rem) / base;
ret_rev.push(rem as u8)
},
None => {
while carry != 0 {
rem = carry % base;
carry = (carry - rem) / base;
ret_rev.push(rem as u8);
}
break;
}
}
}
let ret : Vec<u8> =
ret_rev.iter().rev().map(|x| x.clone()).collect();
ret
}
pub fn base_conv_vec(
src : &Vec<u8>,
radix_src : u8,
radix_dest : u8
) -> Vec<u8> {
let mut result : Vec<u8> = Vec::new();
result.push(0);
for i in src {
result = arrnum_int_mult(&result,
radix_dest, radix_src);
result = arrnum_int_add(
&result,
radix_dest,
i.clone()
);
}
result
}
pub fn base_conv_float(
src : &Vec<u8>,
radix_src : u8,
radix_dest : u8
) -> f64 {
//it would require a lot of addl code
// to implement this for arbitrary string input.
//until then, the below operates as an outline
// of how it would work.
let mut result : Vec<u8> = Vec::new();
result.push(0);
let mut factor : f64 = radix_dest as f64;
let radix_src_float : f64 = radix_src as f64;
let mut i = 0;
let mut r :f64 = 0 as f64;
factor /= 10.;
for u in src {
if i > 15 { break; }
i+=1;
factor /= radix_src_float;
r += factor * (u.clone() as f64)
}
r
}
pub fn str_to_arrnum(
src: &str,
radix_def_src : &RadixDef
) -> Vec<u8> {
let mut intermed_in : Vec<u8> = Vec::new();
for c in src.chars() {
match radix_def_src.from_char::<>(c) {
Some(u) => { intermed_in.push(u); }
None => {} //todo err msg on incorrect
}
}
intermed_in
}
pub fn arrnum_to_str(
src: &Vec<u8>,
radix_def_dest : &RadixDef
) -> String {
let mut str_out = String::new();
for u in src.iter() {
match radix_def_dest.from_u8(u.clone()) {
Some(c) => {
str_out.push(c);
}
None => {} //todo
}
}
str_out
}
#[allow(unused_variables)]
pub fn base_conv_str(
src: &str,
radix_def_src : &RadixDef,
radix_def_dest : &RadixDef
) -> String {
let intermed_in : Vec<u8> =
str_to_arrnum(src, radix_def_src);
let intermed_out = base_conv_vec(
&intermed_in,
radix_def_src.get_max(),
radix_def_dest.get_max(),
);
arrnum_to_str(&intermed_out, radix_def_dest)
}
pub trait RadixDef {
fn get_max (&self) -> u8;
fn from_char (&self, x:char) -> Option<u8>;
fn from_u8 (&self, x:u8) -> Option<char>;
}
pub struct RadixTen;
const ZERO_ASC : u8 = '0' as u8;
const UPPER_A_ASC : u8 = 'A' as u8;
const LOWER_A_ASC : u8 = 'a' as u8;
impl RadixDef for RadixTen {
fn get_max(&self) -> u8 { 10 }
fn from_char (&self, c:char) -> Option<u8> {
match c {
'0'...'9' => Some(c as u8 - ZERO_ASC),
_ => None
}
}
fn from_u8 (&self, u:u8) -> Option<char> {
match u {
0...9 => Some((ZERO_ASC + u) as char),
_ => None
}
}
}
pub struct RadixHex;
impl RadixDef for RadixHex {
fn get_max(&self) -> u8 { 16 }
fn from_char (&self, c:char) -> Option<u8> {
match c {
'0'...'9' => Some(c as u8 - ZERO_ASC),
'A'...'F' => Some(c as u8 +10 - UPPER_A_ASC),
'a'...'f' => Some(c as u8 +10 - LOWER_A_ASC),
_ => None
}
}
fn from_u8 (&self, u:u8) -> Option<char> {
match u {
0...9 => Some((ZERO_ASC + u) as char),
10...15 => Some((UPPER_A_ASC + (u-10)) as char),
_ => None
}
}
}
@@ -0,0 +1,84 @@
//! formatter for %g %G decimal subs
use super::super::format_field::FormatField;
use super::super::formatter::{InPrefix,FormatPrimitive,Formatter};
use super::float_common::{FloatAnalysis,
get_primitive_dec,
primitive_to_str_common};
fn get_len_fprim(
fprim : &FormatPrimitive
) -> usize {
let mut len = 0;
if let Some(ref s) = fprim.prefix { len += s.len(); }
if let Some(ref s) = fprim.pre_decimal { len += s.len(); }
if let Some(ref s) = fprim.post_decimal { len += s.len(); }
if let Some(ref s) = fprim.suffix { len += s.len(); }
len
}
pub struct Decf {
as_num : f64
}
impl Decf {
pub fn new() -> Decf {
Decf { as_num: 0.0 }
}
}
impl Formatter for Decf {
fn get_primitive(
&self,
field : &FormatField,
inprefix : &InPrefix,
str_in : &str
) -> Option<FormatPrimitive> {
let second_field = field.second_field.unwrap_or(6)+1;
let analysis = FloatAnalysis::analyze(
str_in,
inprefix,
Some(second_field as usize+1),
None
);
let mut f_sci = get_primitive_dec(
inprefix,
&str_in[inprefix.offset..],
&analysis,
second_field as usize,
Some(*field.field_char == 'G'));
//strip trailing zeroes
match f_sci.post_decimal.clone() {
Some(ref post_dec) => {
let mut i = post_dec.len();
{
let mut it = post_dec.chars();
while let Some(c) = it.next_back() {
if c != '0' { break; }
i-=1;
}
}
if i != post_dec.len() {
f_sci.post_decimal =
Some(String::from(&post_dec[0..i]));
}
}
None => {}
}
let f_fl = get_primitive_dec(
inprefix,
&str_in[inprefix.offset..],
&analysis,
second_field as usize,
None);
Some(if get_len_fprim(&f_fl) >= get_len_fprim(&f_sci) {
f_sci
} else { f_fl })
}
fn primitive_to_str(
&self,
prim: &FormatPrimitive,
field: FormatField) -> String {
primitive_to_str_common(
prim,
&field
)
}
}
@@ -0,0 +1,331 @@
use super::super::format_field::{FormatField};
use super::super::formatter::{InPrefix,Base,FormatPrimitive,warn_incomplete_conv,get_it_at};
use super::base_conv;
use super::base_conv::{RadixDef};
// if the memory, copy, and comparison cost of chars
// becomes an issue, we can always operate in vec<u8> here
// rather than just at de_hex
pub struct FloatAnalysis {
pub len_important: usize,
//none means no decimal point.
pub decimal_pos: Option<usize>,
pub follow: Option<char>
}
impl FloatAnalysis {
pub fn analyze(
str_in: &str,
inprefix: &InPrefix,
max_sd_opt: Option<usize>,
max_after_dec_opt: Option<usize>,
) -> FloatAnalysis {
// this fn assumes
// the input string
// has no leading spaces or 0s
let mut str_it = get_it_at(inprefix.offset, str_in);
let mut ret = FloatAnalysis {
len_important: 0,
decimal_pos: None,
follow: None
};
let mut i=0;
while let Some(c) = str_it.next() { match c{
e @ '0'...'9' | e @ 'A'...'F' | e @ 'a'...'f' => {
match inprefix.radix_in {
Base::Ten => {
match e {
'0'...'9' => {},
_ => {
warn_incomplete_conv(str_in);
break;
}
}
}
_ => {}
}
if let Some(max_sd) = max_sd_opt {
if i == max_sd {
//follow is used in cases of %g
//where the character right after the last
//sd is considered is rounded affecting
//the previous digit in 1/2 of instances
ret.follow = Some(e);
} else if ret.decimal_pos.is_some() && i > max_sd {
break;
}
}
if let Some(p) = ret.decimal_pos {
if let Some(max_after_dec) = max_after_dec_opt {
if (i-1) - p == max_after_dec {
break
}
}
}
},
'.' => {
if ret.decimal_pos.is_none() {
ret.decimal_pos = Some(i);
} else {
warn_incomplete_conv(str_in);
break;
}
}
_ => {
println!("awarn2");
warn_incomplete_conv(str_in);
break;
}
}; i+=1; }
ret.len_important = i;
ret
}
}
fn de_hex(
src: &str,
before_decimal: bool
) -> String {
let rten = base_conv::RadixTen;
let rhex = base_conv::RadixHex;
if before_decimal {
base_conv::base_conv_str(src, &rhex, &rten)
} else {
let as_arrnum_hex =base_conv::str_to_arrnum(src, &rhex);
let s = format!("{}", base_conv::base_conv_float(
&as_arrnum_hex,
rhex.get_max(),
rten.get_max()
));
if s.len() > 2 {
String::from(&s[2..])
} else {
//zero
s
}
}
}
// takes a string in,
// truncates to a position,
// bumps the last digit up one,
// and if the digit was nine
// propagate to the next, etc.
fn _round_str_from(
in_str : &str,
position : usize
) -> (String, bool) {
let mut it=in_str[0..position].chars();
let mut rev = String::new();
let mut i = position;
let mut finished_in_dec=false;
while let Some(c)=it.next_back() {
i-=1;
match c {
'9' => { rev.push('0'); }
e @ _ => {
rev.push(
((e as u8)+1) as char);
finished_in_dec = true;
break;
}
}
}
let mut fwd = String::from(&in_str[0..i]);
for ch in rev.chars().rev() {
fwd.push(ch);
}
(fwd, finished_in_dec)
}
fn round_terminal_digit(
before_dec: String,
after_dec: String,
position: usize
) -> (String, String) {
if position < after_dec.len() {
let digit_at_pos:char;
{
digit_at_pos=(&after_dec[position..position+1])
.chars().next().expect("");
}
match digit_at_pos {
'5'...'9' => {
let (new_after_dec, finished_in_dec) =
_round_str_from(&after_dec, position);
if finished_in_dec {
return (before_dec, new_after_dec)
} else {
let (new_before_dec, _) =
_round_str_from(&before_dec,
before_dec.len());
return (new_before_dec, new_after_dec)
}
//TODO
},
_ =>{ }
}
}
(before_dec, after_dec)
}
pub fn get_primitive_dec(
inprefix : &InPrefix,
str_in : &str,
analysis : &FloatAnalysis,
last_dec_place : usize,
sci_mode : Option<bool>
) -> FormatPrimitive {
let mut f : FormatPrimitive = Default::default();
//add negative sign section
if inprefix.sign == -1 {
f.prefix = Some(String::from("-"));
}
// assign the digits before and after the decimal points
// to separate slices. If no digits after decimal point,
// assign 0
let (mut first_segment_raw, second_segment_raw) =
match analysis.decimal_pos {
Some(pos) => {
(&str_in[..pos], &str_in[pos+1..])
},
None => { (&str_in[..], "0") }
};
if first_segment_raw.len() == 0 {
first_segment_raw = "0";
}
// convert to string, de_hexifying if input is in hex.
let (first_segment, second_segment) =
match inprefix.radix_in {
Base::Hex => {
(de_hex(first_segment_raw, true),
de_hex(second_segment_raw, false))
}
_ => {
(String::from(first_segment_raw),
String::from(second_segment_raw))
}
};
let (pre_dec_unrounded, post_dec_unrounded, mantissa) =
if sci_mode.is_some() {
if first_segment.len() > 1 {
let mut post_dec = String::from(&first_segment[1..]);
post_dec.push_str(&second_segment);
(String::from(&first_segment[0..1]),
post_dec,
first_segment.len() as isize -1)
} else {
match first_segment.chars().next() {
Some('0') => {
let mut it = second_segment.chars().enumerate();
let mut m : isize = 0;
let mut pre = String::from("0");
let mut post = String::from("0");
while let Some((i,c)) = it.next() { match c {
'0' => {}
_ => {
m=((i as isize)+1) * -1;
pre = String::from(
&second_segment[i..i+1]);
post = String::from(
&second_segment[i+1..]);
break;
}
} }
(pre, post, m)
},
Some(_) => {
(first_segment, second_segment, 0)
},
None => {
panic!(
"float_common: no chars in first segment.");
}
}
}
} else {
(first_segment, second_segment, 0)
};
let (pre_dec_draft, post_dec_draft) =
round_terminal_digit(pre_dec_unrounded,
post_dec_unrounded,
last_dec_place-1);
f.pre_decimal=Some(pre_dec_draft);
f.post_decimal=Some(post_dec_draft);
if let Some(capitalized) = sci_mode {
let si_ind = if capitalized { 'E' } else { 'e' };
f.suffix=Some(if mantissa >=0 {
format!("{}+{:02}", si_ind, mantissa)
} else {
//negative sign is considered in format!s
// leading zeroes
format!("{}{:03}", si_ind, mantissa)
});
}
f
}
pub fn primitive_to_str_common(
prim: &FormatPrimitive,
field: &FormatField
) -> String {
let mut final_str = String::new();
match prim.prefix {
Some(ref prefix) => {
final_str.push_str(&prefix);
},
None => {}
}
match prim.pre_decimal {
Some(ref pre_decimal) => {
final_str.push_str(&pre_decimal);
}
None => {
panic!("error, format primitives provided to int, will, incidentally under correct behavior, always have a pre_dec value.");
}
}
let decimal_places = field.second_field.unwrap_or(6);
match prim.post_decimal {
Some(ref post_decimal) => {
if post_decimal.len() > 0 && decimal_places > 0 {
final_str.push('.');
let len_avail=post_decimal.len() as u32;
if decimal_places >= len_avail {
//println!("dec {}, len avail {}", decimal_places, len_avail);
final_str.push_str(post_decimal);
if *field.field_char != 'g' &&
*field.field_char != 'G' {
let diff = decimal_places - len_avail;
for _ in 0..diff {
final_str.push('0');
}
}
} else {
//println!("printing to only {}", decimal_places);
final_str.push_str(
&post_decimal[0..decimal_places as usize]);
}
}
}
None => {
panic!("error, format primitives provided to int, will, incidentally under correct behavior, always have a pre_dec value.");
}
}
match prim.suffix {
Some(ref suffix) => {
final_str.push_str(suffix);
},
None => {}
}
final_str
}
@@ -0,0 +1,48 @@
//! formatter for %f %F common-notation floating-point subs
use super::super::format_field::FormatField;
use super::super::formatter::{InPrefix,FormatPrimitive,Formatter};
use super::float_common::{FloatAnalysis,
get_primitive_dec,
primitive_to_str_common};
pub struct Floatf {
as_num : f64
}
impl Floatf {
pub fn new() -> Floatf {
Floatf { as_num: 0.0 }
}
}
impl Formatter for Floatf {
fn get_primitive(
&self,
field : &FormatField,
inprefix : &InPrefix,
str_in : &str
) -> Option<FormatPrimitive> {
let second_field = field.second_field.unwrap_or(6)+1;
let analysis = FloatAnalysis::analyze(
&str_in,
inprefix,
None,
Some(second_field as usize)
);
let f = get_primitive_dec(
inprefix,
&str_in[inprefix.offset..],
&analysis,
second_field as usize,
None);
Some(f)
}
fn primitive_to_str(
&self,
prim: &FormatPrimitive,
field: FormatField) -> String {
primitive_to_str_common(
prim,
&field
)
}
}
@@ -0,0 +1,297 @@
//! formatter for unsigned and signed int subs
//! unsigned ints: %X %x (hex u64) %o (octal u64) %u (base ten u64)
//! signed ints: %i %d (both base ten i64)
use std::u64;
use std::i64;
use super::super::format_field::FormatField;
use super::super::formatter::{InPrefix,FormatPrimitive,Base,Formatter,warn_incomplete_conv,get_it_at};
pub struct Intf {
a : u32
}
// see the Intf::analyze() function below
struct IntAnalysis {
check_past_max : bool,
past_max : bool,
is_zero: bool,
len_digits: u8
}
impl Intf {
pub fn new() -> Intf {
Intf { a:0 }
}
// take a ref to argument string, and basic information
// about prefix (offset, radix, sign), and analyze string
// to gain the IntAnalysis information above
// check_past_max: true if the number *may* be above max,
// but we don't know either way. One of several reasons
// we may have to parse as int.
// past_max: true if the object is past max, false if not
// in the future we should probably combine these into an
// Option<bool>
// is_zero: true if number is zero, false otherwise
// len_digits: length of digits used to create the int
// important, for example, if we run into a non-valid character
fn analyze(
str_in: &str,
signed_out: bool,
inprefix: &InPrefix
) -> IntAnalysis {
// the maximum number of digits we could conceivably
// have before the decimal point without exceeding the
// max
let mut str_it = get_it_at(inprefix.offset, str_in);
let max_sd_in =
if signed_out {
match inprefix.radix_in {
Base::Ten => 19,
Base::Octal => 21,
Base::Hex => 16
}
} else {
match inprefix.radix_in {
Base::Ten => 20,
Base::Octal => 22,
Base::Hex => 16
}
};
let mut ret = IntAnalysis {
check_past_max: false,
past_max: false,
is_zero: false,
len_digits : 0
};
// todo turn this to a while let now that we know
// no special behavior on EOI break
loop {
let c_opt = str_it.next();
if let Some(c) = c_opt { match c {
'0'...'9' | 'a'...'f' | 'A'...'F' => {
if ret.len_digits == 0 && c == '0' {
ret.is_zero = true;
} else if ret.is_zero {
ret.is_zero = false;
}
ret.len_digits += 1;
if ret.len_digits == max_sd_in {
if let Some(next_ch) = str_it.next() {
match next_ch {
'0'...'9' => {
ret.past_max = true;
}
_ => {
// force conversion
// to check if its above max.
// todo: spin out convert
// into fn, call it here to try
// read val, on Ok()
// save val for reuse later
// that way on same-base in and out
// we don't needlessly convert int
// to str, we can just copy it over.
ret.check_past_max = true;
str_it.put_back(next_ch);
}
}
if ret.past_max { break; }
} else { ret.check_past_max = true; }
}
}
_ => {
warn_incomplete_conv(str_in);
break;
}
} } else {
//breaks on EOL
break;
}
}
ret
}
// get a FormatPrimitive of the maximum value for the field char
// and given sign
fn get_max(
fchar : char,
sign : i8
) -> FormatPrimitive {
let mut fmt_prim : FormatPrimitive = Default::default();
fmt_prim.pre_decimal = Some(String::from(match fchar {
'd' | 'i' => match sign {
1 => "9223372036854775807",
_ => {
fmt_prim.prefix = Some(String::from("-"));
"9223372036854775808"
}
},
'x' | 'X' => "ffffffffffffffff",
'o' => "1777777777777777777777",
'u' | _ => "18446744073709551615"
}));
fmt_prim
}
// conv_from_segment contract:
// 1. takes
// - a string that begins with a non-zero digit, and proceeds
// with zero or more following digits until the end of the string
// - a radix to interpret those digits as
// - a char that communicates:
// whether to interpret+output the string as an i64 or u64
// what radix to write the parsed number as.
// 2. parses it as a rust integral type
// 3. outputs FormatPrimitive with:
// - if the string falls within bounds:
// number parsed and written in the correct radix
// - if the string falls outside bounds:
// for i64 output, the int minimum or int max (depending on sign)
// for u64 output, the u64 max in the output radix
fn conv_from_segment(
segment : &str,
radix_in : Base,
fchar : char,
sign : i8,
) ->
FormatPrimitive
{
match fchar {
'i' | 'd' => {
match i64::from_str_radix(segment, radix_in as u32) {
Ok(i) => {
let mut fmt_prim : FormatPrimitive =
Default::default();
if sign == -1 {
fmt_prim.prefix = Some(String::from("-"));
}
fmt_prim.pre_decimal =
Some(format!("{}", i));
fmt_prim
}
Err(_) => Intf::get_max(fchar, sign)
}
},
_ => {
match u64::from_str_radix(segment, radix_in as u32) {
Ok(u) => {
let mut fmt_prim : FormatPrimitive =
Default::default();
let u_f =
if sign == -1 { u64::MAX - (u -1)
} else { u };
fmt_prim.pre_decimal = Some(match fchar {
'X' => format!("{:X}", u_f),
'x' => format!("{:x}", u_f),
'o' => format!("{:o}", u_f),
_ => format!("{}", u_f)
});
fmt_prim
}
Err(_) => Intf::get_max(fchar, sign)
}
}
}
}
}
impl Formatter for Intf {
fn get_primitive(
&self,
field : &FormatField,
inprefix : &InPrefix,
str_in : &str
) -> Option<FormatPrimitive> {
let begin = inprefix.offset;
//get information about the string. see Intf::Analyze
// def above.
let convert_hints = Intf::analyze(str_in,
*field.field_char == 'i' || *field.field_char == 'd',
inprefix);
//We always will have a formatprimitive to return
Some(if convert_hints.len_digits == 0 || convert_hints.is_zero {
//if non-digit or end is reached before a non-zero digit
let mut fmt_prim : FormatPrimitive = Default::default();
fmt_prim.pre_decimal=Some(String::from("0"));
fmt_prim
} else if ! convert_hints.past_max {
//if the number is or may be below the bounds limit
let radix_out = match *field.field_char {
'd' | 'i' | 'u' => Base::Ten,
'x' | 'X' => Base::Hex,
'o' | _ => Base::Octal
};
let radix_mismatch = ! radix_out.eq(&inprefix.radix_in);
let decr_from_max :bool = inprefix.sign == -1 &&
*field.field_char !='i';
let end = begin + convert_hints.len_digits as usize;
// convert to int if any one of these is true:
// - number of digits in int indicates it may be past max
// - we're subtracting from the max
// - we're converting the base
if convert_hints.check_past_max
|| decr_from_max || radix_mismatch {
//radix of in and out is the same.
let segment = String::from(&str_in[begin..end]);
let m = Intf::conv_from_segment(
&segment,
inprefix.radix_in.clone(),
*field.field_char,
inprefix.sign);
m
} else {
//otherwise just do a straight string copy.
let mut fmt_prim : FormatPrimitive = Default::default();
// this is here and not earlier because
// zero doesn't get a sign, and conv_from_segment
// creates its format primitive separately
if inprefix.sign == -1 && *field.field_char == 'i' {
fmt_prim.prefix = Some(String::from("-"));
}
fmt_prim.pre_decimal = Some(String::from
(&str_in[begin..end]));
fmt_prim
}
} else {
Intf::get_max(*field.field_char, inprefix.sign)
})
}
fn primitive_to_str(
&self,
prim: &FormatPrimitive,
field: FormatField) -> String {
let mut finalstr : String = String::new();
match prim.prefix {
Some(ref prefix) => {
finalstr.push_str(&prefix);
},
None => {}
}
//integral second fields is zero-padded minimum-width
//which gets handled before general minimum-width
match prim.pre_decimal {
Some(ref pre_decimal) => {
match field.second_field {
Some(min) => {
let mut i = min;
let len = pre_decimal.len() as u32;
while i > len {
finalstr.push('0');
i -= 1;
}
}
None => {}
}
finalstr.push_str(&pre_decimal);
}
None => {
panic!("error, format primitives provided to int, will, incidentally under correct behavior, always have a pre_dec value.");
}
}
finalstr
}
}
@@ -0,0 +1,6 @@
pub mod intf;
pub mod floatf;
pub mod scif;
pub mod decf;
mod float_common;
mod base_conv;
@@ -0,0 +1,47 @@
//! formatter for %e %E scientific notation subs
use super::super::format_field::FormatField;
use super::super::formatter::{InPrefix,FormatPrimitive,Formatter};
use super::float_common::{FloatAnalysis,
get_primitive_dec,
primitive_to_str_common};
pub struct Scif {
as_num : f64
}
impl Scif {
pub fn new() -> Scif {
Scif { as_num: 0.0 }
}
}
impl Formatter for Scif {
fn get_primitive(
&self,
field : &FormatField,
inprefix : &InPrefix,
str_in : &str
) -> Option<FormatPrimitive> {
let second_field = field.second_field.unwrap_or(6)+1;
let analysis = FloatAnalysis::analyze(
str_in,
inprefix,
Some(second_field as usize+1),
None
);
let f = get_primitive_dec(
inprefix,
&str_in[inprefix.offset..],
&analysis,
second_field as usize,
Some(*field.field_char == 'E'));
Some(f)
}
fn primitive_to_str(
&self,
prim: &FormatPrimitive,
field: FormatField) -> String {
primitive_to_str_common(
prim,
&field
)
}
}
+4
View File
@@ -0,0 +1,4 @@
pub mod format_field;
mod formatter;
mod formatters;
pub mod num_format;

Some files were not shown because too many files have changed in this diff Show More