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;
}
"#
)
}
}