// Panopticon - A libre program analysis library for machine code
// Copyright (C) 2014-2018  The Panopticon Developers
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

use std::env;
use std::fs::File;
use std::io::Write;
use std::path::Path;

#[derive(Clone,Copy,Debug)]
enum Argument {
    Literal,
    LiteralWidth,
    NoOffset,
    Constant,
    Undefined,
}

impl Argument {
    pub fn match_expr(&self, pos: &'static str) -> String {
        match self {
            &Argument::Literal => format!("( ${}:expr )", pos),
            &Argument::LiteralWidth => format!("( ${}:expr ) : ${}_w:tt", pos, pos),
            &Argument::NoOffset => format!("${}:tt : ${}_w:tt", pos, pos),
            &Argument::Constant => format!("[ ${}:tt ] : ${}_w:tt", pos, pos),
            &Argument::Undefined => "?".to_string(),
        }
    }

    pub fn arg_expr(&self, pos: &'static str) -> String {
        match self {
            &Argument::Literal => format!("( ${} )", pos),
            &Argument::LiteralWidth => format!("( ${} ) : ${}_w", pos, pos),
            &Argument::NoOffset => format!("${} : ${}_w", pos, pos),
            &Argument::Constant => format!("[ ${} ] : ${}_w", pos, pos),
            &Argument::Undefined => "?".to_string(),
        }
    }
}

const BOILERPLATE2: &'static str = "
let ret: $crate::Result<Vec<$crate::Statement>> = match stmt[0].sanity_check() {
    Ok(()) => {
        {let _ = &$segs;}
        {let _ = &$names;}
        let mut tail: $crate::Result<Vec<$crate::Statement>> = { rreil2!( ($names,$segs) { $($cdr)* } ) };
        match tail {
            Ok(ref mut other) => {
                stmt.extend(other.drain(..));
                Ok(stmt)
            }
            Err(e) => Err(e),
        }
    }
    Err(e) => Err(e).into(),
};

ret
";

const VARIABLES: &'static [Argument] = &[
    Argument::Literal,
    Argument::LiteralWidth,
    Argument::NoOffset,
];

const VALUES: &'static [Argument] = &[
    Argument::Literal,
    Argument::LiteralWidth,
    Argument::NoOffset,
    Argument::Constant,
    Argument::Undefined,
];

fn write_binary_operations(f: &mut File) {
    f.write_all(
        b"
#[macro_export]
macro_rules! rreil2_binop {
    "
        )
        .unwrap();

    for a in VARIABLES.iter() {
        for b in VALUES.iter() {
            for c in VALUES.iter() {
                f.write_fmt(
                    format_args!(
                        "
    // {:?} := {:?}, {:?}
    ( $names:tt # $segs:tt # $op:ident # {}, {} , {} ; $($cdr:tt)*) => {{{{
        let mut stmt = vec![$crate::Statement::Expression{{
            op: $crate::Operation::$op(rreil_val!($names # {}),rreil_val!($names # {})),
            result: rreil_var!($names # {})
        }}];
        {}
    }}}};
                ",
                a,
                b,
                c,
                a.match_expr("a"),
                b.match_expr("b"),
                c.match_expr("c"),
                b.arg_expr("b"),
                c.arg_expr("c"),
                a.arg_expr("a"),
                BOILERPLATE2
                )
                    )
                    .unwrap();
            }
        }
    }
    f.write_all(
        b"}
    "
    )
        .unwrap();
}

fn write_unary_operations(f: &mut File) {
    f.write_all(
        b"
#[macro_export]
macro_rules! rreil2_unop {
    "
        )
        .unwrap();

    for a in VARIABLES.iter() {
        for b in VALUES.iter() {
            f.write_fmt(
                format_args!(
                    "
    // {:?} := {:?}
    ( $names:tt # $segs:tt # $op:ident # {}, {} ; $($cdr:tt)*) => {{{{
        let mut stmt = vec![$crate::Statement::Expression{{
            op: $crate::Operation::$op(rreil_val!($names # {})),
            result: rreil_var!($names # {})
        }}];
        {}
    }}}};
                ",
                a,
                b,
                a.match_expr("a"),
                b.match_expr("b"),
                b.arg_expr("b"),
                a.arg_expr("a"),
                BOILERPLATE2
                )
                )
                .unwrap();
        }
    }
    f.write_all(
        b"}
    "
    )
        .unwrap();
}

fn write_store_operation(f: &mut File) {
    f.write_all(
        b"
#[macro_export]
macro_rules! rreil_store {
    "
        )
        .unwrap();

    for addr in VALUES.iter() {
        for val in VALUES.iter() {
            // Little Endian Store
            f.write_fmt(format_args!("
    // {:?} := {:?}
    ( $names:tt # $segs:tt # $bank:ident # le # $sz:tt # {} , {} ; $($cdr:tt)*) => {{{{
        let mut stmt = vec![$crate::Statement::Memory{{
            op: $crate::MemoryOperation::Store{{
                segment: $crate::Segment{{
                    name: $segs.insert(&$crate::Name::new(stringify!($bank).into(),None)),
                }},
                endianess: $crate::Endianess::Little,
                bytes: rreil_imm!($sz),
                address: rreil_val!($names # {}),
                value: rreil_val!($names # {}),
            }},
            result: $crate::Segment{{
                name: $segs.insert(&$crate::Name::new(stringify!($bank).into(),None)),
            }},
        }}];
        {}
    }}}};
            ",addr,val,
            addr.match_expr("addr"),val.match_expr("val"),
            addr.arg_expr("addr"),val.arg_expr("val"),
            BOILERPLATE2)).unwrap();

            // Big Endian Store
            f.write_fmt(format_args!("
    // *({:?}) := {:?}
    ( $names:tt # $segs:tt # $bank:ident # be # $sz:tt # {} , {} ; $($cdr:tt)*) => {{{{
        let mut stmt = vec![$crate::Statement::Memory{{
            op: $crate::MemoryOperation::Store{{
                segment: $crate::Segment{{
                    name: $segs.insert(&$crate::Name::new(stringify!($bank).into(),None)),
                }},
                endianess: $crate::Endianess::Big,
                bytes: rreil_imm!($sz),
                address: rreil_val!($names # {}),
                value: rreil_val!($names # {}),
            }},
            result: $crate::Segment{{
                name: $segs.insert(&$crate::Name::new(stringify!($bank).into(),None)),
            }},
        }}];
        {}
    }}}};
            ",addr,val,
            addr.match_expr("addr"),val.match_expr("val"),
            addr.arg_expr("addr"),val.arg_expr("val"),
            BOILERPLATE2)).unwrap();
        }
    }

    f.write_all(b"}").unwrap();
}

fn write_load_operation(f: &mut File) {
    f.write_all(
        b"
#[macro_export]
macro_rules! rreil_load {
    "
        )
        .unwrap();

    for res in VARIABLES.iter() {
        for addr in VALUES.iter() {
            // Little Endian Load
            f.write_fmt(format_args!("
    // {:?} := *({:?})
    ( $names:tt # $segs:tt # $bank:ident # le # $sz:tt # {} , {} ; $($cdr:tt)*) => {{{{
        let mut stmt = vec![$crate::Statement::Expression{{
            op: $crate::Operation::Load(
                $crate::Segment{{
                    name: $segs.insert(&$crate::Name::new(stringify!($bank).into(),None)),
                }},
                $crate::Endianess::Little,
                rreil_imm!($sz),
                rreil_val!($names # {})
            ),
            result: rreil_var!($names # {}),
        }}];
        {}
    }}}};
            ",res,addr,
            res.match_expr("res"),addr.match_expr("addr"),
            addr.arg_expr("addr"),res.arg_expr("res"),
            BOILERPLATE2)).unwrap();

            // Big Endian Store
            f.write_fmt(format_args!("
    // {:?} := *({:?})
    ( $names:tt # $segs:tt # $bank:ident # be # $sz:tt # {} , {} ; $($cdr:tt)*) => {{{{
        let mut stmt = vec![$crate::Statement::Expression{{
            op: $crate::Operation::Load(
                $crate::Segment{{
                    name: $segs.insert(&$crate::Name::new(stringify!($bank).into(),None)),
                }},
                $crate::Endianess::Big,
                rreil_imm!($sz),
                rreil_val!($names # {})
            ),
            result: rreil_var!($names # {}),
        }}];
        {}
    }}}};
            ",res,addr,
            res.match_expr("res"),addr.match_expr("addr"),
            addr.arg_expr("addr"),res.arg_expr("res"),
            BOILERPLATE2)).unwrap();
        }
    }

    f.write_all(b"}").unwrap();
}

fn write_extraction_operations(f: &mut File) {
    f.write_all(
        b"
#[macro_export]
macro_rules! rreil2_extop {
    "
        )
        .unwrap();

    for a in VARIABLES.iter() {
        for b in VALUES.iter() {
            f.write_fmt(
                format_args!(
                    "
    // {:?} := {:?}
    ( $names:tt # $segs:tt # $op:ident # $sz:tt # {}, {} ; $($cdr:tt)*) => {{{{
        let mut stmt = vec![$crate::Statement::Expression{{
            op: $crate::Operation::$op(rreil_imm!($sz),rreil_val!($names # {})),
            result: rreil_var!($names # {})
        }}];
        {}
    }}}};
                ",
                a,
                b,
                a.match_expr("a"),
                b.match_expr("b"),
                b.arg_expr("b"),
                a.arg_expr("a"),
                BOILERPLATE2
                )
                )
                .unwrap();
        }
    }
    f.write_all(
        b"}
    "
    )
        .unwrap();
}

fn write_selection_operations(f: &mut File) {
    f.write_all(
        b"
#[macro_export]
macro_rules! rreil2_selop {
    "
        )
        .unwrap();

    for a in VARIABLES.iter() {
        for b in VALUES.iter() {
            f.write_fmt(
                format_args!(
                    "
    // {:?} := {:?}
    ( $names:tt # $segs:tt # $op:ident # $off:tt # $sz:tt # {}, {} ; $($cdr:tt)*) => {{{{
        let mut stmt = vec![$crate::Statement::Expression{{
            op: $crate::Operation::$op(rreil_imm!($off),rreil_imm!($sz),rreil_val!($names # {})),
            result: rreil_var!($names # {})
        }}];
        {}
    }}}};
                ",
                a,
                b,
                a.match_expr("a"),
                b.match_expr("b"),
                b.arg_expr("b"),
                a.arg_expr("a"),
                BOILERPLATE2
                )
                )
                .unwrap();
        }
    }
    f.write_all(
        b"}
    "
    )
        .unwrap();
}

fn write_call_operations(f: &mut File) {
    f.write_all(
        b"
#[macro_export]
macro_rules! rreil2_callop {
    "
        )
        .unwrap();

    for a in VALUES.iter() {
        f.write_fmt(
            format_args!(
                "
    // call {:?}
    ( $names:tt # $segs:tt # {} ; $($cdr:tt)* ) => {{{{
        let mut stmt = vec![$crate::Statement::Flow{{
            op: $crate::FlowOperation::IndirectCall{{
                target: rreil_var!($names # {})
            }}
        }}];
        {}
    }}}};
                ",
                a,
                a.match_expr("a"),
                a.arg_expr("a"),
                BOILERPLATE2
                )
            )
            .unwrap();
    }
    f.write_all(
        b"}
    "
    )
        .unwrap();
}

fn write_ret_operations(f: &mut File) {
    f.write_fmt(
        format_args!(
            "
#[macro_export]
macro_rules! rreil2_retop {{
    // ret
    ( $names:tt # $segs:tt # ; $($cdr:tt)* ) => {{{{
        let mut stmt = vec![$crate::Statement::Flow{{
            op: $crate::FlowOperation::Return,
        }}];
        {}
    }}}};
}}
"
    ,BOILERPLATE2)).unwrap();
}

fn main() {
    let out_dir = env::var("OUT_DIR").unwrap();
    let dest_path = Path::new(&out_dir).join("rreil.rs");
    let mut f = File::create(&dest_path).unwrap();

    write_binary_operations(&mut f);
    write_unary_operations(&mut f);
    write_store_operation(&mut f);
    write_load_operation(&mut f);
    write_ret_operations(&mut f);
    write_call_operations(&mut f);
    write_extraction_operations(&mut f);
    write_selection_operations(&mut f);
}
