use super::*;
use lazy_static::lazy_static;
use regex::Regex;
use std::convert::TryFrom;
use std::fmt;
use std::hash::Hasher;
use std::io;
use std::ops::Deref;
lazy_static! {
static ref VARNAME: Regex = Regex::new(r#"(?x)
^
[_A-Za-z0-9\u{C0}-\u{D6}\u{D8}-\u{F6}\u{F8}-\u{2FF}\u{370}-\u{37D}\u{37F}-\u{1FFF}\u{200C}-\u{200D}\u{2070}-\u{218F}\u{2C00}-\u{2FEF}\u{3001}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFFD}\U{10000}-\U{EFFFF}]
[_A-Za-z0-9\u{B7}\u{C0}-\u{D6}\u{D8}-\u{F6}\u{F8}-\u{2FF}\u{300}-\u{37D}\u{37F}-\u{1FFF}\u{200C}-\u{200D}\u{203F}-\u{2040}\u{2070}-\u{218F}\u{2C00}-\u{2FEF}\u{3001}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFFD}\U{10000}-\U{EFFFF}]*
$
"#).unwrap();
}
#[derive(Clone, Copy, Debug, Eq, Ord)]
pub struct Variable<TD: TermData>(TD);
impl<TD> Variable<TD>
where
TD: TermData,
{
pub fn new<U>(name: U) -> Result<Self>
where
U: AsRef<str>,
TD: From<U>,
{
if VARNAME.is_match(name.as_ref()) {
Ok(Variable(name.into()))
} else {
Err(TermError::InvalidVariableName(name.as_ref().to_string()))
}
}
pub fn new_unchecked<U>(name: U) -> Self
where
U: AsRef<str>,
TD: From<U>,
{
debug_assert!(
VARNAME.is_match(name.as_ref()),
"invalid variable name {:?}",
name.as_ref()
);
Variable(name.into())
}
pub fn as_ref(&self) -> Variable<&TD> {
Variable(&self.0)
}
pub fn as_ref_str(&self) -> Variable<&str> {
Variable(self.0.as_ref())
}
pub fn map<F, TD2>(self, f: F) -> Variable<TD2>
where
F: FnMut(TD) -> TD2,
TD2: TermData,
{
let mut f = f;
Variable(f(self.0))
}
pub fn map_into<TD2>(self) -> Variable<TD2>
where
TD: Into<TD2>,
TD2: TermData,
{
self.map(Into::into)
}
pub fn clone_map<'a, U, F>(&'a self, factory: F) -> Variable<U>
where
U: TermData,
F: FnMut(&'a str) -> U,
{
let mut factory = factory;
Variable(factory(self.0.as_ref()))
}
pub fn clone_into<'src, U>(&'src self) -> Variable<U>
where
U: TermData + From<&'src str>,
{
self.clone_map(Into::into)
}
pub fn as_str(&self) -> &str {
self.0.as_ref()
}
pub fn write_fmt<W>(&self, w: &mut W) -> fmt::Result
where
W: fmt::Write,
{
w.write_char('?')?;
w.write_str(self.as_str())
}
pub fn write_io<W>(&self, w: &mut W) -> io::Result<()>
where
W: io::Write,
{
w.write_all(b"?")?;
w.write_all(self.as_str().as_bytes())
}
}
impl<TD: TermData> TTerm for Variable<TD> {
fn kind(&self) -> TermKind {
TermKind::Variable
}
fn value_raw(&self) -> RawValue {
self.0.as_ref().into()
}
fn as_dyn(&self) -> &dyn TTerm {
self
}
}
impl<TD, TE> PartialEq<TE> for Variable<TD>
where
TD: TermData,
TE: TTerm + ?Sized,
{
fn eq(&self, other: &TE) -> bool {
term_eq(self, other)
}
}
impl<TD, TE> PartialOrd<TE> for Variable<TD>
where
TD: TermData,
TE: TTerm + ?Sized,
{
fn partial_cmp(&self, other: &TE) -> Option<std::cmp::Ordering> {
Some(term_cmp(self, other))
}
}
impl<TD> Hash for Variable<TD>
where
TD: TermData,
{
fn hash<H: Hasher>(&self, state: &mut H) {
term_hash(self, state)
}
}
impl<TD> Deref for Variable<TD>
where
TD: TermData,
{
type Target = TD;
fn deref(&self) -> &TD {
&self.0
}
}
impl<TD> fmt::Display for Variable<TD>
where
TD: TermData,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.write_fmt(f)
}
}
impl<TD> TryFrom<Term<TD>> for Variable<TD>
where
TD: TermData,
{
type Error = TermError;
fn try_from(term: Term<TD>) -> Result<Self, Self::Error> {
match term {
Term::Variable(var) => Ok(var),
_ => Err(TermError::UnsupportedKind(term.to_string())),
}
}
}
impl<'a, T, U> TryFrom<&'a Term<U>> for Variable<T>
where
T: TermData + From<&'a str>,
U: TermData,
{
type Error = TermError;
fn try_from(term: &'a Term<U>) -> Result<Self, Self::Error> {
match term {
Term::Variable(var) => Ok(var.clone_into()),
_ => Err(TermError::UnsupportedKind(term.to_string())),
}
}
}
impl<TD> TryCopyTerm for Variable<TD>
where
TD: TermData + for<'x> From<&'x str>,
{
type Error = TermError;
fn try_copy<T>(term: &T) -> Result<Self, Self::Error>
where
T: TTerm + ?Sized,
{
if term.kind() == TermKind::Variable {
Ok(Self::new_unchecked(term.value_raw().0))
} else {
Err(TermError::UnsupportedKind(term_to_string(term)))
}
}
}
impl<'a, TD: TermData + 'a> std::borrow::Borrow<dyn TTerm + 'a> for Variable<TD> {
fn borrow(&self) -> &(dyn TTerm + 'a) {
self
}
}
#[cfg(test)]
#[allow(clippy::bool_assert_comparison)] mod test {
use super::*;
use test_case::test_case;
#[test_case("" => false ; "empty")]
#[test_case("hans" => true ; "no questionmark")]
#[test_case("ha?ns" => false ; "include unallowed char")]
#[test_case("_" => true ; "underscore")]
#[test_case("1" => true ; "number")]
#[test_case("hans_the_1" => true ; "mixed")]
fn check_regex(to_check: &str) -> bool {
VARNAME.is_match(to_check)
}
#[test_case("" => "invalid name" ; "empty")]
#[test_case("hans" => "?hans" ; "no questionmark")]
#[test_case("ha?ns" => "invalid name" ; "include unallowed char")]
#[test_case("_" => "?_" ; "underscore")]
#[test_case("1" => "?1" ; "number")]
#[test_case("hans_the_1" => "?hans_the_1" ; "mixed")]
fn check_write_fmt(to_check: &str) -> String {
let var: Variable<&str> = if let Ok(var) = Variable::new(to_check) {
var
} else {
return "invalid name".to_owned();
};
let mut buf = String::new();
match var.write_fmt(&mut buf) {
Ok(_) => buf,
Err(_) => "failed write".to_owned(),
}
}
#[test_case("" => b"invalid name".to_vec() ; "empty")]
#[test_case("hans" => b"?hans".to_vec() ; "no questionmark")]
#[test_case("ha?ns" => b"invalid name".to_vec() ; "include unallowed char")]
#[test_case("_" => b"?_".to_vec() ; "underscore")]
#[test_case("1" => b"?1".to_vec() ; "number")]
#[test_case("hans_the_1" => b"?hans_the_1".to_vec() ; "mixed")]
fn check_write_io(to_check: &str) -> Vec<u8> {
let var: Variable<&str> = if let Ok(var) = Variable::new(to_check) {
var
} else {
return b"invalid name".to_vec();
};
let mut buf = Vec::new();
match var.write_io(&mut buf) {
Ok(_) => buf,
Err(_) => b"failed write".to_vec(),
}
}
#[test]
fn map() {
let input: Variable<&str> = Variable::new("test").unwrap();
let expect: Variable<&str> = Variable::new("TEST").unwrap();
let mut cnt = 0;
let mut invoked = 0;
let cl = input.clone_map(|s: &str| {
cnt += s.len();
invoked += 1;
s.to_ascii_uppercase()
});
assert_eq!(cl, expect);
assert_eq!(cnt, "test".len());
assert_eq!(invoked, 1);
cnt = 0;
invoked = 0;
let mapped = input.map(|s: &str| {
cnt += s.len();
invoked += 1;
s.to_ascii_uppercase()
});
assert_eq!(mapped, expect);
assert_eq!(cnt, "test".len());
assert_eq!(invoked, 1);
assert_eq!(
cl.map_into::<Box<str>>(),
mapped.clone_into::<std::sync::Arc<str>>()
);
}
}