use super::*;
use crate::linking::LinkingConfiguration;
use std::{
cmp::Ordering,
collections::{BTreeMap, HashMap},
path::{Path, PathBuf},
};
type ReturnRange = std::ops::Range<usize>;
type ReturnRangeMap<'a> = fxhash::FxHashMap<&'a Path, ReturnRange>;
pub type ModsMap = BTreeMap<PathBuf, SourceCode>;
#[derive(Debug, PartialEq, Clone)]
pub struct Input {
pub items: Vec<Item>,
pub stmts: Vec<Statement>,
pub crates: Vec<CrateType>,
}
#[derive(Clone)]
pub struct SourceCode {
pub items: Vec<Item>,
pub stmts: Vec<StmtGrp>,
pub crates: Vec<CrateType>,
}
impl SourceCode {
pub fn new() -> Self {
Self {
items: Vec::new(),
stmts: Vec::new(),
crates: Vec::new(),
}
}
}
#[derive(Clone)]
pub struct StmtGrp(pub Vec<Statement>);
impl StmtGrp {
pub fn src_line(&self) -> String {
let mut buf = String::with_capacity(self.assign_let_binding_length(0));
let stmts = &self.0;
for stmt in stmts {
buf.push_str(&stmt.expr);
if stmt.semi {
buf.push(';');
}
buf.push(' ');
}
buf.pop();
buf
}
fn assign_let_binding(&self, input_num: usize, buf: &mut String) {
let stmts = &self.0;
for stmt in &stmts[0..stmts.len().saturating_sub(1)] {
buf.push_str(&stmt.expr);
if stmt.semi {
buf.push(';');
}
buf.push('\n');
}
if stmts.len() > 0 {
buf.push_str("let out");
buf.push_str(&input_num.to_string());
buf.push_str(" = ");
buf.push_str(&stmts[stmts.len() - 1].expr);
buf.push(';');
}
}
fn assign_let_binding_length(&self, input_num: usize) -> usize {
let stmts = &self.0;
let mut cap = 0;
for stmt in &stmts[0..stmts.len().saturating_sub(1)] {
cap += 1 + stmt.expr.len();
if stmt.semi {
cap += 1;
}
}
cap += if stmts.len() > 0 {
7 + input_num.to_string().len() + 3 + stmts[stmts.len() - 1].expr.len() + 1
} else {
0
};
cap
}
}
pub fn construct_source_code<'a>(
mods_map: &'a ModsMap,
linking_config: &LinkingConfiguration,
) -> (String, ReturnRangeMap<'a>) {
let (cap, map) = calc_capacity(mods_map, linking_config);
let mut contents = String::with_capacity(cap);
for external in linking_config.external_libs.iter() {
external.construct_code_str(&mut contents);
}
if let Some(lib) = mods_map.get(Path::new("lib")) {
code::append_buffer(
lib,
&into_mod_path_vec(Path::new("lib")),
linking_config,
&mut contents,
);
}
for (prev_lvl, new_lvl, file, src_code) in mods_map_with_lvls(mods_map) {
match new_lvl.cmp(&prev_lvl) {
Ordering::Equal | Ordering::Less => {
let diff = prev_lvl - new_lvl;
for _ in 0..=diff {
contents.push('}');
}
contents.push('\n');
}
_ => (),
}
contents.push_str("mod ");
contents.push_str(
file.iter()
.last()
.and_then(|x| x.to_str())
.expect("should convert fine"),
);
contents.push_str(" {\n");
code::append_buffer(
src_code,
&into_mod_path_vec(file),
linking_config,
&mut contents,
);
}
let lvl = mods_map_with_lvls(mods_map)
.last()
.map(|x| x.1)
.unwrap_or(0);
for _ in 0..lvl {
contents.push('}');
}
debug_assert_eq!(
cap,
contents.len(),
"failed at calculating the correct capacity"
);
(contents, map)
}
pub fn eval_fn_name<S: AsRef<str>>(mod_path: &[S], buf: &mut String) {
buf.push('_');
for p in mod_path {
buf.push_str(p.as_ref());
buf.push('_');
}
buf.push_str("intern_eval");
}
fn eval_fn_name_length<S: AsRef<str>>(mod_path: &[S]) -> usize {
12 + mod_path.iter().map(|x| x.as_ref().len() + 1).sum::<usize>()
}
pub fn into_mod_path_vec(path: &Path) -> Vec<&str> {
path.iter().filter_map(|x| x.to_str()).collect()
}
fn mods_map_with_lvls(
mods_map: &ModsMap,
) -> impl Iterator<Item = (usize, usize, &Path, &SourceCode)> {
let mut prev = 0;
mods_map
.iter()
.filter(|x| x.0 != Path::new("lib"))
.map(move |x| {
let c = x.0.iter().count();
let r = (prev, c, x.0.as_path(), x.1);
prev = c;
r
})
}
fn calc_capacity<'a>(
mods_map: &'a ModsMap,
linking_config: &LinkingConfiguration,
) -> (usize, ReturnRangeMap<'a>) {
fn mv_rng(mut rng: ReturnRange, by: usize) -> ReturnRange {
rng.start += by;
rng.end += by;
rng
}
let mut cap = 0;
let mut map =
HashMap::with_capacity_and_hasher(mods_map.len(), fxhash::FxBuildHasher::default());
for external in linking_config.external_libs.iter() {
cap += external.construct_code_str_length();
}
if let Some(lib) = mods_map.get(Path::new("lib")) {
let (src_code_len, src_code_return) =
append_buffer_length(lib, &into_mod_path_vec(Path::new("lib")), linking_config);
map.insert(Path::new("lib"), mv_rng(src_code_return, cap));
cap += src_code_len;
}
for (prev_lvl, new_lvl, file, src_code) in mods_map_with_lvls(mods_map) {
match new_lvl.cmp(&prev_lvl) {
Ordering::Equal | Ordering::Less => {
cap += prev_lvl - new_lvl + 2;
}
_ => (),
}
cap += 4;
cap += file
.iter()
.last()
.and_then(|x| x.to_str())
.map(|x| x.len())
.unwrap_or(0);
cap += 3;
let (src_code_len, src_code_return) =
append_buffer_length(src_code, &into_mod_path_vec(file), linking_config);
map.insert(file, mv_rng(src_code_return, cap));
cap += src_code_len;
}
let lvl = mods_map_with_lvls(mods_map)
.last()
.map(|x| x.1)
.unwrap_or(0);
cap += lvl;
(cap, map)
}
fn append_buffer<S: AsRef<str>>(
src_code: &SourceCode,
mod_path: &[S],
linking_config: &linking::LinkingConfiguration,
buf: &mut String,
) {
for item in src_code.items.iter().filter(|x| x.1) {
buf.push_str(item.0.as_str());
buf.push('\n');
}
buf.push_str("#[no_mangle]\npub extern \"C\" fn ");
eval_fn_name(mod_path, buf);
buf.push('(');
linking_config.construct_fn_args(buf);
buf.push_str(") -> kserd::Kserd<'static> {\n");
let c = src_code.stmts.len();
if c >= 1 {
src_code.stmts.iter().enumerate().for_each(|(i, x)| {
x.assign_let_binding(i, buf);
buf.push('\n');
});
buf.push_str("kserd::ToKserd::into_kserd(out");
buf.push_str(&c.saturating_sub(1).to_string());
buf.push_str(").unwrap().to_owned()\n");
} else {
buf.push_str("kserd::Kserd::new_str(\"no statements\")\n");
}
buf.push_str("}\n");
for item in src_code.items.iter().filter(|x| !x.1) {
buf.push_str(item.0.as_str());
buf.push('\n');
}
}
fn append_buffer_length<S: AsRef<str>>(
src_code: &SourceCode,
mod_path: &[S],
linking_config: &linking::LinkingConfiguration,
) -> (usize, ReturnRange) {
let mut cap = src_code
.items
.iter()
.filter(|x| x.1)
.map(|x| x.0.len() + 1)
.sum();
cap += 31 + eval_fn_name_length(mod_path) + 1 + linking_config.construct_fn_args_length() + 29;
let c = src_code.stmts.len();
let (add, rng) = if c >= 1 {
let stmts = src_code
.stmts
.iter()
.enumerate()
.map(|(i, x)| x.assign_let_binding_length(i) + 1)
.sum::<usize>();
let return_str = 30
+ c.saturating_sub(1).to_string().len()
+ 22;
(
stmts + return_str,
cap + stmts..cap + stmts + return_str - 1,
)
} else {
(39, cap..cap + 38)
};
cap += add + 2;
cap += src_code
.items
.iter()
.filter(|x| !x.1)
.map(|x| x.0.len() + 1)
.sum::<usize>();
(cap, rng)
}
pub type Item = (String, bool);
#[derive(Debug, PartialEq, Clone)]
pub struct Statement {
pub expr: String,
pub semi: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CrateType {
pub src_line: String,
pub cargo_name: String,
}
impl CrateType {
pub fn parse_str(string: &str) -> Result<Self, &'static str> {
let line = string
.replace(";", "")
.replace("_", "-")
.trim()
.split("\n")
.nth(0)
.expect("string should have one line")
.to_string();
if line.contains("extern crate ") {
Ok(CrateType {
src_line: string.to_string(),
cargo_name: line
.split(" ")
.nth(2)
.expect("should always have trailing item")
.to_string(),
})
} else {
Err("line needs `extern crate NAME;`")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn file_map_with_lvls_test() {
let map = vec![
("one".into(), SourceCode::new()),
("one/two".into(), SourceCode::new()),
("one/two/three".into(), SourceCode::new()),
("lib".into(), SourceCode::new()),
("two".into(), SourceCode::new()),
]
.into_iter()
.collect();
let mut i = mods_map_with_lvls(&map).map(|x| (x.0, x.1));
assert_eq!(i.next(), Some((0, 1)));
assert_eq!(i.next(), Some((1, 2)));
assert_eq!(i.next(), Some((2, 3)));
assert_eq!(i.next(), Some((3, 1)));
assert_eq!(i.next(), None);
}
#[test]
fn test_parse_crate() {
let err = Err("line needs `extern crate NAME;`");
let c = CrateType::parse_str("extern crat name;");
assert_eq!(c, err);
let c = CrateType::parse_str("extern crate ");
assert_eq!(c, err);
let c = CrateType::parse_str("extern crate ;");
assert_eq!(c, err);
let s = String::from("extern crate somelib;");
let c = CrateType::parse_str(&s);
assert_eq!(
c,
Ok(CrateType {
src_line: s,
cargo_name: String::from("somelib"),
})
);
let s = String::from("extern crate some-lib;");
let c = CrateType::parse_str(&s);
assert_eq!(
c,
Ok(CrateType {
src_line: s,
cargo_name: String::from("some-lib"),
})
);
let s = String::from("extern crate some lib;");
let c = CrateType::parse_str(&s);
assert_eq!(
c,
Ok(CrateType {
src_line: s,
cargo_name: String::from("some"),
})
);
let s = String::from("extern crate some_lib;");
let c = CrateType::parse_str(&s);
assert_eq!(
c,
Ok(CrateType {
src_line: s,
cargo_name: String::from("some-lib"),
})
);
}
#[test]
fn assign_let_binding_test() {
let mut grp = StmtGrp(vec![]);
let mut s = String::new();
grp.assign_let_binding(0, &mut s);
let ans = "";
assert_eq!(&s, ans);
assert_eq!(grp.assign_let_binding_length(0), ans.len());
grp.0.push(Statement {
expr: "a".to_string(),
semi: false,
});
let mut s = String::new();
grp.assign_let_binding(0, &mut s);
let ans = "let out0 = a;";
assert_eq!(&s, ans);
assert_eq!(grp.assign_let_binding_length(0), ans.len());
grp.0.push(Statement {
expr: "b".to_string(),
semi: false,
});
let mut s = String::new();
grp.assign_let_binding(0, &mut s);
let ans = "a\nlet out0 = b;";
assert_eq!(&s, ans);
assert_eq!(grp.assign_let_binding_length(0), ans.len());
let mut s = String::new();
grp.assign_let_binding(100, &mut s);
let ans = "a\nlet out100 = b;";
assert_eq!(&s, ans);
assert_eq!(grp.assign_let_binding_length(100), ans.len());
}
#[test]
fn construct_test() {
use linking::LinkingConfiguration;
let mut src_code = SourceCode::new();
let mod_path: &[&str] = &[];
let linking_config = LinkingConfiguration::default();
let mut s = String::new();
append_buffer(&src_code, &mod_path, &linking_config, &mut s);
let (len, rng) = append_buffer_length(&src_code, &mod_path, &linking_config);
let ans = r##"#[no_mangle]
pub extern "C" fn _intern_eval() -> kserd::Kserd<'static> {
kserd::Kserd::new_str("no statements")
}
"##;
assert_eq!(&s, ans);
assert_eq!(len, ans.len());
assert_eq!(rng, 73..111);
assert_eq!(&ans[rng], r#"kserd::Kserd::new_str("no statements")"#);
let mod_path = ["some".to_string(), "path".to_string()];
let mut s = String::new();
append_buffer(&src_code, &mod_path, &linking_config, &mut s);
let (len, rng) = append_buffer_length(&src_code, &mod_path, &linking_config);
let ans = r##"#[no_mangle]
pub extern "C" fn _some_path_intern_eval() -> kserd::Kserd<'static> {
kserd::Kserd::new_str("no statements")
}
"##;
assert_eq!(&s, ans);
assert_eq!(len, ans.len());
assert_eq!(rng, 83..121);
assert_eq!(&ans[rng], r#"kserd::Kserd::new_str("no statements")"#);
let linking_config = LinkingConfiguration {
data_type: Some("String".to_string()),
..Default::default()
};
let mut s = String::new();
append_buffer(&src_code, &mod_path, &linking_config, &mut s);
let (len, rng) = append_buffer_length(&src_code, &mod_path, &linking_config);
let ans = r##"#[no_mangle]
pub extern "C" fn _some_path_intern_eval(app_data: &String) -> kserd::Kserd<'static> {
kserd::Kserd::new_str("no statements")
}
"##;
assert_eq!(&s, ans);
assert_eq!(len, ans.len());
assert_eq!(rng, 100..138);
assert_eq!(&ans[rng], r#"kserd::Kserd::new_str("no statements")"#);
src_code.items.push(("fn a() {}".to_string(), false));
src_code.items.push(("fn b() {}".to_string(), false));
let mut s = String::new();
append_buffer(&src_code, &mod_path, &linking_config, &mut s);
let (len, rng) = append_buffer_length(&src_code, &mod_path, &linking_config);
let ans = r##"#[no_mangle]
pub extern "C" fn _some_path_intern_eval(app_data: &String) -> kserd::Kserd<'static> {
kserd::Kserd::new_str("no statements")
}
fn a() {}
fn b() {}
"##;
assert_eq!(&s, ans);
assert_eq!(len, ans.len());
assert_eq!(rng, 100..138);
assert_eq!(&ans[rng], r#"kserd::Kserd::new_str("no statements")"#);
src_code.stmts.push(StmtGrp(vec![
Statement {
expr: "let a = 1".to_string(),
semi: true,
},
Statement {
expr: "b".to_string(),
semi: false,
},
]));
src_code.stmts.push(StmtGrp(vec![
Statement {
expr: "let c = 2".to_string(),
semi: true,
},
Statement {
expr: "d".to_string(),
semi: false,
},
]));
src_code
.items
.push(("#![feature(UP_TOP)]".to_string(), true));
let mut s = String::new();
append_buffer(&src_code, &mod_path, &linking_config, &mut s);
let (len, rng) = append_buffer_length(&src_code, &mod_path, &linking_config);
let ans = r##"#![feature(UP_TOP)]
#[no_mangle]
pub extern "C" fn _some_path_intern_eval(app_data: &String) -> kserd::Kserd<'static> {
let a = 1;
let out0 = b;
let c = 2;
let out1 = d;
kserd::ToKserd::into_kserd(out1).unwrap().to_owned()
}
fn a() {}
fn b() {}
"##;
assert_eq!(&s, ans);
assert_eq!(len, ans.len());
assert_eq!(rng, 170..222);
assert_eq!(
&ans[rng],
"kserd::ToKserd::into_kserd(out1).unwrap().to_owned()"
);
}
#[test]
fn construct_src_test() {
let v = SourceCode::new();
let linking = LinkingConfiguration::default();
let map = vec![
("lib".into(), v.clone()),
("test".into(), v.clone()),
("foo/bar".into(), v.clone()),
("test/inner".into(), v.clone()),
("foo".into(), v.clone()),
("test/inner2".into(), v.clone()),
]
.into_iter()
.collect();
let (s, map) = construct_source_code(&map, &linking);
let ans = r##"#[no_mangle]
pub extern "C" fn _lib_intern_eval() -> kserd::Kserd<'static> {
kserd::Kserd::new_str("no statements")
}
mod foo {
#[no_mangle]
pub extern "C" fn _foo_intern_eval() -> kserd::Kserd<'static> {
kserd::Kserd::new_str("no statements")
}
mod bar {
#[no_mangle]
pub extern "C" fn _foo_bar_intern_eval() -> kserd::Kserd<'static> {
kserd::Kserd::new_str("no statements")
}
}}
mod test {
#[no_mangle]
pub extern "C" fn _test_intern_eval() -> kserd::Kserd<'static> {
kserd::Kserd::new_str("no statements")
}
mod inner {
#[no_mangle]
pub extern "C" fn _test_inner_intern_eval() -> kserd::Kserd<'static> {
kserd::Kserd::new_str("no statements")
}
}
mod inner2 {
#[no_mangle]
pub extern "C" fn _test_inner2_intern_eval() -> kserd::Kserd<'static> {
kserd::Kserd::new_str("no statements")
}
}}"##;
let return_stmt = r#"kserd::Kserd::new_str("no statements")"#;
assert_eq!(&s, ans);
assert_eq!(
&ans[map.get(Path::new("lib")).unwrap().clone()],
return_stmt
);
assert_eq!(
&ans[map.get(Path::new("foo")).unwrap().clone()],
return_stmt
);
assert_eq!(
&ans[map.get(Path::new("foo/bar")).unwrap().clone()],
return_stmt
);
assert_eq!(
&ans[map.get(Path::new("test")).unwrap().clone()],
return_stmt
);
assert_eq!(
&ans[map.get(Path::new("test/inner")).unwrap().clone()],
return_stmt
);
assert_eq!(
&ans[map.get(Path::new("test/inner2")).unwrap().clone()],
return_stmt
);
}
#[test]
fn eval_fn_name_test() {
let path: Vec<String> = ["some", "lib", "module", "path"]
.iter()
.map(|x| x.to_string())
.collect();
let mut s = String::new();
eval_fn_name(&path, &mut s);
let ans = "_some_lib_module_path_intern_eval";
assert_eq!(&s, ans);
assert_eq!(eval_fn_name_length(&path), ans.len());
let mut s = String::new();
eval_fn_name::<&str>(&[], &mut s);
let ans = "_intern_eval";
assert_eq!(&s, ans);
assert_eq!(eval_fn_name_length::<&str>(&[]), ans.len());
}
#[test]
fn into_mod_path_test() {
assert_eq!(
into_mod_path_vec(Path::new("test/mod")),
vec!["test".to_string(), "mod".to_owned()]
);
assert_eq!(
into_mod_path_vec(Path::new("test")),
vec!["test".to_owned()]
);
assert_eq!(
into_mod_path_vec(Path::new("test/mod/something")),
vec!["test".to_string(), "mod".to_owned(), "something".to_owned()]
);
assert_eq!(into_mod_path_vec(Path::new("")), Vec::<String>::new());
assert_eq!(
into_mod_path_vec(Path::new("test/inner2")),
vec!["test".to_owned(), "inner2".to_owned()]
);
}
#[test]
fn item_placement_test() {
let mut v = SourceCode::new();
v.items.push(("Test1".to_string(), false));
v.items.push(("Up Top".to_string(), true));
let linking = LinkingConfiguration::default();
let map = vec![("lib".into(), v)].into_iter().collect();
let (s, map) = construct_source_code(&map, &linking);
let ans = r##"Up Top
#[no_mangle]
pub extern "C" fn _lib_intern_eval() -> kserd::Kserd<'static> {
kserd::Kserd::new_str("no statements")
}
Test1
"##;
assert_eq!(&s, ans);
}
}