#[macro_use] extern crate log; extern crate toml; extern crate colored; extern crate lalrpop_util; pub mod modelica_parser; pub mod modelica_ast; pub mod transpile_scheme; pub mod transpile_js; use std::path::Path; use std::fs; use std::io::Read; use std::fs::File; use lalrpop_util::ParseError; use colored::*; #[derive(Debug, PartialEq)] pub struct ModelMetadata { pub name_en: String, pub description_en: Option, pub vars: Vec, } #[derive(Debug, PartialEq)] pub enum ModelVarType { Independent, Dependent, Constant, Time, State, Parameter, } #[derive(Debug, PartialEq)] pub struct ModelVar { pub slug: String, pub name_en: Option, pub vtype: ModelVarType, pub latex: Option, pub units_si: Option, } #[derive(Debug, PartialEq)] pub struct ModelEntry { pub ast: modelica_ast::ModelicaModel, pub metadata: ModelMetadata, pub markdown: String, } pub fn parse_metadata(raw: String) -> Result { let root = toml::Parser::new(&raw).parse().unwrap(); let model = root.get("model") .expect("missing 'model' section") .as_table().unwrap(); let variables = root.get("variables") .expect("missing 'variables' section") .as_table().unwrap(); let mut vars = vec![]; for (slug, info) in variables { let info = info.as_table().unwrap(); let vtype = match info.get("type").unwrap().as_str().unwrap() { "independent" => ModelVarType::Independent, "dependent" => ModelVarType::Dependent, "constant" => ModelVarType::Constant, "time" => ModelVarType::Time, "state" => ModelVarType::State, "parameter" => ModelVarType::Parameter, other => return Err(format!("Unknown variable type: {}", other)), }; vars.push(ModelVar { slug: slug.to_string(), name_en: info.get("name-en").map(|x| x.as_str().unwrap().to_string()), vtype: vtype, latex: info.get("latex").map(|x| x.as_str().unwrap().to_string()), units_si: info.get("units-si").map(|x| x.as_str().unwrap().to_string()), }); } Ok(ModelMetadata { name_en: model.get("name-en").unwrap().as_str().unwrap().to_string(), description_en: model.get("description-en").map(|x| x.as_str().unwrap().to_string()), vars: vars, }) } pub fn load_model_entry(p: &Path) -> Result { debug!("Attempting to load model from: {:?}", p); let ast = { let mut s = String::new(); try!(File::open(p.join("model.modelica")).and_then(|mut f| f.read_to_string(&mut s)).map_err(|e| e.to_string())); try!(modelica_parser::parse_model(&s).map_err(|e| format!("{:?}", e))) }; let metadata = { let mut s = String::new(); try!(File::open(p.join("metadata.toml")).and_then(|mut f| f.read_to_string(&mut s)).map_err(|e| e.to_string())); parse_metadata(s).unwrap() }; let markdown = { let mut s = String::new(); try!(File::open(p.join("page.md")).and_then(|mut f| f.read_to_string(&mut s)).map_err(|e| e.to_string())); s }; Ok(ModelEntry { ast: ast, metadata: metadata, markdown: markdown, }) } pub fn search_models(p: &Path) -> Vec { if fs::metadata(p).unwrap().is_dir() { fs::read_dir(p).unwrap() .map(|x| x.unwrap()) .filter(|x| x.metadata().unwrap().is_dir()) .filter(|x| x.path().join("model.modelica").exists()) .map(|x| x.path().to_string_lossy().to_string()) .collect() } else { vec![] } } fn pp_segment(raw: &str, start: usize, end: usize) -> String { let mut line_start = 0; let mut num = 0; let mut ret = String::new(); for line in raw.lines() { num += 1; let line_end = line_start + line.len(); if (line_start <= start) && (start < line_end) { ret += &format!(" {}\n{:>3} {} {}{}{}\n {} {}{}\n", "|".blue().bold(), num.to_string().blue().bold(), "|".blue().bold(), raw[line_start..start].normal(), raw[start..end].red().bold(), if end < line_end { raw[end..line_end].normal() } else { "".normal() }, "|".blue().bold(), std::iter::repeat(" ").take(start - line_start).collect::(), std::iter::repeat("^").take(end - start).collect::().red().bold()); } line_start += line.len() + 1; if line_start > end { break }; } ret } pub fn pp_parseerror(raw: &str, pe: ParseError) -> String { match pe { ParseError::InvalidToken{location} => { format!("{} invalid token starting at:\n{}", "parse error:".red().bold(), pp_segment(raw, location, location+1)) }, ParseError::UnrecognizedToken{token: Some((start, (_, tok), end)), expected} => { format!("{} unrecognized token '{}' (expected one of {:?}):\n{}", "parse error:".red().bold(), tok, expected, pp_segment(raw, start, end)) }, ParseError::UnrecognizedToken{token: None, expected} => { format!("{} premature end-of-file (expected one of {:?})", "parse error:".red().bold(), expected) }, ParseError::ExtraToken{token: (start, (_, tok), end)} => { format!("{} unexpected extra token '{}':\n{}", "parse error:".red().bold(), tok, pp_segment(raw, start, end)) }, _ => { format!("{} {:?}", "parse error:".red().bold(), pe) }, } } /* ******************************** Tests ******************************* */ #[test] fn test_parse_metadata() { let raw = r#" [model] name-en = "Bogus Dummy Model" [variables] "#.to_string(); assert_eq!(parse_metadata(raw).unwrap(), ModelMetadata { name_en: "Bogus Dummy Model".to_string(), description_en: None, vars: vec![], }); } #[test] fn test_load_model_entry() { load_model_entry(Path::new("./examples/classic_gravitation/")).unwrap(); } #[test] fn test_search_models() { assert_eq!(search_models(Path::new("./examples/")).len() > 1, true); } #[test] fn test_lexical() { assert_eq!(&format!("{:?}", modelica_parser::parse_integer("+123").unwrap()), "123"); assert_eq!(&format!("{:?}", modelica_parser::parse_integer("-9").unwrap()), "-9"); assert_eq!(&format!("{:?}", modelica_parser::parse_float("-1.0e0").unwrap()), "-1"); assert_eq!(&format!("{:?}", modelica_parser::parse_float("123.456").unwrap()), "123.456"); } #[test] fn test_parse() { let example1 = r#"model MinimalModel Real x; equation x = 1; end MinimalModel; "#; assert_eq!(&format!("{:?}", modelica_parser::parse_model(example1).unwrap()), example1); let example2 = r#"model MinimalModel parameter Real a; Real b; equation connect(a, b); a = 1; b = ((abs(a) + 2) / 4); end MinimalModel; "#; assert_eq!(&format!("{:?}", modelica_parser::parse_model(example2).unwrap()), example2); }