use std::{collections::BTreeMap, fmt, error::Error};
use regex::Regex;
use yaml_rust::{Yaml, YamlLoader};
use crate::{
ContextHandle, NodeID, node,
name::NameSpace,
Content, PartialContent,
content::{PolyForContent, MonoForContent},
};
#[derive(Debug, Clone)]
pub(crate) enum YamlScriptError {
Empty,
Multiple,
NotADict,
KeyNotString,
NameNotString,
NameDup,
PolyInvalid,
PolyAmbiguous,
ShortPolyWithWords,
MonoInvalid,
LinkInvalid,
LinkReversed,
LinkList,
}
impl fmt::Display for YamlScriptError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.description())
}
}
impl Error for YamlScriptError {
fn description(&self) -> &str {
use YamlScriptError::*;
match self {
Empty => "YAML description is empty",
Multiple => "Multiple YAML descriptions",
NotADict => "Bad YAML description (not a dictionary)",
KeyNotString => "Non-string key in YAML description",
NameNotString => "Non-string c-e structure name in YAML description",
NameDup => "Duplicated c-e structure name in YAML description",
PolyInvalid => "Invalid YAML description of a polynomial",
PolyAmbiguous => "Ambiguous YAML description of a polynomial",
ShortPolyWithWords => {
"Multi-word node name is invalid in short YAML description of a polynomial"
}
MonoInvalid => "Invalid monomial in YAML description of a polynomial",
LinkInvalid => "Invalid link in YAML description of a polynomial",
LinkReversed => "Reversed link in YAML description of a polynomial",
LinkList => "Link list is invalid in YAML description of a polynomial",
}
}
}
fn do_share_name<S: AsRef<str>>(
nodes: &mut NameSpace,
name: S,
single_word_only: bool,
) -> Result<NodeID, Box<dyn Error>> {
if single_word_only && name.as_ref().contains(char::is_whitespace) {
Err(Box::new(YamlScriptError::ShortPolyWithWords))
} else {
Ok(NodeID(nodes.share_name(name)))
}
}
fn post_process_port_description<S: AsRef<str>>(
nodes: &mut NameSpace,
description: S,
single_word_only: bool,
) -> Result<Vec<NodeID>, Box<dyn Error>> {
if description.as_ref().contains(',') {
let result: Result<Vec<NodeID>, Box<dyn Error>> = description
.as_ref()
.split(',')
.map(|s| do_share_name(nodes, s.trim(), single_word_only))
.collect();
let ids = result?;
Ok(ids)
} else {
let id = do_share_name(nodes, description.as_ref().trim(), single_word_only)?;
Ok(vec![id])
}
}
type PortParsed = (Vec<NodeID>, node::Face);
fn do_parse_port_description<S: AsRef<str>>(
nodes: &mut NameSpace,
description: S,
single_word_only: bool,
) -> Result<Option<PortParsed>, Box<dyn Error>> {
lazy_static! {
static ref TX_RE: Regex = Regex::new(r"^(.*[^><])(>+|\s+effects)$").unwrap();
static ref RX_RE: Regex = Regex::new(r"^(.*[^><])(<+|\s+causes)$").unwrap();
}
if let Some(cap) = TX_RE.captures(description.as_ref()) {
let ids = post_process_port_description(nodes, &cap[1], single_word_only)?;
Ok(Some((ids, node::Face::Tx)))
} else if let Some(cap) = RX_RE.captures(description.as_ref()) {
let ids = post_process_port_description(nodes, &cap[1], single_word_only)?;
Ok(Some((ids, node::Face::Rx)))
} else {
Ok(None)
}
}
fn parse_port_description<S: AsRef<str>>(
ctx: &ContextHandle,
description: S,
) -> Option<PortParsed> {
let ref mut nodes = ctx.lock().unwrap().nodes;
do_parse_port_description(nodes, description, false).unwrap_or_else(|_| unreachable!())
}
fn parse_link_description<S: AsRef<str> + Copy>(
ctx: &ContextHandle,
description: S,
valid_face: node::Face,
single_word_only: bool,
) -> Result<(NodeID, bool), Box<dyn Error>> {
let ref mut nodes = ctx.lock().unwrap().nodes;
let link_with_colink = do_parse_port_description(nodes, description, single_word_only)?;
if let Some((ids, face)) = link_with_colink {
if face == valid_face {
if ids.len() == 1 {
Ok((ids[0], true))
} else {
Err(Box::new(YamlScriptError::LinkList))
}
} else {
Err(Box::new(YamlScriptError::LinkReversed))
}
} else {
let id = do_share_name(nodes, description, single_word_only)?;
Ok((id, false))
}
}
#[derive(Debug)]
pub(crate) struct YamlContent {
name: Option<String>,
meta: BTreeMap<String, Yaml>,
content: PartialContent,
}
impl YamlContent {
pub(crate) fn new(ctx: &ContextHandle) -> Self {
YamlContent {
name: Default::default(),
meta: Default::default(),
content: PartialContent::new(ctx),
}
}
fn add_ports(
&mut self,
ids: &[NodeID],
face: node::Face,
poly_yaml: &Yaml,
) -> Result<(), Box<dyn Error>> {
assert!(!ids.is_empty());
let mut poly_content = PolyForContent::new();
match poly_yaml {
Yaml::String(other_name) => {
let (other_id, with_colink) = parse_link_description(
self.content.get_context(),
other_name.trim(),
!face,
true,
)?;
poly_content.add_mono(vec![other_id]);
if with_colink {
if face == node::Face::Tx {
self.content.add_to_causes(other_id, &[ids.to_owned()]);
} else {
self.content.add_to_effects(other_id, &[ids.to_owned()]);
}
}
}
Yaml::Array(table) => {
let mut is_flat = true;
for value in table {
match value {
Yaml::String(other_name) => {
let (other_id, with_colink) = parse_link_description(
self.content.get_context(),
other_name.trim(),
!face,
true,
)?;
poly_content.add_mono(vec![other_id]);
if with_colink {
if face == node::Face::Tx {
self.content.add_to_causes(other_id, &[ids.to_owned()]);
} else {
self.content.add_to_effects(other_id, &[ids.to_owned()]);
}
}
}
Yaml::Array(table) => {
is_flat = false;
let mut mono_content = MonoForContent::new();
for value in table {
if let Some(other_name) = value.as_str() {
let (other_id, with_colink) = parse_link_description(
self.content.get_context(),
other_name.trim(),
!face,
false,
)?;
mono_content.add_node(other_id);
if with_colink {
if face == node::Face::Tx {
self.content.add_to_causes(other_id, &[ids.to_owned()]);
} else {
self.content
.add_to_effects(other_id, &[ids.to_owned()]);
}
}
} else {
return Err(Box::new(YamlScriptError::LinkInvalid))
}
}
poly_content.add_mono(mono_content.into_content());
}
_ => return Err(Box::new(YamlScriptError::MonoInvalid)),
}
}
if is_flat {
return Err(Box::new(YamlScriptError::PolyAmbiguous))
}
}
_ => return Err(Box::new(YamlScriptError::PolyInvalid)),
}
if face == node::Face::Tx {
for &id in ids {
self.content.add_to_effects(id, poly_content.as_content());
}
} else {
for &id in ids {
self.content.add_to_causes(id, poly_content.as_content());
}
}
Ok(())
}
fn add_entry(&mut self, key: &Yaml, value: &Yaml) -> Result<(), Box<dyn Error>> {
if let Some(key) = key.as_str() {
let key = key.trim();
let port_parsed = parse_port_description(self.content.get_context(), key);
if let Some((ids, face)) = port_parsed {
self.add_ports(&ids, face, value)
} else if key == "name" {
if let Some(name) = value.as_str() {
if self.name.is_none() {
self.name = Some(name.trim().to_owned());
Ok(())
} else {
Err(Box::new(YamlScriptError::NameDup))
}
} else {
Err(Box::new(YamlScriptError::NameNotString))
}
} else {
self.meta.insert(key.to_string(), value.clone());
Ok(())
}
} else {
Err(Box::new(YamlScriptError::KeyNotString))
}
}
fn from_yaml(ctx: &ContextHandle, yaml: &Yaml) -> Result<Self, Box<dyn Error>> {
if let Yaml::Hash(ref dict) = yaml {
let mut result = Self::new(ctx);
for (key, value) in dict {
result.add_entry(key, value)?;
}
Ok(result)
} else {
Err(Box::new(YamlScriptError::NotADict))
}
}
pub(crate) fn from_str<S: AsRef<str>>(
ctx: &ContextHandle,
script: S,
) -> Result<Self, Box<dyn Error>> {
let docs = YamlLoader::load_from_str(script.as_ref())?;
if docs.is_empty() {
Err(Box::new(YamlScriptError::Empty))
} else if docs.len() == 1 {
let result = Self::from_yaml(ctx, &docs[0])?;
Ok(result)
} else {
Err(Box::new(YamlScriptError::Multiple))
}
}
}
impl Content for YamlContent {
fn get_script(&self) -> Option<&str> {
None }
fn get_name(&self) -> Option<&str> {
if let Some(ref name) = self.name {
Some(name.as_ref())
} else {
None
}
}
fn get_carrier_ids(&mut self) -> Vec<NodeID> {
self.content.get_carrier_ids()
}
fn get_causes_by_id(&self, id: NodeID) -> Option<&Vec<Vec<NodeID>>> {
self.content.get_causes_by_id(id)
}
fn get_effects_by_id(&self, id: NodeID) -> Option<&Vec<Vec<NodeID>>> {
self.content.get_effects_by_id(id)
}
}