use super::*;
use std::path::PathBuf;
use std::collections::HashMap;
#[derive(Debug, PartialEq, Clone)]
pub enum BlockItem
{
Property(String, Expr),
Child(Option<String>, Child)
}
#[derive(Debug, PartialEq, Clone)]
pub enum Child
{
Defined(Block),
Ref(String)
}
pub type Block = Arc<BlockInfo>;
#[derive(Debug, PartialEq, Clone)]
pub struct BlockInfo
{
pub kind: String,
pub name: Option<String>,
pub properties: HashMap<String, Expr>,
pub children: Vec<(Option<String>, Child)>
}
pub enum DocumentElem
{
Root(Block),
Ref(Block),
Include(PathBuf)
}
pub struct Document
{
pub elems: Vec<DocumentElem>
}
#[derive(Debug, PartialEq, Clone)]
pub struct Enum
{
pub name: String,
pub value: Option<Box<Expr>>
}
#[derive(Debug, PartialEq, Clone)]
pub enum Expr
{
Null,
Bool(bool),
Int(i64),
Float(f64),
Str(String),
List(Vec<Expr>),
Map(HashMap<String, Expr>),
Enum(Enum),
Path(PathBuf)
}
peg::parser!
{
grammar confiner_grammar(root_path: &std::path::Path) for str
{
rule alpha() -> char = ch:quiet!{['a'..='z' | 'A'..='Z' | '_']} { ch }
rule digit() -> char = ch:quiet!{['0'..='9']} { ch }
rule hex() = quiet!{['a'..='f' | 'A'..='F' | '0'..='9']}
rule sep() = quiet!{_ "," _}
rule _() = quiet!{[' ' | '\t' | '\n']*}
rule identifier() -> String
= id:$(alpha() ( alpha() / digit() )*) { id.to_owned() }
/ expected!("Identifier")
rule bool_expr() -> bool = "true" { true } / "false" { false }
rule int_expr() -> i64
= "0x" n:$(hex()+)
{? i64::from_str_radix(n, 16).or(Err("Hex Integer")) }
/ n:$("-"? digit()+)
{? i64::from_str_radix(n, 10).or(Err("Decimal Integer")) }
rule float_expr() -> f64
= n:$(
(['-'|'+'])?
digit()+
(
"." digit()+
/ ("." digit()+)? "e" (['+'|'-'])? digit()+
)
)
{? n.parse().or(Err("Float")) }
rule literal_str_char(hashes: usize) -> char
= !("\"" "#"*<{hashes}>) ch:[_] { ch }
rule literal_str_expr(hashes: usize) -> String
= "\"" chars:literal_str_char(hashes)* "\"" "#"*<{hashes}>
{ chars.into_iter().collect() }
/ "#" s:literal_str_expr(hashes + 1) { s }
rule str_char() -> char
= ch:[^ '\n' | '"' | '\\'] { ch }
/ "\\n" { '\n' }
/ "\\r" { '\r' }
/ "\\" ch:[_] { ch }
rule str_expr() -> String
= "\"" chars:str_char()* "\"" { chars.into_iter().collect() }
/ "#" s:literal_str_expr(1) { s }
rule list_expr() -> Vec<Expr>
= "[" _ vec:(expr() ** sep()) _ "]" { vec }
rule key_value() -> (String, Expr)
= key:identifier() _ "=" _ value:expr() { (key, value) }
rule map_expr() -> HashMap<String, Expr>
= "{" _ vec:(key_value() ** sep()) _ "}"
{ vec.into_iter().collect() }
rule enum_expr() -> Enum
= "!" name:identifier() _ value:expr()
{ Enum { name, value: Some(Box::new(value)) } }
/ "!" name:identifier()
{ Enum { name, value: None } }
rule path_char() -> char
= ch:alpha() { ch }
/ ch:digit() { ch }
/ ch:['-' | '/' | '.'] { ch }
/ "\\n" { '\n' }
/ "\\r" { '\r' }
/ "\\" ch:[_] { ch }
rule path_expr() -> PathBuf
= "./" chars:path_char()+
{ root_path.join(&chars.into_iter().collect::<String>()) }
/ "." { root_path.to_owned() }
pub rule expr() -> Expr
= "null" { Expr::Null }
/ val:bool_expr() { Expr::Bool(val) }
/ val:float_expr() { Expr::Float(val) }
/ val:int_expr() { Expr::Int(val) }
/ val:str_expr() { Expr::Str(val) }
/ val:list_expr() { Expr::List(val) }
/ val:map_expr() { Expr::Map(val) }
/ val:enum_expr() { Expr::Enum(val) }
/ val:path_expr() { Expr::Path(val) }
/ expected!("Expression")
rule role() -> String = "@" role:identifier() { role }
rule block_item() -> BlockItem
= kv:key_value()
{ BlockItem::Property(kv.0, kv.1) }
/ role:role()? _ child:block()
{ BlockItem::Child(role, Child::Defined(child)) }
/ role:role()? _ "&" name:identifier()
{ BlockItem::Child(role, Child::Ref(name)) }
pub rule block() -> Block
= name:identifier()? _ ":" _ kind:identifier() _
"{" _ items:(block_item() ** sep()) _ "}"
{
let mut properties = HashMap::<String, Expr>::new();
let mut children = Vec::<(Option<String>, Child)>::new();
for item in items
{
match item
{
BlockItem::Property(k, v) =>
{ properties.insert(k, v); },
BlockItem::Child(role, child) =>
{ children.push((role, child)); }
}
}
Arc::new(BlockInfo { kind, name, properties, children })
}
pub rule document_elem() -> DocumentElem
= "&" _ block:block() { DocumentElem::Ref(block) }
/ block:block() { DocumentElem::Root(block) }
/ "@include" _ path:path_expr() { DocumentElem::Include(path) }
pub rule document() -> Document
= _ elems:(document_elem() ** _) _ { Document { elems } }
}
}
pub use confiner_grammar::document;
pub use confiner_grammar::block;
pub use confiner_grammar::expr;
#[test]
fn test_block_info_empty_body()
{
assert_eq!(
*block(": kind {}", "/".as_ref()).unwrap(),
BlockInfo {
kind: "kind".to_owned(),
name: None,
properties: Default::default(),
children: Default::default()
}
);
assert_eq!(
*block("name: kind {}", "/".as_ref()).unwrap(),
BlockInfo {
kind: "kind".to_owned(),
name: Some("name".to_owned()),
children: Default::default(),
properties: Default::default()
}
);
}
#[test]
fn test_child_types()
{
assert_eq!(
block(": kind { &ref, : child {} }", "/".as_ref())
.unwrap().children,
vec![
(None, Child::Ref("ref".to_owned())),
(None, Child::Defined(
grammar::block(": child {}", "/".as_ref()).unwrap()))
]
)
}
#[test]
fn test_child_roles()
{
assert_eq!(
block(": kind { @rol &ref, @rol : child {} }", "/".as_ref())
.unwrap().children,
vec![
(Some("rol".to_owned()), Child::Ref("ref".to_owned())),
(Some("rol".to_owned()), Child::Defined(
grammar::block(": child {}", "/".as_ref()).unwrap()))
]
)
}
#[test]
fn test_expr_true()
{
assert_eq!(
expr("true", "/".as_ref()).unwrap(),
Expr::Bool(true)
);
}
#[test]
fn test_expr_false()
{
assert_eq!(
expr("false", "/".as_ref()).unwrap(),
Expr::Bool(false)
);
}
#[test]
fn test_expr_int()
{
assert_eq!(expr("100", "/".as_ref()).unwrap(), Expr::Int(100));
assert_eq!(expr("-100", "/".as_ref()).unwrap(), Expr::Int(-100));
assert_eq!(expr("0x100", "/".as_ref()).unwrap(), Expr::Int(0x100));
}
#[test]
fn test_expr_float()
{
assert_eq!(expr("0.1", "/".as_ref()).unwrap(), Expr::Float(0.1));
assert_eq!(expr("-1e6", "/".as_ref()).unwrap(), Expr::Float(-1000000.0));
}
#[test]
fn test_expr_str()
{
assert_eq!(
expr(r#""\"Hello\nWorld\"""#, "/".as_ref()).unwrap(),
Expr::Str("\"Hello\nWorld\"".to_owned())
);
assert_eq!(
expr(r#""""#, "/".as_ref()).unwrap(),
Expr::Str("".to_owned())
);
}
#[test]
fn test_expr_list()
{
assert_eq!(
expr("[1, 2, 3]", "/".as_ref()).unwrap(),
Expr::List(vec![Expr::Int(1), Expr::Int(2), Expr::Int(3)])
);
}
#[test]
fn test_expr_map()
{
assert_eq!(
expr(r#"{ key = "Hello", yek = "olleH" }"#, "/".as_ref()).unwrap(),
Expr::Map(vec![
("key".to_owned(), Expr::Str("Hello".to_owned())),
("yek".to_owned(), Expr::Str("olleH".to_owned()))
].into_iter().collect())
);
}
#[test]
fn test_expr_enum_with_data()
{
assert_eq!(
expr("!name", "/".as_ref()).unwrap(),
Expr::Enum(Enum{ name: "name".to_owned(), value: None })
);
}
#[test]
fn test_expr_enum_no_data()
{
assert_eq!(
expr("!name 2", "/".as_ref()).unwrap(),
Expr::Enum(Enum {
name: "name".to_owned(),
value: Some(Box::new(Expr::Int(2)))
})
);
}
#[test]
fn test_expr_path()
{
assert_eq!(
expr("./test.txt", "/".as_ref()).unwrap(),
Expr::Path(From::from("/test.txt"))
);
assert_eq!(
expr("./test.txt".as_ref(), "/directory".as_ref()).unwrap(),
Expr::Path(From::from("/directory/test.txt"))
);
assert_eq!(
expr(".".as_ref(), "/directory".as_ref()).unwrap(),
Expr::Path(From::from("/directory"))
);
}
#[test]
fn test_hash_str()
{
assert_eq!(
expr("#\"\"\"#", "/".as_ref()).unwrap(),
Expr::Str("\"".to_owned())
);
assert_eq!(
expr("##\"#\"\"##", "/".as_ref()).unwrap(),
Expr::Str("#\"".to_owned())
);
assert_eq!(
expr("#\"\\\"#", "/".as_ref()).unwrap(),
Expr::Str("\\".to_owned())
);
}