| use crate::string::parse_string; |
| use crate::{context_tag, end_delimiter, utils::*}; |
| use nom::combinator::map_res; |
| use nom::Err; |
| use nom::{ |
| branch::alt, |
| bytes::complete::tag, |
| character::complete::char, |
| combinator::{cut, map, opt}, |
| error::{context, convert_error, VerboseError}, |
| multi::{many0, separated_list0}, |
| sequence::{delimited, tuple}, |
| }; |
| use std::collections::HashMap; |
| use std::ops::{Deref, DerefMut}; |
| use std::path::Path; |
| |
| /// a dictionary in a blueprint file |
| #[derive(Debug, PartialEq, Clone, Eq)] |
| pub struct Map(pub HashMap<String, Value>); |
| impl Deref for Map { |
| type Target = HashMap<String, Value>; |
| fn deref(&self) -> &Self::Target { |
| &self.0 |
| } |
| } |
| impl DerefMut for Map { |
| fn deref_mut(&mut self) -> &mut Self::Target { |
| &mut self.0 |
| } |
| } |
| fn parse_dict(input: &str) -> VerboseResult<Map> { |
| context( |
| "dict", |
| map( |
| delimited( |
| tuple((space_or_comments, context_tag!("{"), space_or_comments)), |
| separated_list0(char(','), parse_module_entry), |
| end_delimiter!("}"), |
| ), |
| |entries| Map(entries.into_iter().collect()), |
| ), |
| )(input) |
| } |
| #[derive(Debug, PartialEq, Clone, Eq)] |
| pub struct Function { |
| pub name: String, |
| pub args: Vec<Value>, |
| } |
| fn parse_function(input: &str) -> VerboseResult<Function> { |
| context( |
| "function", |
| map( |
| tuple(( |
| space_or_comments, |
| identifier, |
| space_or_comments, |
| delimited( |
| tuple((space_or_comments, context_tag!("("), space_or_comments)), |
| separated_list0(comma, parse_expr), |
| end_delimiter!(")"), |
| ), |
| )), |
| |(_, name, _, args)| Function { |
| name: name.to_string(), |
| args, |
| }, |
| ), |
| )(input) |
| } |
| /// a value in a blueprint file |
| #[derive(Debug, PartialEq, Clone, Eq)] |
| pub enum Value { |
| String(String), |
| Integer(i64), |
| Array(Vec<Value>), |
| Boolean(bool), |
| Map(Map), |
| Ident(String), |
| ConcatExpr(Vec<Value>), |
| Function(Function), |
| } |
| // convert value from str |
| impl From<&str> for Value { |
| fn from(s: &str) -> Self { |
| Value::String(s.to_string()) |
| } |
| } |
| fn parse_value(input: &str) -> VerboseResult<Value> { |
| context( |
| "value", |
| alt(( |
| map(parse_array, Value::Array), |
| map(parse_function, Value::Function), |
| map(string_literal, Value::String), |
| map(parse_bool, Value::Boolean), |
| map(parse_dict, Value::Map), |
| map(parse_int, Value::Integer), |
| map(identifier, |x| Value::Ident(x.to_string())), |
| )), |
| )(input) |
| } |
| fn concat_value_string(values: Vec<Value>) -> Result<Value, &'static str> { |
| let mut result = String::new(); |
| for value in values { |
| match value { |
| Value::String(s) => result.push_str(&s), |
| _ => Err("value is not a string")?, |
| } |
| } |
| Ok(Value::String(result)) |
| } |
| fn concat_value_array(values: Vec<Value>) -> Result<Value, &'static str> { |
| let mut result = Vec::new(); |
| for value in values { |
| match value { |
| Value::Array(a) => result.extend(a), |
| _ => Err("value is not an array")?, |
| } |
| } |
| Ok(Value::Array(result)) |
| } |
| pub(crate) fn parse_expr(input: &str) -> VerboseResult<Value> { |
| // in bp, value can be combined with '+' operator |
| // this parser parse the expression and combine the values |
| // into a single value, if there is no Ident in the values |
| context( |
| "expr", |
| map_res( |
| separated_list0( |
| tuple((space_or_comments, char('+'), space_or_comments)), |
| parse_value, |
| ), |
| |values| { |
| match values.len() { |
| 0 => Err("no value"), |
| 1 => Ok(values[0].clone()), |
| _ => { |
| // if there is one ident we cannot concat |
| if values |
| .iter() |
| .any(|v| matches!(v, Value::Ident(_) | Value::Function(_))) |
| { |
| return Ok(Value::ConcatExpr(values)); |
| } |
| match &values[0] { |
| Value::String(_) => concat_value_string(values), |
| Value::Array(_) => concat_value_array(values), |
| _ => Err("first value is not a string"), |
| } |
| } |
| } |
| }, |
| ), |
| )(input) |
| } |
| pub(crate) fn parse_array(input: &str) -> VerboseResult<Vec<Value>> { |
| context( |
| "array", |
| delimited( |
| ws(char('[')), |
| separated_list0(comma, parse_expr), |
| end_delimiter!("]"), |
| ), |
| )(input) |
| } |
| |
| /// a blueprint file |
| #[derive(Debug, PartialEq, Clone, Eq)] |
| pub struct BluePrint { |
| /// variables in the blueprint file |
| /// found in root of the file in the form of `key = value` |
| pub variables: HashMap<String, Value>, |
| /// all ordered modules in the blueprint file |
| pub modules: Vec<Module>, |
| } |
| |
| /// a module in a blueprint file |
| #[derive(Debug, PartialEq, Clone, Eq)] |
| pub struct Module { |
| pub typ: String, |
| pub entries: HashMap<String, Value>, |
| } |
| impl Module { |
| /// get an attribute value from a module |
| pub fn get(&self, key: &str) -> Option<&Value> { |
| self.entries.get(key) |
| } |
| /// get a string attribute value from a module |
| pub fn get_string(&self, key: &str) -> Option<&String> { |
| match self.get(key) { |
| Some(Value::String(s)) => Some(s), |
| _ => None, |
| } |
| } |
| /// get a boolean attribute value from a module |
| pub fn get_bool(&self, key: &str) -> Option<bool> { |
| match self.get(key) { |
| Some(Value::Boolean(b)) => Some(*b), |
| _ => None, |
| } |
| } |
| /// get an array attribute value from a module |
| pub fn get_array(&self, key: &str) -> Option<&Vec<Value>> { |
| match self.get(key) { |
| Some(Value::Array(a)) => Some(a), |
| _ => None, |
| } |
| } |
| /// get a map attribute value from a module |
| pub fn get_map(&self, key: &str) -> Option<&Map> { |
| match self.get(key) { |
| Some(Value::Map(d)) => Some(d), |
| _ => None, |
| } |
| } |
| /// get an identifier attribute value from a module |
| pub fn get_ident(&self, key: &str) -> Option<&String> { |
| match self.get(key) { |
| Some(Value::Ident(i)) => Some(i), |
| _ => None, |
| } |
| } |
| /// get an integer attribute value from a module |
| pub fn get_int(&self, key: &str) -> Option<i64> { |
| match self.get(key) { |
| Some(Value::Integer(i)) => Some(*i), |
| _ => None, |
| } |
| } |
| } |
| /// parse a module entry, with `:` as delimiter |
| pub(crate) fn parse_module_entry(input: &str) -> VerboseResult<(String, Value)> { |
| _parse_module_entry(input, ':') |
| } |
| /// second form of module entry, with `=` as delimiter |
| pub(crate) fn parse_module_entry2(input: &str) -> VerboseResult<(String, Value)> { |
| _parse_module_entry(input, '=') |
| } |
| pub(crate) fn _parse_module_entry(input: &str, delimiter: char) -> VerboseResult<(String, Value)> { |
| context( |
| "module entry", |
| map( |
| tuple(( |
| space_or_comments, |
| alt(( |
| map(identifier, |x| x.to_string()), |
| parse_string::<VerboseError<&str>>, |
| )), |
| space_or_comments, |
| char(delimiter), |
| space_or_comments, |
| cut(parse_expr), |
| space_or_comments, |
| )), |
| |(_, key, _, _, _, value, _)| (key.to_string(), value), |
| ), |
| )(input) |
| } |
| |
| pub(crate) fn parse_module(input: &str) -> VerboseResult<Module> { |
| // parse a identifier followed by a module of entries |
| let (input, _) = space_or_comments(input)?; |
| let (input, ident) = identifier(input)?; |
| let (input, _) = space_or_comments(input)?; |
| let (input, module) = context( |
| "module", |
| alt(( |
| map( |
| delimited( |
| tuple((space_or_comments, context_tag!("{"), space_or_comments)), |
| separated_list0(char(','), parse_module_entry), |
| end_delimiter!("}"), |
| ), |
| |entries| entries.into_iter().collect(), |
| ), |
| map( |
| delimited( |
| tuple((space_or_comments, context_tag!("("), space_or_comments)), |
| separated_list0(char(','), parse_module_entry2), |
| end_delimiter!(")"), |
| ), |
| |entries| entries.into_iter().collect(), |
| ), |
| )), |
| )(input)?; |
| Ok(( |
| input, |
| Module { |
| typ: ident.to_string(), |
| entries: module, |
| }, |
| )) |
| } |
| |
| pub(crate) fn parse_define(input: &str) -> VerboseResult<(String, String, Value)> { |
| context( |
| "define", |
| map( |
| tuple(( |
| space_or_comments, |
| identifier, |
| space_or_comments, |
| alt((tag("="), tag("+="))), |
| space_or_comments, |
| cut(parse_expr), |
| space_or_comments, |
| )), |
| |(_, key, _, op, _, value, _)| (key.to_string(), op.to_string(), value), |
| ), |
| )(input) |
| } |
| |
| pub(crate) fn parse_blueprint(input: &str) -> VerboseResult<BluePrint> { |
| let mut entries = Vec::new(); |
| let mut variables = HashMap::new(); |
| let (input, _) = context( |
| "blueprint", |
| many0(alt(( |
| map(parse_module, |b| { |
| entries.push(b); |
| () |
| }), |
| map_res(parse_define, |(k, op, v)| match op.as_str() { |
| "=" => { |
| variables.insert(k, v); |
| Ok(()) |
| } |
| "+=" => { |
| let e = variables.entry(k); |
| match e { |
| std::collections::hash_map::Entry::Occupied(prev) => { |
| let prev = prev.into_mut(); |
| match prev { |
| Value::String(s) => { |
| match v { |
| Value::String(s2) => { |
| s.push_str(&s2); |
| } |
| _ => Err("cannot append value to string")?, |
| } |
| } |
| Value::Array(a) => { |
| match v { |
| Value::Array(a2) => { |
| a.extend(a2); |
| } |
| Value::Ident(_) => { |
| Err("FIXME in this case, we should turn the Array into ConcatExpr")? |
| } |
| _ => Err("cannot append value to array")?, |
| } |
| } |
| Value::Integer(i) => { |
| match v { |
| Value::Integer(i2) => { |
| *i += i2; |
| } |
| _ => Err("cannot append value to integer")?, |
| } |
| } |
| _ => Err("cannot append value to this type")?, |
| } |
| } |
| std::collections::hash_map::Entry::Vacant(_) => Err("variable not found")?, |
| } |
| Ok(()) |
| } |
| _ => Err("unknown operator"), |
| }), |
| space_or_comments1, |
| ))), |
| )(input)?; |
| Ok(( |
| input, |
| BluePrint { |
| variables: variables, |
| modules: entries, |
| }, |
| )) |
| } |
| |
| pub(crate) fn format_err(input: &str, err: Err<VerboseError<&str>>) -> String { |
| match err { |
| Err::Error(e) | Err::Failure(e) => convert_error(input, e.into()), |
| Err::Incomplete(_) => "Incomplete".to_string(), |
| } |
| } |
| impl BluePrint { |
| /// parse an Android.bp file from a string |
| pub fn parse(input: &str) -> Result<Self, String> { |
| match parse_blueprint(input) { |
| Ok((rest, result)) => { |
| if rest.len() > 0 { |
| return Err(format!("Unexpected left input: {}", rest)); |
| } |
| Ok(result) |
| } |
| Err(err) => Err(format_err(input, err)), |
| } |
| } |
| /// parse an Android.bp file from a file path |
| pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, String> { |
| let input = std::fs::read_to_string(path).map_err(|e| e.to_string())?; |
| Self::parse(&input) |
| } |
| /// get all modules of a specific type |
| pub fn modules_by_type<'a>(&'a self, typ: &'static str) -> impl Iterator<Item = &'a Module> { |
| self.modules.iter().filter(move |b| b.typ == typ) |
| } |
| } |