grammar<'err>(
lookup: &line_col::LineColLookup<'input>,
diagnostics: &'err mut Vec<Diagnostic>
);
use crate::ast;
use crate::diagnostic::{Diagnostic, DiagnosticKind};
use crate::javadoc;
pub OptAidl: Option<ast::Aidl> = {
<p:Package> <vi:Import*> <vdp:DeclaredParcelable*> <oi:OptItem> => {
oi.map(|item| ast::Aidl {
package: p,
imports: vi,
declared_parcelables: vdp,
item,
})
}
}
// e.g. package x.y.z;
pub(crate) Package: ast::Package = {
<fp1:@L> PACKAGE <sp1:@L> <name:QualifiedName> <sp2:@R> <fp2:@R> ";" => {
ast::Package {
name,
symbol_range: ast::Range::new(lookup, sp1, sp2),
full_range: ast::Range::new(lookup, fp1, fp2),
}
}
}
// e.g. import x.y.z;
pub(crate) Import: ast::Import = {
<fp1:@L> IMPORT <sp1:@L> <v:(<IDENT> ".")+> <n:IDENT> <sp2:@R> <fp2:@R> ";" => {
ast::Import {
path: v.join("."),
name: n.to_owned(),
symbol_range: ast::Range::new(lookup, sp1, sp2),
full_range: ast::Range::new(lookup, fp1, fp2),
}
}
}
// e.g. x OR x.y.z
QualifiedName: String = {
<v:(<IDENT> ".")*> <n:IDENT> => {
if v.is_empty() {
n.to_owned()
} else {
format!("{}.{}", v.join("."), n)
}
}
}
// e.g. parcelable MyParcelable;
pub(crate) DeclaredParcelable: ast::Import = {
<annotations:AnnotationList>
<fp1:@L> PARCELABLE <sp1:@L> <v:(<IDENT> ".")*> <n:IDENT> <sp2:@R> ";" <fp2:@R> => {
ast::Import {
path: v.join("."),
name: n.to_owned(),
symbol_range: ast::Range::new(lookup, sp1, sp2),
full_range: ast::Range::new(lookup, fp1, fp2),
}
}
}
// Interface, parcelable or enum
OptItem: Option<ast::Item> = {
<i:Interface> => Some(ast::Item::Interface(i)),
<p:Parcelable> => Some(ast::Item::Parcelable(p)),
<e:Enum> => Some(ast::Item::Enum(e)),
! =>? {
if let Some(d) = Diagnostic::from_error_recovery("Invalid item", lookup, <>) {
diagnostics.push(d);
}
Ok(None)
},
}
// e.g. interface Xyz { ... }
pub(crate) Interface: ast::Interface = {
<p0:@L>
<annotations:AnnotationList>
<fp1:@L> <oneway:ONEWAY?> INTERFACE <sp1:@L> <s:IDENT> <sp2:@R> "{" <v:OptInterfaceElement*> "}" <fp2:@R> => {
// Convert Vec<Option<InterfaceElement>> into Vec<InterfaceElement>
let elements: Vec<ast::InterfaceElement> = v.into_iter().flatten().collect();
ast::Interface {
oneway: oneway.is_some(),
name: s.into(),
elements,
annotations,
doc: javadoc::get_javadoc(input, p0),
full_range: ast::Range::new(&lookup, fp1, fp2),
symbol_range: ast::Range::new(&lookup, sp1, sp2),
}
}
}
// Method or const (with error recovery)
OptInterfaceElement: Option<ast::InterfaceElement> = {
<m:Method> => Some(ast::InterfaceElement::Method(m)),
<c:Const> => Some(ast::InterfaceElement::Const(c)),
! =>? {
if let Some(d) = Diagnostic::from_error_recovery("Invalid interface element", lookup, <>) {
diagnostics.push(d);
}
Ok(None)
},
}
// e.g. parcelable Xyz { ... }
pub(crate) Parcelable: ast::Parcelable = {
<p0:@L>
<annotations:AnnotationList>
<fp1:@L> PARCELABLE <sp1:@L> <s:IDENT> <sp2:@R> "{" <v:OptParcelableElement*> "}" <fp2:@R> => {
// Convert Vec<Option<ParcelableElement>> into Vec<ParcelableElement>
let elements: Vec<ast::ParcelableElement> = v.into_iter().flatten().collect();
ast::Parcelable {
name: s.into(),
elements,
annotations,
doc: javadoc::get_javadoc(input, p0),
full_range: ast::Range::new(&lookup, fp1, fp2),
symbol_range: ast::Range::new(&lookup, sp1, sp2),
}
}
}
// Field or const (with error recovery)
OptParcelableElement: Option<ast::ParcelableElement> = {
<f:Field> => Some(ast::ParcelableElement::Field(f)),
<c:Const> => Some(ast::ParcelableElement::Const(c)),
! =>? {
if let Some(d) = Diagnostic::from_error_recovery("Invalid parcelable element", lookup, <>) {
diagnostics.push(d);
}
Ok(None)
},
}
// e.g. enum Xyz { ... }
pub(crate) Enum: ast::Enum = {
<p0:@L>
<annotations:AnnotationList>
<fp1:@L> ENUM <sp1:@L> <s:IDENT> <sp2:@R> "{" <v:CommaSeparated<OptEnumElement>> "}" <fp2:@R> => {
// Convert Vec<Option<EnumElement>> into Vec<EnumElement>
let elements: Vec<ast::EnumElement> = v.into_iter().flatten().collect();
ast::Enum {
name: s.into(),
elements,
annotations,
doc: javadoc::get_javadoc(input, p0),
full_range: ast::Range::new(&lookup, fp1, fp2),
symbol_range: ast::Range::new(&lookup, sp1, sp2),
}
}
}
// Enum element (with error recovery)
OptEnumElement: Option<ast::EnumElement> = {
<el:EnumElement> => Some(el),
! =>? {
if let Some(d) = Diagnostic::from_error_recovery("Invalid enum element", lookup, <>) {
diagnostics.push(d);
}
Ok(None)
},
}
// e.g. @Annotation String myMethod(...) = 2;
pub(crate) Method: ast::Method = {
<p0:@L>
<annotations:AnnotationList>
<fp1:@L> <owp1:@L> <oneway:ONEWAY?> <owp2:@R> <rt:Type>
<sp1:@L> <n:IDENT> <sp2:@R>
"(" <args:CommaSeparated<Arg>> ")"
<vp1:@L> <v:("=" <INTEGER>)?> <vp2:@R> // TODO: only [0-9]+
<fp2:@R> ";" => {
ast::Method {
oneway: oneway.is_some(),
name: n.to_owned(),
return_type: rt,
args,
annotations,
doc: javadoc::get_javadoc(input, p0),
transact_code: match v.map(str::parse) {
Some(Ok(v)) => Some(v),
Some(Err(e)) => {
diagnostics.push(Diagnostic {
kind: DiagnosticKind::Error,
range: ast::Range::new(&lookup, vp1 + 2, vp2),
message: format!("Invalid method transact code: {}", e),
context_message: None,
hint: None,
related_infos: Vec::new(),
});
None
},
None => None,
},
full_range: ast::Range::new(&lookup, fp1, fp2),
symbol_range: ast::Range::new(&lookup, sp1, sp2),
transact_code_range: ast::Range::new(&lookup, vp1, vp2),
oneway_range: ast::Range::new(&lookup, owp1, owp2),
}
}
}
// e.g. @Annotation inout MyType argName
pub(crate) Arg: ast::Arg = {
<p0:@L>
<d:Direction>
<annotations:AnnotationList>
<t:Type> <sp1:@L> <n:IDENT?> <p2:@R> => {
ast::Arg {
direction: d,
name: n.map(str::to_owned),
arg_type: t,
symbol_range: ast::Range::new(&lookup, sp1, p2),
full_range: ast::Range::new(&lookup, p0, p2),
annotations,
doc: javadoc::get_javadoc(input, p0),
}
}
}
// Arg direction (in, out or inout)
Direction: ast::Direction = {
<p1:@L> <d:DIRECTION?> <p2:@R> => {
match d {
Some("in") => ast::Direction::In(ast::Range::new(&lookup, p1, p2)),
Some("out") => ast::Direction::Out(ast::Range::new(&lookup, p1, p2)),
Some("inout") => ast::Direction::InOut(ast::Range::new(&lookup, p1, p2)),
None => ast::Direction::Unspecified,
_ => unreachable!(),
}
}
}
// e.g. @Annotation const int XYZ = 3;
pub(crate) Const: ast::Const = {
<p0:@L>
<annotations:AnnotationList>
<fp1:@L> CONST <t:Type>
<sp1:@L> <n:IDENT> <sp2:@R>
"=" <v:Value>
<fp2:@R> ";" => {
ast::Const {
name: n.to_owned(),
const_type: t,
value: v.to_owned(),
annotations,
doc: javadoc::get_javadoc(input, p0),
full_range: ast::Range::new(&lookup, fp1, fp2),
symbol_range: ast::Range::new(&lookup, sp1, sp2),
}
},
}
// e.g. @Annotation String myField;
pub(crate) Field: ast::Field = {
<p0:@L>
<annotations:AnnotationList>
<fp1:@L> <t:Type>
<sp1:@L> <n:IDENT> <sp2:@R>
<v:("=" <Value>)?>
<fp2:@R> ";" => {
ast::Field {
name: n.to_owned(),
field_type: t,
value: v,
annotations,
doc: javadoc::get_javadoc(input, p0),
full_range: ast::Range::new(&lookup, fp1, fp2),
symbol_range: ast::Range::new(&lookup, sp1, sp2),
}
}
}
// e.g. @Annotation ELEMENT = 3
EnumElement: ast::EnumElement = {
<p0:@L>
<annotations:AnnotationList>
<fp1:@L>
<sp1:@L> <n:IDENT> <sp2:@R>
<v:("=" <EnumValue>)?>
<fp2:@R> => {
ast::EnumElement {
name: n.to_owned(),
value: v.map(str::to_owned),
doc: javadoc::get_javadoc(input, p0),
full_range: ast::Range::new(&lookup, fp1, fp2),
symbol_range: ast::Range::new(&lookup, sp1, sp2),
}
}
}
pub(crate) Type: ast::Type = {
TypeVoid,
TypePrimitive,
TypeString,
TypeCharSequence,
TypeArray,
TypeList,
TypeMap,
TypeCustom,
}
TypeVoid: ast::Type = {
<p1:@L> <n:VOID> <p2:@R> =>
ast::Type::simple_type(n, ast::TypeKind::Void, lookup, p1, p2)
}
TypePrimitive: ast::Type = {
<p1:@L> <n:PRIMITIVE> <p2:@R> =>
ast::Type::simple_type(n, ast::TypeKind::Primitive, lookup, p1, p2)
}
TypeString: ast::Type = {
<p1:@L> <n:STRING> <p2:@R> =>
ast::Type::simple_type(n, ast::TypeKind::String, lookup, p1, p2)
}
TypeCharSequence: ast::Type = {
<p1:@L> <n:CHAR_SEQUENCE> <p2:@R> =>
ast::Type::simple_type(n, ast::TypeKind::CharSequence, lookup, p1, p2)
}
TypeArray: ast::Type = {
<fp1:@L> <sp1:@L> <p:Type> <sp2:@R> "[" "]" <fp2:@R> => {
ast::Type::array(p, &lookup, sp1, sp2, fp1, fp2)
},
}
TypeList: ast::Type = {
<fp1:@L> <sp1:@L> LIST <sp2:@R> "<" <p:Type> ">" <fp2:@R> => {
ast::Type::list(p, &lookup, sp1, sp2, fp1, fp2)
},
<p1:@L> LIST <p2:@R> => {
ast::Type::non_generic_list(&lookup, p1, p2)
},
}
TypeMap: ast::Type = {
<fp1:@L> <sp1:@L> MAP <sp2:@R> "<"
<k:Type> "," <v:Type>
">" <fp2:@R> => {
ast::Type::map(k, v, &lookup, sp1, sp2, fp1, fp2)
},
<p1:@L> MAP <p2:@R> => {
ast::Type::non_generic_map(&lookup, p1, p2)
},
}
// Unresolved custom type (should be an interface, a parcelable or an enum)
TypeCustom: ast::Type = {
<p1:@L> <n:QualifiedName> <p2:@R> => {
let range = ast::Range::new(&lookup, p1, p2);
ast::Type {
name: n.to_owned(),
kind: ast::TypeKind::Unresolved,
generic_types: vec![],
symbol_range: range.clone(),
full_range: range,
}
}
}
#[inline]
AnnotationList: Vec<ast::Annotation> = {
// Convert Vec<Option<Annotation>> into Vec<Annotation>
<v:OptAnnotation*> => v.into_iter().flatten().collect()
}
pub(crate) OptAnnotation: Option<ast::Annotation> = {
<n:ANNOTATION> <v:("(" <CommaSeparated<AnnotationParam>> ")")?> => {
Some(ast::Annotation {
name: n.to_owned(),
key_values: v.unwrap_or_default().into_iter().collect(),
})
},
}
AnnotationParam: (String, Option<String>) = {
<k:IDENT> <v:("=" <AnnotationValue>)?> => (k.to_owned(), v.map(str::to_owned))
}
pub(crate) Value: String = {
<v:INTEGER> => v.to_string(),
<v:FLOAT> => v.to_string(),
<v:QUOTED_STRING> => v.to_string(),
<v:BOOLEAN> => v.to_string(),
"{" "}" => "{}".to_string(),
"{" Value+ ("," <Value>)* ","? "}" => "{...}".to_string(),
<a:IDENT> "." <b:IDENT> => format!("{a}.{b}"),
// TODO: also accept arithmetic, hexadecimal values, ...
}
pub(crate) AnnotationValue: &'input str = {
INTEGER,
FLOAT,
QUOTED_STRING,
BOOLEAN,
}
pub(crate) EnumValue: &'input str = {
INTEGER,
FLOAT,
QUOTED_STRING,
BOOLEAN,
}
// Comma separated list with optional trailing comma
CommaSeparated<T>: Vec<T> = {
<mut v:(<T> ",")*> <e:T?> => match e {
None => v,
Some(e) => {
v.push(e);
v
}
}
}
// Tokens
match {
// Whitespaces, comments, EOL
r"\s*" => { }, // The default whitespace skipping is disabled and an `ignore pattern` is specified
r"//[^\n\r]*[\n\r]*" => { }, // Skip `// comments`
r"/\*[^*]*\*+(?:[^/*][^*]*\*+)*/" => { }, // Skip `/* comments */`
"package" => PACKAGE,
"import" => IMPORT,
"interface" => INTERFACE,
"parcelable" => PARCELABLE,
"enum" => ENUM,
"oneway" => ONEWAY,
"const" => CONST,
r"(inout|in|out)" => DIRECTION,
"void" => VOID,
r"(byte|short|int|long|float|double|boolean|char)" => PRIMITIVE,
"String" => STRING,
"CharSequence" => CHAR_SEQUENCE,
"List" => LIST,
"Map" => MAP,
r#""[^"\n\r]*""# => QUOTED_STRING,
r#"(true|false)"# => BOOLEAN,
r"@[a-zA-Z_][a-zA-Z0-9_]*" => ANNOTATION,
// Signs
";", ",", "{", "}", "(", ")", "[", "]", "<", ">", "=", ".", "-",
} else {
// Reserved keywords
// Note: currently only for Java and C++ but Rust should be ideally covered, too
r"(break|case|catch|char|class|continue|default|do|double|else|enum|false|float|for|goto|if|int|long|new|private|protected|public|return|short|static|switch|this|throw|true|try|void|volatile|while)" => RESERVED_KEYWORD,
} else {
r"[a-zA-Z_][a-zA-Z0-9_]*" => IDENT,
r"[0-9]+" => INTEGER,
} else {
r"[+-]?(\d*\.)?\d+[f]?" => FLOAT,
}