protobuf-ast-parser 0.1.0

A Protocol Buffers (proto2/proto3) parser that produces a typed AST with comments
Documentation
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);