| #[derive(Debug, Copy, Clone, Eq, PartialEq)] |
| enum OpType { |
| Operator, |
| Function, |
| } |
| |
| use self::OpType::*; |
| |
| struct Op { |
| token: &'static str, |
| operator: &'static str, |
| example: (&'static str, &'static str), |
| precedence: u8, |
| n_args: u8, |
| op_type: OpType, |
| } |
| |
| pub fn write_op_macro() -> ::std::io::Result<()> { |
| let out_dir = ::std::env::var("OUT_DIR").unwrap(); |
| let dest = ::std::path::Path::new(&out_dir).join("op.rs"); |
| let mut f = ::std::fs::File::create(&dest).unwrap(); |
| |
| // Operator precedence is taken from |
| // https://doc.rust-lang.org/reference.html#operator-precedence |
| // |
| // We choose 16 as the highest precedence (functions are set to 255 but it doesn't matter |
| // for them). We also only use operators that are left associative so we don't have to worry |
| // about that. |
| let ops = &[ |
| Op { |
| token: "*", |
| operator: "Prod", |
| example: ("P2 * P3", "P6"), |
| precedence: 16, |
| n_args: 2, |
| op_type: Operator, |
| }, |
| Op { |
| token: "/", |
| operator: "Quot", |
| example: ("P6 / P2", "P3"), |
| precedence: 16, |
| n_args: 2, |
| op_type: Operator, |
| }, |
| Op { |
| token: "%", |
| operator: "Mod", |
| example: ("P5 % P3", "P2"), |
| precedence: 16, |
| n_args: 2, |
| op_type: Operator, |
| }, |
| Op { |
| token: "+", |
| operator: "Sum", |
| example: ("P2 + P3", "P5"), |
| precedence: 15, |
| n_args: 2, |
| op_type: Operator, |
| }, |
| Op { |
| token: "-", |
| operator: "Diff", |
| example: ("P2 - P3", "N1"), |
| precedence: 15, |
| n_args: 2, |
| op_type: Operator, |
| }, |
| Op { |
| token: "<<", |
| operator: "Shleft", |
| example: ("U1 << U5", "U32"), |
| precedence: 14, |
| n_args: 2, |
| op_type: Operator, |
| }, |
| Op { |
| token: ">>", |
| operator: "Shright", |
| example: ("U32 >> U5", "U1"), |
| precedence: 14, |
| n_args: 2, |
| op_type: Operator, |
| }, |
| Op { |
| token: "&", |
| operator: "And", |
| example: ("U5 & U3", "U1"), |
| precedence: 13, |
| n_args: 2, |
| op_type: Operator, |
| }, |
| Op { |
| token: "^", |
| operator: "Xor", |
| example: ("U5 ^ U3", "U6"), |
| precedence: 12, |
| n_args: 2, |
| op_type: Operator, |
| }, |
| Op { |
| token: "|", |
| operator: "Or", |
| example: ("U5 | U3", "U7"), |
| precedence: 11, |
| n_args: 2, |
| op_type: Operator, |
| }, |
| Op { |
| token: "==", |
| operator: "Eq", |
| example: ("P5 == P3 + P2", "True"), |
| precedence: 10, |
| n_args: 2, |
| op_type: Operator, |
| }, |
| Op { |
| token: "!=", |
| operator: "NotEq", |
| example: ("P5 != P3 + P2", "False"), |
| precedence: 10, |
| n_args: 2, |
| op_type: Operator, |
| }, |
| Op { |
| token: "<=", |
| operator: "LeEq", |
| example: ("P6 <= P3 + P2", "False"), |
| precedence: 10, |
| n_args: 2, |
| op_type: Operator, |
| }, |
| Op { |
| token: ">=", |
| operator: "GrEq", |
| example: ("P6 >= P3 + P2", "True"), |
| precedence: 10, |
| n_args: 2, |
| op_type: Operator, |
| }, |
| Op { |
| token: "<", |
| operator: "Le", |
| example: ("P4 < P3 + P2", "True"), |
| precedence: 10, |
| n_args: 2, |
| op_type: Operator, |
| }, |
| Op { |
| token: ">", |
| operator: "Gr", |
| example: ("P5 < P3 + P2", "False"), |
| precedence: 10, |
| n_args: 2, |
| op_type: Operator, |
| }, |
| Op { |
| token: "cmp", |
| operator: "Compare", |
| example: ("cmp(P2, P3)", "Less"), |
| precedence: !0, |
| n_args: 2, |
| op_type: Function, |
| }, |
| Op { |
| token: "sqr", |
| operator: "Square", |
| example: ("sqr(P2)", "P4"), |
| precedence: !0, |
| n_args: 1, |
| op_type: Function, |
| }, |
| Op { |
| token: "abs", |
| operator: "AbsVal", |
| example: ("abs(N2)", "P2"), |
| precedence: !0, |
| n_args: 1, |
| op_type: Function, |
| }, |
| Op { |
| token: "cube", |
| operator: "Cube", |
| example: ("cube(P2)", "P8"), |
| precedence: !0, |
| n_args: 1, |
| op_type: Function, |
| }, |
| Op { |
| token: "pow", |
| operator: "Exp", |
| example: ("pow(P2, P3)", "P8"), |
| precedence: !0, |
| n_args: 2, |
| op_type: Function, |
| }, |
| Op { |
| token: "min", |
| operator: "Minimum", |
| example: ("min(P2, P3)", "P2"), |
| precedence: !0, |
| n_args: 2, |
| op_type: Function, |
| }, |
| Op { |
| token: "max", |
| operator: "Maximum", |
| example: ("max(P2, P3)", "P3"), |
| precedence: !0, |
| n_args: 2, |
| op_type: Function, |
| }, |
| ]; |
| |
| use std::io::Write; |
| write!( |
| f, |
| " |
| /** |
| Convenient type operations. |
| |
| Any types representing values must be able to be expressed as `ident`s. That means they need to be |
| in scope. |
| |
| For example, `P5` is okay, but `typenum::P5` is not. |
| |
| You may combine operators arbitrarily, although doing so excessively may require raising the |
| recursion limit. |
| |
| # Example |
| ```rust |
| #![recursion_limit=\"128\"] |
| #[macro_use] extern crate typenum; |
| use typenum::consts::*; |
| |
| fn main() {{ |
| assert_type!( |
| op!(min((P1 - P2) * (N3 + N7), P5 * (P3 + P4)) == P10) |
| ); |
| }} |
| ``` |
| Operators are evaluated based on the operator precedence outlined |
| [here](https://doc.rust-lang.org/reference.html#operator-precedence). |
| |
| The full list of supported operators and functions is as follows: |
| |
| {} |
| |
| They all expand to type aliases defined in the `operator_aliases` module. Here is an expanded list, |
| including examples: |
| |
| ", |
| ops.iter() |
| .map(|op| format!("`{}`", op.token)) |
| .collect::<Vec<_>>() |
| .join(", ") |
| )?; |
| |
| //write!(f, "Token | Alias | Example\n ===|===|===\n")?; |
| |
| for op in ops.iter() { |
| write!( |
| f, |
| "---\nOperator `{token}`. Expands to `{operator}`. |
| |
| ```rust |
| # #[macro_use] extern crate typenum; |
| # use typenum::*; |
| # fn main() {{ |
| assert_type_eq!(op!({ex0}), {ex1}); |
| # }} |
| ```\n |
| ", |
| token = op.token, |
| operator = op.operator, |
| ex0 = op.example.0, |
| ex1 = op.example.1 |
| )?; |
| } |
| |
| write!( |
| f, |
| "*/ |
| #[macro_export] |
| macro_rules! op {{ |
| ($($tail:tt)*) => ( __op_internal__!($($tail)*) ); |
| }} |
| |
| #[doc(hidden)] |
| #[macro_export] |
| macro_rules! __op_internal__ {{ |
| " |
| )?; |
| |
| // We first us the shunting-yard algorithm to produce our tokens in Polish notation. |
| // See: https://en.wikipedia.org/wiki/Shunting-yard_algorithm |
| |
| // Note: Due to macro asymmetry, "the top of the stack" refers to the first element, not the |
| // last |
| |
| // ----------------------------------------------------------------------------------------- |
| // Stage 1: There are tokens to be read: |
| |
| // ------- |
| // Case 1: Token is a function => Push it onto the stack: |
| for fun in ops.iter().filter(|f| f.op_type == Function) { |
| write!( |
| f, |
| " |
| (@stack[$($stack:ident,)*] @queue[$($queue:ident,)*] @tail: {f_token} $($tail:tt)*) => ( |
| __op_internal__!(@stack[{f_op}, $($stack,)*] @queue[$($queue,)*] @tail: $($tail)*) |
| );", |
| f_token = fun.token, |
| f_op = fun.operator |
| )?; |
| } |
| |
| // ------- |
| // Case 2: Token is a comma => Until the top of the stack is a LParen, |
| // Pop operators from stack to queue |
| |
| // Base case: Top of stack is LParen, ditch comma and continue |
| write!( |
| f, |
| " |
| (@stack[LParen, $($stack:ident,)*] @queue[$($queue:ident,)*] @tail: , $($tail:tt)*) => ( |
| __op_internal__!(@stack[LParen, $($stack,)*] @queue[$($queue,)*] @tail: $($tail)*) |
| );" |
| )?; |
| // Recursive case: Not LParen, pop from stack to queue |
| write!( |
| f, |
| " |
| (@stack[$stack_top:ident, $($stack:ident,)*] @queue[$($queue:ident,)*] @tail: , $($tail:tt)*) => ( |
| __op_internal__!(@stack[$($stack,)*] @queue[$stack_top, $($queue,)*] @tail: , $($tail)*) |
| );" |
| )?; |
| |
| // ------- |
| // Case 3: Token is an operator, o1: |
| for o1 in ops.iter().filter(|op| op.op_type == Operator) { |
| // If top of stack is operator o2 with o1.precedence <= o2.precedence, |
| // Then pop o2 off stack onto queue: |
| for o2 in ops.iter() |
| .filter(|op| op.op_type == Operator) |
| .filter(|o2| o1.precedence <= o2.precedence) |
| { |
| write!( |
| f, |
| " |
| (@stack[{o2_op}, $($stack:ident,)*] @queue[$($queue:ident,)*] @tail: {o1_token} $($tail:tt)*) => ( |
| __op_internal__!(@stack[$($stack,)*] @queue[{o2_op}, $($queue,)*] @tail: {o1_token} $($tail)*) |
| );", |
| o2_op = o2.operator, |
| o1_token = o1.token |
| )?; |
| } |
| // Base case: push o1 onto stack |
| write!( |
| f, |
| " |
| (@stack[$($stack:ident,)*] @queue[$($queue:ident,)*] @tail: {o1_token} $($tail:tt)*) => ( |
| __op_internal__!(@stack[{o1_op}, $($stack,)*] @queue[$($queue,)*] @tail: $($tail)*) |
| );", |
| o1_op = o1.operator, |
| o1_token = o1.token |
| )?; |
| } |
| |
| // ------- |
| // Case 4: Token is "(": push it onto stack as "LParen". Also convert the ")" to "RParen" to |
| // appease the macro gods: |
| write!( |
| f, |
| " |
| (@stack[$($stack:ident,)*] @queue[$($queue:ident,)*] @tail: ( $($stuff:tt)* ) $($tail:tt)* ) |
| => ( |
| __op_internal__!(@stack[LParen, $($stack,)*] @queue[$($queue,)*] |
| @tail: $($stuff)* RParen $($tail)*) |
| );" |
| )?; |
| |
| // ------- |
| // Case 5: Token is "RParen": |
| // 1. Pop from stack to queue until we see an "LParen", |
| // 2. Kill the "LParen", |
| // 3. If the top of the stack is a function, pop it onto the queue |
| // 2. Base case: |
| write!( |
| f, |
| " |
| (@stack[LParen, $($stack:ident,)*] @queue[$($queue:ident,)*] @tail: RParen $($tail:tt)*) => ( |
| __op_internal__!(@rp3 @stack[$($stack,)*] @queue[$($queue,)*] @tail: $($tail)*) |
| );" |
| )?; |
| // 1. Recursive case: |
| write!( |
| f, |
| " |
| (@stack[$stack_top:ident, $($stack:ident,)*] @queue[$($queue:ident,)*] @tail: RParen $($tail:tt)*) |
| => ( |
| __op_internal__!(@stack[$($stack,)*] @queue[$stack_top, $($queue,)*] @tail: RParen $($tail)*) |
| );" |
| )?; |
| // 3. Check for function: |
| for fun in ops.iter().filter(|f| f.op_type == Function) { |
| write!( |
| f, |
| " |
| (@rp3 @stack[{fun_op}, $($stack:ident,)*] @queue[$($queue:ident,)*] @tail: $($tail:tt)*) => ( |
| __op_internal__!(@stack[$($stack,)*] @queue[{fun_op}, $($queue,)*] @tail: $($tail)*) |
| );", |
| fun_op = fun.operator |
| )?; |
| } |
| // 3. If no function found: |
| write!( |
| f, |
| " |
| (@rp3 @stack[$($stack:ident,)*] @queue[$($queue:ident,)*] @tail: $($tail:tt)*) => ( |
| __op_internal__!(@stack[$($stack,)*] @queue[$($queue,)*] @tail: $($tail)*) |
| );" |
| )?; |
| |
| // ------- |
| // Case 6: Token is a number: Push it onto the queue |
| write!( |
| f, |
| " |
| (@stack[$($stack:ident,)*] @queue[$($queue:ident,)*] @tail: $num:ident $($tail:tt)*) => ( |
| __op_internal__!(@stack[$($stack,)*] @queue[$num, $($queue,)*] @tail: $($tail)*) |
| );" |
| )?; |
| |
| // ------- |
| // Case 7: Out of tokens: |
| // Base case: Stack empty: Start evaluating |
| write!( |
| f, |
| " |
| (@stack[] @queue[$($queue:ident,)*] @tail: ) => ( |
| __op_internal__!(@reverse[] @input: $($queue,)*) |
| );" |
| )?; |
| // Recursive case: Pop stack to queue |
| write!( |
| f, |
| " |
| (@stack[$stack_top:ident, $($stack:ident,)*] @queue[$($queue:ident,)*] @tail:) => ( |
| __op_internal__!(@stack[$($stack,)*] @queue[$stack_top, $($queue,)*] @tail: ) |
| );" |
| )?; |
| |
| // ----------------------------------------------------------------------------------------- |
| // Stage 2: Reverse so we have RPN |
| write!( |
| f, |
| " |
| (@reverse[$($revved:ident,)*] @input: $head:ident, $($tail:ident,)* ) => ( |
| __op_internal__!(@reverse[$head, $($revved,)*] @input: $($tail,)*) |
| );" |
| )?; |
| write!( |
| f, |
| " |
| (@reverse[$($revved:ident,)*] @input: ) => ( |
| __op_internal__!(@eval @stack[] @input[$($revved,)*]) |
| );" |
| )?; |
| |
| // ----------------------------------------------------------------------------------------- |
| // Stage 3: Evaluate in Reverse Polish Notation |
| // Operators / Operators with 2 args: |
| for op in ops.iter().filter(|op| op.n_args == 2) { |
| // Note: We have to switch $a and $b here, otherwise non-commutative functions are backwards |
| write!( |
| f, |
| " |
| (@eval @stack[$a:ty, $b:ty, $($stack:ty,)*] @input[{op}, $($tail:ident,)*]) => ( |
| __op_internal__!(@eval @stack[$crate::{op}<$b, $a>, $($stack,)*] @input[$($tail,)*]) |
| );", |
| op = op.operator |
| )?; |
| } |
| // Operators with 1 arg: |
| for op in ops.iter().filter(|op| op.n_args == 1) { |
| write!( |
| f, |
| " |
| (@eval @stack[$a:ty, $($stack:ty,)*] @input[{op}, $($tail:ident,)*]) => ( |
| __op_internal__!(@eval @stack[$crate::{op}<$a>, $($stack,)*] @input[$($tail,)*]) |
| );", |
| op = op.operator |
| )?; |
| } |
| |
| // Wasn't a function or operator, so must be a value => push onto stack |
| write!( |
| f, |
| " |
| (@eval @stack[$($stack:ty,)*] @input[$head:ident, $($tail:ident,)*]) => ( |
| __op_internal__!(@eval @stack[$head, $($stack,)*] @input[$($tail,)*]) |
| );" |
| )?; |
| |
| // No input left: |
| write!( |
| f, |
| " |
| (@eval @stack[$stack:ty,] @input[]) => ( |
| $stack |
| );" |
| )?; |
| |
| // ----------------------------------------------------------------------------------------- |
| // Stage 0: Get it started |
| write!( |
| f, |
| " |
| ($($tail:tt)* ) => ( |
| __op_internal__!(@stack[] @queue[] @tail: $($tail)*) |
| );" |
| )?; |
| |
| write!( |
| f, |
| " |
| }}" |
| )?; |
| |
| Ok(()) |
| } |