[axl_dox] added support for @-notation (e.g @brief, as well as \brief)

//..............................................................................
//
//  This file is part of the AXL library.
//
//  AXL is distributed under the MIT license.
//  For details see accompanying license.txt file,
//  the public copy of which is also available at:
//  http://tibbo.com/downloads/archive/axl/license.txt
//
//..............................................................................

// warning C4065: switch statement contains 'default' but no 'case' labels

#pragma warning(disable: 4065)

namespace axl {
namespace dox {

//..............................................................................

%%{

machine dox;
write data;

#. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
#
# definitions
#

# whitespace

ws = [ \t\r]+;

# non-whitespace

nws = [^ \t\r\n]+;

# regular char (non-whitespace and non-escape)

rc = [^ \t\r\n\\];

#. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
#
# main machine
#

main := |*

[@\\] 'headerfile' { createToken(TokenKind_Import); };
[@\\] 'importfile' { createToken(TokenKind_Import); };
[@\\] 'import'     { createToken(TokenKind_Import); };
[@\\] 'enum'       { createToken(TokenKind_Enum); };
[@\\] 'enumvalue'  { createToken(TokenKind_EnumValue); };
[@\\] 'struct'     { createToken(TokenKind_Struct); };
[@\\] 'union'      { createToken(TokenKind_Union); };
[@\\] 'class'      { createToken(TokenKind_Class); };
[@\\] 'alias'      { createToken(TokenKind_Alias); };
[@\\] 'var'        { createToken(TokenKind_Variable); };
[@\\] 'variable'   { createToken(TokenKind_Variable); };
[@\\] 'field'      { createToken(TokenKind_Field); };
[@\\] 'fn'         { createToken(TokenKind_Function); };
[@\\] 'function'   { createToken(TokenKind_Function); };
[@\\] 'method'     { createToken(TokenKind_Function); };
[@\\] 'overload'   { createToken(TokenKind_Overload); };
[@\\] 'prop'       { createToken(TokenKind_Property); };
[@\\] 'property'   { createToken(TokenKind_Property); };
[@\\] 'event'      { createToken(TokenKind_Event); };
[@\\] 'typedef'    { createToken(TokenKind_Typedef); };
[@\\] 'namespace'  { createToken(TokenKind_Namespace); };
[@\\] 'group'      { createToken(TokenKind_Group); };
[@\\] 'defgroup'   { createToken(TokenKind_Group); };
[@\\] 'addtogroup' { createToken(TokenKind_Group); };
[@\\] 'ingroup'    { createToken(TokenKind_InGroup); };
[@\\] 'subgroup'   { createToken(TokenKind_SubGroup); };
[@\\] 'grouporder' { createToken(TokenKind_GroupOrder); };
[@\\] 'title'      { createToken(TokenKind_Title); };
[@\\] 'brief'      { createToken(TokenKind_Brief); };
[@\\] 'details'    { createToken(TokenKind_Details); };
[@\\] 'param'      { createToken(TokenKind_Param); };
[@\\] 'result'     { createToken(TokenKind_Return); };
[@\\] 'return'     { createToken(TokenKind_Return); };
[@\\] 'returns'    { createToken(TokenKind_Return); };
[@\\] 'see'        { createToken(TokenKind_SeeAlso); };
[@\\] 'seealso'    { createToken(TokenKind_SeeAlso); };
[@\\] 'sa'         { createToken(TokenKind_SeeAlso); };
[@\\] 'internal'   { createToken(TokenKind_Internal); };
[@\\] 'footnote'   { createToken(TokenKind_Footnote); };

[@\\] rc*          { createTextToken(TokenKind_CustomCommand, 1); };

'@{'               { createToken(TokenKind_OpeningBrace); };
'@}'               { createToken(TokenKind_ClosingBrace); };

rc ([^\n]* nws)?   { createTextToken(TokenKind_Text); };

'\n' ws?           { createNewLineToken(), newLine(ts + 1); };
ws                 ;

*|;

}%%

//..............................................................................

void
Lexer::init()
{
	%% write init;
}

void
Lexer::exec()
{
	%% write exec;
}

//..............................................................................

} // namespace dox
} // namespace axl

//..............................................................................
//
//  This file is part of the AXL library.
//
//  AXL is distributed under the MIT license.
//  For details see accompanying license.txt file,
//  the public copy of which is also available at:
//  http://tibbo.com/downloads/archive/axl/license.txt
//
//..............................................................................

// warning C4065: switch statement contains 'default' but no 'case' labels

#pragma warning(disable: 4065)

namespace axl {
namespace dox {

//..............................................................................

%%{

machine dox;
write data;

#. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
#
# definitions
#

# whitespace

ws = [ \t\r]+;

# non-whitespace

nws = [^ \t\r\n]+;

# regular char (non-whitespace and non-escape)

rc = [^ \t\r\n\\];

#. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
#
# main machine
#

main := |*

'\\headerfile'    { createToken(TokenKind_Import); };
'\\importfile'    { createToken(TokenKind_Import); };
'\\import'        { createToken(TokenKind_Import); };
'\\enum'          { createToken(TokenKind_Enum); };
'\\enumvalue'     { createToken(TokenKind_EnumValue); };
'\\struct'        { createToken(TokenKind_Struct); };
'\\union'         { createToken(TokenKind_Union); };
'\\class'         { createToken(TokenKind_Class); };
'\\alias'         { createToken(TokenKind_Alias); };
'\\var'           { createToken(TokenKind_Variable); };
'\\variable'      { createToken(TokenKind_Variable); };
'\\field'         { createToken(TokenKind_Field); };
'\\fn'            { createToken(TokenKind_Function); };
'\\function'      { createToken(TokenKind_Function); };
'\\method'        { createToken(TokenKind_Function); };
'\\overload'      { createToken(TokenKind_Overload); };
'\\prop'          { createToken(TokenKind_Property); };
'\\property'      { createToken(TokenKind_Property); };
'\\event'         { createToken(TokenKind_Event); };
'\\typedef'       { createToken(TokenKind_Typedef); };
'\\namespace'     { createToken(TokenKind_Namespace); };
'\\group'         { createToken(TokenKind_Group); };
'\\defgroup'      { createToken(TokenKind_Group); };
'\\addtogroup'    { createToken(TokenKind_Group); };
'\\ingroup'       { createToken(TokenKind_InGroup); };
'\\subgroup'      { createToken(TokenKind_SubGroup); };
'\\title'         { createToken(TokenKind_Title); };
'\\brief'         { createToken(TokenKind_Brief); };
'\\details'       { createToken(TokenKind_Details); };
'\\see'           { createToken(TokenKind_SeeAlso); };
'\\seealso'       { createToken(TokenKind_SeeAlso); };
'\\sa'            { createToken(TokenKind_SeeAlso); };
'\\internal'      { createToken(TokenKind_Internal); };
'\\footnote'      { createToken(TokenKind_Footnote); };

'\\' rc*          { createTextToken(TokenKind_CustomCommand, 1); };

'@{'              { createToken(TokenKind_OpeningBrace); };
'@}'              { createToken(TokenKind_ClosingBrace); };

rc ([^\n]* nws)?  { createTextToken(TokenKind_Text); };

'\n' ws?          { createNewLineToken(), newLine(ts + 1); };
ws                ;

*|;

}%%

//..............................................................................

void
Lexer::init()
{
	%% write init;
}

void
Lexer::exec()
{
	%% write exec;
}

//..............................................................................

} // namespace dox
} // namespace axl


removed warnings for windows build

removed warnings for windows build

#include "confscanner.h"

#ifdef WIN32
#include <windows.h>
#include <shlwapi.h>
#else
extern "C" {
#include <fnmatch.h>
}
#endif

using std::cerr;
using std::cout;
using std::endl;
using std::string;
using std::vector;
using std::map;
using std::set;
using std::ifstream;

%%{
  machine ConfFileScanner;
  write data;

  # store current char pointer p in tmp_p
  action st_tok { tmp_p = p; }

  # assign a string token to tmp_string variable
  action string_tok {
    tmp_string.assign(tmp_p, p-tmp_p);
  }

  # add groupname to groupset_ if glob expression matches
  action group_add
  {
    // check wether glob expression matches
    if(match_at_level_[level_])
    {
      tmp_string.assign(tmp_p, p-tmp_p);
      // clear groupset_ first, if we did not use '+='
      if(!arg_to_be_added_)
        groupset_.clear();

      groupset_.insert(tmp_string);
    }
  }

  # set cblock_ to selected documentation block as specified in an expression
  # "add(specifier)"
  action set_docu_block
  {
    tmp_string.assign(tmp_p, p-tmp_p);
    if(tmp_string == "brief")
      cblock_ = &docuheader_;
    else if(tmp_string == "doc")
      cblock_ = &docubody_;
    else if(tmp_string == "extra")
      cblock_ = &docuextra_;
  }

  # set clist_ to selected documentation block list as specified in an
  # expression "add(params)" or "add(return)"
  action set_docu_list
  {
    tmp_string.assign(tmp_p, p-tmp_p);
    //cerr << "docu_list: " << tmp_string << "\n";
    if(tmp_string == "params")
    {
      //cerr << "paramlist\n";
      clist_ = &param_list_;
    }
    else if(tmp_string == "return")
      clist_ = &return_list_;
  }

  # set clistmap_ to selected documentation block list map as specified in an
  # expression "add(fields)"
  action set_docu_list_map
  {
    tmp_string.assign(tmp_p, p-tmp_p);
    if(tmp_string == "fields")
      clistmap_ = &field_docu_;
  }

  # add line of a block to the cblock structure which can be one of
  # docublock, docuheader, docuextra or clist[] or clistmap[][]
  action add_block_line
  {
    // check wether glob expression matches
    if(match_at_level_[level_])
    {
      //cerr << "glob matched!" << endl;
      tmp_string.assign(tmp_p, p-tmp_p);
      string :: size_type found = tmp_string.find_first_not_of(" \t");
      if(found == string::npos || string(tmp_string.substr(found)) != "\"\"")
      {
        found = tmp_string.rfind("\"");
        if(found != string::npos && string(tmp_string.substr(found-1)) == "\"\"")
        {
          //cerr << "adding: " << tmp_string.substr(0, found-1) + '\n';
          //cerr << endl;
          (*cblock_).push_back(tmp_string.substr(0, found-1) + '\n');
          /*if(!param_list_["grid"].empty())
            cerr << "afterwards param_list_[\"grid\"][0] " << param_list_["grid"][0];*/
        }
        else
        {
          //cerr << "a2: " << tmp_string + '\n';
          (*cblock_).push_back(tmp_string + '\n');
        }
      }
      opt = true;
    }
  }

  # set the cblock pointer to clist_[value]
  action set_block_from_list
  {
    // check wether glob expression matches
    if(match_at_level_[level_])
    {
      tmp_string.assign(tmp_p, p-tmp_p);
      //cerr << "ts:" << tmp_string << ":ets\n";//<< " " << tmp_p << "p: " << p << "\n";
      cblock_ = &((*clist_)[tmp_string]);
    }
  }

  # set the cblock pointer to clist_[value1][value2]
  action set_block_from_listmap
  {
    string s(tmp_p, p-tmp_p);
    cblock_ = &((*clistmap_)[tmp_string][s]);
  }

  action reg_glob
  {
    tmp_string.assign(tmp_p, p-tmp_p);
/*    cerr << "register glob: " << tmp_string << endl;
    cerr << " on top of globlist_stack " << endl;*/
    //cerr_stack();
//    cerr << "\n";
    // TODO: use globlist_map
    globlist_stack_.back().push_back(tmp_string);
  }

  # end of file
  EOF = 0;

  # end of line (CRLF, LF)
  EOL = '\r'? . '\n';

  # any not end of file character
  default = ^0;

  # identifier
  IDENT = [A-Z]+;

  # matlab identifier
  MIDENT = [A-Za-z][A-Za-z0-9_]*;

  # glob expression
  GLOB = (default - [\n \t;\{])+;

  # swallow till the end of line
  garbling := (default - '\n')* . EOL @{line++;fret;};

  # parameter values for single documentation blocks
  add_param_docu_block = ('brief'|'doc'|'extra');
  # parameter values for lists of documentation blocks
  add_param_docu_list = ('params'|'return');
  # parameter values for a map of lists of documentation blocks
  add_param_docu_list_map = ('fields');

  # matlab white space or comment
  MWSOC = (
           [ \t]
           | EOL @{line++;}
           | (('#'. any) -'##') @{ /*cerr << "l: " << line << "\n";*/ fhold;fcall garbling; }
          );
#  MWSOC = ( [ \t\n]);

  # equal sign or += sign
  EQ = ('+'? @{arg_to_be_added_ = true;} . '=');

  # end of a rule sign
  ENDRULE = ';' @{arg_to_be_added_ = false;};

  # add line in a documentation block
  docu_block_line :=
    (
     # default action (just copy text to tmp_p)
     ( ( default - ["\n] )+ >{ if(opt) { tmp_p = p; } } )
     |
     # end line in documenation block (calls add_block_line)
     EOL @(add_block_line) @{line++;}
     |
     # end of documentation block (calls add_block_line)
     '"""' @(add_block_line) @{cblock_ = 0; fret;}
     |
     # ignore quotes (less than 3 in a row)
     ('"'{1,2} . (default - ["\n])) @{ fhold; opt=false; }
    )**;

  # begin documentation block
  docu_block = ('"""'
                @{
                opt=true;
                if(!arg_to_be_added_)
                {
                  if(cblock_ != 0)
                    (*cblock_).clear();
                }
                fcall docu_block_line;
                }
               );

  # documentation list item (something like key => value)
  docu_list_item =
    (
     ( MIDENT
         >{tmp_p = p;}
         %(set_block_from_list)
     ) . MWSOC*
     . '=>' . MWSOC* . docu_block
    );

  # a list of documentation block lists
  docu_list = (
    (
     '('
     . ( MWSOC* . docu_list_item . MWSOC* . ',')*
     . MWSOC* . docu_list_item . MWSOC* . ')'
    )
    |
    docu_list_item
  );

  # documentation list map item
  docu_list_map_item =
    (
     # matlab identifier (structure)
     (MIDENT
       >{tmp_p = p;/* cerr << "dlmi\n";*/} %(string_tok)
     )
     . '.'
     # matlab identifer (fieldname)
     .(MIDENT
       >{tmp_p = p;/* cerr << "dlmi2\n";*/} %(set_block_from_listmap)
      )
     . MWSOC*
     . '=>' . MWSOC* . docu_block
    );

  # a map of documentation block lists
  docu_list_map = (
    (
     '('
     . ( MWSOC* . docu_list_map_item . MWSOC* . ',')*
     . MWSOC* . docu_list_map_item . MWSOC* . ')'
    )
    |
    docu_list_map_item
  );

  # list of file matching words
  globlist =
    (GLOB >{/*cerr << "glob_list:\n"; cerr.write(p, 10); cerr << endl;*/ tmp_p = p;} %(reg_glob))
    . (MWSOC* . [ \t]+ . MWSOC*
        . (GLOB >{/*cerr << "glob list2\n"; cerr.write(p, 20); cerr << endl;*/ tmp_p = p;} %(reg_glob)))*;

  # list of group names
  grouplist = (
     # matlab identifier (group name)
    ((MIDENT >(st_tok)
             %(group_add))
     . ',')*
     # matlab identifier (group name)
    .(MIDENT >(st_tok)
      %(group_add))
    );

  # specify all possible rules
  rules := (
    # glob list
    ('glob' .MWSOC* . '=' . MWSOC* . globlist . MWSOC* . '{')
      @{ check_glob_level_up(); fcall rules; }
    |
    (MWSOC)
    |
    # groups rule
    ('groups'. MWSOC* . EQ
     . MWSOC* . grouplist . MWSOC* . ENDRULE)
    |
    # add rule
    ('add(' %{/*cerr << "add:" << '\n';*/ tmp_p = p;}
     . (
        # add documentation block (brief, docu, extra)
        ((add_param_docu_block
            %(set_docu_block)
         )
         . ')' . MWSOC* . EQ
         . MWSOC* . docu_block . MWSOC* . ENDRULE )
        |
        # add documentation list (documentation block for a parameter or a
        # parameter or a return value)
        ((add_param_docu_list
            %(set_docu_list)
         )
         . ')' . MWSOC* . EQ
         . MWSOC* :> docu_list . MWSOC* . ENDRULE )
        |
        # add a map of documentation lists (documentation blocks for structure
        # fields of parameters or return values)
        ((add_param_docu_list_map
            %(set_docu_list_map)
         )
         . ')' . MWSOC* . EQ
         . MWSOC* :> docu_list_map . MWSOC* ENDRULE )
       )
    )
    |
    # go level down (end of a glob match block)
    ('}') @{ go_level_down(); fret; }
    )*;

  # main parser (first entry point) This block comprises variable definitions.
  main := (
           # ignored characters
           MWSOC
           | (
              # variable definitions (not yet implemented)
              (MIDENT
                 >(st_tok)
                 %(string_tok)
              ). MWSOC* . ':=' . MWSOC*
              . (GLOB >st_tok )
              . (MWSOC*
                 . (GLOB)
                )*
              . MWSOC*
              . ';'
                @{
                  arglist_ = &vars_[tmp_string];
                  arglist_->push_back(std::string(tmp_p, p - tmp_p));
                 }
             )
           |
           # end variable definitions
           '##' . [ \t]* . EOL @{ line++; /*cerr<<"-> rules\n";*/ fgoto rules; }
          )**;
}%%

// this method is called when a glob ... {} block ends
void ConfFileScanner :: go_level_down()
{
  globlist_stack_.pop_back();
  globlist_stack_.back().clear();
  level_--;
}

// checks wether the string is matched by a glob from the globlist_stack_ at
// level \a l
bool ConfFileScanner :: check_for_match(int l, const char * str)
{
  typedef GlobList :: iterator iterator;
  // get globlist at stack level \a l
  GlobList & gl = globlist_stack_[l];

  iterator endit = gl.end();
  // iterate over all globs
  for( iterator it = gl.begin(); it != endit; it++ )
  {
    const char * glob = (*it).c_str();
#ifndef WIN32
    int flags = 0;
#endif
    if (glob[0] == '.' && glob[1] == '/')
    {
      glob = &glob[2];
#ifndef WIN32
      flags = FNM_PATHNAME;
#endif
    }

    // check wether str matches glob
#ifdef WIN32
    if(PathMatchSpec(str,glob))
#else
    if(fnmatch(glob, str, flags) == 0)
#endif
    {
      return true;
    }
  }
  return false;
}

// check recursively wether file named \a s is matched by a glob from the
// globlist_stack_
bool ConfFileScanner :: check_glob_rec(int l, const string & s)
{
  string str;
  string :: size_type found;
  // exit condition (if filename is matched up to level level_+1 the check was
  // successful.
  if(l == level_+1)
  {
    //cerr << "matched " << s << endl;
    return true;
  }

  found = s.find("/"); // try to match dir in path
  while(found != string :: npos)
  {
    str = s.substr(0, found);
    // first try to match the prepended path by a glob at this level, ...
    if(check_for_match(l, str.c_str())
        // ... then try to match the rest of the substring at a higher level.
        && check_glob_rec(l+1, s.substr(found+1)))
    {
      //cerr << "matched " << s << endl;
      return true;
    }

    found = s.find("/", found+1); // try to match more dirs
  }
  if(l == level_) // try also to match the entire string at this level
  {
    if(check_for_match(l, s.c_str()))
    {
      //cerr << "matched " << s << endl;
      return true;
    }
  }

  return false;
}

// this method is called after a glob ... = {} block begins
// and sets the match_at_level_ structure for the active file named
// \a filename_
void ConfFileScanner :: check_glob_level_up()
{
  // update match_at_level_
  match_at_level_[level_+1] = check_glob_rec(0, filename_);

  // add empty glob list
  globlist_stack_.push_back(GlobList());
  level_++;
}

// constructor
ConfFileScanner
:: ConfFileScanner(const std::string & filename, const std::string & conffilename)
 : buf(new char[BUFSIZE]), line(1), have(0), top(0), opt(true),
   filename_(filename),
   conffile_(conffilename == "" ? "./doxygen/mtoc.conf" : conffilename),
   confistream_(get_conffile()),
   level_(0),
   arg_to_be_added_(false)
{
  //cerr << "filename" << filename << endl;
  if ( (confistream_.rdstate() & ifstream::failbit ) != 0 )
  {
    cerr << "Error opening configuration file '" << conffile_ << "'\n";
    exit(-2);
  }
  globlist_stack_.push_back(GlobList());
  // at level 0 every file is matched ( no glob checking )
  match_at_level_[0] = true;
}

// returns the name of the configuration file
const char * ConfFileScanner ::
get_conffile()
{
  return conffile_.c_str();
}

// run the conffile scanner
int ConfFileScanner :: execute()
{
  std::ios::sync_with_stdio(false);

  %% write init;

  /* Do the first read. */
  bool done = false;
  while ( !done )
  {
    char *p = buf + have;
    char *tmp_p = p;// *tmp_p2 = p;
    // string for temporary tokens
    string tmp_string;
    // spare space in buffer
    int space = BUFSIZE - have;

    if ( space == 0 )
    {
      /* We filled up the buffer trying to scan a token. */
      cerr << "OUT OF BUFFER SPACE" << endl;
      exit(1);
    }

    // read configuration file chunk in buffer
    confistream_.read( p, space );
    int len = confistream_.gcount();
    char *pe = p + len;
    char *rpe = pe;
    char *eof = 0;

    /* If we see eof then append the EOF char. */
    if ( confistream_.eof() )
    {
      eof = pe;
      done = true;
    }
    else
    {
      /* Find the last semicolon by searching backwards. This
       * is where we will stop processing on this iteration. */
      while ( *pe!= ';' && pe > p )
        pe--;
    }

    //std::cerr << "execute parser" << std::endl;
    %% write exec;
    //std::cerr << "finish parser" << std::endl;

    /* Check if we failed. */
    if ( cs == ConfFileScanner_error )
    {
      /* Machine failed before finding a token. */
      cerr << "in conffile: PARSE ERROR in line " << line << endl;
      exit(1);
    }

    have = rpe - pe;
    /* cerr << "memmove by " << have << "bytes\n";*/
    memmove( buf, pe, have );
  }
  //cerr << "afterwards param_list_[\"grid\"][0] " << param_list_["grid"][0];

  return 0;
}
/* vim: set et sw=2 ft=ragel: */

#include "confscanner.h"

#ifdef WIN32
#include <windows.h>
#include <shlwapi.h>
#else
extern "C" {
#include <fnmatch.h>
}
#endif

using std::cerr;
using std::cout;
using std::endl;
using std::string;
using std::vector;
using std::map;
using std::set;
using std::ifstream;

%%{
  machine ConfFileScanner;
  write data;

  # store current char pointer p in tmp_p
  action st_tok { tmp_p = p; }

  # assign a string token to tmp_string variable
  action string_tok {
    tmp_string.assign(tmp_p, p-tmp_p);
  }

  # add groupname to groupset_ if glob expression matches
  action group_add
  {
    // check wether glob expression matches
    if(match_at_level_[level_])
    {
      tmp_string.assign(tmp_p, p-tmp_p);
      // clear groupset_ first, if we did not use '+='
      if(!arg_to_be_added_)
        groupset_.clear();

      groupset_.insert(tmp_string);
    }
  }

  # set cblock_ to selected documentation block as specified in an expression
  # "add(specifier)"
  action set_docu_block
  {
    tmp_string.assign(tmp_p, p-tmp_p);
    if(tmp_string == "brief")
      cblock_ = &docuheader_;
    else if(tmp_string == "doc")
      cblock_ = &docubody_;
    else if(tmp_string == "extra")
      cblock_ = &docuextra_;
  }

  # set clist_ to selected documentation block list as specified in an
  # expression "add(params)" or "add(return)"
  action set_docu_list
  {
    tmp_string.assign(tmp_p, p-tmp_p);
    //cerr << "docu_list: " << tmp_string << "\n";
    if(tmp_string == "params")
    {
      //cerr << "paramlist\n";
      clist_ = &param_list_;
    }
    else if(tmp_string == "return")
      clist_ = &return_list_;
  }

  # set clistmap_ to selected documentation block list map as specified in an
  # expression "add(fields)"
  action set_docu_list_map
  {
    tmp_string.assign(tmp_p, p-tmp_p);
    if(tmp_string == "fields")
      clistmap_ = &field_docu_;
  }

  # add line of a block to the cblock structure which can be one of
  # docublock, docuheader, docuextra or clist[] or clistmap[][]
  action add_block_line
  {
    // check wether glob expression matches
    if(match_at_level_[level_])
    {
      //cerr << "glob matched!" << endl;
      tmp_string.assign(tmp_p, p-tmp_p);
      string :: size_type found = tmp_string.find_first_not_of(" \t");
      if(found == string::npos || string(tmp_string.substr(found)) != "\"\"")
      {
        found = tmp_string.rfind("\"");
        if(found != string::npos && string(tmp_string.substr(found-1)) == "\"\"")
        {
          //cerr << "adding: " << tmp_string.substr(0, found-1) + '\n';
          //cerr << endl;
          (*cblock_).push_back(tmp_string.substr(0, found-1) + '\n');
          /*if(!param_list_["grid"].empty())
            cerr << "afterwards param_list_[\"grid\"][0] " << param_list_["grid"][0];*/
        }
        else
        {
          //cerr << "a2: " << tmp_string + '\n';
          (*cblock_).push_back(tmp_string + '\n');
        }
      }
      opt = true;
    }
  }

  # set the cblock pointer to clist_[value]
  action set_block_from_list
  {
    // check wether glob expression matches
    if(match_at_level_[level_])
    {
      tmp_string.assign(tmp_p, p-tmp_p);
      //cerr << "ts:" << tmp_string << ":ets\n";//<< " " << tmp_p << "p: " << p << "\n";
      cblock_ = &((*clist_)[tmp_string]);
    }
  }

  # set the cblock pointer to clist_[value1][value2]
  action set_block_from_listmap
  {
    string s(tmp_p, p-tmp_p);
    cblock_ = &((*clistmap_)[tmp_string][s]);
  }

  action reg_glob
  {
    tmp_string.assign(tmp_p, p-tmp_p);
/*    cerr << "register glob: " << tmp_string << endl;
    cerr << " on top of globlist_stack " << endl;*/
    //cerr_stack();
//    cerr << "\n";
    // TODO: use globlist_map
    globlist_stack_.back().push_back(tmp_string);
  }

  # end of file
  EOF = 0;

  # end of line (CRLF, LF)
  EOL = '\r'? . '\n';

  # any not end of file character
  default = ^0;

  # identifier
  IDENT = [A-Z]+;

  # matlab identifier
  MIDENT = [A-Za-z][A-Za-z0-9_]*;

  # glob expression
  GLOB = (default - [\n \t;\{])+;

  # swallow till the end of line
  garbling := (default - '\n')* . EOL @{line++;fret;};

  # parameter values for single documentation blocks
  add_param_docu_block = ('brief'|'doc'|'extra');
  # parameter values for lists of documentation blocks
  add_param_docu_list = ('params'|'return');
  # parameter values for a map of lists of documentation blocks
  add_param_docu_list_map = ('fields');

  # matlab white space or comment
  MWSOC = (
           [ \t]
           | EOL @{line++;}
           | (('#'. any) -'##') @{ /*cerr << "l: " << line << "\n";*/ fhold;fcall garbling; }
          );
#  MWSOC = ( [ \t\n]);

  # equal sign or += sign
  EQ = ('+'? @{arg_to_be_added_ = true;} . '=');

  # end of a rule sign
  ENDRULE = ';' @{arg_to_be_added_ = false;};

  # add line in a documentation block
  docu_block_line :=
    (
     # default action (just copy text to tmp_p)
     ( ( default - ["\n] )+ >{ if(opt) { tmp_p = p; } } )
     |
     # end line in documenation block (calls add_block_line)
     EOL @(add_block_line) @{line++;}
     |
     # end of documentation block (calls add_block_line)
     '"""' @(add_block_line) @{cblock_ = 0; fret;}
     |
     # ignore quotes (less than 3 in a row)
     ('"'{1,2} . (default - ["\n])) @{ fhold; opt=false; }
    )**;

  # begin documentation block
  docu_block = ('"""'
                @{
                opt=true;
                if(!arg_to_be_added_)
                {
                  if(cblock_ != 0)
                    (*cblock_).clear();
                }
                fcall docu_block_line;
                }
               );

  # documentation list item (something like key => value)
  docu_list_item =
    (
     ( MIDENT
         >{tmp_p = p;}
         %(set_block_from_list)
     ) . MWSOC*
     . '=>' . MWSOC* . docu_block
    );

  # a list of documentation block lists
  docu_list = (
    (
     '('
     . ( MWSOC* . docu_list_item . MWSOC* . ',')*
     . MWSOC* . docu_list_item . MWSOC* . ')'
    )
    |
    docu_list_item
  );

  # documentation list map item
  docu_list_map_item =
    (
     # matlab identifier (structure)
     (MIDENT
       >{tmp_p = p;/* cerr << "dlmi\n";*/} %(string_tok)
     )
     . '.'
     # matlab identifer (fieldname)
     .(MIDENT
       >{tmp_p = p;/* cerr << "dlmi2\n";*/} %(set_block_from_listmap)
      )
     . MWSOC*
     . '=>' . MWSOC* . docu_block
    );

  # a map of documentation block lists
  docu_list_map = (
    (
     '('
     . ( MWSOC* . docu_list_map_item . MWSOC* . ',')*
     . MWSOC* . docu_list_map_item . MWSOC* . ')'
    )
    |
    docu_list_map_item
  );

  # list of file matching words
  globlist =
    (GLOB >{/*cerr << "glob_list:\n"; cerr.write(p, 10); cerr << endl;*/ tmp_p = p;} %(reg_glob))
    . (MWSOC* . [ \t]+ . MWSOC*
        . (GLOB >{/*cerr << "glob list2\n"; cerr.write(p, 20); cerr << endl;*/ tmp_p = p;} %(reg_glob)))*;

  # list of group names
  grouplist = (
     # matlab identifier (group name)
    ((MIDENT >(st_tok)
             %(group_add))
     . ',')*
     # matlab identifier (group name)
    .(MIDENT >(st_tok)
      %(group_add))
    );

  # specify all possible rules
  rules := (
    # glob list
    ('glob' .MWSOC* . '=' . MWSOC* . globlist . MWSOC* . '{')
      @{ check_glob_level_up(); fcall rules; }
    |
    (MWSOC)
    |
    # groups rule
    ('groups'. MWSOC* . EQ
     . MWSOC* . grouplist . MWSOC* . ENDRULE)
    |
    # add rule
    ('add(' %{/*cerr << "add:" << '\n';*/ tmp_p = p;}
     . (
        # add documentation block (brief, docu, extra)
        ((add_param_docu_block
            %(set_docu_block)
         )
         . ')' . MWSOC* . EQ
         . MWSOC* . docu_block . MWSOC* . ENDRULE )
        |
        # add documentation list (documentation block for a parameter or a
        # parameter or a return value)
        ((add_param_docu_list
            %(set_docu_list)
         )
         . ')' . MWSOC* . EQ
         . MWSOC* :> docu_list . MWSOC* . ENDRULE )
        |
        # add a map of documentation lists (documentation blocks for structure
        # fields of parameters or return values)
        ((add_param_docu_list_map
            %(set_docu_list_map)
         )
         . ')' . MWSOC* . EQ
         . MWSOC* :> docu_list_map . MWSOC* ENDRULE )
       )
    )
    |
    # go level down (end of a glob match block)
    ('}') @{ go_level_down(); fret; }
    )*;

  # main parser (first entry point) This block comprises variable definitions.
  main := (
           # ignored characters
           MWSOC
           | (
              # variable definitions (not yet implemented)
              (MIDENT
                 >(st_tok)
                 %(string_tok)
              ). MWSOC* . ':=' . MWSOC*
              . (GLOB >st_tok )
              . (MWSOC*
                 . (GLOB)
                )*
              . MWSOC*
              . ';'
                @{
                  arglist_ = &vars_[tmp_string];
                  arglist_->push_back(std::string(tmp_p, p - tmp_p));
                 }
             )
           |
           # end variable definitions
           '##' . [ \t]* . EOL @{ line++; /*cerr<<"-> rules\n";*/ fgoto rules; }
          )**;
}%%

// this method is called when a glob ... {} block ends
void ConfFileScanner :: go_level_down()
{
  globlist_stack_.pop_back();
  globlist_stack_.back().clear();
  level_--;
}

// checks wether the string is matched by a glob from the globlist_stack_ at
// level \a l
bool ConfFileScanner :: check_for_match(int l, const char * str)
{
  typedef GlobList :: iterator iterator;
  // get globlist at stack level \a l
  GlobList & gl = globlist_stack_[l];

  iterator endit = gl.end();
  // iterate over all globs
  for( iterator it = gl.begin(); it != endit; it++ )
  {
    const char * glob = (*it).c_str();
    int flags = 0;
    if (glob[0] == '.' && glob[1] == '/')
    {
      glob = &glob[2];
#ifndef WIN32
      flags = FNM_PATHNAME;
#endif
    }

    // check wether str matches glob
#ifdef WIN32
    if(PathMatchSpec(str,glob))
#else
    if(fnmatch(glob, str, flags) == 0)
#endif
    {
      return true;
    }
  }
  return false;
}

// check recursively wether file named \a s is matched by a glob from the
// globlist_stack_
bool ConfFileScanner :: check_glob_rec(int l, const string & s)
{
  string str;
  string :: size_type found;
  // exit condition (if filename is matched up to level level_+1 the check was
  // successful.
  if(l == level_+1)
  {
    //cerr << "matched " << s << endl;
    return true;
  }

  found = s.find("/"); // try to match dir in path
  while(found != string :: npos)
  {
    str = s.substr(0, found);
    // first try to match the prepended path by a glob at this level, ...
    if(check_for_match(l, str.c_str())
        // ... then try to match the rest of the substring at a higher level.
        && check_glob_rec(l+1, s.substr(found+1)))
    {
      //cerr << "matched " << s << endl;
      return true;
    }

    found = s.find("/", found+1); // try to match more dirs
  }
  if(l == level_) // try also to match the entire string at this level
  {
    if(check_for_match(l, s.c_str()))
    {
      //cerr << "matched " << s << endl;
      return true;
    }
  }

  return false;
}

// this method is called after a glob ... = {} block begins
// and sets the match_at_level_ structure for the active file named
// \a filename_
void ConfFileScanner :: check_glob_level_up()
{
  // update match_at_level_
  match_at_level_[level_+1] = check_glob_rec(0, filename_);

  // add empty glob list
  globlist_stack_.push_back(GlobList());
  level_++;
}

// constructor
ConfFileScanner
:: ConfFileScanner(const std::string & filename, const std::string & conffilename)
 : buf(new char[BUFSIZE]), line(1), have(0), top(0), opt(true),
   filename_(filename),
   conffile_(conffilename == "" ? "./doxygen/mtoc.conf" : conffilename),
   confistream_(get_conffile()),
   level_(0),
   arg_to_be_added_(false)
{
  //cerr << "filename" << filename << endl;
  if ( (confistream_.rdstate() & ifstream::failbit ) != 0 )
  {
    cerr << "Error opening configuration file '" << conffile_ << "'\n";
    exit(-2);
  }
  globlist_stack_.push_back(GlobList());
  // at level 0 every file is matched ( no glob checking )
  match_at_level_[0] = true;
}

// returns the name of the configuration file
const char * ConfFileScanner ::
get_conffile()
{
  return conffile_.c_str();
}

// run the conffile scanner
int ConfFileScanner :: execute()
{
  std::ios::sync_with_stdio(false);

  %% write init;

  /* Do the first read. */
  bool done = false;
  while ( !done )
  {
    char *p = buf + have;
    char *tmp_p = p;// *tmp_p2 = p;
    // string for temporary tokens
    string tmp_string;
    // spare space in buffer
    int space = BUFSIZE - have;

    if ( space == 0 )
    {
      /* We filled up the buffer trying to scan a token. */
      cerr << "OUT OF BUFFER SPACE" << endl;
      exit(1);
    }

    // read configuration file chunk in buffer
    confistream_.read( p, space );
    int len = confistream_.gcount();
    char *pe = p + len;
    char *rpe = pe;
    char *eof = 0;

    /* If we see eof then append the EOF char. */
    if ( confistream_.eof() )
    {
      eof = pe;
      done = true;
    }
    else
    {
      /* Find the last semicolon by searching backwards. This
       * is where we will stop processing on this iteration. */
      while ( *pe!= ';' && pe > p )
        pe--;
    }

    //std::cerr << "execute parser" << std::endl;
    %% write exec;
    //std::cerr << "finish parser" << std::endl;

    /* Check if we failed. */
    if ( cs == ConfFileScanner_error )
    {
      /* Machine failed before finding a token. */
      cerr << "in conffile: PARSE ERROR in line " << line << endl;
      exit(1);
    }

    have = rpe - pe;
    /* cerr << "memmove by " << have << "bytes\n";*/
    memmove( buf, pe, have );
  }
  //cerr << "afterwards param_list_[\"grid\"][0] " << param_list_["grid"][0];

  return 0;
}
/* vim: set et sw=2 ft=ragel: */


Simplify how callbacks are handled

Simplify how callbacks are handled

/* 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.
 */

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

#include "http11.h"

static int calc_offset(const char *fpc, const char *buf) {
    return fpc - buf;
}

static int calc_length(const char *fpc, const char *buf, const int offset) {
    return fpc - buf - offset;
}

static const char *create_ptr(const char *buf, const int offset) {
    return buf + offset;
}

void handle_callback(HTTPParser *parser,
                     const char *fpc,
                     const char *data,
                     int (*callback)(const char *, size_t)) {
    if (callback != NULL) {
        parser->error = callback(
            create_ptr(data, parser->state->mark),
            calc_length(fpc, data, parser->state->mark)
        );
    }
}

%%{
    machine http_parser;

    action mark {
        parser->state->mark = calc_offset(fpc, data);
    }

    action request_method {
        handle_callback(parser, fpc, data, parser->request_method);

        if (parser->error) {
            fgoto *http_parser_error;
        }
    }

    action request_uri {
        handle_callback(parser, fpc, data, parser->request_uri);

        if (parser->error) {
            fgoto *http_parser_error;
        }
    }

    action http_version {
        handle_callback(parser, fpc, data, parser->http_version);

        if (parser->error) {
            fgoto *http_parser_error;
        }
    }

    CRLF = ( "\r\n" | "\n" ) ;
    SP = " " ;

    tchar = ( "!" | "#" | "$" | "%" | "&" | "'" | "*" | "+" | "-" | "." | "^" |
              "_" | "`" | "|" | "~" | digit | alpha ) ;
    token = tchar+ ;

    method = token >mark %request_method ;
    request_target = ( any -- CRLF )+ >mark %request_uri ;
    http_version = ( "HTTP" "/" digit "." digit ) >mark %request_uri ;

    request_line = method SP request_target SP http_version CRLF ;
    http_message = ( request_line ) CRLF ;

main := http_message;

}%%

%% write data;

struct _HTTPParserState {
  int cs;
  int mark;
};

HTTPParser *HTTPParser_create() {
    HTTPParser *parser = malloc(sizeof(*parser));

    if (!parser) {
        return NULL;
    }

    parser->state = malloc(sizeof *parser->state);

    if (!parser->state) {
        HTTPParser_destroy(parser);
        return NULL;
    }

    return parser;
}

void HTTPParser_init(HTTPParser *parser) {
    %% access parser->state->;
    %% write init;

    parser->state->mark = 0;

    parser->finished = false;
    parser->error = 0;
}

size_t HTTPParser_execute(HTTPParser *parser, const char *data, size_t len, size_t off) {
    const char *p = data + off;
    const char *pe = data + len;

    %% access parser->state->;
    %% write exec;

    if (parser->state->cs == http_parser_error || parser->state->cs >= http_parser_first_final ) {
        parser->finished = true;

        if (parser->state->cs == http_parser_error && !parser->error) {
            parser->error = 1;
        }
    }

    return 1;
}

void HTTPParser_destroy(HTTPParser *parser) {
    if (parser) {
        free(parser->state);
        free(parser);
    }
}

/* 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.
 */

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

#include "http11.h"

static int calc_offset(const char *fpc, const char *buf) {
    return fpc - buf;
}

static int calc_length(const char *fpc, const char *buf, const int offset) {
    return fpc - buf - offset;
}

static const char *create_ptr(const char *buf, const int offset) {
    return buf + offset;
}

%%{
    machine http_parser;

    action mark {
        parser->state->mark = calc_offset(fpc, data);
    }

    action request_method {
        if (parser->request_method != NULL) {
            parser->error = parser->request_method(
                create_ptr(data, parser->state->mark),
                calc_length(fpc, data, parser->state->mark)
            );

            if (parser->error) {
                fgoto *http_parser_error;
            }
        }
    }

    action request_uri {
        if (parser->request_uri != NULL) {
            parser->error = parser->request_uri(
                create_ptr(data, parser->state->mark),
                calc_length(fpc, data, parser->state->mark)
            );

            if (parser->error) {
                fgoto *http_parser_error;
            }
        }
    }

    action http_version {
        if (parser->http_version != NULL) {
            parser->error = parser->http_version(
                create_ptr(data, parser->state->mark),
                calc_length(fpc, data, parser->state->mark)
            );

            if (parser->error) {
                fgoto *http_parser_error;
            }
        }
    }

    CRLF = ( "\r\n" | "\n" ) ;
    SP = " " ;

    tchar = ( "!" | "#" | "$" | "%" | "&" | "'" | "*" | "+" | "-" | "." | "^" |
              "_" | "`" | "|" | "~" | digit | alpha ) ;
    token = tchar+ ;

    method = token >mark %request_method ;
    request_target = ( any -- CRLF )+ >mark %request_uri ;
    http_version = ( "HTTP" "/" digit "." digit ) >mark %request_uri ;

    request_line = method SP request_target SP http_version CRLF ;
    http_message = ( request_line ) CRLF ;

main := http_message;

}%%

%% write data;

struct _HTTPParserState {
  int cs;
  int mark;
};

HTTPParser *HTTPParser_create() {
    HTTPParser *parser = malloc(sizeof(*parser));

    if (!parser) {
        return NULL;
    }

    parser->state = malloc(sizeof *parser->state);

    if (!parser->state) {
        HTTPParser_destroy(parser);
        return NULL;
    }

    return parser;
}

void HTTPParser_init(HTTPParser *parser) {
    %% access parser->state->;
    %% write init;

    parser->state->mark = 0;

    parser->finished = false;
    parser->error = 0;
}

size_t HTTPParser_execute(HTTPParser *parser, const char *data, size_t len, size_t off) {
    const char *p = data + off;
    const char *pe = data + len;

    %% access parser->state->;
    %% write exec;

    if (parser->state->cs == http_parser_error || parser->state->cs >= http_parser_first_final ) {
        parser->finished = true;

        if (parser->state->cs == http_parser_error && !parser->error) {
            parser->error = 1;
        }
    }

    return 1;
}

void HTTPParser_destroy(HTTPParser *parser) {
    if (parser) {
        free(parser->state);
        free(parser);
    }
}


Query string parser will accept pairs with a key but no value (e.g. 'derp=')

#include "querystring.h"
#include "url.h"

%%{
	machine parse_querystring;
	alphtype unsigned char;
	action mark { ts = p; }

	# Implements a parser for query strings as defined in:
	# https://en.wikipedia.org/wiki/Query_string

	key = [^=]+
		>{ key.ptr = p; }
		%{ key.len = p - key.ptr; }
		;

	value = [^&]+
		>{ val.ptr = p; }
		%{ val.len = p - val.ptr; }
		;

	pair = (key "=" value)

		   >{ str_init(&key);
		   	  str_init(&val); }
		   %{
		   		if( str_len(&key) ) {
		   			key.len = php_url_decode((char*)key.ptr, key.len);
		      		val.len = php_url_decode((char*)val.ptr, val.len);
		      		output = strpair_add(output, &key, &val);
		   		}
		   	}
		   	;

	main := pair ( "&" pair )* ;

	write data;
}%%

pair_t *querystring_parse(str_t *buf) {
	// XXX: the input buffer will be mangled during parsing
	//      because of the php_url_decode stage
	int cs;
    unsigned char *p, *pe, *ts, *eof;
    pair_t *output = NULL;

    str_t key;
    str_t val;

    p = buf->ptr;
    eof = pe = buf->ptr + buf->len;
    ts = p;

    %% write init;
    %% write exec;

    return output;
}

#include "querystring.h"
#include "url.h"

%%{
	machine parse_querystring;
	alphtype unsigned char;
	action mark { ts = p; }

	# Implements a parser for query strings as defined in:
	# https://en.wikipedia.org/wiki/Query_string

	key = [^=]+
		>{ key.ptr = p; }
		%{ key.len = p - key.ptr; }
		;

	value = [^&]+
		>{ val.ptr = p; }
		%{ val.len = p - val.ptr; }
		;

	pair = (key "=" value)

		   >{ str_init(&key);
		   	  str_init(&val); }
		   %{
		   		if( str_len(&key) && str_len(&val) ) {
		   			key.len = php_url_decode((char*)key.ptr, key.len);
		      		val.len = php_url_decode((char*)val.ptr, val.len);
		      		output = strpair_add(output, &key, &val);
		   		}
		   	}
		   	;

	main := pair ( "&" pair )* ;

	write data;
}%%

pair_t *querystring_parse(str_t *buf) {
	// XXX: the input buffer will be mangled during parsing
	//      because of the php_url_decode stage
	int cs;
    unsigned char *p, *pe, *ts, *eof;
    pair_t *output = NULL;

    str_t key;
    str_t val;

    p = buf->ptr;
    eof = pe = buf->ptr + buf->len;
    ts = p;

    %% write init;
    %% write exec;

    return output;
}


more tests

more tests

#include "parser.h"
#include <stdio.h>
#include <assert.h>
#include <string.h>

#ifndef TRUE
# define TRUE 1
# define FALSE 0
#endif
#ifndef MIN
# define MIN(a,b) (a < b ? a : b)
#endif
#define REMAINING (pe - p)
#define CURRENT (parser->current_request)
#define CONTENT_LENGTH (parser->current_request->content_length)

#define eip_empty(parser) (parser->eip_stack[0] == NULL)

static void eip_push
  ( ebb_parser *parser
  , ebb_element *element
  )
{
  int i = 0;
  /* NO BOUNDS CHECKING - LIVING ON THE EDGE! */
  for(i = 0; parser->eip_stack[i] != NULL; i++) {;}
  //printf("push! (stack size before: %d)\n", i);
  parser->eip_stack[i] = element;
}

static ebb_element* eip_pop
  ( ebb_parser *parser
  )
{
  int i;
  ebb_element *top = NULL;
  assert( ! eip_empty(parser) );
  /* NO BOUNDS CHECKING - LIVING ON THE EDGE! */
  for(i = 0; parser->eip_stack[i] != NULL; i++) {;}
  //printf("pop! (stack size before: %d)\n", i);
  top = parser->eip_stack[i-1];
  parser->eip_stack[i-1] = NULL;
  return top;
}

%%{
  machine ebb_parser;

  action mark {
    //printf("mark!\n");
    eip = parser->new_element();
    eip->base = p;
    eip_push(parser, eip);
  }

  action mmark {
    //printf("mmark!\n");
    eip = parser->new_element();
    eip->base = p;
    eip_push(parser, eip);
  }

  action write_field {
    //printf("write_field!\n");
    assert(parser->header_field_element == NULL);
    eip = eip_pop(parser);
    last = ebb_element_last(eip);
    last->len = p - last->base;
    parser->header_field_element = eip;
    assert(eip_empty(parser) && "eip_stack must be empty after header field");
  }

  action write_value {
    //printf("write_value!\n");
    assert(parser->header_field_element != NULL);

    eip = eip_pop(parser);
    last = ebb_element_last(eip);
    last->len = p - eip->base;

    if(parser->http_field)
      parser->http_field( parser->data
                        , parser->header_field_element
                        , eip
                        );
    if(parser->header_field_element->free)
      parser->header_field_element->free(parser->header_field_element);
    if(eip->free)
      eip->free(eip);
    eip = parser->header_field_element = NULL;
  }

  action request_uri {
    //printf("request uri\n");
    eip = eip_pop(parser);
    last = ebb_element_last(eip);
    last->len = p - eip->base;
    if(parser->request_uri)
      parser->request_uri(parser->data, eip);
    if(eip->free)
      eip->free(eip);
  }

  action fragment {
    //printf("fragment\n");
    eip = eip_pop(parser);
    last = ebb_element_last(eip);
    last->len = p - eip->base;
    if(parser->fragment)
      parser->fragment(parser->data, eip);
    if(eip->free)
      eip->free(eip);
  }

  action query_string {
    //printf("query  string\n");
    eip = eip_pop(parser);
    last = ebb_element_last(eip);
    last->len = p - eip->base;
    if(parser->query_string)
      parser->query_string(parser->data, eip);
    if(eip->free)
      eip->free(eip);
  }

  action request_path {
    //printf("request path\n");
    eip = eip_pop(parser);
    last = ebb_element_last(eip);
    last->len = p - eip->base;
    if(parser->request_path)
      parser->request_path(parser->data, eip);
    if(eip->free)
      eip->free(eip);
  }

  action content_length {
    printf("content_length!\n");
    CURRENT->content_length *= 10;
    CURRENT->content_length += *p - '0';
  }

  action use_identity_encoding {
    printf("use identity encoding\n");
    CURRENT->transfer_encoding = EBB_IDENTITY;
  }

  action use_chunked_encoding {
    printf("use chunked encoding\n");
    CURRENT->transfer_encoding = EBB_CHUNKED;
  }

  action trailer {
    printf("trailer\n");
    /* not implemenetd yet. (do requests even have trailing headers?) */
  }

  action request_method {
    //printf("request method\n");
    eip = eip_pop(parser);
    eip->len = p - eip->base;
    if(parser->request_method)
      parser->request_method(parser->data, eip);
    if(eip->free)
      eip->free(eip);
  }

  action version_major {
    CURRENT->version_major *= 10;
    CURRENT->version_major += *p - '0';
  }

  action version_minor {
    CURRENT->version_minor *= 10;
    CURRENT->version_minor += *p - '0';
  }

  action add_to_chunk_size {
    printf("add to chunk size\n");
    parser->chunk_size *= 16;
    /* XXX: this can be optimized slightly */
    if( 'A' <= *p && *p <= 'F')
      parser->chunk_size += *p - 'A' + 10;
    else if( 'a' <= *p && *p <= 'f')
      parser->chunk_size += *p - 'a' + 10;
    else if( '0' <= *p && *p <= '9')
      parser->chunk_size += *p - '0';
    else
      assert(0 && "bad hex char");
  }

  action skip_chunk_data {
    printf("skip chunk data\n");
    printf("chunk_size: %d\n", parser->chunk_size);
    if(parser->chunk_size > REMAINING) {
      parser->eating = TRUE;
      parser->chunk_handler(parser->data, p, REMAINING);
      parser->chunk_size -= REMAINING;
      fhold;
      fbreak;
    } else {
      parser->chunk_handler(parser->data, p, parser->chunk_size);
      p += parser->chunk_size;
      parser->chunk_size = 0;
      parser->eating = FALSE;
      fhold;
      fgoto chunk_end;
    }
  }

  action end_chunked_body {
    printf("end chunked body\n");
    if(parser->request_complete)
      parser->request_complete(parser->data);
    fret; // goto Request;
  }

  action start_req {
    if(parser->first_request) {
      for(request = parser->first_request; request->next; request = request->next) {;}
      request->next = parser->new_request(parser->data);
      request = request->next;
      request->next = NULL;
      CURRENT = request;
    } else {
      parser->first_request = parser->new_request(parser->data);
      CURRENT = parser->first_request;
    }
  }

#
##
###
#### HTTP/1.1 STATE MACHINE
###
##   RequestHeaders and character types are from
#    Zed Shaw's beautiful Mongrel parser.

  CRLF = "\r\n";

# character types
  CTL = (cntrl | 127);
  safe = ("$" | "-" | "_" | ".");
  extra = ("!" | "*" | "'" | "(" | ")" | ",");
  reserved = (";" | "/" | "?" | ":" | "@" | "&" | "=" | "+");
  unsafe = (CTL | " " | "\"" | "#" | "%" | "<" | ">");
  national = any -- (alpha | digit | reserved | extra | safe | unsafe);
  unreserved = (alpha | digit | safe | extra | national);
  escape = ("%" xdigit xdigit);
  uchar = (unreserved | escape);
  pchar = (uchar | ":" | "@" | "&" | "=" | "+");
  tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t");

# elements
  token = (ascii -- (CTL | tspecials));
#  qdtext = token -- "\"";
#  quoted_pair = "\" ascii;
#  quoted_string = "\"" (qdtext | quoted_pair )* "\"";

#  headers
  scheme = ( alpha | digit | "+" | "-" | "." )* ;
  absolute_uri = (scheme ":" (uchar | reserved )*);
  path = ( pchar+ ( "/" pchar* )* ) ;
  query = ( uchar | reserved )* >mark %query_string ;
  param = ( pchar | "/" )* ;
  params = ( param ( ";" param )* ) ;
  rel_path = ( path? (";" params)? ) ("?" query)?;
  absolute_path = ( "/"+ rel_path ) >mmark %request_path;
  Request_URI = ( "*" | absolute_uri | absolute_path ) >mark %request_uri;
  Fragment = ( uchar | reserved )* >mark %fragment;
  Method = ( upper | digit | safe ){1,20} >mark %request_method;
  http_number = (digit+ $version_major "." digit+ $version_minor);
  HTTP_Version = ( "HTTP/" http_number );

  field_name = ( token -- ":" )+ %write_field;
  field_value = ((any - " ") any*)? >mark %write_value;

  head_sep = ":" " "**;
  message_header = field_name head_sep field_value :> CRLF;

  cl = "Content-Length"i %write_field  head_sep
       digit+ >mark $content_length %write_value;

  te = "Transfer-Encoding"i %write_field %use_chunked_encoding head_sep
       "identity"i >mark %use_identity_encoding %write_value;

  t =  "Trailer"i %write_field head_sep
        field_value %trailer;

  rest = (field_name head_sep field_value);

  header  = cl     @(headers,4)
          | te     @(headers,4)
          | t      @(headers,4)
          | rest   @(headers,1)
          ;

  Request_Line = ( Method " " Request_URI ("#" Fragment)? " " HTTP_Version CRLF ) ;
  RequestHeader = Request_Line (header >mark :> CRLF)* :> CRLF;

# chunked message
  trailing_headers = message_header*;
  #chunk_ext_val   = token | quoted_string;
  chunk_ext_val = token*;
  chunk_ext_name = token*;
  chunk_extension = ( ";" " "* chunk_ext_name ("=" chunk_ext_val)? )*;
  last_chunk = "0"+ chunk_extension CRLF;
  chunk_size = (xdigit* [1-9a-fA-F] xdigit*) $add_to_chunk_size;
  chunk_end  = CRLF;
  chunk_body = any >skip_chunk_data;
  chunk_begin = chunk_size chunk_extension CRLF;
  chunk = chunk_begin chunk_body chunk_end;
  ChunkedBody := chunk* last_chunk trailing_headers CRLF @end_chunked_body;

  Request = RequestHeader @{
    if(CURRENT->transfer_encoding == EBB_CHUNKED) {
      printf("\nchunked!\n\n");
      fcall ChunkedBody;
    } else {
      /*
       * EAT BODY
       * this is very ugly. sorry.
       *
       */
      if( CURRENT->content_length == 0) {

        if( parser->request_complete )
          parser->request_complete(parser->data);

      } else if( CURRENT->content_length < REMAINING ) {
        /*
         *
         * FINISH EATING THE BODY. there is still more
         * on the buffer - so we just let it continue
         * parsing after we're done
         *
         */
        p += 1;
        if( parser->chunk_handler )
          parser->chunk_handler(parser->data, p, CURRENT->content_length);

        p += CURRENT->content_length;
        CURRENT->body_read = CURRENT->content_length;

        assert(0 <= REMAINING);

        if( parser->request_complete )
          parser->request_complete(parser->data);

        fhold;

      } else {
        /*
         * The body is larger than the buffer
         * EAT REST OF BUFFER
         * there is still more to read though. this will
         * be handled on the next invokion of ebb_parser_execute
         * right before we enter the state machine.
         *
         */
        p += 1;
        size_t eat = REMAINING;

        if( parser->chunk_handler )
          parser->chunk_handler(parser->data, p, eat);

        p += eat;
        CURRENT->body_read += eat;

        assert(CURRENT->body_read < CURRENT->content_length);
        assert(REMAINING == 0);

        fhold; fbreak;
      }
    }
  };

# sequence of requests (for keep-alive)
  main := (Request >start_req)+;
}%%

%% write data;

#define COPYSTACK(dest, src)  for(i = 0; i < PARSER_STACK_SIZE; i++) { dest[i] = src[i]; }

void ebb_parser_init
  ( ebb_parser *parser
  )
{
  int i;

  int cs = 0;
  int top = 0;
  int stack[PARSER_STACK_SIZE];
  %% write init;
  parser->cs = cs;
  parser->top = top;
  COPYSTACK(parser->stack, stack);

  parser->chunk_size = 0;
  parser->eating = 0;

  parser->eip_stack[0] = NULL;
  parser->current_request = NULL;
  parser->first_request = NULL;
  parser->header_field_element = NULL;

  parser->nread = 0;

  parser->new_element = NULL;
  parser->new_request = NULL;
  parser->request_complete = NULL;
  parser->chunk_handler = NULL;
  parser->http_field = NULL;
  parser->request_method = NULL;
  parser->request_uri = NULL;
  parser->fragment = NULL;
  parser->request_path = NULL;
  parser->query_string = NULL;
}

/** exec **/
size_t ebb_parser_execute
  ( ebb_parser *parser
  , const char *buffer
  , size_t len
  )
{
  ebb_element *eip, *last;
  ebb_request *request;
  const char *p, *pe;
  int i, cs = parser->cs;

  int top = parser->top;
  int stack[PARSER_STACK_SIZE];
  COPYSTACK(stack, parser->stack);

  assert(parser->new_element && "undefined callback");
  assert(parser->new_request && "undefined callback");

  p = buffer;
  pe = buffer+len;

  if(0 < parser->chunk_size && parser->eating) {
    /*
     *
     * eat chunked body
     *
     */
    printf("eat chunk body (before parse)\n");
    size_t eat = MIN(len, parser->chunk_size);
    if(eat == parser->chunk_size) {
      parser->eating = FALSE;
    }
    parser->chunk_handler(parser->data, p, eat);
    p += eat;
    parser->chunk_size -= eat;
    //printf("eat: %d\n", eat);
  } else if( parser->current_request &&
             CURRENT->content_length > 0 &&
             CURRENT->body_read > 0) {
    /*
     *
     * eat normal body
     *
     */
    printf("eat normal body (before parse)\n");
    size_t eat = MIN(len, CURRENT->content_length - CURRENT->body_read);

    parser->chunk_handler(parser->data, p, eat);
    p += eat;
    CURRENT->body_read += eat;

    if(CURRENT->body_read == CURRENT->content_length)
      if(parser->request_complete)
        parser->request_complete(parser->data);

  }

  /* each on the eip stack gets expanded */
  for(i = 0; parser->eip_stack[i] != NULL; i++) {
    last = ebb_element_last(parser->eip_stack[i]);
    last->next = parser->new_element();
    last->next->base = buffer;
  }

  %% write exec;

  parser->cs = cs;
  parser->top = top;
  COPYSTACK(parser->stack, stack);

  parser->nread += p - buffer;

  /* each on the eip stack gets len */
  for(i = 0; parser->eip_stack[i] != NULL; i++) {
    parser->eip_stack[i]->len = pe - parser->eip_stack[i]->base;
    assert( parser->eip_stack[i]->base < pe && "mark is after buffer end");
  }

  assert(p <= pe && "buffer overflow after parsing execute");

  return(p - buffer);
}

int ebb_parser_has_error
  ( ebb_parser *parser
  )
{
  return parser->cs == ebb_parser_error;
}

int ebb_parser_is_finished
  ( ebb_parser *parser
  )
{
  return parser->cs == ebb_parser_first_final;
}

void ebb_request_init
  ( ebb_request *request
  )
{
  request->content_length = 0;
  request->version_major = 0;
  request->version_minor = 0;
  request->transfer_encoding = EBB_IDENTITY;
  request->complete = FALSE;
  request->next = NULL;
  request->free = NULL;
}

int ebb_element_init
  ( ebb_element *element
  )
{
  element->base = NULL;
  element->len = 0;
  element->next = NULL;
  element->free = NULL;
}

ebb_element* ebb_element_last
  ( ebb_element *element
  )
{
  for( ; element->next; element = element->next) {;}
  return element;
}

size_t ebb_element_len
  ( ebb_element *element
  )
{
  size_t len;
  for(len = 0; element; element = element->next)
    len += element->len;
  return len;
}

void ebb_element_strcpy
  ( ebb_element *element
  , char *dest
  )
{
  dest[0] = '\0';
  for( ; element; element = element->next)
    strncat(dest, element->base, element->len);
}

void ebb_element_printf
  ( ebb_element *element
  , const char *format
  )
{
  char str[1000];
  ebb_element_strcpy(element, str);
  printf(format, str);
}

#ifdef UNITTEST
#include <stdlib.h>

static ebb_parser parser;
struct request_data {
  char request_method[500];
  char request_path[500];
  char request_uri[500];
  char fragment[500];
  char query_string[500];
  char body[500];
  ebb_request request;
};
static struct request_data requests[5];
static int num_requests;

ebb_element* new_element ()
{
  ebb_element *el = malloc(sizeof(ebb_element));
  ebb_element_init(el);
  return el;
}

ebb_request* new_request ()
{
  requests[num_requests].body[0] = 0;
  ebb_request *r = &requests[num_requests].request ;
  ebb_request_init(r);
  printf("new request %d\n", num_requests);
  return r;
}

void request_complete()
{
  printf("request complete\n");
  num_requests++;
}

void request_method_cb(void *data, ebb_element *el)
{
  ebb_element_strcpy(el, requests[num_requests].request_method);
}

void request_path_cb(void *data, ebb_element *el)
{
  ebb_element_strcpy(el, requests[num_requests].request_path);
}

void request_uri_cb(void *data, ebb_element *el)
{
  ebb_element_strcpy(el, requests[num_requests].request_uri);
}

void fragment_cb(void *data, ebb_element *el)
{
  ebb_element_strcpy(el, requests[num_requests].fragment);
}

void http_field_cb(void *data, ebb_element *field, ebb_element *value)
{
  ebb_element_printf(field, "field: %s\n");
  ebb_element_printf(value, "value: %s\n");
}

void query_string_cb(void *data, ebb_element *el)
{
  ebb_element_strcpy(el, requests[num_requests].query_string);
}

void chunk_handler(void *data, const char *p, size_t len)
{
  strncat(requests[num_requests].body, p, len);
  printf("chunk_handler: '%s'\n", requests[num_requests].body);
}

int test_error
  ( const char *buf
  )
{
  size_t traversed = 0;
  num_requests = 0;

  ebb_parser_init(&parser);

  parser.new_element = new_element;
  parser.new_request = new_request;
  parser.request_complete = request_complete;
  parser.http_field = http_field_cb;
  parser.request_method = request_method_cb;
  parser.request_path = request_path_cb;
  parser.request_uri = request_uri_cb;
  parser.fragment = fragment_cb;
  parser.query_string = query_string_cb;
  parser.chunk_handler = chunk_handler;

  traversed = ebb_parser_execute(&parser, buf, strlen(buf));

  return ebb_parser_has_error(&parser);
}

int main()
{

  assert(test_error("hello world"));
  assert(test_error("GET / HTP/1.1\r\n\r\n"));

  assert(!test_error("GET /hello/world HTTP/1.1\r\n\r\n"));
  assert(1 == num_requests);
  assert(0 == strcmp(requests[0].body, ""));
  assert(0 == strcmp(requests[0].fragment, ""));
  assert(0 == strcmp(requests[0].query_string, ""));
  assert(0 == strcmp(requests[0].request_method, "GET"));
  assert(0 == strcmp(requests[0].request_path, "/hello/world"));
  assert(1 == requests[0].request.version_major);
  assert(1 == requests[0].request.version_minor);

  assert(!test_error("GET /hello/world HTTP/1.1\r\nAccept: */*\r\n\r\n"));
  assert(1 == num_requests);
  assert(0 == strcmp(requests[0].body, ""));
  assert(0 == strcmp(requests[0].fragment, ""));
  assert(0 == strcmp(requests[0].query_string, ""));
  assert(0 == strcmp(requests[0].request_method, "GET"));
  assert(0 == strcmp(requests[0].request_path, "/hello/world"));
  assert(0 == strcmp(requests[0].request_uri, "/hello/world"));
  assert(1 == requests[0].request.version_major);
  assert(1 == requests[0].request.version_minor);

  // error if there is a body without content length
  assert(test_error("GET /hello/world HTTP/1.1\r\nAccept: */*\r\nHELLO\r\n"));

  // no error if there is a is body with content length
  assert(!test_error("GET / HTTP/1.1\r\nContent-Length: 5\r\n\r\nHELLO"));
  assert(0 == strcmp(requests[0].body, "HELLO"));

  // two requests
  assert(!test_error("GET /hello HTTP/1.1\r\n\r\nGET /world HTTP/1.1\r\n\r\n"));
  assert(2 == num_requests);

  // two requests with bodies
  assert(!test_error("POST /hello HTTP/1.1\r\nContent-Length: 5\r\n\r\nHelloPOST /world HTTP/1.1\r\nContent-Length: 5\r\n\r\nWorld"));
  assert(0 == strcmp(requests[0].body, "Hello"));
  assert(0 == strcmp(requests[1].body, "World"));
  assert(2 == num_requests);

  // three requests with bodies
  assert(!test_error("POST /hello HTTP/1.1\r\nContent-Length: 5\r\n\r\nHelloPOST /world HTTP/1.1\r\nContent-Length: 5\r\n\r\nWorldGET /hello HTTP/1.1\r\nContent-Length: 5\r\n\r\nShoes"));
  assert(0 == strcmp(requests[0].body, "Hello"));
  assert(0 == strcmp(requests[1].body, "World"));
  assert(0 == strcmp(requests[2].body, "Shoes"));
  assert(3 == num_requests);

  // chunked body
  assert(!test_error("POST /hello HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n1e\r\nall your base are belong to us\r\n0\r\n\r\n"));
  assert(0 == strcmp(requests[0].body, "all your base are belong to us"));
  assert(1 == num_requests);

  // chunked body and normal body
  assert(!test_error("POST /hello HTTP/1.1\r\nContent-Length: 5\r\n\r\nHelloPOST /hello HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n1e\r\nall your base are belong to us\r\n0\r\n\r\n"));
  assert(0 == strcmp(requests[0].body, "Hello"));
  assert(0 == strcmp(requests[1].body, "all your base are belong to us"));
  assert(2 == num_requests);

  printf("okay\n");
  return 0;
}

#endif

#include "parser.h"
#include <stdio.h>
#include <assert.h>
#include <string.h>

#ifndef TRUE
# define TRUE 1
# define FALSE 0
#endif
#ifndef MIN
# define MIN(a,b) (a < b ? a : b)
#endif
#define REMAINING (pe - p)
#define CURRENT (parser->current_request)
#define CONTENT_LENGTH (parser->current_request->content_length)

static void eat_body
  ( ebb_parser *parser
  )
{
}

#define eip_empty(parser) (parser->eip_stack[0] == NULL)

static void eip_push
  ( ebb_parser *parser
  , ebb_element *element
  )
{
  int i = 0;
  /* NO BOUNDS CHECKING - LIVING ON THE EDGE! */
  for(i = 0; parser->eip_stack[i] != NULL; i++) {;}
  //printf("push! (stack size before: %d)\n", i);
  parser->eip_stack[i] = element;
}

static ebb_element* eip_pop
  ( ebb_parser *parser
  )
{
  int i;
  ebb_element *top = NULL;
  assert( ! eip_empty(parser) );
  /* NO BOUNDS CHECKING - LIVING ON THE EDGE! */
  for(i = 0; parser->eip_stack[i] != NULL; i++) {;}
  //printf("pop! (stack size before: %d)\n", i);
  top = parser->eip_stack[i-1];
  parser->eip_stack[i-1] = NULL;
  return top;
}

%%{
  machine ebb_parser;

  action mark {
    //printf("mark!\n");
    eip = parser->new_element();
    eip->base = p;
    eip_push(parser, eip);
  }

  action mmark {
    //printf("mmark!\n");
    eip = parser->new_element();
    eip->base = p;
    eip_push(parser, eip);
  }

  action write_field {
    //printf("write_field!\n");
    assert(parser->header_field_element == NULL);
    eip = eip_pop(parser);
    last = ebb_element_last(eip);
    last->len = p - last->base;
    parser->header_field_element = eip;
    assert(eip_empty(parser) && "eip_stack must be empty after header field");
  }

  action write_value {
    //printf("write_value!\n");
    assert(parser->header_field_element != NULL);

    eip = eip_pop(parser);
    last = ebb_element_last(eip);
    last->len = p - eip->base;

    if(parser->http_field)
      parser->http_field( parser->data
                        , parser->header_field_element
                        , eip
                        );
    if(parser->header_field_element->free)
      parser->header_field_element->free(parser->header_field_element);
    if(eip->free)
      eip->free(eip);
    eip = parser->header_field_element = NULL;
  }

  action request_uri {
    //printf("request uri\n");
    eip = eip_pop(parser);
    last = ebb_element_last(eip);
    last->len = p - eip->base;
    if(parser->request_uri)
      parser->request_uri(parser->data, eip);
    if(eip->free)
      eip->free(eip);
  }

  action fragment {
    //printf("fragment\n");
    eip = eip_pop(parser);
    last = ebb_element_last(eip);
    last->len = p - eip->base;
    if(parser->fragment)
      parser->fragment(parser->data, eip);
    if(eip->free)
      eip->free(eip);
  }

  action query_string {
    //printf("query  string\n");
    eip = eip_pop(parser);
    last = ebb_element_last(eip);
    last->len = p - eip->base;
    if(parser->query_string)
      parser->query_string(parser->data, eip);
    if(eip->free)
      eip->free(eip);
  }

  action request_path {
    //printf("request path\n");
    eip = eip_pop(parser);
    last = ebb_element_last(eip);
    last->len = p - eip->base;
    if(parser->request_path)
      parser->request_path(parser->data, eip);
    if(eip->free)
      eip->free(eip);
  }

  action content_length {
    printf("content_length!\n");
    CURRENT->content_length *= 10;
    CURRENT->content_length += *p - '0';
  }

  action use_identity_encoding {
    printf("use identity encoding\n");
    CURRENT->transfer_encoding = EBB_IDENTITY;
  }

  action use_chunked_encoding {
    printf("use chunked encoding\n");
    CURRENT->transfer_encoding = EBB_CHUNKED;
  }

  action trailer {
    printf("trailer\n");
    /* not implemenetd yet. (do requests even have trailing headers?) */
  }

  action request_method {
    //printf("request method\n");
    eip = eip_pop(parser);
    eip->len = p - eip->base;
    if(parser->request_method)
      parser->request_method(parser->data, eip);
    if(eip->free)
      eip->free(eip);
  }

  action version_major {
    CURRENT->version_major *= 10;
    CURRENT->version_major += *p - '0';
  }

  action version_minor {
    CURRENT->version_minor *= 10;
    CURRENT->version_minor += *p - '0';
  }

  action add_to_chunk_size {
    printf("add to chunk size\n");
    parser->chunk_size *= 16;
    /* XXX: this can be optimized slightly */
    if( 'A' <= *p && *p <= 'F')
      parser->chunk_size += *p - 'A' + 10;
    else if( 'a' <= *p && *p <= 'f')
      parser->chunk_size += *p - 'a' + 10;
    else if( '0' <= *p && *p <= '9')
      parser->chunk_size += *p - '0';
    else
      assert(0 && "bad hex char");
  }

  action skip_chunk_data {
    printf("skip chunk data\n");
    printf("chunk_size: %d\n", parser->chunk_size);
    if(parser->chunk_size > REMAINING) {
      parser->eating = TRUE;
      parser->chunk_handler(parser->data, p, REMAINING);
      parser->chunk_size -= REMAINING;
      fhold;
      fbreak;
    } else {
      parser->chunk_handler(parser->data, p, parser->chunk_size);
      p += parser->chunk_size;
      parser->chunk_size = 0;
      parser->eating = FALSE;
      fhold;
      fgoto chunk_end;
    }
  }

  action end_chunked_body {
    printf("end chunked body\n");
    fret; // goto Request;
  }

  action start_req {
    if(parser->first_request) {
      for(request = parser->first_request; request->next; request = request->next) {;}
      request->next = parser->new_request(parser->data);
      request = request->next;
      request->next = NULL;
      CURRENT = request;
    } else {
      parser->first_request = parser->new_request(parser->data);
      CURRENT = parser->first_request;
    }
  }

#
##
###
#### HTTP/1.1 STATE MACHINE
###
##   RequestHeaders and character types are from
#    Zed Shaw's beautiful Mongrel parser.

  CRLF = "\r\n";

# character types
  CTL = (cntrl | 127);
  safe = ("$" | "-" | "_" | ".");
  extra = ("!" | "*" | "'" | "(" | ")" | ",");
  reserved = (";" | "/" | "?" | ":" | "@" | "&" | "=" | "+");
  unsafe = (CTL | " " | "\"" | "#" | "%" | "<" | ">");
  national = any -- (alpha | digit | reserved | extra | safe | unsafe);
  unreserved = (alpha | digit | safe | extra | national);
  escape = ("%" xdigit xdigit);
  uchar = (unreserved | escape);
  pchar = (uchar | ":" | "@" | "&" | "=" | "+");
  tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t");

# elements
  token = (ascii -- (CTL | tspecials));
#  qdtext = token -- "\"";
#  quoted_pair = "\" ascii;
#  quoted_string = "\"" (qdtext | quoted_pair )* "\"";

#  headers
  scheme = ( alpha | digit | "+" | "-" | "." )* ;
  absolute_uri = (scheme ":" (uchar | reserved )*);
  path = ( pchar+ ( "/" pchar* )* ) ;
  query = ( uchar | reserved )* >mark %query_string ;
  param = ( pchar | "/" )* ;
  params = ( param ( ";" param )* ) ;
  rel_path = ( path? (";" params)? ) ("?" query)?;
  absolute_path = ( "/"+ rel_path ) >mmark %request_path;
  Request_URI = ( "*" | absolute_uri | absolute_path ) >mark %request_uri;
  Fragment = ( uchar | reserved )* >mark %fragment;
  Method = ( upper | digit | safe ){1,20} >mark %request_method;
  http_number = (digit+ $version_major "." digit+ $version_minor);
  HTTP_Version = ( "HTTP/" http_number );

  field_name = ( token -- ":" )+ %write_field;
  field_value = ((any - " ") any*)? >mark %write_value;

  head_sep = ":" " "**;
  message_header = field_name head_sep field_value :> CRLF;

  cl = "Content-Length"i %write_field  head_sep
       digit+ >mark $content_length %write_value;

  te = "Transfer-Encoding"i %write_field head_sep %use_chunked_encoding
       "identity"i >mark %use_identity_encoding %write_value;

  t =  "Trailer"i %write_field head_sep
        field_value %trailer;

  rest = (field_name head_sep field_value);

  header  = cl     @(headers,4)
          | te     @(headers,4)
          | t      @(headers,4)
          | rest   @(headers,1)
          ;

  Request_Line = ( Method " " Request_URI ("#" Fragment)? " " HTTP_Version CRLF ) ;
  RequestHeader = Request_Line (header >mark :> CRLF)* :> CRLF;

# chunked message
  trailing_headers = message_header*;
  #chunk_ext_val   = token | quoted_string;
  chunk_ext_val = token*;
  chunk_ext_name = token*;
  chunk_extension = ( ";" " "* chunk_ext_name ("=" chunk_ext_val)? )*;
  last_chunk = "0"+ chunk_extension CRLF;
  chunk_size = (xdigit* [1-9a-fA-F] xdigit*) $add_to_chunk_size;
  chunk_end  = CRLF;
  chunk_body = any >skip_chunk_data;
  chunk_begin = chunk_size chunk_extension CRLF;
  chunk = chunk_begin chunk_body chunk_end;
  ChunkedBody := chunk* last_chunk trailing_headers CRLF @end_chunked_body;

  Request = RequestHeader @{
    if(CURRENT->transfer_encoding == EBB_CHUNKED) {
      fcall ChunkedBody;
    } else {
      /*
       * EAT BODY
       * this is very ugly. sorry.
       *
       */
      if( CURRENT->content_length == 0) {

        if( parser->request_complete )
          parser->request_complete(parser->data);

      } else if( CURRENT->content_length < REMAINING ) {
        /*
         *
         * FINISH EATING THE BODY. there is still more
         * on the buffer - so we just let it continue
         * parsing after we're done
         *
         */
        p += 1;
        if( parser->chunk_handler )
          parser->chunk_handler(parser->data, p, CURRENT->content_length);

        p += CURRENT->content_length;
        CURRENT->body_read = CURRENT->content_length;

        assert(0 <= REMAINING);

        if( parser->request_complete )
          parser->request_complete(parser->data);

        fhold;

      } else {
        /*
         * The body is larger than the buffer
         * EAT REST OF BUFFER
         * there is still more to read though. this will
         * be handled on the next invokion of ebb_parser_execute
         * right before we enter the state machine.
         *
         */
        p += 1;
        size_t eat = REMAINING;

        if( parser->chunk_handler )
          parser->chunk_handler(parser->data, p, eat);

        p += eat;
        CURRENT->body_read += eat;

        assert(CURRENT->body_read < CURRENT->content_length);
        assert(REMAINING == 0);

        fhold; fbreak;
      }
    }
  };

# sequence of requests (for keep-alive)
  main := (Request >start_req)+;
}%%

%% write data;

#define COPYSTACK(dest, src)  for(i = 0; i < PARSER_STACK_SIZE; i++) { dest[i] = src[i]; }

void ebb_parser_init
  ( ebb_parser *parser
  )
{
  int i;

  int cs = 0;
  int top = 0;
  int stack[PARSER_STACK_SIZE];
  %% write init;
  parser->cs = cs;
  parser->top = top;
  COPYSTACK(parser->stack, stack);

  parser->chunk_size = 0;
  parser->eating = 0;

  parser->eip_stack[0] = NULL;
  parser->current_request = NULL;
  parser->first_request = NULL;
  parser->header_field_element = NULL;

  parser->nread = 0;

  parser->new_element = NULL;
  parser->new_request = NULL;
  parser->request_complete = NULL;
  parser->chunk_handler = NULL;
  parser->http_field = NULL;
  parser->request_method = NULL;
  parser->request_uri = NULL;
  parser->fragment = NULL;
  parser->request_path = NULL;
  parser->query_string = NULL;
}

/** exec **/
size_t ebb_parser_execute
  ( ebb_parser *parser
  , const char *buffer
  , size_t len
  )
{
  ebb_element *eip, *last;
  ebb_request *request;
  const char *p, *pe;
  int i, cs = parser->cs;

  int top = parser->top;
  int stack[PARSER_STACK_SIZE];
  COPYSTACK(stack, parser->stack);

  assert(parser->new_element && "undefined callback");
  assert(parser->new_request && "undefined callback");

  p = buffer;
  pe = buffer+len;

  if(0 < parser->chunk_size && parser->eating) {
    /*
     *
     * eat chunked body
     *
     */
    printf("eat chunk body (before parse)\n");
    size_t eat = MIN(len, parser->chunk_size);
    if(eat == parser->chunk_size) {
      parser->eating = FALSE;
    }
    parser->chunk_handler(parser->data, p, eat);
    p += eat;
    parser->chunk_size -= eat;
    //printf("eat: %d\n", eat);
  } else if( parser->current_request &&
             CURRENT->content_length > 0 &&
             CURRENT->body_read > 0) {
    /*
     *
     * eat normal body
     *
     */
    printf("eat normal body (before parse)\n");
    size_t eat = MIN(len, CURRENT->content_length - CURRENT->body_read);

    parser->chunk_handler(parser->data, p, eat);
    p += eat;
    CURRENT->body_read += eat;

    if(CURRENT->body_read == CURRENT->content_length)
      if(parser->request_complete)
        parser->request_complete(parser->data);

  }

  /* each on the eip stack gets expanded */
  for(i = 0; parser->eip_stack[i] != NULL; i++) {
    last = ebb_element_last(parser->eip_stack[i]);
    last->next = parser->new_element();
    last->next->base = buffer;
  }

  %% write exec;

  parser->cs = cs;
  parser->top = top;
  COPYSTACK(parser->stack, stack);

  parser->nread += p - buffer;

  /* each on the eip stack gets len */
  for(i = 0; parser->eip_stack[i] != NULL; i++) {
    parser->eip_stack[i]->len = pe - parser->eip_stack[i]->base;
    assert( parser->eip_stack[i]->base < pe && "mark is after buffer end");
  }

  assert(p <= pe && "buffer overflow after parsing execute");

  return(p - buffer);
}

int ebb_parser_has_error
  ( ebb_parser *parser
  )
{
  return parser->cs == ebb_parser_error;
}

int ebb_parser_is_finished
  ( ebb_parser *parser
  )
{
  return parser->cs == ebb_parser_first_final;
}

void ebb_request_init
  ( ebb_request *request
  )
{
  request->content_length = 0;
  request->version_major = 0;
  request->version_minor = 0;
  request->transfer_encoding = EBB_IDENTITY;
  request->complete = FALSE;
  request->next = NULL;
  request->free = NULL;
}

int ebb_element_init
  ( ebb_element *element
  )
{
  element->base = NULL;
  element->len = 0;
  element->next = NULL;
  element->free = NULL;
}

ebb_element* ebb_element_last
  ( ebb_element *element
  )
{
  for( ; element->next; element = element->next) {;}
  return element;
}

size_t ebb_element_len
  ( ebb_element *element
  )
{
  size_t len;
  for(len = 0; element; element = element->next)
    len += element->len;
  return len;
}

void ebb_element_strcpy
  ( ebb_element *element
  , char *dest
  )
{
  dest[0] = '\0';
  for( ; element; element = element->next)
    strncat(dest, element->base, element->len);
}

void ebb_element_printf
  ( ebb_element *element
  , const char *format
  )
{
  char str[1000];
  ebb_element_strcpy(element, str);
  printf(format, str);
}

#ifdef UNITTEST
#include <stdlib.h>

static ebb_parser parser;
struct request_data {
  char request_method[500];
  char request_path[500];
  char request_uri[500];
  char fragment[500];
  char query_string[500];
  char body[500];
  ebb_request request;
};
static struct request_data requests[5];
static int num_requests;

ebb_element* new_element ()
{
  ebb_element *el = malloc(sizeof(ebb_element));
  ebb_element_init(el);
  return el;
}

ebb_request* new_request ()
{
  requests[num_requests].body[0] = 0;
  ebb_request *r = &requests[num_requests].request ;
  ebb_request_init(r);
  printf("new request %d\n", num_requests);
  return r;
}

void request_complete()
{
  printf("request complete\n");
  num_requests++;
}

void request_method_cb(void *data, ebb_element *el)
{
  ebb_element_strcpy(el, requests[num_requests].request_method);
}

void request_path_cb(void *data, ebb_element *el)
{
  ebb_element_strcpy(el, requests[num_requests].request_path);
}

void request_uri_cb(void *data, ebb_element *el)
{
  ebb_element_strcpy(el, requests[num_requests].request_uri);
}

void fragment_cb(void *data, ebb_element *el)
{
  ebb_element_strcpy(el, requests[num_requests].fragment);
}

void http_field_cb(void *data, ebb_element *field, ebb_element *value)
{
  ebb_element_printf(field, "field: %s\n");
  ebb_element_printf(value, "value: %s\n");
}

void query_string_cb(void *data, ebb_element *el)
{
  ebb_element_strcpy(el, requests[num_requests].query_string);
}

void chunk_handler(void *data, const char *p, size_t len)
{
  strncat(requests[num_requests].body, p, len);
  printf("chunk_handler: '%s'\n", requests[num_requests].body);
}

int test_error
  ( const char *buf
  )
{
  size_t traversed = 0;
  num_requests = 0;

  ebb_parser_init(&parser);

  parser.new_element = new_element;
  parser.new_request = new_request;
  parser.request_complete = request_complete;
  parser.http_field = http_field_cb;
  parser.request_method = request_method_cb;
  parser.request_path = request_path_cb;
  parser.request_uri = request_uri_cb;
  parser.fragment = fragment_cb;
  parser.query_string = query_string_cb;
  parser.chunk_handler = chunk_handler;

  traversed = ebb_parser_execute(&parser, buf, strlen(buf));

  return ebb_parser_has_error(&parser);
}

int main()
{

  assert(test_error("hello world"));
  assert(test_error("GET / HTP/1.1\r\n\r\n"));

  assert(!test_error("GET /hello/world HTTP/1.1\r\n\r\n"));
  assert(1 == num_requests);
  assert(0 == strcmp(requests[0].body, ""));
  assert(0 == strcmp(requests[0].fragment, ""));
  assert(0 == strcmp(requests[0].query_string, ""));
  assert(0 == strcmp(requests[0].request_method, "GET"));
  assert(0 == strcmp(requests[0].request_path, "/hello/world"));
  assert(1 == requests[0].request.version_major);
  assert(1 == requests[0].request.version_minor);

  assert(!test_error("GET /hello/world HTTP/1.1\r\nAccept: */*\r\n\r\n"));
  assert(1 == num_requests);
  assert(0 == strcmp(requests[0].body, ""));
  assert(0 == strcmp(requests[0].fragment, ""));
  assert(0 == strcmp(requests[0].query_string, ""));
  assert(0 == strcmp(requests[0].request_method, "GET"));
  assert(0 == strcmp(requests[0].request_path, "/hello/world"));
  assert(0 == strcmp(requests[0].request_uri, "/hello/world"));
  assert(1 == requests[0].request.version_major);
  assert(1 == requests[0].request.version_minor);

  // error if there is a body without content length
  assert(test_error("GET /hello/world HTTP/1.1\r\nAccept: */*\r\nHELLO\r\n"));

  // no error if there is a is body with content length
  assert(!test_error("GET / HTTP/1.1\r\nContent-Length: 5\r\n\r\nHELLO"));
  assert(0 == strcmp(requests[0].body, "HELLO"));

  // two requests
  assert(!test_error("GET /hello HTTP/1.1\r\n\r\nGET /world HTTP/1.1\r\n\r\n"));
  assert(2 == num_requests);

  // two requests with bodies
  assert(!test_error("POST /hello HTTP/1.1\r\nContent-Length: 5\r\n\r\nHelloPOST /world HTTP/1.1\r\nContent-Length: 5\r\n\r\nWorld"));
  assert(2 == num_requests);

  printf("okay\n");
  return 0;
}

#endif