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 {

  public $column = 0;
  private $selector;
  private $tree = null;

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

  public function parse () {
    $lexer = new Trekel_Lexer($this->selector);
    while ($lexer->yylex()) {
      $this->column = $lexer->column;
      $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);

  // lines are 0-indexed
  $column = $this->column;

  $message = "Could not compile query: error at character {$column}\n";
  $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.

// Just a selector statement
start ::= selectors(IN). {
  if (!IN) {
    $this->tree = null;
  } else {
    $this->tree = IN->coerce('Trekel_Node_Selector_Descendant');
  }
}

// selector statement, but starting with the '>'.
// This allows you to match the root node
start ::= CHILD selectors(IN). {
  if (!IN) {
    $this->tree = null;
  } else {
    $this->tree = IN->coerce('Trekel_Node_Selector_Child');
  }
}

// Build the recursion
selectors(OUT) ::= selectors(A) selector(B). {
  if (A) {
    A->pushChild(B);
    OUT = A;
  } else {
    OUT = B;
  }
}
selectors(OUT) ::= . { OUT = null; }

selector(OUT) ::= type_selector(IN). { OUT = IN; }
selector(OUT) ::= attribute_selector(IN). { OUT = IN; }
selector(OUT) ::= pseudo_selector(IN). { OUT = IN; }
selector(OUT) ::= relational_selector(IN). { OUT = IN; }

// Just some identifier.
// In CSS, "div ul li a" would be a sequence of four type selectors
type_selector(OUT) ::= IDENTIFIER(A). {
  #echo "found type selector:";
  #print_r(A);
  #echo "\n";
  $desc = new Trekel_Node_Selector_Descendant();
  $desc->setIdentifier(A);
  OUT = $desc;
}

// Same, but with attributes: div[id='header']
attribute_selector(OUT) ::= IDENTIFIER(A) attribute(B). {
  $desc = new Trekel_Node_Selector_Descendant();
  $desc->setIdentifier(A);
  $desc->setChild(B);
  OUT = $desc;
}

// With pseudo classes, div:first
pseudo_selector(OUT) ::= IDENTIFIER(A) pseudo(B). {
  $desc = new Trekel_Node_Selector_Descendant();
  $desc->setIdentifier(A);
  $desc->setChild(B);
  OUT = $desc;
}

pseudo_selector(OUT) ::= IDENTIFIER(A) pseudo(B) attribute(C). {
  $desc = new Trekel_Node_Selector_Descendant();
  $desc->setIdentifier(A);
  $desc->setChild(B);
  B->setChild(C);
  OUT = $desc;
}

pseudo_selector(OUT) ::= IDENTIFIER(A) attribute(B) pseudo(C). {
  $desc = new Trekel_Node_Selector_Descendant();
  $desc->setIdentifier(A);
  $desc->setChild(B);
  B->setChild(C);
  OUT = $desc;
}

pseudo(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;
}

relational_selector(OUT) ::= selector(A) SIBLING selector(B). {
  B = B->coerce('Trekel_Node_Selector_Sibling');
  A->pushChild(B);
  OUT = A;
}
relational_selector(OUT) ::= selector(A) ADJACENT selector(B). {
  B = B->coerce('Trekel_Node_Selector_Adjacent');
  A->pushChild(B);
  OUT = A;
}
relational_selector(OUT) ::= selector(A) CHILD selector(B). {
  B = B->coerce('Trekel_Node_Selector_Child');
  A->pushChild(B);
  OUT = A;
}

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

// [<id>="value"]
attribute(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(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(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(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(OUT) ::= LBRACKET property(ID) CONTAINS string(VAL) RBRACKET. {
  $attr = new Trekel_Node_Attribute_Contains();
  $attr->setIdentifier(ID);
  $attr->setValue(VAL);
  OUT = $attr;
}

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