use std::convert::TryInto;
use regex::RegexBuilder;
use crate::ast;
use crate::ivt::*;
use crate::parser::{parse_cddl, slice_parse_cddl};
use crate::util::ValidateError;
pub type FlattenResult<T> = std::result::Result<T, ValidateError>;
pub fn flatten_from_str(cddl_input: &str) -> FlattenResult<RulesByName> {
let cddl = parse_cddl(cddl_input)?;
flatten(&cddl)
}
pub fn slice_flatten_from_str(cddl_input: &str) -> FlattenResult<RulesWithStrings> {
let cddl = slice_parse_cddl(cddl_input)?;
slice_flatten(&cddl)
}
pub fn flatten(cddl: &ast::Cddl) -> FlattenResult<RulesByName> {
cddl.rules.iter().map(flatten_rule).collect()
}
pub fn slice_flatten(cddl: &ast::CddlSlice) -> FlattenResult<RulesWithStrings> {
cddl.rules
.iter()
.map(|(rule, s)| {
let (name, flat) = flatten_rule(rule)?;
Ok((name, (flat, s.clone())))
})
.collect()
}
fn flatten_rule(rule: &ast::Rule) -> FlattenResult<(String, RuleDef)> {
use ast::RuleVal;
let node = match &rule.val {
RuleVal::AssignType(t) => flatten_type(t)?,
RuleVal::AssignGroup(g) => flatten_groupentry(g)?,
};
let ruledef = RuleDef {
generic_parms: rule.generic_parms.clone(),
node,
};
Ok((rule.name.clone(), ruledef))
}
fn flatten_type(ty: &ast::Type) -> FlattenResult<Node> {
let options: FlattenResult<Vec<Node>> = ty.0.iter().map(flatten_type1).collect();
let options = options?;
match options.len() {
0 => Err(ValidateError::Unsupported(
"flatten type with 0 options".into(),
)),
1 => Ok(options.into_iter().next().unwrap()),
_ => Ok(Node::Choice(Choice { options })),
}
}
fn flatten_type1(ty1: &ast::Type1) -> FlattenResult<Node> {
use ast::Type1::{Control, Range, Simple};
match ty1 {
Simple(ty2) => flatten_type2(ty2),
Range(r) => flatten_range(r),
Control(ctl) => flatten_control(ctl),
}
}
fn flatten_control(ctl: &ast::TypeControl) -> FlattenResult<Node> {
let ctl_result = match ctl.op.as_str() {
"size" => control_size(ctl)?,
"lt" => control_lt(ctl)?,
"le" => control_le(ctl)?,
"gt" => control_gt(ctl)?,
"ge" => control_ge(ctl)?,
"regexp" => control_regex(ctl)?,
"cbor" => control_cbor(ctl)?,
_ => return Err(ValidateError::Unsupported("control operator".into())),
};
Ok(Node::Control(ctl_result))
}
fn control_size(ctl: &ast::TypeControl) -> FlattenResult<Control> {
let target = flatten_type2(&ctl.target)?;
let size = flatten_type2(&ctl.arg)?;
match size {
Node::Literal(Literal::Int(_)) => {}
Node::Range(_) => {}
Node::Rule(_) => {}
_ => return Err(ValidateError::Unsupported(".size limit type".into())),
};
Ok(Control::Size(CtlOpSize {
target: Box::new(target),
size: Box::new(size),
}))
}
fn control_lt(ctl: &ast::TypeControl) -> FlattenResult<Control> {
let target = flatten_type2(&ctl.target)?;
let lt = flatten_type2(&ctl.arg)?;
match lt {
Node::Literal(Literal::Int(_)) => {}
Node::Rule(_) => {}
_ => return Err(ValidateError::Unsupported(".lt limit type".into())),
};
Ok(Control::Lt(CtlOpLt {
target: Box::new(target),
lt: Box::new(lt),
}))
}
fn control_le(ctl: &ast::TypeControl) -> FlattenResult<Control> {
let target = flatten_type2(&ctl.target)?;
let le = flatten_type2(&ctl.arg)?;
match le {
Node::Literal(Literal::Int(_)) => {}
Node::Rule(_) => {}
_ => return Err(ValidateError::Unsupported(".le limit type".into())),
};
Ok(Control::Le(CtlOpLe {
target: Box::new(target),
le: Box::new(le),
}))
}
fn control_gt(ctl: &ast::TypeControl) -> FlattenResult<Control> {
let target = flatten_type2(&ctl.target)?;
let gt = flatten_type2(&ctl.arg)?;
match gt {
Node::Literal(Literal::Int(_)) => {}
Node::Rule(_) => {}
_ => return Err(ValidateError::Unsupported(".gt limit type".into())),
};
Ok(Control::Gt(CtlOpGt {
target: Box::new(target),
gt: Box::new(gt),
}))
}
fn control_ge(ctl: &ast::TypeControl) -> FlattenResult<Control> {
let target = flatten_type2(&ctl.target)?;
let ge = flatten_type2(&ctl.arg)?;
match ge {
Node::Literal(Literal::Int(_)) => {}
Node::Rule(_) => {}
_ => return Err(ValidateError::Unsupported(".ge limit type".into())),
};
Ok(Control::Ge(CtlOpGe {
target: Box::new(target),
ge: Box::new(ge),
}))
}
fn control_regex(ctl: &ast::TypeControl) -> FlattenResult<Control> {
let target = flatten_type2(&ctl.target)?;
let regexp_node = flatten_type2(&ctl.arg)?;
match target {
Node::PreludeType(PreludeType::Tstr) => {}
_ => {
return Err(ValidateError::Structural("bad regexp target type".into()));
}
}
match regexp_node {
Node::Literal(Literal::Text(re_str)) => {
let re = RegexBuilder::new(&re_str)
.size_limit(1 << 20)
.build()
.map_err(|_| ValidateError::Structural("malformed regexp".into()))?;
Ok(Control::Regexp(CtlOpRegexp { re }))
}
_ => Err(ValidateError::Structural("improper regexp type".into())),
}
}
fn control_cbor(ctl: &ast::TypeControl) -> FlattenResult<Control> {
let target = flatten_type2(&ctl.target)?;
let node = Box::new(flatten_type2(&ctl.arg)?);
match target {
Node::PreludeType(PreludeType::Bstr) => {}
_ => {
return Err(ValidateError::Structural("bad .cbor target type".into()));
}
}
Ok(Control::Cbor(CtlOpCbor { node }))
}
fn range_point(point: &ast::Type2) -> FlattenResult<Node> {
let node = match point {
ast::Type2::Value(v) => flatten_value(v),
ast::Type2::Typename(t) => flatten_name_generic(t),
_ => Err(ValidateError::Structural(
"bad type on range operator".into(),
)),
}?;
match node {
Node::Rule(_) | Node::Literal(_) => Ok(node),
_ => Err(ValidateError::Structural(
"bad type on range operator".into(),
)),
}
}
fn flatten_range(range: &ast::TypeRange) -> FlattenResult<Node> {
let start = range_point(&range.start)?;
let end = range_point(&range.end)?;
Ok(Node::Range(Range {
start: start.into(),
end: end.into(),
inclusive: range.inclusive,
}))
}
fn flatten_value(value: &ast::Value) -> FlattenResult<Node> {
use ast::Value;
match value {
Value::Text(s) => Ok(literal_text(s)),
Value::Uint(u) => {
let value = num_to_i128(*u)?;
Ok(literal_int(value))
}
Value::Nint(n) => {
let value = num_to_i128(*n)?;
Ok(literal_int(value))
}
Value::Float(f) => Ok(literal_float(*f)),
Value::Bytes(b) => Ok(literal_bytes(b.clone())),
}
}
fn flatten_type2(ty2: &ast::Type2) -> FlattenResult<Node> {
use ast::Type2;
match ty2 {
Type2::Value(v) => flatten_value(v),
Type2::Typename(s) => flatten_name_generic(s),
Type2::Parethesized(t) => flatten_type(t),
Type2::Map(g) => flatten_map(g),
Type2::Array(g) => flatten_array(g),
Type2::Unwrap(r) => Ok(Node::Unwrap(flatten_rule_generic(r)?)),
Type2::ChoiceifyInline(g) => flatten_choiceify_inline(g),
Type2::Choiceify(r) => flatten_choiceify(r),
}
}
fn flatten_typename(name: &str) -> FlattenResult<Node> {
let unsupported = |s: &str| -> FlattenResult<Node> {
let msg = format!("prelude type '{}'", s);
Err(ValidateError::Unsupported(msg))
};
let result = match name {
"any" => Node::PreludeType(PreludeType::Any),
"nil" | "null" => Node::PreludeType(PreludeType::Nil),
"bool" => Node::PreludeType(PreludeType::Bool),
"false" => literal_bool(false),
"true" => literal_bool(true),
"int" => Node::PreludeType(PreludeType::Int),
"uint" => Node::PreludeType(PreludeType::Uint),
"nint" => Node::PreludeType(PreludeType::Nint),
"float" => Node::PreludeType(PreludeType::Float),
"tstr" | "text" => Node::PreludeType(PreludeType::Tstr),
"bstr" | "bytes" => Node::PreludeType(PreludeType::Bstr),
"float16" | "float32" | "float64" | "float16-32" | "float32-64" => {
Node::PreludeType(PreludeType::Float)
}
"tdate" | "uri" | "b64url" | "b64legacy" | "regexp" | "mime-message" => {
Node::PreludeType(PreludeType::Tstr)
}
"biguint" | "bignint" | "encoded-cbor" => Node::PreludeType(PreludeType::Bstr),
"eb64url" | "eb64legacy" | "eb16" | "cbor-any" => Node::PreludeType(PreludeType::Any),
"time" => return unsupported(name),
"bigint" | "integer" | "unsigned" | "number" => return unsupported(name),
"decfrac" | "bigfloat" | "undefined" => return unsupported(name),
_ => Node::Rule(Rule::new_name(name)),
};
Ok(result)
}
fn flatten_rule_generic(name_generic: &ast::NameGeneric) -> FlattenResult<Rule> {
let result = flatten_name_generic(name_generic);
match result {
Ok(Node::Rule(r)) => Ok(r),
_ => Err(ValidateError::GenericError),
}
}
fn flatten_name_generic(name_generic: &ast::NameGeneric) -> FlattenResult<Node> {
let mut node = flatten_typename(&name_generic.name)?;
match node {
Node::Rule(ref mut r) => {
for arg in &name_generic.generic_args {
let arg_node = flatten_type1(arg)?;
r.generic_args.push(arg_node);
}
}
_ => {
if !name_generic.generic_args.is_empty() {
return Err(ValidateError::GenericError);
}
}
}
Ok(node)
}
fn flatten_map(group: &ast::Group) -> FlattenResult<Node> {
let kvs = flatten_group(group)?;
Ok(Node::Map(Map { members: kvs }))
}
fn flatten_array(group: &ast::Group) -> FlattenResult<Node> {
let kvs = flatten_group(group)?;
Ok(Node::Array(Array { members: kvs }))
}
fn flatten_group(group: &ast::Group) -> FlattenResult<Vec<Node>> {
let group_choices = &group.0;
if group_choices.len() == 1 {
let groupchoice = &group_choices[0];
flatten_groupchoice(groupchoice)
} else {
let options: FlattenResult<Vec<Node>> = group_choices
.iter()
.map(|gc| {
let inner_members = flatten_groupchoice(gc)?;
Ok(Node::Group(Group {
members: inner_members,
}))
})
.collect();
let options = options?;
Ok(vec![Node::Choice(Choice { options })])
}
}
fn flatten_groupchoice(groupchoice: &ast::GrpChoice) -> FlattenResult<Vec<Node>> {
let group_entries = &groupchoice.0;
let kvs: FlattenResult<Vec<Node>> = group_entries.iter().map(flatten_groupentry).collect();
kvs
}
fn flatten_groupentry(group_entry: &ast::GrpEnt) -> FlattenResult<Node> {
let node = flatten_groupentry_val(&group_entry.val)?;
Ok(occur_wrap(&group_entry.occur, node))
}
fn flatten_groupentry_val(gev: &ast::GrpEntVal) -> FlattenResult<Node> {
use ast::GrpEntVal;
match gev {
GrpEntVal::Member(m) => flatten_member(m),
GrpEntVal::Groupname(s) => flatten_typename(s),
GrpEntVal::Parenthesized(g) => {
let nodes = flatten_group(g)?;
Ok(Node::Group(Group { members: nodes }))
}
}
}
fn occur_wrap(occur: &Option<ast::Occur>, node: Node) -> Node {
match &occur {
Some(o) => Node::Occur(Occur::new(o.clone(), node)),
None => node,
}
}
fn flatten_member(member: &ast::Member) -> FlattenResult<Node> {
match &member.key {
Some(key) => {
let cut = key.cut;
let key = flatten_memberkey(key)?;
let value = flatten_type(&member.value)?;
Ok(Node::KeyValue(KeyValue::new(key, value, cut)))
}
None => flatten_type(&member.value),
}
}
fn num_to_i128<T>(n: T) -> FlattenResult<i128>
where
T: TryInto<i128> + std::fmt::Display + Copy,
{
n.try_into().map_err(|_| {
let msg = format!("integer conversion failed: {}", n);
ValidateError::Structural(msg)
})
}
fn flatten_memberkey(memberkey: &ast::MemberKey) -> FlattenResult<Node> {
use ast::MemberKeyVal;
match &memberkey.val {
MemberKeyVal::Bareword(s) => {
Ok(literal_text(s.clone()))
}
MemberKeyVal::Type1(t1) => flatten_type1(t1),
MemberKeyVal::Value(v) => flatten_value(v),
}
}
fn flatten_choiceify(name: &ast::NameGeneric) -> FlattenResult<Node> {
let rule = flatten_rule_generic(name)?;
Ok(Node::Choiceify(rule))
}
fn flatten_choiceify_inline(group: &ast::Group) -> FlattenResult<Node> {
let kvs = flatten_group(group)?;
Ok(Node::ChoiceifyInline(Array { members: kvs }))
}
#[cfg(test)]
#[macro_use]
mod test_utils {
use super::*;
use std::collections::BTreeMap;
impl From<&str> for Rule {
fn from(s: &str) -> Self {
Rule {
name: s.to_string(),
generic_args: vec![],
}
}
}
impl From<&str> for Node {
fn from(s: &str) -> Self {
Node::Rule(Rule::from(s))
}
}
impl From<Node> for RuleDef {
fn from(n: Node) -> Self {
RuleDef {
generic_parms: Vec::default(),
node: n,
}
}
}
pub fn make_rules(mut list: Vec<(&str, Node)>) -> RulesByName {
list.drain(..)
.map(|(s, n)| (s.to_string(), RuleDef::from(n)))
.collect()
}
pub fn make_rule<T: Into<Node>>(name: &str, node: T) -> RulesByName {
let node: Node = node.into();
let mut result = BTreeMap::new();
result.insert(name.to_string(), RuleDef::from(node));
result
}
pub fn make_generic_rule(name: &str, generic_parms: &[&str], node: Node) -> RulesByName {
let mut result = BTreeMap::new();
let generic_parms: Vec<String> = generic_parms.iter().map(|s| String::from(*s)).collect();
result.insert(
name.to_string(),
RuleDef {
generic_parms,
node,
},
);
result
}
pub trait CreateLiteral {
fn literal(self) -> Node;
}
impl CreateLiteral for &str {
fn literal(self) -> Node {
Node::Literal(Literal::Text(self.to_string()))
}
}
impl CreateLiteral for i64 {
fn literal(self) -> Node {
Node::Literal(Literal::Int(self.into()))
}
}
pub fn tstr() -> Node {
Node::PreludeType(PreludeType::Tstr)
}
pub fn make_map() -> Map {
Map {
members: Vec::new(),
}
}
pub fn make_array() -> Array {
Array {
members: Vec::new(),
}
}
pub trait Append {
fn append<T: Into<Node>>(self, t: T) -> Self;
}
impl Append for Map {
fn append<T: Into<Node>>(mut self, t: T) -> Self {
let node: Node = t.into();
self.members.push(node);
self
}
}
impl Append for Array {
fn append<T: Into<Node>>(mut self, t: T) -> Self {
let node: Node = t.into();
self.members.push(node);
self
}
}
impl From<Map> for Node {
fn from(m: Map) -> Self {
Node::Map(m)
}
}
impl From<Array> for Node {
fn from(a: Array) -> Self {
Node::Array(a)
}
}
impl From<KeyValue> for Node {
fn from(kv: KeyValue) -> Self {
Node::KeyValue(kv)
}
}
impl From<Rule> for Node {
fn from(r: Rule) -> Self {
Node::Rule(r)
}
}
impl From<Control> for Node {
fn from(r: Control) -> Self {
Node::Control(r)
}
}
#[derive(Copy, Clone)]
pub enum KvCut {
Cut,
NoCut,
}
pub use KvCut::*;
impl From<KvCut> for bool {
fn from(c: KvCut) -> bool {
match c {
Cut => true,
NoCut => false,
}
}
}
pub fn kv(k: Node, v: Node, cut: KvCut) -> KeyValue {
KeyValue {
key: Box::new(k),
value: Box::new(v),
cut: cut.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::test_utils::*;
use super::*;
#[test]
fn test_flatten_literal_int() {
let cddl_input = r#"thing = 1"#;
let result = flatten_from_str(cddl_input).unwrap();
let expected = make_rule("thing", 1.literal());
assert_eq!(result, expected);
let cddl_input = r#"thing = -1"#;
let result = flatten_from_str(cddl_input).unwrap();
let expected = make_rule("thing", (-1i64).literal());
assert_eq!(result, expected);
}
#[test]
fn test_flatten_literal_tstr() {
let cddl_input = r#"thing = "abc""#;
let result = flatten_from_str(cddl_input).unwrap();
let expected = make_rule("thing", "abc".literal());
assert_eq!(result, expected);
}
#[test]
fn test_flatten_prelude_reference() {
let cddl_input = r#"thing = int"#;
let result = flatten_from_str(cddl_input).unwrap();
let expected = make_rule("thing", Node::PreludeType(PreludeType::Int));
assert_eq!(result, expected);
}
#[test]
fn test_flatten_type_reference() {
let cddl_input = r#"thing = foo"#;
let result = flatten_from_str(cddl_input).unwrap();
assert_eq!(result, make_rule("thing", Rule::from("foo")));
}
#[test]
fn test_flatten_map() {
let cddl_input = r#"thing = { foo: tstr }"#;
let result = flatten_from_str(cddl_input).unwrap();
let expected = make_rule("thing", make_map().append(kv("foo".literal(), tstr(), Cut)));
assert_eq!(result, expected);
let cddl_input = r#"thing = { tstr => tstr }"#;
let result = flatten_from_str(cddl_input).unwrap();
let expected = make_rule("thing", make_map().append(kv(tstr(), tstr(), NoCut)));
assert_eq!(result, expected);
let cddl_input = r#"foo = "bar" thing = { foo => tstr }"#;
let result = flatten_from_str(cddl_input).unwrap();
let expected = make_rules(vec![
("foo", "bar".literal()),
(
"thing",
make_map().append(kv("foo".into(), tstr(), NoCut)).into(),
),
]);
assert_eq!(result, expected);
}
#[test]
fn test_flatten_generic() {
let cddl_input = "message<t, v> = [t, v]";
let result = flatten_from_str(cddl_input).unwrap();
let expected = make_generic_rule(
"message",
&["t", "v"],
make_array()
.append(Rule::from("t"))
.append(Rule::from("v"))
.into(),
);
assert_eq!(result, expected);
}
#[test]
fn test_control_op() {
let cddl_input = "four_bytes = tstr .size 4";
let result = flatten_from_str(cddl_input).unwrap();
let expected = make_rule(
"four_bytes",
Control::Size(CtlOpSize {
target: Box::new(tstr()),
size: Box::new(4.literal()),
}),
);
assert_eq!(result, expected);
}
}