nailgun 0.1.0

A PEG parser generator.
Documentation
// Copyright 2014 Strahinja Val Markovic
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#![allow(non_snake_case)]
#![deny(deprecated)]

#[cfg(not(test))]
pub use base::{Node, ParseState, Data, Children, NodeContents, PreOrderNodes};

#[macro_use]
mod base;

macro_rules! rule(
  (
    $name:ident <- $body:expr
  ) => (
    pub fn $name<'a>( parse_state: &base::ParseState<'a> )
         -> std::option::Option< base::ParseResult<'a> > {
      use base::Expression;
      use base::Node;
      use base::ParseResult;
      use std::clone::Clone;
      use std::option::Option::{Some, None};

      match $body.apply( parse_state ) {
        Some( result ) => {
          let state = result.parse_state.clone();
          Some( ParseResult::oneNode(
              Node::withChildren( stringify!( $name ), result.nodes ), state ) )
        }
        _ => None
      }
    }
  );
);

#[cfg(not(test))]
pub fn parse<'a>( input: &'a [u8] ) -> Option< Node<'a> > {
  let parse_state = ParseState { input: input, offset: 0 };
  match rules::Grammar( &parse_state ) {
    Some( result ) => Some( result.nodes.into_iter().next().unwrap() ),
    _ => None
  }
}


mod rules {
  #![no_implicit_prelude]

  use base;
  use std;

  // RULES START

  rule!( Grammar <- seq!( ex!( Spacing ), plus!( ex!( Definition ) ), ex!( EndOfFile ) ) );
  rule!( Definition <- seq!( ex!( Identifier ), ex!( ARROW ), ex!( Expression ) ) );
  rule!( Expression <- seq!( ex!( Sequence ), star!( seq!( ex!( SLASH ), ex!( Sequence ) ) ) ) );
  rule!( Sequence <- star!( ex!( Prefix ) ) );
  rule!( Prefix <- seq!( opt!( or!( ex!( AND ), ex!( NOT ), ex!( FUSE ) ) ), ex!( Suffix ) ) );
  rule!( Suffix <- seq!( ex!( Primary ), opt!( or!( ex!( QUESTION ), ex!( STAR ), ex!( PLUS ) ) ) ) );
  rule!( Primary <- or!( seq!( ex!( Identifier ), not!( ex!( ARROW ) ) ), seq!( ex!( OPEN ), ex!( Expression ), ex!( CLOSE ) ), ex!( Literal ), ex!( Class ), ex!( DOT ) ) );
  rule!( Identifier <- seq!( fuse!( seq!( ex!( IdentStart ), star!( ex!( IdentCont ) ) ) ), ex!( Spacing ) ) );
  rule!( IdentStart <- class!( "a-zA-Z_" ) );
  rule!( IdentCont <- or!( ex!( IdentStart ), class!( "0-9" ) ) );
  rule!( Literal <- seq!( fuse!( or!( seq!( class!( "'" ), star!( seq!( not!( class!( "'" ) ), ex!( Char ) ) ), class!( "'" ) ), seq!( class!( "\"" ), star!( seq!( not!( class!( "\"" ) ), ex!( Char ) ) ), class!( "\"" ) ) ) ), ex!( Spacing ) ) );
  rule!( Class <- seq!( lit!( "[" ), star!( seq!( not!( lit!( "]" ) ), ex!( Range ) ) ), lit!( "]" ), ex!( Spacing ) ) );
  rule!( Range <- or!( seq!( ex!( Char ), lit!( "-" ), ex!( Char ) ), ex!( Char ) ) );
  rule!( Char <- or!( seq!( lit!( "\\" ), class!( "nrt'\"[]\\" ) ), seq!( lit!( "\\" ), class!( "0-2" ), class!( "0-7" ), class!( "0-7" ) ), seq!( lit!( "\\" ), class!( "0-7" ), opt!( class!( "0-7" ) ) ), seq!( not!( lit!( "\\" ) ), &base::Dot ) ) );
  rule!( ARROW <- or!( ex!( FUSEARROW ), ex!( LEFTARROW ) ) );
  rule!( LEFTARROW <- seq!( lit!( "<-" ), ex!( Spacing ) ) );
  rule!( FUSEARROW <- seq!( lit!( "<~" ), ex!( Spacing ) ) );
  rule!( SLASH <- seq!( lit!( "/" ), ex!( Spacing ) ) );
  rule!( AND <- seq!( lit!( "&" ), ex!( Spacing ) ) );
  rule!( NOT <- seq!( lit!( "!" ), ex!( Spacing ) ) );
  rule!( QUESTION <- seq!( lit!( "?" ), ex!( Spacing ) ) );
  rule!( STAR <- seq!( lit!( "*" ), ex!( Spacing ) ) );
  rule!( PLUS <- seq!( lit!( "+" ), ex!( Spacing ) ) );
  rule!( OPEN <- seq!( lit!( "(" ), ex!( Spacing ) ) );
  rule!( CLOSE <- seq!( lit!( ")" ), ex!( Spacing ) ) );
  rule!( DOT <- seq!( lit!( "." ), ex!( Spacing ) ) );
  rule!( FUSE <- seq!( lit!( "~" ), ex!( Spacing ) ) );
  rule!( Spacing <- fuse!( star!( or!( ex!( Space ), ex!( Comment ) ) ) ) );
  rule!( Comment <- fuse!( seq!( lit!( "#" ), star!( seq!( not!( ex!( EndOfLine ) ), &base::Dot ) ), ex!( EndOfLine ) ) ) );
  rule!( Space <- or!( lit!( " " ), lit!( "\t" ), ex!( EndOfLine ) ) );
  rule!( EndOfLine <- or!( lit!( "\r\n" ), lit!( "\n" ), lit!( "\r" ) ) );
  rule!( EndOfFile <- not!( &base::Dot ) );

  // RULES END

  #[cfg(test)]
  mod tests {
    use super::{EndOfFile, EndOfLine, Space, Comment, Spacing, Char, Range,
                Class, Literal, Identifier, Definition, Grammar};

    macro_rules! consumes(
      (
        $name:ident, $input:expr
      ) => (
        {
          use base::ParseResult;
          use std::option::Option::Some;

          match $name( &input_state!( $input ) ) {
            Some( ParseResult{ nodes: _, parse_state } ) => {
              parse_state.input.is_empty()
            }
            _ => false
          }
        }
      );
    );

    macro_rules! matches(
      (
        $name:ident, $input:expr
      ) => (
        $name( &input_state!( $input ) ).is_some()
      );
    );

    #[test]
    fn Grammar_Works() {
      assert!( consumes!( Grammar,
                          r#"
        h16           <- HEXDIGIT (HEXDIGIT (HEXDIGIT HEXDIGIT?)?)?
        ls32          <- h16 ":" h16 / IPv4address
        IPv4address   <- dec_octet "." dec_octet "." dec_octet "." dec_octet"#
        ) );
    }

    #[test]
    fn Definition_Works() {
      assert!( consumes!( Definition,
                          "Grammar <- Spacing Definition+ EndOfFile" ) );
      assert!( consumes!( Definition,
                          r#"Char <- '\\' [nrt'"\[\]\\] /
                                    '\\' [0-2][0-7][0-7] /
                                    '\\' [0-7][0-7]? /
                                    !'\\' ."# ) );
    }

    #[test]
    fn Identifier_Works() {
      assert!( consumes!( Identifier, "abc" ) );
      assert!( consumes!( Identifier, "a" ) );
      assert!( consumes!( Identifier, "_" ) );
      assert!( consumes!( Identifier, "a123" ) );
      assert!( consumes!( Identifier, "a  \n" ) );

      assert!( !consumes!( Identifier, "1a" ) );
      assert!( !consumes!( Identifier, "1" ) );
      assert!( !consumes!( Identifier, "Ć" ) );
    }

    #[test]
    fn Literal_Works() {
      assert!( consumes!( Literal, "'abc'" ) );
      assert!( consumes!( Literal, r#""abc""# ) );
      assert!( consumes!( Literal, "'abc'  \n" ) );
      assert!( !consumes!( Literal, "'abc''bb'" ) );
    }

    #[test]
    fn Class_Works() {
      assert!( consumes!( Class, "[a-z]" ) );
      assert!( consumes!( Class, "[a-z]  \n" ) );
      assert!( consumes!( Class, "[abc]" ) );
      assert!( consumes!( Class, "[abc0-9g]" ) );
    }

    #[test]
    fn Range_Works() {
      assert!( consumes!( Range, "a-z" ) );
      assert!( consumes!( Range, "a" ) );
    }

    #[test]
    fn Char_Works() {
      assert!( consumes!( Char, r"\n" ) );
      assert!( consumes!( Char, r"\]" ) );
      assert!( consumes!( Char, r"\\" ) );
      assert!( consumes!( Char, r"\'" ) );
      assert!( consumes!( Char, "a" ) );
      assert!( consumes!( Char, "x" ) );
      assert!( consumes!( Char, "Ć" ) );
      assert!( consumes!( Char, "" ) );
      assert!( consumes!( Char, r"\277" ) );
      assert!( consumes!( Char, r"\77" ) );
      assert!( consumes!( Char, r"\7" ) );

      assert!( !consumes!( Char, "aa" ) );
    }

    #[test]
    fn Spacing_Works() {
      assert!( consumes!( Spacing, "  \t #g\n" ) );
      assert!( consumes!( Spacing, "#a\n  #1\n" ) );

      // Spacing DOES match here because at the top level, it is a star
      // expression which can match consuming nothing.
      assert!( matches!( Spacing, "" ) );
      assert!( !consumes!( Spacing, "#" ) );
      assert!( !consumes!( Spacing, "a" ) );
    }

    #[test]
    fn Comment_Works() {
      assert!( consumes!( Comment, "#\n" ) );
      assert!( consumes!( Comment, "# foo! \n" ) );
      assert!( !matches!( Comment, "\n" ) );
      assert!( !matches!( Comment, "#" ) );
      assert!( !matches!( Comment, "a" ) );
    }

    #[test]
    fn Space_Works() {
      assert!( consumes!( Space, " " ) );
      assert!( consumes!( Space, "\t" ) );
      assert!( consumes!( Space, "\n" ) );
      assert!( !matches!( Space, "a" ) );
    }

    #[test]
    fn EndOfLine_Works() {
      assert!( consumes!( EndOfLine, "\n" ) );
      assert!( consumes!( EndOfLine, "\r" ) );
      assert!( consumes!( EndOfLine, "\r\n" ) );
      assert!( !matches!( EndOfLine, "a" ) );
    }

    #[test]
    fn EndOfFile_Works() {
      assert!( consumes!( EndOfFile, "" ) );
      assert!( !matches!( EndOfFile, "a" ) );
    }
  }
}