use super::*;
use crate::linking::LinkingConfiguration;
use std::{
borrow::Borrow,
cmp::Ordering,
collections::{BTreeMap, BTreeSet, HashMap},
error, fmt,
io::{self},
path::{Path, PathBuf},
};
type ReturnRange = std::ops::Range<usize>;
type ReturnRangeMap<'a> = fxhash::FxHashMap<&'a Path, ReturnRange>;
pub type ModsMap = BTreeMap<PathBuf, SourceCode>;
pub type StaticFiles = BTreeSet<StaticFile>;
#[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 Default for SourceCode {
fn default() -> Self {
Self {
items: Vec::new(),
stmts: Vec::new(),
crates: Vec::new(),
}
}
}
impl SourceCode {
pub fn clear(&mut self) {
self.items.clear();
self.stmts.clear();
self.crates.clear();
}
}
#[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.is_empty() {
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.is_empty() {
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,
static_files: &StaticFiles,
) -> (String, ReturnRangeMap<'a>) {
let (cap, map) = calc_capacity(mods_map, linking_config, static_files);
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")) {
for n in static_files
.iter()
.map(|x| x.path.as_path())
.filter_map(static_file_mod_name)
{
contents += "mod ";
contents += n;
contents += ";\n";
}
append_buffer(
lib,
&into_mod_path_vec(Path::new("lib")),
linking_config,
&StaticFiles::new(),
&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");
append_buffer(
src_code,
&into_mod_path_vec(file),
linking_config,
static_files,
&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,
static_files: &StaticFiles,
) -> (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 static_files_len: usize = static_files
.iter()
.map(|x| x.path.as_path())
.filter_map(static_file_mod_name)
.map(|x| x.len() + 6)
.sum();
cap += static_files_len;
let (src_code_len, src_code_return) = append_buffer_length(
lib,
&into_mod_path_vec(Path::new("lib")),
linking_config,
&StaticFiles::new(),
);
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,
static_files,
);
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,
static_files: &StaticFiles,
buf: &mut String,
) {
for item in src_code.items.iter().filter(|x| x.1) {
buf.push_str(item.0.as_str());
buf.push('\n');
}
if !linking_config.persistent_module_code.is_empty() {
buf.push_str(&linking_config.persistent_module_code);
buf.push('\n');
}
for f in static_files
.iter()
.map(|x| x.path.as_path())
.filter_map(static_file_mod_name)
{
buf.push_str("use crate::");
buf.push_str(f);
buf.push_str(";\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().into_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,
static_files: &StaticFiles,
) -> (usize, ReturnRange) {
let mut cap: usize = src_code
.items
.iter()
.filter(|x| x.1)
.map(|x| x.0.len() + 1)
.sum();
if !linking_config.persistent_module_code.is_empty() {
cap += linking_config.persistent_module_code.len() + 1;
}
cap += static_files
.iter()
.map(|x| x.path.as_path())
.filter_map(static_file_mod_name)
.map(|x| x.len() + 13)
.sum::<usize>();
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()
+ 24;
(
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')
.next()
.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;`")
}
}
}
pub struct StaticFile {
pub path: PathBuf,
pub codehash: Box<[u8; 32]>,
pub crates: Vec<CrateType>,
}
impl PartialEq for StaticFile {
fn eq(&self, other: &Self) -> bool {
self.path == other.path
}
}
impl Eq for StaticFile {}
impl Ord for StaticFile {
fn cmp(&self, other: &Self) -> Ordering {
self.path.cmp(&other.path)
}
}
impl PartialOrd for StaticFile {
fn partial_cmp(&self, other: &StaticFile) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Borrow<Path> for StaticFile {
fn borrow(&self) -> &Path {
&self.path
}
}
#[derive(Debug)]
pub enum AddingStaticFileError {
InvalidPath(&'static str),
Io(io::Error),
}
impl error::Error for AddingStaticFileError {}
impl fmt::Display for AddingStaticFileError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AddingStaticFileError::InvalidPath(p) => {
write!(f, "path is not valid for static file: {}", p)
}
AddingStaticFileError::Io(e) => write!(f, "an io error occurred: {}", e),
}
}
}
pub fn validate_static_file_path(path: &Path) -> Result<(), &'static str> {
if path.extension().and_then(|x| x.to_str()) != Some("rs") {
return Err("file must be a .rs");
}
if let Some(name) = path.file_stem().and_then(|x| x.to_str()) {
valid_identifier(name)?;
}
if let Some(parent) = path.parent() {
for component in parent.iter() {
let s = component.to_str().ok_or("contains non-ascii characters")?;
valid_identifier(s)?;
}
}
Ok(())
}
fn valid_identifier(s: &str) -> Result<(), &'static str> {
let first = s.chars().next();
if s.is_empty() {
Err("must contain one or more characters")
} else if !s.is_ascii() {
Err("contains non-ascii characters")
} else if s.starts_with('_') && s.chars().count() <= 1 {
Err("must contain two or more characters")
} else if s.chars().any(|c| !c.is_ascii_alphanumeric() && c != '_') {
Err("can only contain a-z,A-Z,0-9, or _ characters")
} else if first != Some('_') && !first.unwrap().is_ascii_alphabetic() {
Err("must start with letter or _")
} else {
Ok(())
}
}
pub fn parse_crates_in_file(s: &str) -> (&str, Vec<CrateType>) {
let mut v = Vec::new();
let mut start = 0;
for (idx, ch) in s.char_indices() {
let end = idx + ch.len_utf8();
if ch == ';' {
match CrateType::parse_str(&s[start..end]) {
Ok(c) => {
v.push(c);
start = end;
}
Err(_) => {
break;
}
}
}
}
(&s[start..], v)
}
pub fn static_file_mod_name(path: &Path) -> Option<&str> {
let mut iter = path.iter();
let fst = iter.next();
let snd = iter.next();
match (fst, snd) {
(None, _) => None,
(Some(f), None) => Path::new(f).file_stem().and_then(|x| x.to_str()),
(Some(f), Some(snd)) => {
if snd == "mod.rs" {
f.to_str()
} else {
None
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn file_map_with_lvls_test() {
let map = vec![
("one".into(), SourceCode::default()),
("one/two".into(), SourceCode::default()),
("one/two/three".into(), SourceCode::default()),
("lib".into(), SourceCode::default()),
("two".into(), SourceCode::default()),
]
.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::default();
let mod_path: &[&str] = &[];
let linking_config = LinkingConfiguration::default();
let mut s = String::new();
append_buffer(
&src_code,
&mod_path,
&linking_config,
&StaticFiles::new(),
&mut s,
);
let (len, rng) =
append_buffer_length(&src_code, &mod_path, &linking_config, &StaticFiles::new());
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,
&StaticFiles::new(),
&mut s,
);
let (len, rng) =
append_buffer_length(&src_code, &mod_path, &linking_config, &StaticFiles::new());
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 mut linking_config = LinkingConfiguration {
data_type: Some("String".to_string()),
..Default::default()
};
let mut s = String::new();
append_buffer(
&src_code,
&mod_path,
&linking_config,
&StaticFiles::new(),
&mut s,
);
let (len, rng) =
append_buffer_length(&src_code, &mod_path, &linking_config, &StaticFiles::new());
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,
&StaticFiles::new(),
&mut s,
);
let (len, rng) =
append_buffer_length(&src_code, &mod_path, &linking_config, &StaticFiles::new());
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));
linking_config
.persistent_module_code
.push_str("some-injected-persistent-code");
let mut s = String::new();
append_buffer(
&src_code,
&mod_path,
&linking_config,
&StaticFiles::new(),
&mut s,
);
let (len, rng) =
append_buffer_length(&src_code, &mod_path, &linking_config, &StaticFiles::new());
let ans = r##"#![feature(UP_TOP)]
some-injected-persistent-code
#[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().into_owned()
}
fn a() {}
fn b() {}
"##;
assert_eq!(&s, ans);
assert_eq!(len, ans.len());
assert_eq!(rng, 200..254);
assert_eq!(
&ans[rng],
"kserd::ToKserd::into_kserd(out1).unwrap().into_owned()"
);
}
#[test]
fn construct_src_test() {
let v = SourceCode::default();
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, &StaticFiles::new());
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::default();
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, &StaticFiles::new());
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);
}
#[test]
fn valid_identifier_test() {
assert_eq!(valid_identifier("valid"), Ok(()));
assert_eq!(valid_identifier("_also_valid2"), Ok(()));
assert_eq!(
valid_identifier("_"),
Err("must contain two or more characters")
);
assert_eq!(
valid_identifier(""),
Err("must contain one or more characters")
);
assert_eq!(valid_identifier("-❤"), Err("contains non-ascii characters"));
assert_eq!(
valid_identifier("invalid-name"),
Err("can only contain a-z,A-Z,0-9, or _ characters")
);
assert_eq!(
valid_identifier("9name"),
Err("must start with letter or _")
);
}
#[test]
fn valid_path_test() {
let p = |s| Path::new(s);
assert_eq!(validate_static_file_path(p("valid.rs")), Ok(()));
assert_eq!(
validate_static_file_path(p("valid")),
Err("file must be a .rs")
);
assert_eq!(
validate_static_file_path(p("./invalid.rs")),
Err("can only contain a-z,A-Z,0-9, or _ characters")
);
assert_eq!(
validate_static_file_path(p("valid/../invalid.rs")),
Err("can only contain a-z,A-Z,0-9, or _ characters")
);
assert_eq!(
validate_static_file_path(p("/invalid.rs")),
Err("can only contain a-z,A-Z,0-9, or _ characters")
);
assert_eq!(validate_static_file_path(p("valid/also_valid.rs")), Ok(()));
}
#[test]
fn test_parsing_crates_in_file() {
assert_eq!(parse_crates_in_file(""), ("", vec![]));
assert_eq!(
parse_crates_in_file("let a = 1; let b = 2;"),
("let a = 1; let b = 2;", vec![])
);
assert_eq!(
parse_crates_in_file("extern crate rand; let a = 1;"),
(
" let a = 1;",
vec![CrateType::parse_str("extern crate rand;").unwrap()]
)
);
}
#[test]
fn test_static_file_mod_name() {
assert_eq!(static_file_mod_name("name.rs".as_ref()), Some("name"));
assert_eq!(static_file_mod_name("foo".as_ref()), Some("foo"));
assert_eq!(static_file_mod_name("foo/mod.rs".as_ref()), Some("foo"));
assert_eq!(static_file_mod_name("foo/bar.rs".as_ref()), None);
assert_eq!(static_file_mod_name("mod.rs".as_ref()), Some("mod"));
assert_eq!(static_file_mod_name("mod/mod.rs".as_ref()), Some("mod"));
assert_eq!(static_file_mod_name("mod/foo.rs".as_ref()), None);
assert_eq!(static_file_mod_name("./mod.rs".as_ref()), Some("."));
}
#[test]
fn test_static_file_adding_to_lib() {
let v = SourceCode::default();
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 static_files = vec![
StaticFile {
path: "foo2/bar.rs".into(),
codehash: Box::new([0; 32]),
crates: vec![],
},
StaticFile {
path: "foo2/mod.rs".into(),
codehash: Box::new([0; 32]),
crates: vec![],
},
StaticFile {
path: "bar2.rs".into(),
codehash: Box::new([0; 32]),
crates: vec![],
},
]
.into_iter()
.collect();
let (s, map) = construct_source_code(&map, &linking, &static_files);
let ans = r##"mod bar2;
mod foo2;
#[no_mangle]
pub extern "C" fn _lib_intern_eval() -> kserd::Kserd<'static> {
kserd::Kserd::new_str("no statements")
}
mod foo {
use crate::bar2;
use crate::foo2;
#[no_mangle]
pub extern "C" fn _foo_intern_eval() -> kserd::Kserd<'static> {
kserd::Kserd::new_str("no statements")
}
mod bar {
use crate::bar2;
use crate::foo2;
#[no_mangle]
pub extern "C" fn _foo_bar_intern_eval() -> kserd::Kserd<'static> {
kserd::Kserd::new_str("no statements")
}
}}
mod test {
use crate::bar2;
use crate::foo2;
#[no_mangle]
pub extern "C" fn _test_intern_eval() -> kserd::Kserd<'static> {
kserd::Kserd::new_str("no statements")
}
mod inner {
use crate::bar2;
use crate::foo2;
#[no_mangle]
pub extern "C" fn _test_inner_intern_eval() -> kserd::Kserd<'static> {
kserd::Kserd::new_str("no statements")
}
}
mod inner2 {
use crate::bar2;
use crate::foo2;
#[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")"#;
println!("{}", s);
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 test_stmtgrp_src_line() {
let grp = StmtGrp(vec![
Statement {
expr: "a".into(),
semi: false,
},
Statement {
expr: "b".into(),
semi: true,
},
]);
let s = grp.src_line();
println!("{}", s);
assert_eq!(&s, "a b;");
}
#[test]
fn test_static_file_ord() {
let sf1 = StaticFile {
path: "foo.rs".into(),
codehash: Box::new([0; 32]),
crates: vec![],
};
let sf2 = StaticFile {
path: "foo.rs".into(),
codehash: Box::new([0; 32]),
crates: vec![],
};
assert_eq!(sf1.partial_cmp(&sf2), Some(Ordering::Equal));
}
#[test]
fn test_err_display() {
let err = AddingStaticFileError::Io(io::Error::new(io::ErrorKind::NotFound, "what"));
assert_eq!(&err.to_string(), "an io error occurred: what");
let err = AddingStaticFileError::InvalidPath("foo.txt".into());
assert_eq!(
&err.to_string(),
"path is not valid for static file: foo.txt"
);
}
#[test]
fn test_static_file_adding_to_lib_with_crate() {
let v = SourceCode::default();
let linking = LinkingConfiguration::default();
let map = vec![("lib".into(), v.clone())].into_iter().collect();
let static_files = vec![
StaticFile {
path: "foo2/bar.rs".into(),
codehash: Box::new([0; 32]),
crates: vec![CrateType::parse_str("extern crate rand;").unwrap()],
},
StaticFile {
path: "foo2/mod.rs".into(),
codehash: Box::new([0; 32]),
crates: vec![],
},
StaticFile {
path: "bar2.rs".into(),
codehash: Box::new([0; 32]),
crates: vec![],
},
]
.into_iter()
.collect();
let (s, map) = construct_source_code(&map, &linking, &static_files);
let ans = r##"mod bar2;
mod foo2;
#[no_mangle]
pub extern "C" fn _lib_intern_eval() -> kserd::Kserd<'static> {
kserd::Kserd::new_str("no statements")
}
"##;
let return_stmt = r#"kserd::Kserd::new_str("no statements")"#;
println!("{}", s);
assert_eq!(&s, ans);
assert_eq!(
&ans[map.get(Path::new("lib")).unwrap().clone()],
return_stmt
);
}
}