use std::fmt::Display;
use std::fmt::Error as FormatError;
use std::fmt::{Formatter};
use std::collections::BTreeMap;
use super::tokenizer::Pos;
use super::errors::{Error, ErrorCollector};
use super::parser::Node as P;
use super::parser::{Node, Document};
use super::tokenizer::TokenType as T;
use self::Ast::*;
use self::NullKind::*;
use self::ScalarKind::*;
use self::Tag::*;
use options::{Options, Include, DoInclude};
#[derive(Debug)]
pub enum ScalarKind {
Plain,
Quoted,
}
#[derive(Debug)]
pub enum NullKind {
Implicit,
Explicit,
}
#[derive(Debug)]
pub enum Tag {
NonSpecific,
LocalTag(String),
GlobalTag(String),
}
impl Tag {
pub fn is_specific(&self) -> bool {
match *self {
NonSpecific => false,
_ => true,
}
}
}
#[derive(Debug)]
pub enum Ast {
Map(Pos, Tag, BTreeMap<String, Ast>),
Seq(Pos, Tag, Vec<Ast>),
Scalar(Pos, Tag, ScalarKind, String),
Null(Pos, Tag, NullKind),
}
impl Display for Ast {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FormatError> {
match *self {
Map(_, _, _) => write!(fmt, "Map"),
Seq(_, _, _) => write!(fmt, "Seq"),
Scalar(_, _, _, _) => write!(fmt, "Scalar"),
Null(_, _, _) => write!(fmt, "Null"),
}
}
}
impl Ast {
pub fn pos(&self) -> Pos {
match *self {
Map(ref pos, _, _) => pos.clone(),
Seq(ref pos, _, _) => pos.clone(),
Scalar(ref pos, _, _, _) => pos.clone(),
Null(ref pos, _, _) => pos.clone(),
}
}
pub fn tag<'x>(&'x self) -> &'x Tag {
match *self {
Map(_, ref tag, _) => tag,
Seq(_, ref tag, _) => tag,
Scalar(_, ref tag, _, _) => tag,
Null(_, ref tag, _) => tag,
}
}
pub fn with_tag(self, tag: Tag) -> Ast {
match self {
Map(pos, _, children) => Map(pos, tag, children),
Seq(pos, _, children) => Seq(pos, tag, children),
Scalar(pos, _, style, value) => Scalar(pos, tag, style, value),
Null(pos, _, kind) => Null(pos, tag, kind),
}
}
pub fn void(pos: &Pos) -> Ast {
Ast::Null(pos.clone(), Tag::NonSpecific, NullKind::Implicit)
}
}
struct Context<'a, 'b: 'a> {
options: &'a Options<'b>,
err: &'a ErrorCollector,
}
fn pos_for_node<'x>(node: &Node<'x>) -> Pos {
match *node {
P::Map(_, _, _, ref tokens) => tokens[0].start.clone(),
P::Seq(_, _, _, ref tokens) => tokens[0].start.clone(),
P::Scalar(_, _, _, ref token) => token.start.clone(),
P::ImplicitNull(_, _, ref pos) => pos.clone(),
P::Alias(_, ref token, _) => token.start.clone(),
}
}
fn parse_key<'x>(node: &Node, value: &'x Node<'x>, merge: &mut Option<&Node<'x>>)
-> Result<String, Option<Error>>
{
match *node {
P::Scalar(_, _, ref key, ref tok) => {
if tok.kind == T::PlainString && &key[..] == "<<" {
*merge = Some(value);
return Err(None);
}
Ok(key.clone())
}
P::ImplicitNull(_, _, _) => Ok("".to_string()),
P::Alias(_, _, ref node) => {
parse_key(node, value, merge)
}
ref node => {
Err(Some(Error::preprocess_error(&pos_for_node(node),
"Non scalar keys are not supported yet"
.to_string())))
}
}
}
impl<'a, 'b: 'a> Context<'a, 'b> {
fn process(&mut self, node: &'a Node<'a>) -> Ast {
match *node {
P::Map(ref origtag, _, _, ref tokens) => {
let pos = tokens[0].start.clone();
let tag = self.string_to_tag(&pos, origtag);
let mut mapping = BTreeMap::new();
self.merge_mapping(&mut mapping, node);
return Map(pos, tag, mapping);
}
P::Seq(ref origtag, _, _, ref tokens) => {
let pos = tokens[0].start.clone();
let tag = self.string_to_tag(&pos, origtag);
let mut seq = Vec::new();
self.merge_sequence(&mut seq, node);
return Seq(pos, tag, seq);
}
P::Scalar(Some("!*Include"), _anch, ref val, ref tok) => {
return self.options.include(&tok.start,
&Include::File { filename: val }, self.err);
}
P::Scalar(Some("!*IncludeSeq"), _anch, ref val, ref tok) => {
return self.options.include(&tok.start,
&Include::Sequence { pattern: val }, self.err);
}
P::Scalar(Some("!*IncludeMap"), _anch, ref val, ref tok) => {
return self.options.include(&tok.start,
&Include::Mapping { pattern: val }, self.err);
}
P::Scalar(ref tag, _, ref val, ref tok) => {
let pos = tok.start.clone();
let tag = self.string_to_tag(&pos, tag);
if tok.kind == T::PlainString {
if &val[..] == "~" || &val[..] == "null" {
return Ast::Null(tok.start.clone(), tag, Explicit);
} else {
return Ast::Scalar(pos, tag, Plain, val.clone());
}
} else {
return Ast::Scalar(pos, tag, Quoted, val.clone());
}
}
P::ImplicitNull(ref tag, _, ref pos) => {
let tag = self.string_to_tag(pos, tag);
return Ast::Null(pos.clone(), tag, Implicit);
}
P::Alias(_, _, ref node) => {
return self.process(node);
}
}
}
fn merge_mapping(&mut self, target: &mut BTreeMap<String, Ast>,
node: &'a Node<'a>)
{
match *node {
P::Map(_, _, ref children, _) => {
let mut merge = None;
for (k, v) in children.iter() {
let string_key = match parse_key(k, v, &mut merge) {
Ok(k) => k,
Err(None) => continue, Err(Some(e)) => {
self.err.add_error(e);
continue;
}
};
let value = self.process(v);
if !target.contains_key(&string_key) {
target.insert(string_key, value);
}
}
match merge {
Some(node) => self.merge_mapping(target, node),
_ => {}
}
}
P::Seq(_, _, ref lst, _) => {
for item in lst.iter() {
self.merge_mapping(target, item);
}
}
P::Scalar(Some("!*Include"), _anch, ref val, ref tok) => {
self.merge_mapping_ast(target,
self.options.include(&tok.start,
&Include::File { filename: val }, self.err));
}
P::Alias(_, _, ref node) => {
self.merge_mapping(target, node);
}
_ => {
self.err.add_error(Error::preprocess_error(&pos_for_node(node),
"Value of merge key must be either mapping or \
list of mappings".to_string()));
}
}
}
fn merge_mapping_ast(&mut self, target: &mut BTreeMap<String, Ast>,
ast: Ast)
{
match ast {
Map(_, _, children) => {
for (k, v) in children.into_iter() {
if !target.contains_key(&k) {
target.insert(k, v);
}
}
}
Seq(_, _, lst) => {
for item in lst.into_iter() {
self.merge_mapping_ast(target, item);
}
}
node => {
self.err.add_error(Error::preprocess_error(&node.pos(),
"Value of merge key must be either mapping or \
list of mappings".to_string()));
}
}
}
fn merge_sequence(&mut self, target: &mut Vec<Ast>,
node: &'a Node<'a>)
{
match *node {
P::Seq(_, _, ref children, _) => {
for item in children.iter() {
match *item {
P::Seq(Some("!*Unpack"), _, ref children, _) => {
for child in children.iter() {
self.merge_sequence(target, child);
}
}
_ => {
let value = self.process(item);
target.push(value);
}
}
}
}
P::Alias(_, _, ref node) => {
self.merge_sequence(target, node);
}
P::Scalar(Some("!*Include"), _anch, ref val, ref tok) => {
let ast = self.options.include(&tok.start,
&Include::File { filename: val }, self.err);
match ast {
Seq(_, _, vec) => {
target.extend(vec)
}
other => {
self.err.add_error(
Error::preprocess_error(&pos_for_node(node),
"The of !*Unpack node must be sequence"
.to_string()));
target.push(other);
}
}
}
_ => {
self.err.add_error(Error::preprocess_error(&pos_for_node(node),
"The of !*Unpack node must be sequence".to_string()));
}
}
}
fn string_to_tag(&mut self, pos: &Pos, src: &Option<&'a str>)
-> Tag
{
match *src {
Some(val) => {
let mut pieces = val.splitn(3, '!');
assert!(pieces.next().unwrap() == "");
match (pieces.next().unwrap(), pieces.next()) {
("", None) => {
self.err.add_error(Error::preprocess_error(pos,
"Unexpected empty tag".to_string()));
NonSpecific
}
(val, None) => {
LocalTag(val.to_string())
}
("", Some(_)) => {
self.err.add_error(Error::preprocess_error(pos,
"Global tags are unsupported yet".to_string()));
NonSpecific
}
(_, Some(_)) => {
self.err.add_error(Error::preprocess_error(pos,
"Tag prefixes are unsupported yet".to_string()));
NonSpecific
}
}
}
None => NonSpecific,
}
}
}
pub fn process(opt: &Options, doc: Document, err: &ErrorCollector) -> Ast {
let mut ctx = Context {
options: opt,
err: err,
};
return ctx.process(&doc.root);
}