orbitc 1.1.0

The Orbit Compiler Infrastructure.
Documentation
use std::{
    ffi::OsStr,
    fs::write,
    io::{self, Write},
    path::Path,
    process::Command,
};

use c_emit::{CArg, Code as CCode, VarInit};
use tempfile::NamedTempFile;

#[derive(Debug, Clone)]
pub struct Code<S: AsRef<str>> {
    pub lines: Vec<CodeLine<S>>,
}

#[derive(Debug, Clone)]
pub enum CodeLine<S: AsRef<str>> {
    Printf(Vec<Arg<S>>),
    Scanf { name: S, prompt: S },
    Let { name: S, value: Arg<S> },
}

#[derive(Debug, Clone, Copy)]
pub enum Arg<S: AsRef<str>> {
    String(S),
    Ident(S),
    Int32(i32),
    Int64(i64),
    Float(f32),
    Double(f64),
    Bool(bool),
    Char(char),
}

impl<S: AsRef<str>> Code<S> {
    pub fn transpile(&self) -> String {
        let mut c = CCode::new();

        for line in &self.lines {
            match line {
                CodeLine::Printf(v) => {
                    c.include("stdio.h");

                    let mut root = match v.first().expect("Couldn't find root string!") {
                        Arg::String(s) => s.as_ref(),
                        _ => panic!("Couldn't find root string!"),
                    }
                    .to_string();

                    if v.len() > 1 {
                        let mut subs = vec![];

                        for sub in &v[1..] {
                            subs.push(match sub {
                                Arg::String(s) => {
                                    root.push_str("%s");
                                    CArg::String(s.as_ref())
                                }
                                Arg::Ident(ident) => {
                                    root.push_str("%s");
                                    CArg::Ident(ident.as_ref())
                                }
                                Arg::Int32(n) => {
                                    root.push_str("%d");
                                    CArg::Int32(*n)
                                }
                                Arg::Int64(n) => {
                                    root.push_str("%d");
                                    CArg::Int64(*n)
                                }
                                Arg::Float(f) => {
                                    root.push_str("%f");
                                    CArg::Float(*f)
                                }
                                Arg::Double(f) => {
                                    root.push_str("%lf");
                                    CArg::Double(*f)
                                }
                                Arg::Bool(b) => {
                                    root.push_str("%d");
                                    CArg::Bool(*b)
                                }
                                Arg::Char(c) => {
                                    root.push_str("%c");
                                    CArg::Char(*c)
                                }
                            });
                        }

                        let mut args = vec![CArg::String(&root)];

                        args.extend(subs);

                        c.call_func_with_args("printf", args);
                    } else {
                        c.call_func_with_args("printf", vec![CArg::String(&root)]);
                    }
                }
                CodeLine::Scanf { name, prompt } => {
                    c.include("stdio.h");

                    c.new_var(name, VarInit::SizeString(1024));
                    c.call_func_with_args("printf", vec![CArg::String(prompt.as_ref())]);

                    c.call_func_with_args(
                        "scanf",
                        vec![CArg::String("%s"), CArg::Ident(name.as_ref())],
                    );
                }
                CodeLine::Let { name, value } => {
                    let name = name.as_ref();

                    match value {
                        Arg::String(s) => {
                            let s = s.as_ref();
                            c.new_var(name, VarInit::String(s));
                        }
                        Arg::Ident(_) => todo!(),
                        Arg::Int32(i) => {
                            c.new_var(name, VarInit::Int32(*i));
                        }
                        Arg::Int64(i) => {
                            c.new_var(name, VarInit::Int64(*i));
                        }
                        Arg::Float(f) => {
                            c.new_var(name, VarInit::Float(*f));
                        }
                        Arg::Double(f) => {
                            c.new_var(name, VarInit::Double(*f));
                        }
                        Arg::Bool(b) => {
                            c.new_var(name, VarInit::Bool(*b));
                        }
                        Arg::Char(c_) => {
                            c.new_var(name, VarInit::Char(*c_));
                        }
                    };
                }
            }
        }

        c.to_string()
    }

    pub fn export_c<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
        let path = path.as_ref();
        let contents = self.transpile();

        write(path, contents)
    }

    pub fn build<O: AsRef<OsStr>>(&self, path: O) -> io::Result<NamedTempFile> {
        let path = path.as_ref();
        let tmp = NamedTempFile::new()?;

        let tmpath = tmp.path();

        self.export_c(tmpath)?;

        let output = Command::new("gcc")
            .arg("-x")
            .arg("c")
            .arg(tmpath)
            .arg("-o")
            .arg(path)
            .output()
            .expect("Couldn't compile correctly!");

        io::stdout().write_all(&output.stdout)?;
        io::stderr().write_all(&output.stderr)?;

        Ok(tmp)
    }
}

#[cfg(test)]
mod tests {
    use crate::Arg::String;

    use super::{Arg, Code, CodeLine};

    #[test]
    fn test_printf() {
        let c = Code {
            lines: vec![CodeLine::Printf(vec![String("Hello, "), String("world")])],
        };

        assert_eq!(
            c.transpile(),
            r##"#include<stdio.h>
int main() {
printf("Hello, %s","world");
return 0;
}
"##
        )
    }

    #[test]
    fn test_scanf() {
        let c = Code {
            lines: vec![CodeLine::Scanf {
                name: "a",
                prompt: "Enter name: ",
            }],
        };

        assert_eq!(
            c.transpile(),
            r##"#include<stdio.h>
int main() {
char a[1024];
printf("Enter name: ");
scanf("%s",a);
return 0;
}
"##
        )
    }

    #[test]
    fn test_let_string() {
        let c = Code {
            lines: vec![CodeLine::Let {
                name: "s",
                value: Arg::String("Hello, world!"),
            }],
        };

        assert_eq!(
            c.transpile(),
            r#"int main() {
char s[]="Hello, world!";
return 0;
}
"#
        )
    }

    #[test]
    fn test_let_i32() {
        let c = Code {
            lines: vec![CodeLine::Let {
                name: "i",
                value: Arg::Int32(i32::MAX),
            }],
        };

        assert_eq!(
            c.transpile(),
            format!(
                r#"int main() {{
int i={};
return 0;
}}
"#,
                i32::MAX
            )
        )
    }

    #[test]
    fn test_let_i64() {
        let c = Code {
            lines: vec![CodeLine::Let {
                name: "i",
                value: Arg::Int64(i64::MAX),
            }],
        };

        assert_eq!(
            c.transpile(),
            format!(
                r#"int main() {{
int i={};
return 0;
}}
"#,
                i64::MAX
            )
        )
    }

    #[test]
    fn test_let_float() {
        let c = Code {
            lines: vec![CodeLine::Let {
                name: "f",
                value: Arg::Float(f32::MAX),
            }],
        };

        assert_eq!(
            c.transpile(),
            format!(
                r#"int main() {{
float f={};
return 0;
}}
"#,
                f32::MAX
            )
        )
    }

    #[test]
    fn test_let_double() {
        let c = Code {
            lines: vec![CodeLine::Let {
                name: "f",
                value: Arg::Double(f64::MAX),
            }],
        };

        assert_eq!(
            c.transpile(),
            format!(
                r#"int main() {{
double f={};
return 0;
}}
"#,
                f64::MAX
            )
        )
    }

    #[test]
    fn test_let_bool() {
        let c = Code {
            lines: vec![CodeLine::Let {
                name: "b",
                value: Arg::Bool(true),
            }],
        };

        assert_eq!(
            c.transpile(),
            r#"#include<stdbool.h>
int main() {
bool b=true;
return 0;
}
"#
        )
    }

    #[test]
    fn test_let_char() {
        let c = Code {
            lines: vec![CodeLine::Let {
                name: "c",
                value: Arg::Char('c'),
            }],
        };

        assert_eq!(
            c.transpile(),
            r#"int main() {
char c='c';
return 0;
}
"#
        )
    }
}