fixed_macro_impl/
lib.rs

1mod dispatch;
2
3use std::str::FromStr;
4
5use proc_macro::TokenStream;
6use proc_macro_error::{abort, proc_macro_error};
7
8use quote::{format_ident, quote};
9use syn::{
10    parse::{self, Parse, ParseStream},
11    parse_macro_input, Ident, Lit, Token,
12};
13
14struct FixedType {
15    signed: bool,
16    int_bits: u8,
17    frac_bits: u8,
18}
19
20impl FixedType {
21    pub fn type_ident(&self) -> Ident {
22        let int_name = if self.signed { 'I' } else { 'U' };
23        format_ident!("{}{}F{}", int_name, self.int_bits, self.frac_bits)
24    }
25
26    pub fn from_ident(ident: &Ident) -> Result<Self, &'static str> {
27        fn parse_size(s: &str) -> Option<u8> {
28            if s.chars().next()?.is_ascii_digit() {
29                let num = u8::from_str(s).ok()?;
30                if num <= 128 {
31                    return Some(num);
32                }
33            }
34            None
35        }
36        let name = ident.to_string();
37        let signed = match name.chars().next().ok_or("?")? {
38            'I' => true,
39            'U' => false,
40            _ => return Err("type name must start with `I` or `U`"),
41        };
42        let f_pos = name.find('F').ok_or("type name must contain `F`")?;
43        let int_bits = parse_size(&name[1..f_pos]).ok_or("invalid number of integer bits")?;
44        let frac_bits =
45            parse_size(&name[f_pos + 1..]).ok_or("invalid number of fractional bits")?;
46        if ![8, 16, 32, 64, 128].contains(&((int_bits as u16) + (frac_bits as u16))) {
47            return Err("total number of bits must be 8, 16, 32, 64 or 128");
48        }
49        Ok(FixedType {
50            signed,
51            int_bits,
52            frac_bits,
53        })
54    }
55}
56
57fn normalize_float(float: &str) -> Result<String, &'static str> {
58    let mut float = float.to_owned();
59    let mut exp = match float.find('e') {
60        Some(idx) => {
61            let exp = i8::from_str(&float[idx + 1..]).or(Err("exponent out of range"))?;
62            float.truncate(idx);
63            exp
64        }
65        _ => 0,
66    };
67    let idx = float.find('.').unwrap_or(float.len());
68    let mut int = float[..idx].to_owned();
69    let mut frac = float[idx + 1..].to_owned();
70    while exp > 0 {
71        if !frac.is_empty() {
72            int.push(frac.remove(0));
73        } else {
74            int.push('0');
75        }
76        exp -= 1;
77    }
78    while exp < 0 {
79        if !int.is_empty() {
80            frac.insert(0, int.remove(int.len() - 1));
81        } else {
82            frac.insert(0, '0');
83        }
84        exp += 1;
85    }
86    Ok(format!("{}.{}", int, frac))
87}
88
89fn parse_fixed_literal(lit: &Lit) -> Result<String, &'static str> {
90    match *lit {
91        Lit::Int(ref int) => {
92            if !int.suffix().is_empty() {
93                Err("unexpected suffix")
94            } else {
95                Ok(int.base10_digits().into())
96            }
97        }
98        Lit::Float(ref float) => {
99            if !float.suffix().is_empty() {
100                Err("unexpected suffix")
101            } else {
102                let float = normalize_float(float.base10_digits())?;
103                Ok(float)
104            }
105        }
106        _ => Err("expected int or float"),
107    }
108}
109
110struct FixedInput {
111    ident: Ident,
112    neg: bool,
113    lit: Lit,
114}
115
116impl Parse for FixedInput {
117    fn parse(input: ParseStream) -> parse::Result<Self> {
118        let mut neg = false;
119        if input.peek(Token![-]) {
120            neg = true;
121            let _ = input.parse::<Token![-]>();
122        }
123        let lit = input.parse()?;
124        input.parse::<Token![:]>()?;
125        let ident = input.parse()?;
126        Ok(Self { ident, neg, lit })
127    }
128}
129
130/// Create a fixed-point constant value which is parsed at compile time.
131///
132/// The literal accepted by the macro uses the same syntax for int and float literals
133/// as Rust itself, this includes underscores and scientific notation.
134///
135/// The syntax of the macro is as follows:
136///
137/// ```ignore
138/// fixed!(<value>: <type>)
139/// ```
140///
141/// where `<value>` is an integer literal or a float literal, and `<type>` is either of the
142/// form `I<i>F<f>` or `U<i>F<f>`, matching one of the type aliases provided in
143/// [`fixed::types`](https://docs.rs/fixed/latest/fixed/types/index.html). Note in particular
144/// that `<value>` has to be a literal and not an arbitrary arithmetic expression, and that
145/// `<type>` is considered a special identifier, so that it doesn't have to be imported first.
146///
147/// ### Examples
148///
149/// ```rust
150/// use fixed_macro::fixed;
151/// use fixed::types::U8F8;
152///
153/// let x1 = fixed!(-1.23: I32F32);         // float literal (note, the type is not in scope)
154/// const X2: U8F8 = fixed!(1.2: U8F8);     // can be used to initialize const values
155/// let x3 = fixed!(123: U8F8);             // decimal integers work as well
156/// let x4 = fixed!(0x7B: U8F8);            // and hex/oct/bin integers too
157/// let x5 = fixed!(1_234.567_890: I32F32); // underscores are ignored, same as in rustc
158/// let x7 = fixed!(0.12e+01: U8F8);        // scientific notation is also supported
159/// ```
160#[proc_macro]
161#[proc_macro_error]
162pub fn fixed(input: TokenStream) -> TokenStream {
163    let FixedInput { ident, neg, lit } = parse_macro_input!(input as FixedInput);
164    let ty = match FixedType::from_ident(&ident) {
165        Ok(ty) => ty,
166        Err(err) => abort!(ident.span(), "invalid fixed type: {}", err),
167    };
168    if !ty.signed && neg {
169        abort!(lit.span(), "negative value for an unsigned fixed type");
170    }
171    let literal = match parse_fixed_literal(&lit) {
172        Ok(lit) => format!("{}{}", (if neg { "-" } else { "" }), lit),
173        Err(err) => abort!(lit.span(), "invalid fixed value: {}", err),
174    };
175    let bits = match dispatch::fixed_to_literal(ty.int_bits, ty.frac_bits, ty.signed, &literal) {
176        Ok(bits) => bits,
177        Err(err) => abort!(lit.span(), "invalid fixed value: {}", err),
178    };
179    let type_ident = ty.type_ident();
180    let code = quote! { ::fixed_macro::__fixed::types::#type_ident::from_bits(#bits) };
181    code.into()
182}