use crate::Hex;
use crate::Sodg;
use anyhow::{anyhow, Context, Result};
use lazy_static::lazy_static;
use log::trace;
use regex::Regex;
use std::collections::HashMap;
use std::str::FromStr;
pub struct Script {
txt: String,
vars: HashMap<String, u32>,
}
impl Script {
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Script {
Script {
txt: s.to_string(),
vars: HashMap::new(),
}
}
pub fn from_string(s: String) -> Script {
Script::from_str(s.as_str())
}
pub fn deploy_to(&mut self, g: &mut Sodg) -> Result<usize> {
let mut pos = 0;
for cmd in self.commands().iter() {
trace!("#deploy_to: deploying command no.{} '{}'...", pos + 1, cmd);
self.deploy_one(cmd, g)
.context(format!("Failure at the command no.{pos}: '{cmd}'"))?;
pos += 1;
}
Ok(pos)
}
fn commands(&self) -> Vec<String> {
lazy_static! {
static ref STRIP_COMMENTS: Regex = Regex::new("#.*\n").unwrap();
}
let text = self.txt.as_str();
let clean: &str = &STRIP_COMMENTS.replace_all(text, "");
clean
.split(';')
.map(|t| t.trim())
.filter(|t| !t.is_empty())
.map(|t| t.to_string())
.collect()
}
fn deploy_one(&mut self, cmd: &str, g: &mut Sodg) -> Result<()> {
lazy_static! {
static ref LINE: Regex = Regex::new("^([A-Z]+) *\\(([^)]*)\\)$").unwrap();
}
let cap = LINE.captures(cmd).context(format!("Can't parse '{cmd}'"))?;
let args: Vec<String> = cap[2]
.split(',')
.map(|t| t.trim())
.filter(|t| !t.is_empty())
.map(|t| t.to_string())
.collect();
match &cap[1] {
"ADD" => {
let v = self.parse(&args[0], g)?;
g.add(v).context(format!("Failed to ADD({})", &args[0]))
}
"BIND" => {
let v1 = self.parse(&args[0], g)?;
let v2 = self.parse(&args[1], g)?;
let a = &args[2];
g.bind(v1, v2, a).context(format!(
"Failed to BIND({}, {}, {})",
&args[0], &args[1], &args[2]
))
}
"PUT" => {
let v = self.parse(&args[0], g)?;
g.put(v, Self::parse_data(&args[1])?)
.context(format!("Failed to PUT({})", &args[0]))
}
_cmd => Err(anyhow!("Unknown command: {_cmd}")),
}
}
fn parse_data(s: &str) -> Result<Hex> {
lazy_static! {
static ref DATA_STRIP: Regex = Regex::new("[ \t\n\r\\-]").unwrap();
static ref DATA: Regex = Regex::new("^[0-9A-Fa-f]{2}([0-9A-Fa-f]{2})*$").unwrap();
}
let d: &str = &DATA_STRIP.replace_all(s, "");
if DATA.is_match(d) {
let bytes: Vec<u8> = (0..d.len())
.step_by(2)
.map(|i| u8::from_str_radix(&d[i..i + 2], 16).unwrap())
.collect();
Ok(Hex::from_vec(bytes))
} else {
Err(anyhow!("Can't parse data '{s}'"))
}
}
fn parse(&mut self, s: &str, g: &mut Sodg) -> Result<u32> {
let head = s.chars().next().context("Empty identifier".to_string())?;
if head == '$' || head == 'ν' {
let tail: String = s.chars().skip(1).collect::<Vec<_>>().into_iter().collect();
if head == '$' {
Ok(*self.vars.entry(tail).or_insert_with(|| g.next_id()))
} else {
Ok(u32::from_str(tail.as_str()).context(format!("Parsing of '{s}' failed"))?)
}
} else {
let v = u32::from_str(s).context(format!("Parsing of '{s}' failed"))?;
Ok(v)
}
}
}
#[cfg(test)]
use std::str;
#[test]
fn simple_command() -> Result<()> {
let mut g = Sodg::empty();
let mut s = Script::from_str(
"
ADD(0); ADD($ν1); # adding two vertices
BIND(ν0, $ν1, foo );
PUT($ν1 , d0-bf-D1-80-d0-B8-d0-b2-d0-b5-d1-82);
",
);
let total = s.deploy_to(&mut g)?;
assert_eq!(4, total);
assert_eq!("привет", g.data(1)?.to_utf8()?);
assert_eq!(1, g.kid(0, "foo").unwrap());
Ok(())
}