#![allow(clippy::upper_case_acronyms)]
use crate::args::Args;
use crate::err::Error;
use crate::err::Error::RuntimeError;
use crate::libapi::{ArgDecl, ClassDef, ClassMap, Documented, FuncDef, Module, SymDesc};
use crate::sym::Symbol;
use crate::val::{Typed, Val, ValDef, ValType};
use ::std::fs::{File, create_dir_all};
use ::std::io::BufWriter;
use ::std::path::{Path, PathBuf};
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
use serde_json::{Map, Value, json};
pub fn unimplemented(mut args: Args) -> Result<Val, Error> {
println!("Unimplemented stdlib call");
args.void();
Err(RuntimeError)
}
mod arp;
mod dhcp;
mod dns;
mod erspan1;
mod erspan2;
mod erspan3;
mod eth;
mod gre;
mod io;
mod ipv4;
mod netbios;
mod std;
mod text;
mod time;
mod tls;
mod vxlan;
const STDLIB: Module = module! {
resynth mod stdlib {
std => Symbol::Module(&std::MODULE),
text => Symbol::Module(&text::MODULE),
io => Symbol::Module(&io::MODULE),
ipv4 => Symbol::Module(&ipv4::IPV4),
dns => Symbol::Module(&dns::DNS),
netbios => Symbol::Module(&netbios::NETBIOS),
dhcp => Symbol::Module(&dhcp::MODULE),
arp => Symbol::Module(&arp::MODULE),
tls => Symbol::Module(&tls::TLS),
vxlan => Symbol::Module(&vxlan::MODULE),
gre => Symbol::Module(&gre::MODULE),
eth => Symbol::Module(ð::MODULE),
erspan1 => Symbol::Module(&erspan1::MODULE),
erspan2 => Symbol::Module(&erspan2::MODULE),
erspan3 => Symbol::Module(&erspan3::MODULE),
time => Symbol::Module(&time::MODULE),
}
};
pub fn toplevel_module(name: &str) -> Option<&'static Module> {
match STDLIB.get(name) {
None => None,
Some(Symbol::Module(module)) => Some(module),
Some(Symbol::Func(_)) | Some(Symbol::Class(_)) | Some(Symbol::Val(_)) => {
unreachable!();
}
}
}
fn collect_classes(stk: &mut Vec<&'static str>, m: &'static Module, map: &mut ClassMap) {
for SymDesc { name, sym } in m.symtab.iter() {
stk.push(name);
match sym {
Symbol::Module(child) => collect_classes(stk, child, map),
Symbol::Class(cls) => {
let path = format!("{}/{}.md", stk[..stk.len() - 1].join("/"), name);
map.insert(cls, path);
}
_ => {}
}
stk.pop();
}
}
fn doc_root_prefix(depth: usize) -> String {
"../".repeat(depth)
}
pub fn recurse(
out_dir: &Path,
stk: &mut Vec<&'static str>,
m: &'static Module,
class_map: &ClassMap,
) {
let mut mod_path = PathBuf::from(out_dir);
for item in stk.iter() {
mod_path.push(item);
}
create_dir_all(&mod_path).expect("mkdir");
mod_path.push("README.md");
println!("module -> {}", mod_path.display());
let f = File::create(mod_path.clone()).expect("Unable to create file");
let mut wr = BufWriter::new(f);
mod_path.pop();
let doc_root = doc_root_prefix(stk.len());
m.write_docs(&mut wr, class_map, &doc_root)
.expect("Write module docs");
for SymDesc { name, sym } in m.symtab.iter() {
stk.push(name);
match sym {
Symbol::Module(child) => recurse(out_dir, stk, child, class_map),
Symbol::Class(cls) => {
mod_path.push(format!("{}.md", name));
println!("class -> {}", mod_path.display());
let f = File::create(mod_path.clone()).expect("Unable to create file");
let mut wr = BufWriter::new(f);
let doc_root = doc_root_prefix(stk.len() - 1);
cls.write_docs(&mut wr, class_map, &doc_root)
.expect("class doc");
}
_ => {}
}
stk.pop();
}
}
pub fn write_docs(out_dir: &Path) {
create_dir_all(out_dir).expect("mkdir");
let mut stk: Vec<&'static str> = Vec::new();
let mut class_map = ClassMap::new();
collect_classes(&mut stk, &STDLIB, &mut class_map);
recurse(out_dir, &mut stk, &STDLIB, &class_map)
}
const JSON_SAFE_INTEGER: u64 = 9_007_199_254_740_991;
fn valdef_to_json_fields(val: &ValDef) -> Map<String, Value> {
let mut m = Map::new();
m.insert("type".into(), json!(val.val_type().to_string()));
match val {
ValDef::Nil => {}
ValDef::Bool(b) => {
m.insert("value".into(), json!(b));
}
ValDef::U8(n) => {
m.insert("value".into(), json!(n));
}
ValDef::U16(n) => {
m.insert("value".into(), json!(n));
}
ValDef::U32(n) => {
m.insert("value".into(), json!(n));
}
ValDef::U64(n) => {
if *n <= JSON_SAFE_INTEGER {
m.insert("value".into(), json!(n));
} else {
m.insert("value_decimal".into(), json!(n.to_string()));
}
}
ValDef::Ip4(addr) => {
m.insert("value".into(), json!(addr.to_string()));
}
ValDef::Sock4(sock) => {
m.insert("value".into(), json!(sock.to_string()));
}
ValDef::Str(bytes) => match ::std::str::from_utf8(bytes) {
Ok(s) => {
m.insert("value".into(), json!(s));
}
Err(_) => {
m.insert("value_base64".into(), json!(BASE64.encode(bytes)));
}
},
ValDef::Type(t) => {
m.insert("value".into(), json!(t.to_string()));
}
}
m
}
fn arg_to_json(arg: &crate::libapi::ArgDesc) -> Value {
let mut m = Map::new();
m.insert("name".into(), json!(arg.name));
m.insert("doc".into(), json!(arg.doc.trim()));
match &arg.typ {
ArgDecl::Positional(t) => {
m.insert("type".into(), json!(t.to_string()));
m.insert("required".into(), json!(true));
}
ArgDecl::Optional(d) => {
m.insert("type".into(), json!(d.val_type().to_string()));
m.insert("required".into(), json!(false));
m.insert("default".into(), json!(d.to_string()));
}
}
Value::Object(m)
}
fn func_to_json(f: &'static FuncDef) -> Value {
let args: Vec<Value> = f.args.iter().map(arg_to_json).collect();
let collect_type = if f.collect_type == ValType::Void {
Value::Null
} else {
json!(f.collect_type.to_string())
};
json!({
"doc": f.doc.trim(),
"signature": f.to_string(),
"args": args,
"collect_type": collect_type,
"returns": f.return_type.to_string(),
})
}
fn class_to_json(cls: &'static ClassDef) -> Value {
let methods: Map<String, Value> = cls
.symtab
.iter()
.filter_map(|SymDesc { name, sym }| {
if let Symbol::Func(f) = sym {
Some(((*name).to_string(), func_to_json(f)))
} else {
None
}
})
.collect();
json!({
"doc": cls.doc.trim(),
"methods": methods,
})
}
fn collect_json(
m: &'static Module,
stk: &mut Vec<&'static str>,
functions: &mut Map<String, Value>,
classes: &mut Map<String, Value>,
constants: &mut Map<String, Value>,
) {
for SymDesc { name, sym } in m.symtab.iter() {
stk.push(name);
match sym {
Symbol::Module(child) => {
collect_json(child, stk, functions, classes, constants);
}
Symbol::Func(f) => {
functions.insert(stk.join("::"), func_to_json(f));
}
Symbol::Class(cls) => {
classes
.entry(cls.name.to_string())
.or_insert_with(|| class_to_json(cls));
}
Symbol::Val(val) => {
constants.insert(stk.join("::"), Value::Object(valdef_to_json_fields(val)));
}
}
stk.pop();
}
}
pub fn write_stdlib_json(out_path: Option<&Path>) {
let mut functions: Map<String, Value> = Map::new();
let mut classes: Map<String, Value> = Map::new();
let mut constants: Map<String, Value> = Map::new();
let mut stk: Vec<&'static str> = Vec::new();
collect_json(
&STDLIB,
&mut stk,
&mut functions,
&mut classes,
&mut constants,
);
let root = json!({
"version": env!("CARGO_PKG_VERSION"),
"functions": functions,
"classes": classes,
"constants": constants,
});
match out_path {
Some(path) => {
let f = File::create(path).expect("create json output file");
serde_json::to_writer(f, &root).expect("write stdlib json");
}
None => {
serde_json::to_writer(::std::io::stdout(), &root).expect("write stdlib json");
println!();
}
}
}
#[cfg(test)]
mod test;