trekel / src / phplemon / Trekel / Parser.y

//<?php
%name Trekel_Parser

%include {
/**
 * Copyright 2011 Joey Mazzarelli. All rights reserved.
 *
 * Redistribution and use in source, with or without modification, is
 * permitted provided that the following condition is met:
 *
 *    1. Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *
 * THIS SOFTWARE IS PROVIDED BY JOEY MAZZARELLI ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL JOEY MAZZARELLI OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation
 * are those of the authors and should not be interpreted as representing
 * official policies, either expressed or implied, of Joey Mazzarelli.
 */

  require_once 'Trekel.php';

}

%declare_class {class Trekel_Parser}
%include_class {

  private $selector;
  private $tree = null;

  public function setSelector ($selector) {
    $this->selector = trim($selector);
  }

  public function parse () {
    if ($this->selector) {
      $lexer = new Trekel_Lexer($this->selector);
      while ($lexer->yylex()) {
        $this->doParse($lexer->token, $lexer->value);
      }
      $this->doParse(0, 0);
    }
    return $this->tree;
  } // end parse

}

%syntax_error {
  $expect = array();
  foreach ($this->yy_get_expected_tokens($yymajor) as $token) {
    $expect[] = self::$yyTokenName[$token];
  }
  $found = $this->tokenName($yymajor);
  $message = "Unexpected {$this->tokenName($yymajor)}({$TOKEN}), ";
  if (count($expect) == 1) {
    $message .= "expected: {$expect[0]}\n";
  } else {
    $message .= "expected one of: " . implode(',', $expect) . "\n";
  }
  throw new Trekel_ParseException($message);
}

%token_prefix  T_
%left ADJACENT CHILD SIBLING.

start ::= starting_selector(A) selectors(B). {
  if (B) { A->pushChild(B); }
  $this->tree = (A);
}

selectors(OUT) ::= selectors(A) selector(B). {
  if (A) { A->pushChild(B); OUT = A; }
  else   { OUT = B; }
}
selectors(OUT) ::= . { OUT = null; }
selector(OUT) ::= finder(A) filters(B). {
  if (B) { A->pushChild(B); }
  OUT = A;
}

starting_selector(OUT) ::= starting_finder(A) filters(B). {
  if (B) { A->pushChild(B); }
  OUT = A;
}

finder(OUT) ::= starting_finder(IN). { OUT = IN; }
finder(OUT) ::= SIBLING IDENTIFIER(A). {
  $desc = new Trekel_Node_Selector_Sibling();
  $desc->setIdentifier(A);
  OUT = $desc;
}
finder(OUT) ::= ADJACENT IDENTIFIER(A). {
  $desc = new Trekel_Node_Selector_Adjacent();
  $desc->setIdentifier(A);
  OUT = $desc;
}
starting_finder(OUT) ::= CHILD IDENTIFIER(A) . {
  $desc = new Trekel_Node_Selector_Child();
  $desc->setIdentifier(A);
  OUT = $desc;
}
starting_finder(OUT) ::= IDENTIFIER(A) . {
  $desc = new Trekel_Node_Selector_Descendant();
  $desc->setIdentifier(A);
  OUT = $desc;
}

filters(OUT) ::= filters(A) filter(B). {
  if (A) { A->pushChild(B); OUT = A; }
  else   { OUT = B; }
}
filters(OUT) ::= . { OUT = null; }
filter(OUT) ::= attribute_filter(IN). { OUT = IN; }
filter(OUT) ::= pseudo_filter(IN). { OUT = IN; }

// [<id>]
attribute_filter(OUT) ::= LBRACKET property(ID) RBRACKET. {
  $attr = new Trekel_Node_Attribute_Exists();
  $attr->setIdentifier(ID);
  OUT = $attr;
}

// [<id>="value"]
attribute_filter(OUT) ::= LBRACKET property(ID) EQ string(VAL) RBRACKET. {
  $attr = new Trekel_Node_Attribute_Equals();
  $attr->setIdentifier(ID);
  $attr->setValue(VAL);
  OUT = $attr;
}

// [<id>!="value"]
attribute_filter(OUT) ::= LBRACKET property(ID) NEQ string(VAL) RBRACKET. {
  $attr = new Trekel_Node_Attribute_NotEquals();
  $attr->setIdentifier(ID);
  $attr->setValue(VAL);
  OUT = $attr;
}

// [<id>^="value"]
attribute_filter(OUT) ::= LBRACKET property(ID) STARTSWITH string(VAL) RBRACKET. {
  $attr = new Trekel_Node_Attribute_StartsWith();
  $attr->setIdentifier(ID);
  $attr->setValue(VAL);
  OUT = $attr;
}

// [<id>$="value"]
attribute_filter(OUT) ::= LBRACKET property(ID) ENDSWITH string(VAL) RBRACKET. {
  $attr = new Trekel_Node_Attribute_EndsWith();
  $attr->setIdentifier(ID);
  $attr->setValue(VAL);
  OUT = $attr;
}

// [<id>*="value"]
attribute_filter(OUT) ::= LBRACKET property(ID) CONTAINS string(VAL) RBRACKET. {
  $attr = new Trekel_Node_Attribute_Contains();
  $attr->setIdentifier(ID);
  $attr->setValue(VAL);
  OUT = $attr;
}

// [<id>@="value"]
attribute_filter(OUT) ::= LBRACKET property(ID) REGEX string(VAL) RBRACKET. {
  $attr = new Trekel_Node_Attribute_Regex();
  $attr->setIdentifier(ID);
  $attr->setValue(VAL);
  OUT = $attr;
}

// :something
pseudo_filter(OUT) ::= PSEUDO(IN). {
  $type = substr(IN, 1);
  $node = Trekel_Node_Pseudo::create($type);
  if (!$node) {
    throw new Trekel_ParseException("Unrecognized pseudo selector: {$type}");
  }
  OUT = $node;
}

// When the attribute is an identifier, use as an instance variable
property(OUT) ::= IDENTIFIER(ID). { OUT = ID; }

// When the attribute is a method, use as a getter
property(OUT) ::= GETTER(ID). { OUT = ID; }

// Strip of the quotes and just use the value
string(OUT) ::= STRING(VAL). { OUT = substr(VAL, 1, -1); }

// This seems to be the most useful filetype for highlighting and indenting
// vim: set ft=php et ts=2 sw=2:
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.