use crate::{ast, lexer, ast::FromBorrowedIter};
use std::borrow::Cow;
grammar<'input>(input: &'input str);
extern {
type Location = usize;
type Error = lexer::LexicalError<'input>;
enum lexer::Token<'input> {
SingleLineComment => lexer::Token::SingleLineComment(<&'input str>),
MultiLineComment => lexer::Token::MultiLineComment(<&'input str>),
Eq => lexer::Token::Eq,
Colon => lexer::Token::Colon,
Semicolon => lexer::Token::Semicolon,
Comma => lexer::Token::Comma,
Period => lexer::Token::Period,
OpenPth => lexer::Token::OpenPth,
ClosePth => lexer::Token::ClosePth,
OpenBracket => lexer::Token::OpenBracket,
CloseBracket => lexer::Token::CloseBracket,
OpenBrace => lexer::Token::OpenBrace,
CloseBrace => lexer::Token::CloseBrace,
OpenAngle => lexer::Token::OpenAngle,
CloseAngle => lexer::Token::CloseAngle,
Boolean => lexer::Token::Boolean(<bool>),
Integer => lexer::Token::Integer(<i64>),
String => lexer::Token::String(<&'input str>),
Ident => lexer::Token::Ident(<&'input str>),
"to" => lexer::Token::To,
"max" => lexer::Token::Max,
"syntax" => lexer::Token::Syntax,
"option" => lexer::Token::Option,
"package" => lexer::Token::Package,
"import" => lexer::Token::Import,
"service" => lexer::Token::Service,
"rpc" => lexer::Token::Rpc,
"stream" => lexer::Token::Stream,
"returns" => lexer::Token::Returns,
"message" => lexer::Token::Message,
"oneof" => lexer::Token::OneOf,
"extend" => lexer::Token::Extend,
"enum" => lexer::Token::Enum,
"reserved" => lexer::Token::Reserved,
"extensions" => lexer::Token::Extensions,
"optional" => lexer::Token::Optional,
"required" => lexer::Token::Required,
"repeated" => lexer::Token::Repeated,
"map" => lexer::Token::Map,
}
}
// Protocol Buffers 2 and 3 keywords.
Keyword: &'input str = {
"to" => "to",
"max" => "max",
"syntax" => "syntax",
"package" => "package",
"import" => "import",
"service" => "service",
"rpc" => "rpc",
"stream" => "stream",
"returns" => "returns",
"option" => "option",
"message" => "message",
"oneof" => "oneof",
"extend" => "extend",
"enum" => "enum",
"reserved" => "reserved",
"extensions" => "extensions",
"optional" => "optional",
"required" => "required",
"repeated" => "repeated",
"map" => "map",
};
#[inline]
KeywordAsFieldType: &'input str = {
"to" => "to",
"max" => "max",
"syntax" => "syntax",
"package" => "package",
"import" => "import",
"service" => "service",
"rpc" => "rpc",
"stream" => "stream",
"returns" => "returns",
};
#[inline]
KeywordAsRpcMessageType: &'input str = {
"to" => "to",
"max" => "max",
"syntax" => "syntax",
"package" => "package",
"import" => "import",
"service" => "service",
"rpc" => "rpc",
"returns" => "returns",
"option" => "option",
"message" => "message",
"oneof" => "oneof",
"extend" => "extend",
"enum" => "enum",
"reserved" => "reserved",
"extensions" => "extensions",
"optional" => "optional",
"required" => "required",
"repeated" => "repeated",
"map" => "map",
};
#[inline]
IdentLike = { Keyword, Ident };
// [.]optional.package
#[inline]
Path = { SafePath, UnsafePath }
#[inline]
LPath = { SafeLPath, UnsafePath };
// Use-case Unsafe Safe
// ident.ident ++
// keyword.ident ++
UnsafePath: &'input str = <l:@L> Keyword (Period IdentLike)* <r:@R> => &input[l..r];
SafePath: &'input str = <l:@L> Ident (Period IdentLike)* <r:@R> => &input[l..r];
#[inline]
ExactPath<E>: &'input str = <l:@L> E (Period IdentLike)* <r:@R> => &input[l..r];
// Unsafe Safe
// ident.ident ++
// . ident.ident ++
// keyword.ident ++
// . keyword.ident ++
#[inline] UnsafeLPath : &'input str = UnsafePath;
SafeLPath : &'input str = {
<l:@L> Period UnsafePath <r:@R> => &input[l..r],
<l:@L> Period? SafePath <r:@R> => &input[l..r],
};
Range: ast::Range = {
Integer => (<>..(<> + 1)).into(),
<start:Integer> "to" <end:Integer> => (start..end).into(),
<start:Integer> "to" <end:"max"> => (start..).into(),
};
// Lists
// value, value, value
CommaList<T>: Vec<T> = <first:T> <mut rest:(Comma <T>)*> => { rest.insert(0, first); rest };
// value, value, value[,]
TrailingCommaList<T>: Vec<T> = <first:T> <mut rest:(Comma <T>)*> Comma? => { rest.insert(0, first); rest };
// stmt[;] stmt[;] stmt[;]
StmtList<T>: Vec<T> = <v:(<T> Semicolon*)*> => <>;
// <keyword:K> <ident-type:I> {
// <stmt:E>[;]
// <stmt:E>[;]
// <stmt:E>[;]
// }
Block<K, I, E> = K <I> OpenBrace <StmtList<E>> CloseBrace;
// Protocol Buffers file AST
pub Root: Vec<ast::RootEntry<'input>> = StmtList<RootEntryStmt>;
RootEntryStmt: ast::RootEntry<'input> = {
CommentStmt => <>.into(),
"syntax" Eq <String> Semicolon => ast::RootEntry::Syntax(Cow::from(<>)),
"package" <Path> Semicolon => ast::RootEntry::Package(Cow::from(<>)),
"import" <String> Semicolon => ast::RootEntry::Import(Cow::from(<>)),
OptionStmt => <>.into(),
ServiceStmt => <>.into(),
MessageStmt => <>.into(),
ExtendStmt => <>.into(),
EnumStmt => <>.into(),
};
// comment
CommentStmt: ast::Comment<'input> = {
SingleLineComment => ast::Comment::single_line(<>),
MultiLineComment => ast::Comment::multi_line(<>),
};
// option
OptionStmt = "option" <Option> Semicolon;
OptionListStmt: Vec<ast::Option<'input>> = <(OpenBracket <CommaList<Option>> CloseBracket)?> => <>.unwrap_or(vec![]);
Option: ast::Option<'input> = <key:OptionKey> Eq <value:MapValue> => ast::Option::new(key, value);
OptionKey: &'input str = {
<l:@L> OpenPth LPath ClosePth <r:@R> => &input[l..r],
Path,
}
MapValue: ast::MapValue<'input> = {
Boolean => <>.into(),
Integer => <>.into(),
Ident => ast::MapValue::Ident(Cow::from(<>)),
String => ast::MapValue::String(Cow::from(<>)),
Map => <>.into(),
};
Map: ast::Map<'input>
= OpenBrace <TrailingCommaList<(<Ident> Colon <MapValue>)>> CloseBrace
=> ast::Map::<'input>::from_borrowed_iter(<>);
// service [ident] { ... }
ServiceStmt: ast::Service<'input> = Block<"service", IdentLike, ServiceEntry> => ast::Service::new(<>.0, <>.1);
ServiceEntry: ast::ServiceEntry<'input> = {
CommentStmt => <>.into(),
OptionStmt => <>.into(),
RpcStmt => <>.into(),
};
// rpc [ident] ([stream]? [request]) returns ([stream]? [reply])[{} | ; | {};]
RpcStmt: ast::Rpc<'input> = {
"rpc" <ident:IdentLike>
OpenPth <request:StreamIdentLike> ClosePth
"returns"
OpenPth <reply:StreamIdentLike> ClosePth
RpcClose
=> ast::Rpc::new(ident, request.1, reply.1, ast::RpcStream::new(request.0, reply.0))
};
StreamIdentLike: (bool, &'input str) = {
"stream" <LPath> => (true, <>),
SafeLPath => (false, <>),
ExactPath<KeywordAsRpcMessageType> => (false, <>),
};
RpcClose: () = { Semicolon => (), OpenBrace CloseBrace => () };
// message [ident] { ... }
MessageStmt: ast::Message<'input> = Block<"message", IdentLike, MessageEntry> => ast::Message::new(<>.0, <>.1);
MessageEntry: ast::MessageEntry<'input> = {
CommentStmt => <>.into(),
OptionStmt => <>.into(),
MessageStmt => <>.into(),
EnumStmt => <>.into(),
ExtendStmt => <>.into(),
FieldStmt => <>.into(),
OneOfStmt => <>.into(),
ReservedIndicesStmt => <>.into(),
ReservedIdentsStmt => <>.into(),
ExtensionsStmt => <>.into(),
};
// [mod] [type] [ident] = [index];
FieldStmt: ast::Field<'input> = {
<mt:ModFieldType> <ident:IdentLike> Eq <index:Integer> <options:OptionListStmt> Semicolon
=> ast::Field::new(mt.0, mt.1, ident, index, options),
};
ModFieldType: (Option<ast::FieldModifier>, &'input str) = {
// [modifier]? map<k, v>
<modifier:FieldModifier?> <r#type:MapFieldType> => (modifier, r#type),
// [modifier] ident[.any]?
// [modifier] .ident[.any]?
// [modifier] kw[.any]?
// [modifier] .kw[.any]?
<modifier:FieldModifier> <r#type:LPath> => (Some(modifier), r#type),
// ident[.any]?
// .ident[.any]?
// .kw[.any]?
SafeLPath => (None, <>),
// kw*[.any]?
// * - only "allowed" keywords (not used as message entry first keyword. message, enum, option, etc.)
ExactPath<KeywordAsFieldType> => (None, <>),
};
FieldModifier: ast::FieldModifier = {
"optional" => ast::FieldModifier::Optional,
"required" => ast::FieldModifier::Required,
"repeated" => ast::FieldModifier::Repeated,
};
MapFieldType: &'input str = <l:@L> "map" OpenAngle Ident Comma IdentLike CloseAngle <r:@R> => &input[l..r];
// reserved 2, 3, 4 to 6;
ReservedIndicesStmt: ast::ReservedIndices = "reserved" <CommaList<Range>> Semicolon => <>.into();
// reserved "xd", "xdd";
ReservedIdentsStmt: ast::ReservedIdents<'input> = "reserved" <CommaList<String>> Semicolon => <>.into();
// extensions 1000 to max;
ExtensionsStmt: ast::Extensions = "extensions" <CommaList<Range>> Semicolon => <>.into();
// oneof [ident] { ... }
OneOfStmt: ast::OneOf<'input> = Block<"oneof", IdentLike, OneOfEntry> => ast::OneOf::new(<>.0, <>.1);
OneOfEntry: ast::OneOfEntry<'input> = {
CommentStmt => <>.into(),
OptionStmt => <>.into(),
FieldStmt => <>.into(),
};
// extend [ident] { ... }
ExtendStmt: ast::Extend<'input> = Block<"extend", LPath, ExtendEntry> => ast::Extend::new(<>.0, <>.1);
ExtendEntry: ast::ExtendEntry<'input> = {
CommentStmt => <>.into(),
FieldStmt => <>.into(),
};
// enum [ident] { ... }
EnumStmt: ast::Enum<'input> = Block<"enum", IdentLike, EnumEntry> => ast::Enum::new(<>.0, <>.1);
EnumEntry: ast::EnumEntry<'input> = {
CommentStmt => <>.into(),
OptionStmt => <>.into(),
EnumVariantStmt => <>.into(),
};
EnumVariantStmt: ast::EnumVariant<'input>
= <ident:IdentLike> Eq <value:Integer> <options:OptionListStmt> Semicolon
=> ast::EnumVariant::new(ident, value, options);