forked from Proxmox/proxmox
rewrite parse_object
This should support arbitrary expresion, so gobble up elements up to the next comma and then run it through syn::parse2. Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
1791c089b6
commit
10451f65eb
@ -1,22 +1,22 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use proc_macro2::TokenStream;
|
||||
|
||||
use derive_builder::Builder;
|
||||
use failure::{bail, Error};
|
||||
use quote::{quote, ToTokens};
|
||||
|
||||
use super::parsing::Value;
|
||||
use super::parsing::Expression;
|
||||
|
||||
#[derive(Builder)]
|
||||
pub struct ParameterDefinition {
|
||||
pub description: syn::LitStr,
|
||||
#[builder(default)]
|
||||
pub validate: Option<Ident>,
|
||||
pub validate: Option<syn::Expr>,
|
||||
#[builder(default)]
|
||||
pub minimum: Option<syn::Lit>,
|
||||
pub minimum: Option<syn::Expr>,
|
||||
#[builder(default)]
|
||||
pub maximum: Option<syn::Lit>,
|
||||
pub maximum: Option<syn::Expr>,
|
||||
}
|
||||
|
||||
impl ParameterDefinition {
|
||||
@ -24,7 +24,7 @@ impl ParameterDefinition {
|
||||
ParameterDefinitionBuilder::default()
|
||||
}
|
||||
|
||||
pub fn from_object(obj: HashMap<String, Value>) -> Result<Self, Error> {
|
||||
pub fn from_object(obj: HashMap<String, Expression>) -> Result<Self, Error> {
|
||||
let mut def = ParameterDefinition::builder();
|
||||
|
||||
for (key, value) in obj {
|
||||
@ -33,13 +33,13 @@ impl ParameterDefinition {
|
||||
def.description(value.expect_lit_str()?);
|
||||
}
|
||||
"validate" => {
|
||||
def.validate(Some(value.expect_ident()?));
|
||||
def.validate(Some(value.expect_expr()?));
|
||||
}
|
||||
"minimum" => {
|
||||
def.minimum(Some(value.expect_lit()?));
|
||||
def.minimum(Some(value.expect_expr()?));
|
||||
}
|
||||
"maximum" => {
|
||||
def.maximum(Some(value.expect_lit()?));
|
||||
def.maximum(Some(value.expect_expr()?));
|
||||
}
|
||||
other => bail!("invalid key in type definition: {}", other),
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ pub fn api_macro(attr: TokenStream, item: TokenStream) -> Result<TokenStream, Er
|
||||
_ => bail!("expected api definition in braces"),
|
||||
};
|
||||
|
||||
let definition = parse_object(definition)?;
|
||||
let definition = parse_object2(definition)?;
|
||||
|
||||
// Now parse the item, based on which we decide whether this is an API method which needs a
|
||||
// wrapper, or an API type which needs an ApiType implementation!
|
||||
@ -39,7 +39,7 @@ pub fn api_macro(attr: TokenStream, item: TokenStream) -> Result<TokenStream, Er
|
||||
}
|
||||
|
||||
fn handle_function(
|
||||
mut definition: HashMap<String, Value>,
|
||||
mut definition: HashMap<String, Expression>,
|
||||
mut item: syn::ItemFn,
|
||||
) -> Result<TokenStream, Error> {
|
||||
if item.decl.generics.lt_token.is_some() {
|
||||
@ -233,7 +233,7 @@ fn handle_function(
|
||||
and specify its return type via the `returns` property",
|
||||
)
|
||||
})?
|
||||
.expect_ident()?
|
||||
.expect_type()?
|
||||
.into_token_stream(),
|
||||
});
|
||||
|
||||
@ -295,7 +295,7 @@ fn handle_function(
|
||||
}
|
||||
|
||||
fn handle_struct(
|
||||
definition: HashMap<String, Value>,
|
||||
definition: HashMap<String, Expression>,
|
||||
item: &syn::ItemStruct,
|
||||
) -> Result<TokenStream, Error> {
|
||||
if item.generics.lt_token.is_some() {
|
||||
@ -312,7 +312,7 @@ fn handle_struct(
|
||||
}
|
||||
|
||||
fn handle_struct_unnamed(
|
||||
definition: HashMap<String, Value>,
|
||||
definition: HashMap<String, Expression>,
|
||||
name: &Ident,
|
||||
item: &syn::FieldsUnnamed,
|
||||
) -> Result<TokenStream, Error> {
|
||||
@ -349,7 +349,7 @@ fn handle_struct_unnamed(
|
||||
}
|
||||
|
||||
fn handle_struct_named(
|
||||
definition: HashMap<String, Value>,
|
||||
definition: HashMap<String, Expression>,
|
||||
name: &Ident,
|
||||
item: &syn::FieldsNamed,
|
||||
) -> Result<TokenStream, Error> {
|
||||
@ -396,7 +396,7 @@ fn handle_struct_named(
|
||||
|
||||
fn handle_named_struct_fields(
|
||||
item: &syn::FieldsNamed,
|
||||
mut field_def: HashMap<String, Value>,
|
||||
mut field_def: HashMap<String, Expression>,
|
||||
) -> Result<Vec<TokenStream>, Error> {
|
||||
let mut verify_entries = Vec::new();
|
||||
|
||||
|
@ -3,7 +3,7 @@ use std::collections::HashMap;
|
||||
use proc_macro2::{Delimiter, Group, Ident, Span, TokenStream, TokenTree};
|
||||
|
||||
use failure::{bail, Error};
|
||||
use syn::Lit;
|
||||
use syn::{Expr, Lit};
|
||||
|
||||
pub type RawTokenIter = proc_macro2::token_stream::IntoIter;
|
||||
pub type TokenIter = std::iter::Peekable<RawTokenIter>;
|
||||
@ -136,108 +136,133 @@ pub fn finish_hyphenated_name(tokens: &mut TokenIter, name: Ident) -> Result<syn
|
||||
Ok(syn::LitStr::new(&name, span))
|
||||
}
|
||||
|
||||
// parse an object notation:
|
||||
// object := '{' [ member * ] '}'
|
||||
// member := <ident> ':' <member_value>
|
||||
// member_value := [ "optional" ] ( <ident> | <literal> | <object> )
|
||||
#[derive(Debug)]
|
||||
pub enum Value {
|
||||
//Ident(Ident), // eg. `string` or `integer`
|
||||
//Description(syn::LitStr), // eg. `"some text"`
|
||||
Ident(Ident), // eg. `foo`, for referencing stuff, may become `expression`?
|
||||
Literal(syn::Lit), // eg. `123`
|
||||
Negative(syn::Lit), // eg. `-123`
|
||||
Object(HashMap<String, Value>), // eg. `{ key: value }`
|
||||
pub enum Expression {
|
||||
Expr(Expr),
|
||||
Object(HashMap<String, Expression>),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn expect_lit(self) -> Result<syn::Lit, Error> {
|
||||
match self {
|
||||
Value::Literal(lit) => Ok(lit),
|
||||
other => bail!("expected string literal, got: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
pub fn expect_lit_str(self) -> Result<syn::LitStr, Error> {
|
||||
match self {
|
||||
Value::Literal(syn::Lit::Str(lit)) => Ok(lit),
|
||||
Value::Literal(other) => bail!("expected string literal, got: {:?}", other),
|
||||
other => bail!("expected string literal, got: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_ident(self) -> Result<Ident, Error> {
|
||||
match self {
|
||||
Value::Ident(ident) => Ok(ident),
|
||||
other => bail!("expected ident, got: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_object(self) -> Result<HashMap<String, Value>, Error> {
|
||||
match self {
|
||||
Value::Object(obj) => Ok(obj),
|
||||
other => bail!("expected ident, got: {:?}", other),
|
||||
Expression::Expr(expr) => match expr {
|
||||
Expr::Lit(lit) => match lit.lit {
|
||||
Lit::Str(lit) => Ok(lit),
|
||||
other => bail!("expected string literal, got: {:?}", other),
|
||||
}
|
||||
other => bail!("expected string literal, got: {:?}", other),
|
||||
}
|
||||
_ => bail!("expected string literal"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_lit_bool(self) -> Result<syn::LitBool, Error> {
|
||||
match self {
|
||||
Value::Literal(syn::Lit::Bool(lit)) => Ok(lit),
|
||||
Value::Literal(other) => bail!("expected booleanliteral, got: {:?}", other),
|
||||
other => bail!("expected boolean literal, got: {:?}", other),
|
||||
Expression::Expr(expr) => match expr {
|
||||
Expr::Lit(lit) => match lit.lit {
|
||||
Lit::Bool(lit) => Ok(lit),
|
||||
other => bail!("expected boolean literal, got: {:?}", other),
|
||||
}
|
||||
other => bail!("expected boolean literal, got: {:?}", other),
|
||||
}
|
||||
_ => bail!("expected boolean literal"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_expr(self) -> Result<syn::Expr, Error> {
|
||||
match self {
|
||||
Expression::Expr(expr) => Ok(expr),
|
||||
_ => bail!("expected expression, found {:?}", self),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_object(self) -> Result<HashMap<String, Expression>, Error> {
|
||||
match self {
|
||||
Expression::Object(obj) => Ok(obj),
|
||||
_ => bail!("expected object, found an expression"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_type(self) -> Result<syn::ExprPath, Error> {
|
||||
match self {
|
||||
Expression::Expr(expr) => match expr {
|
||||
Expr::Path(path) => Ok(path),
|
||||
other => bail!("expected a type name, got {:?}", other),
|
||||
}
|
||||
_ => bail!("expected a type name, got {:?}", self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_object(tokens: TokenStream) -> Result<HashMap<String, Value>, Error> {
|
||||
pub fn parse_object2(tokens: TokenStream) -> Result<HashMap<String, Expression>, Error> {
|
||||
let mut tokens = tokens.into_iter().peekable();
|
||||
let mut out = HashMap::new();
|
||||
|
||||
loop {
|
||||
if tokens.peek().is_none() {
|
||||
break;
|
||||
}
|
||||
|
||||
let key = need_ident_or_string(&mut tokens)?;
|
||||
match_colon(&mut tokens)?;
|
||||
|
||||
let key = match parse_object_key(&mut tokens)? {
|
||||
Some(key) => key,
|
||||
None => break,
|
||||
};
|
||||
let key_name = key.to_string();
|
||||
|
||||
let member = match tokens.next() {
|
||||
Some(TokenTree::Group(group)) => {
|
||||
if group.delimiter() == Delimiter::Brace {
|
||||
Value::Object(parse_object(group.stream())?)
|
||||
} else {
|
||||
bail!("invalid group delimiter: {:?}", group.delimiter());
|
||||
}
|
||||
}
|
||||
Some(TokenTree::Punct(ref punct)) if punct.as_char() == '-' => {
|
||||
if let Some(TokenTree::Literal(literal)) = tokens.next() {
|
||||
let lit = Lit::new(literal);
|
||||
match lit {
|
||||
Lit::Int(_) | Lit::Float(_) => Value::Negative(lit),
|
||||
_ => bail!("expected literal after unary minus"),
|
||||
}
|
||||
} else {
|
||||
bail!("expected literal value");
|
||||
}
|
||||
}
|
||||
Some(TokenTree::Literal(literal)) => Value::Literal(Lit::new(literal)),
|
||||
Some(TokenTree::Ident(ident)) => Value::Ident(ident),
|
||||
Some(other) => bail!("expected member value at {}", other),
|
||||
None => bail!("missing member value after {}", key_name),
|
||||
};
|
||||
let value = parse_object_value(&mut tokens, &key_name)?;
|
||||
|
||||
if out.insert(key_name.clone(), member).is_some() {
|
||||
if out.insert(key_name.clone(), value).is_some() {
|
||||
bail!("duplicate entry: {}", key_name);
|
||||
}
|
||||
|
||||
comma_or_end(&mut tokens)?;
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn parse_object_key(tokens: &mut TokenIter) -> Result<Option<Name>, Error> {
|
||||
if tokens.peek().is_none() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let key = need_ident_or_string(&mut *tokens)?;
|
||||
match_colon(&mut *tokens)?;
|
||||
Ok(Some(key))
|
||||
}
|
||||
|
||||
fn parse_object_value(tokens: &mut TokenIter, key: &str) -> Result<Expression, Error> {
|
||||
let mut value_tokens = TokenStream::new();
|
||||
|
||||
let mut first = true;
|
||||
loop {
|
||||
let token = match tokens.next() {
|
||||
Some(token) => token,
|
||||
None => {
|
||||
if first {
|
||||
bail!("missing value after key '{}'", key);
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if first {
|
||||
first = false;
|
||||
if let TokenTree::Group(group) = token {
|
||||
let expr = parse_object2(group.stream())?;
|
||||
comma_or_end(tokens)?;
|
||||
return Ok(Expression::Object(expr));
|
||||
}
|
||||
}
|
||||
|
||||
match token {
|
||||
TokenTree::Punct(ref punct) if punct.as_char() == ',' => {
|
||||
// This is the end of the value!
|
||||
break;
|
||||
}
|
||||
_ => value_tokens.extend(vec![token]),
|
||||
}
|
||||
}
|
||||
|
||||
let expr: Expr = syn::parse2(value_tokens)?;
|
||||
|
||||
Ok(Expression::Expr(expr))
|
||||
}
|
||||
|
||||
fn need_ident_or_string(tokens: &mut TokenIter) -> Result<Name, Error> {
|
||||
match tokens.next() {
|
||||
Some(TokenTree::Ident(ident)) => Ok(ident.into()),
|
||||
@ -249,7 +274,7 @@ fn need_ident_or_string(tokens: &mut TokenIter) -> Result<Name, Error> {
|
||||
}
|
||||
}
|
||||
Some(other) => bail!(
|
||||
"expected colon after key in api definition at {:?}",
|
||||
"expected an identifier or a string: {:?}",
|
||||
other.span()
|
||||
),
|
||||
None => bail!("ident expected"),
|
||||
|
Loading…
Reference in New Issue
Block a user