diff sites/all/libraries/ARC2/arc/parsers/ARC2_SPARQLParser.php @ 4:ce11bbd8f642

added modules
author danieleb <danielebarchiesi@me.com>
date Thu, 19 Sep 2013 10:38:44 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sites/all/libraries/ARC2/arc/parsers/ARC2_SPARQLParser.php	Thu Sep 19 10:38:44 2013 +0100
@@ -0,0 +1,777 @@
+<?php
+/**
+ * ARC2 SPARQL Parser
+ *
+ * @author Benjamin Nowack
+ * @license <http://arc.semsol.org/license>
+ * @homepage <http://arc.semsol.org/>
+ * @package ARC2
+ * @version 2010-11-16
+*/
+
+ARC2::inc('TurtleParser');
+
+class ARC2_SPARQLParser extends ARC2_TurtleParser {
+
+  function __construct($a, &$caller) {
+    parent::__construct($a, $caller);
+  }
+
+  function __init() {
+    parent::__init();
+    $this->bnode_prefix = $this->v('bnode_prefix', 'arc'.substr(md5(uniqid(rand())), 0, 4).'b', $this->a);
+    $this->bnode_id = 0;
+    $this->bnode_pattern_index = array('patterns' => array(), 'bnodes' => array());
+  }
+
+  /*  */
+
+  function parse($q, $src = '', $iso_fallback = 'ignore') {
+    $this->setDefaultPrefixes();
+    $this->base = $src ? $this->calcBase($src) : ARC2::getRequestURI();
+    $this->r = array(
+      'base' => '',
+      'vars' => array(),
+      'prefixes' => array()
+    );
+    $this->unparsed_code = $q;
+    list($r, $v) = $this->xQuery($q);
+    if ($r) {
+      $this->r['query'] = $r;
+      $this->unparsed_code = trim($v);
+    }
+    elseif (!$this->getErrors() && !$this->unparsed_code) {
+      $this->addError('Query not properly closed');
+    }
+    $this->r['prefixes'] = $this->prefixes;
+    $this->r['base'] = $this->base;
+    /* remove trailing comments */
+    while (preg_match('/^\s*(\#[^\xd\xa]*)(.*)$/si', $this->unparsed_code, $m)) $this->unparsed_code = $m[2];
+    if ($this->unparsed_code && !$this->getErrors()) {
+      $rest = preg_replace('/[\x0a|\x0d]/i', ' ', substr($this->unparsed_code, 0, 30));
+      $msg = trim($rest) ? 'Could not properly handle "' . $rest . '"' : 'Syntax error, probably an incomplete pattern';
+      $this->addError($msg);
+    }
+  }
+
+  function getQueryInfos() {
+    return $this->v('r', array());
+  }
+
+  /* 1 */
+
+  function xQuery($v) {
+    list($r, $v) = $this->xPrologue($v);
+    foreach (array('Select', 'Construct', 'Describe', 'Ask') as $type) {
+      $m = 'x' . $type . 'Query';
+      if ((list($r, $v) = $this->$m($v)) && $r) {
+        return array($r, $v);
+      }
+    }
+    return array(0, $v);
+  }
+
+  /* 2 */
+
+  function xPrologue($v) {
+    $r = 0;
+    if ((list($sub_r, $v) = $this->xBaseDecl($v)) && $sub_r) {
+      $this->base = $sub_r;
+      $r = 1;
+    }
+    while ((list($sub_r, $v) = $this->xPrefixDecl($v)) && $sub_r) {
+      $this->prefixes[$sub_r['prefix']] = $sub_r['uri'];
+      $r = 1;
+    }
+    return array($r, $v);
+  }
+
+  /* 5.. */
+
+  function xSelectQuery($v) {
+    if ($sub_r = $this->x('SELECT\s+', $v)) {
+      $r = array(
+        'type' => 'select',
+        'result_vars' => array(),
+        'dataset' => array(),
+      );
+      $all_vars = 0;
+      $sub_v = $sub_r[1];
+      /* distinct, reduced */
+      if ($sub_r = $this->x('(DISTINCT|REDUCED)\s+', $sub_v)) {
+        $r[strtolower($sub_r[1])] = 1;
+        $sub_v = $sub_r[2];
+      }
+      /* result vars */
+      if ($sub_r = $this->x('\*\s+', $sub_v)) {
+        $all_vars = 1;
+        $sub_v = $sub_r[1];
+      }
+      else {
+        while ((list($sub_r, $sub_v) = $this->xResultVar($sub_v)) && $sub_r) {
+          $r['result_vars'][] = $sub_r;
+        }
+      }
+      if (!$all_vars && !count($r['result_vars'])) {
+        $this->addError('No result bindings specified.');
+      }
+      /* dataset */
+      while ((list($sub_r, $sub_v) = $this->xDatasetClause($sub_v)) && $sub_r) {
+        $r['dataset'][] = $sub_r;
+      }
+      /* where */
+      if ((list($sub_r, $sub_v) = $this->xWhereClause($sub_v)) && $sub_r) {
+        $r['pattern'] = $sub_r;
+      }
+      else {
+        return array(0, $v);
+      }
+      /* solution modifier */
+      if ((list($sub_r, $sub_v) = $this->xSolutionModifier($sub_v)) && $sub_r) {
+        $r = array_merge($r, $sub_r);
+      }
+      /* all vars */
+      if ($all_vars) {
+        foreach ($this->r['vars'] as $var) {
+          $r['result_vars'][] = array('var' => $var, 'aggregate' => 0, 'alias' => '');
+        }
+        if (!$r['result_vars']) {
+          $r['result_vars'][] = '*';
+        }
+      }
+      return array($r, $sub_v);
+    }
+    return array(0, $v);
+  }
+
+  function xResultVar($v) {
+    return $this->xVar($v);
+  }
+
+  /* 6.. */
+
+  function xConstructQuery($v) {
+    if ($sub_r = $this->x('CONSTRUCT\s*', $v)) {
+      $r = array(
+        'type' => 'construct',
+        'dataset' => array(),
+      );
+      $sub_v = $sub_r[1];
+      /* construct template */
+      if ((list($sub_r, $sub_v) = $this->xConstructTemplate($sub_v)) && is_array($sub_r)) {
+        $r['construct_triples'] = $sub_r;
+      }
+      else {
+        $this->addError('Construct Template not found');
+        return array(0, $v);
+      }
+      /* dataset */
+      while ((list($sub_r, $sub_v) = $this->xDatasetClause($sub_v)) && $sub_r) {
+        $r['dataset'][] = $sub_r;
+      }
+      /* where */
+      if ((list($sub_r, $sub_v) = $this->xWhereClause($sub_v)) && $sub_r) {
+        $r['pattern'] = $sub_r;
+      }
+      else {
+        return array(0, $v);
+      }
+      /* solution modifier */
+      if ((list($sub_r, $sub_v) = $this->xSolutionModifier($sub_v)) && $sub_r) {
+        $r = array_merge($r, $sub_r);
+      }
+      return array($r, $sub_v);
+    }
+    return array(0, $v);
+  }
+
+  /* 7.. */
+
+  function xDescribeQuery($v) {
+    if ($sub_r = $this->x('DESCRIBE\s+', $v)) {
+      $r = array(
+        'type' => 'describe',
+        'result_vars' => array(),
+        'result_uris' => array(),
+        'dataset' => array(),
+      );
+      $sub_v = $sub_r[1];
+      $all_vars = 0;
+      /* result vars/uris */
+      if ($sub_r = $this->x('\*\s+', $sub_v)) {
+        $all_vars = 1;
+        $sub_v = $sub_r[1];
+      }
+      else {
+        do {
+          $proceed = 0;
+          if ((list($sub_r, $sub_v) = $this->xResultVar($sub_v)) && $sub_r) {
+            $r['result_vars'][] = $sub_r;
+            $proceed = 1;
+          }
+          if ((list($sub_r, $sub_v) = $this->xIRIref($sub_v)) && $sub_r) {
+            $r['result_uris'][] = $sub_r;
+            $proceed =1;
+          }
+        } while ($proceed);
+      }
+      if (!$all_vars && !count($r['result_vars']) && !count($r['result_uris'])) {
+        $this->addError('No result bindings specified.');
+      }
+      /* dataset */
+      while ((list($sub_r, $sub_v) = $this->xDatasetClause($sub_v)) && $sub_r) {
+        $r['dataset'][] = $sub_r;
+      }
+      /* where */
+      if ((list($sub_r, $sub_v) = $this->xWhereClause($sub_v)) && $sub_r) {
+        $r['pattern'] = $sub_r;
+      }
+      /* solution modifier */
+      if ((list($sub_r, $sub_v) = $this->xSolutionModifier($sub_v)) && $sub_r) {
+        $r = array_merge($r, $sub_r);
+      }
+      /* all vars */
+      if ($all_vars) {
+        foreach ($this->r['vars'] as $var) {
+          $r['result_vars'][] = array('var' => $var, 'aggregate' => 0, 'alias' => '');
+        }
+      }
+      return array($r, $sub_v);
+    }
+    return array(0, $v);
+  }
+
+  /* 8.. */
+
+  function xAskQuery($v) {
+    if ($sub_r = $this->x('ASK\s+', $v)) {
+      $r = array(
+        'type' => 'ask',
+        'dataset' => array(),
+      );
+      $sub_v = $sub_r[1];
+      /* dataset */
+      while ((list($sub_r, $sub_v) = $this->xDatasetClause($sub_v)) && $sub_r) {
+        $r['dataset'][] = $sub_r;
+      }
+      /* where */
+      if ((list($sub_r, $sub_v) = $this->xWhereClause($sub_v)) && $sub_r) {
+        $r['pattern'] = $sub_r;
+        return array($r, $sub_v);
+      }
+      else {
+        $this->addError('Missing or invalid WHERE clause.');
+      }
+    }
+    return array(0, $v);
+  }
+
+  /* 9, 10, 11, 12 */
+
+  function xDatasetClause($v) {
+    if ($r = $this->x('FROM(\s+NAMED)?\s+', $v)) {
+      $named = $r[1] ? 1 : 0;
+      if ((list($r, $sub_v) = $this->xIRIref($r[2])) && $r) {
+        return array(array('graph' => $r, 'named' => $named), $sub_v);
+      }
+    }
+    return array(0, $v);
+  }
+
+  /* 13 */
+
+  function xWhereClause($v) {
+    if ($r = $this->x('(WHERE)?', $v)) {
+      $v = $r[2];
+    }
+    if ((list($r, $v) = $this->xGroupGraphPattern($v)) && $r) {
+      return array($r, $v);
+    }
+    return array(0, $v);
+  }
+
+  /* 14, 15 */
+
+  function xSolutionModifier($v) {
+    $r = array();
+    if ((list($sub_r, $sub_v) = $this->xOrderClause($v)) && $sub_r) {
+      $r['order_infos'] = $sub_r;
+    }
+    while ((list($sub_r, $sub_v) = $this->xLimitOrOffsetClause($sub_v)) && $sub_r) {
+      $r = array_merge($r, $sub_r);
+    }
+    return ($v == $sub_v) ? array(0, $v) : array($r, $sub_v);
+  }
+
+  /* 18, 19 */
+
+  function xLimitOrOffsetClause($v) {
+    if ($sub_r = $this->x('(LIMIT|OFFSET)', $v)) {
+      $key = strtolower($sub_r[1]);
+      $sub_v = $sub_r[2];
+      if ((list($sub_r, $sub_v) = $this->xINTEGER($sub_v)) && ($sub_r !== false)) {
+        return array(array($key =>$sub_r), $sub_v);
+      }
+      if ((list($sub_r, $sub_v) = $this->xPlaceholder($sub_v)) && ($sub_r !== false)) {
+        return array(array($key =>$sub_r), $sub_v);
+      }
+    }
+    return array(0, $v);
+  }
+
+  /* 16 */
+
+  function xOrderClause($v) {
+    if ($sub_r = $this->x('ORDER BY\s+', $v)) {
+      $sub_v = $sub_r[1];
+      $r = array();
+      while ((list($sub_r, $sub_v) = $this->xOrderCondition($sub_v)) && $sub_r) {
+        $r[] = $sub_r;
+      }
+      if (count($r)) {
+        return array($r, $sub_v);
+      }
+      else {
+        $this->addError('No order conditions specified.');
+      }
+    }
+    return array(0, $v);
+  }
+
+  /* 17, 27 */
+
+  function xOrderCondition($v) {
+    if ($sub_r = $this->x('(ASC|DESC)', $v)) {
+      $dir = strtolower($sub_r[1]);
+      $sub_v = $sub_r[2];
+      if ((list($sub_r, $sub_v) = $this->xBrackettedExpression($sub_v)) && $sub_r) {
+        $sub_r['direction'] = $dir;
+        return array($sub_r, $sub_v);
+      }
+    }
+    elseif ((list($sub_r, $sub_v) = $this->xVar($v)) && $sub_r) {
+      $sub_r['direction'] = 'asc';
+      return array($sub_r, $sub_v);
+    }
+    elseif ((list($sub_r, $sub_v) = $this->xBrackettedExpression($v)) && $sub_r) {
+      return array($sub_r, $sub_v);
+    }
+    elseif ((list($sub_r, $sub_v) = $this->xBuiltInCall($v)) && $sub_r) {
+      $sub_r['direction'] = 'asc';
+      return array($sub_r, $sub_v);
+    }
+    elseif ((list($sub_r, $sub_v) = $this->xFunctionCall($v)) && $sub_r) {
+      $sub_r['direction'] = 'asc';
+      return array($sub_r, $sub_v);
+    }
+    return array(0, $v);
+  }
+
+  /* 20 */
+
+  function xGroupGraphPattern($v) {
+    $pattern_id = substr(md5(uniqid(rand())), 0, 4);
+    if ($sub_r = $this->x('\{', $v)) {
+      $r = array('type' => 'group', 'patterns' => array());
+      $sub_v = $sub_r[1];
+      if ((list($sub_r, $sub_v) = $this->xTriplesBlock($sub_v)) && $sub_r) {
+        $this->indexBnodes($sub_r, $pattern_id);
+        $r['patterns'][] = array('type' => 'triples', 'patterns' => $sub_r);
+      }
+      do {
+        $proceed = 0;
+        if ((list($sub_r, $sub_v) = $this->xGraphPatternNotTriples($sub_v)) && $sub_r) {
+          $r['patterns'][] = $sub_r;
+          $pattern_id = substr(md5(uniqid(rand())), 0, 4);
+          $proceed = 1;
+        }
+        elseif ((list($sub_r, $sub_v) = $this->xFilter($sub_v)) && $sub_r) {
+          $r['patterns'][] = array('type' => 'filter', 'constraint' => $sub_r);
+          $proceed = 1;
+        }
+        if ($sub_r = $this->x('\.', $sub_v)) {
+          $sub_v = $sub_r[1];
+        }
+        if ((list($sub_r, $sub_v) = $this->xTriplesBlock($sub_v)) && $sub_r) {
+          $this->indexBnodes($sub_r, $pattern_id);
+          $r['patterns'][] = array('type' => 'triples', 'patterns' => $sub_r);
+          $proceed = 1;
+        }
+        if ((list($sub_r, $sub_v) = $this->xPlaceholder($sub_v)) && $sub_r) {
+          $r['patterns'][] = $sub_r;
+          $proceed = 1;
+        }
+      } while ($proceed);
+      if ($sub_r = $this->x('\}', $sub_v)) {
+        $sub_v = $sub_r[1];
+        return array($r, $sub_v);
+      }
+      $rest = preg_replace('/[\x0a|\x0d]/i', ' ', substr($sub_v, 0, 30));
+      $this->addError('Incomplete or invalid Group Graph pattern. Could not handle "' . $rest . '"');
+    }
+    return array(0, $v);
+  }
+
+  function indexBnodes($triples, $pattern_id) {
+    $index_id = count($this->bnode_pattern_index['patterns']);
+    $index_id = $pattern_id;
+    $this->bnode_pattern_index['patterns'][] = $triples;
+    foreach ($triples as $t) {
+      foreach (array('s', 'p', 'o') as $term) {
+        if ($t[$term . '_type'] == 'bnode') {
+          $val = $t[$term];
+          if (isset($this->bnode_pattern_index['bnodes'][$val]) && ($this->bnode_pattern_index['bnodes'][$val] != $index_id)) {
+            $this->addError('Re-used bnode label "' .$val. '" across graph patterns');
+          }
+          else {
+            $this->bnode_pattern_index['bnodes'][$val] = $index_id;
+          }
+        }
+      }
+    }
+  }
+
+  /* 22.., 25.. */
+
+  function xGraphPatternNotTriples($v) {
+    if ((list($sub_r, $sub_v) = $this->xOptionalGraphPattern($v)) && $sub_r) {
+      return array($sub_r, $sub_v);
+    }
+    if ((list($sub_r, $sub_v) = $this->xGraphGraphPattern($v)) && $sub_r) {
+      return array($sub_r, $sub_v);
+    }
+    $r = array('type' => 'union', 'patterns' => array());
+    $sub_v = $v;
+    do {
+      $proceed = 0;
+      if ((list($sub_r, $sub_v) = $this->xGroupGraphPattern($sub_v)) && $sub_r) {
+        $r['patterns'][] = $sub_r;
+        if ($sub_r = $this->x('UNION', $sub_v)) {
+          $sub_v = $sub_r[1];
+          $proceed = 1;
+        }
+      }
+    } while ($proceed);
+    $pc = count($r['patterns']);
+    if ($pc == 1) {
+      return array($r['patterns'][0], $sub_v);
+    }
+    elseif ($pc > 1) {
+      return array($r, $sub_v);
+    }
+    return array(0, $v);
+  }
+
+  /* 23 */
+
+  function xOptionalGraphPattern($v) {
+    if ($sub_r = $this->x('OPTIONAL', $v)) {
+      $sub_v = $sub_r[1];
+      if ((list($sub_r, $sub_v) = $this->xGroupGraphPattern($sub_v)) && $sub_r) {
+        return array(array('type' => 'optional', 'patterns' => $sub_r['patterns']), $sub_v);
+      }
+      $this->addError('Missing or invalid Group Graph Pattern after OPTIONAL');
+    }
+    return array(0, $v);
+  }
+
+  /* 24.. */
+
+  function xGraphGraphPattern($v) {
+    if ($sub_r = $this->x('GRAPH', $v)) {
+      $sub_v = $sub_r[1];
+      $r = array('type' => 'graph', 'var' => '', 'uri' => '', 'patterns' => array());
+      if ((list($sub_r, $sub_v) = $this->xVar($sub_v)) && $sub_r) {
+        $r['var'] = $sub_r;
+      }
+      elseif ((list($sub_r, $sub_v) = $this->xIRIref($sub_v)) && $sub_r) {
+        $r['uri'] = $sub_r;
+      }
+      if ($r['var'] || $r['uri']) {
+        if ((list($sub_r, $sub_v) = $this->xGroupGraphPattern($sub_v)) && $sub_r) {
+          $r['patterns'][] = $sub_r;
+          return array($r, $sub_v);
+        }
+        $this->addError('Missing or invalid Graph Pattern');
+      }
+    }
+    return array(0, $v);
+  }
+
+  /* 26.., 27.. */
+
+  function xFilter($v) {
+    if ($r = $this->x('FILTER', $v)) {
+      $sub_v = $r[1];
+      if ((list($r, $sub_v) = $this->xBrackettedExpression($sub_v)) && $r) {
+        return array($r, $sub_v);
+      }
+      if ((list($r, $sub_v) = $this->xBuiltInCall($sub_v)) && $r) {
+        return array($r, $sub_v);
+      }
+      if ((list($r, $sub_v) = $this->xFunctionCall($sub_v)) && $r) {
+        return array($r, $sub_v);
+      }
+      $this->addError('Incomplete FILTER');
+    }
+    return array(0, $v);
+  }
+
+  /* 28.. */
+
+  function xFunctionCall($v) {
+    if ((list($r, $sub_v) = $this->xIRIref($v)) && $r) {
+      if ((list($sub_r, $sub_v) = $this->xArgList($sub_v)) && $sub_r) {
+        return array(array('type' => 'function_call', 'uri' => $r, 'args' => $sub_r), $sub_v);
+      }
+    }
+    return array(0, $v);
+  }
+
+  /* 29 */
+
+  function xArgList($v) {
+    $r = array();
+    $sub_v = $v;
+    $closed = 0;
+    if ($sub_r = $this->x('\(', $sub_v)) {
+      $sub_v = $sub_r[1];
+      do {
+        $proceed = 0;
+        if ((list($sub_r, $sub_v) = $this->xExpression($sub_v)) && $sub_r) {
+          $r[] = $sub_r;
+          if ($sub_r = $this->x('\,', $sub_v)) {
+            $sub_v = $sub_r[1];
+            $proceed = 1;
+          }
+        }
+        if ($sub_r = $this->x('\)', $sub_v)) {
+         $sub_v = $sub_r[1];
+         $closed = 1;
+         $proceed = 0;
+        }
+      } while ($proceed);
+    }
+    return $closed ? array($r, $sub_v) : array(0, $v);
+  }
+
+  /* 30, 31 */
+
+  function xConstructTemplate($v) {
+    if ($sub_r = $this->x('\{', $v)) {
+      $r = array();
+      if ((list($sub_r, $sub_v) = $this->xTriplesBlock($sub_r[1])) && is_array($sub_r)) {
+        $r = $sub_r;
+      }
+      if ($sub_r = $this->x('\}', $sub_v)) {
+        return array($r, $sub_r[1]);
+      }
+    }
+    return array(0, $v);
+  }
+
+  /* 46, 47 */
+
+  function xExpression($v) {
+    if ((list($sub_r, $sub_v) = $this->xConditionalAndExpression($v)) && $sub_r) {
+      $r = array('type' => 'expression', 'sub_type' => 'or', 'patterns' => array($sub_r));
+      do {
+        $proceed = 0;
+        if ($sub_r = $this->x('\|\|', $sub_v)) {
+          $sub_v = $sub_r[1];
+          if ((list($sub_r, $sub_v) = $this->xConditionalAndExpression($sub_v)) && $sub_r) {
+            $r['patterns'][] = $sub_r;
+            $proceed = 1;
+          }
+        }
+      } while ($proceed);
+      return count($r['patterns']) == 1 ? array($r['patterns'][0], $sub_v) : array($r, $sub_v);
+    }
+    return array(0, $v);
+  }
+
+  /* 48.., 49.. */
+
+  function xConditionalAndExpression($v) {
+    if ((list($sub_r, $sub_v) = $this->xRelationalExpression($v)) && $sub_r) {
+      $r = array('type' => 'expression', 'sub_type' => 'and', 'patterns' => array($sub_r));
+      do {
+        $proceed = 0;
+        if ($sub_r = $this->x('\&\&', $sub_v)) {
+          $sub_v = $sub_r[1];
+          if ((list($sub_r, $sub_v) = $this->xRelationalExpression($sub_v)) && $sub_r) {
+            $r['patterns'][] = $sub_r;
+            $proceed = 1;
+          }
+        }
+      } while ($proceed);
+      return count($r['patterns']) == 1 ? array($r['patterns'][0], $sub_v) : array($r, $sub_v);
+    }
+    return array(0, $v);
+  }
+
+  /* 50, 51 */
+
+  function xRelationalExpression($v) {
+    if ((list($sub_r, $sub_v) = $this->xAdditiveExpression($v)) && $sub_r) {
+      $r = array('type' => 'expression', 'sub_type' => 'relational', 'patterns' => array($sub_r));
+      do {
+        $proceed = 0;
+        /* don't mistake '<' + uriref with '<'-operator ("longest token" rule) */
+        if ((list($sub_r, $sub_v) = $this->xIRI_REF($sub_v)) && $sub_r) {
+          $this->addError('Expected operator, found IRIref: "'.$sub_r.'".');
+        }
+        if ($sub_r = $this->x('(\!\=|\=\=|\=|\<\=|\>\=|\<|\>)', $sub_v)) {
+          $op = $sub_r[1];
+          $sub_v = $sub_r[2];
+          $r['operator'] = $op;
+          if ((list($sub_r, $sub_v) = $this->xAdditiveExpression($sub_v)) && $sub_r) {
+            //$sub_r['operator'] = $op;
+            $r['patterns'][] = $sub_r;
+            $proceed = 1;
+          }
+        }
+      } while ($proceed);
+      return count($r['patterns']) == 1 ? array($r['patterns'][0], $sub_v) : array($r, $sub_v);
+    }
+    return array(0, $v);
+  }
+
+  /* 52 */
+
+  function xAdditiveExpression($v) {
+    if ((list($sub_r, $sub_v) = $this->xMultiplicativeExpression($v)) && $sub_r) {
+      $r = array('type' => 'expression', 'sub_type' => 'additive', 'patterns' => array($sub_r));
+      do {
+        $proceed = 0;
+        if ($sub_r = $this->x('(\+|\-)', $sub_v)) {
+          $op = $sub_r[1];
+          $sub_v = $sub_r[2];
+          if ((list($sub_r, $sub_v) = $this->xMultiplicativeExpression($sub_v)) && $sub_r) {
+            $sub_r['operator'] = $op;
+            $r['patterns'][] = $sub_r;
+            $proceed = 1;
+          }
+          elseif ((list($sub_r, $sub_v) = $this->xNumericLiteral($sub_v)) && $sub_r) {
+            $r['patterns'][] = array('type' => 'numeric', 'operator' => $op, 'value' => $sub_r);
+            $proceed = 1;
+          }
+        }
+      } while ($proceed);
+      //return array($r, $sub_v);
+      return count($r['patterns']) == 1 ? array($r['patterns'][0], $sub_v) : array($r, $sub_v);
+    }
+    return array(0, $v);
+  }
+
+  /* 53 */
+
+  function xMultiplicativeExpression($v) {
+    if ((list($sub_r, $sub_v) = $this->xUnaryExpression($v)) && $sub_r) {
+      $r = array('type' => 'expression', 'sub_type' => 'multiplicative', 'patterns' => array($sub_r));
+      do {
+        $proceed = 0;
+        if ($sub_r = $this->x('(\*|\/)', $sub_v)) {
+          $op = $sub_r[1];
+          $sub_v = $sub_r[2];
+          if ((list($sub_r, $sub_v) = $this->xUnaryExpression($sub_v)) && $sub_r) {
+            $sub_r['operator'] = $op;
+            $r['patterns'][] = $sub_r;
+            $proceed = 1;
+          }
+        }
+      } while ($proceed);
+      return count($r['patterns']) == 1 ? array($r['patterns'][0], $sub_v) : array($r, $sub_v);
+    }
+    return array(0, $v);
+  }
+
+  /* 54 */
+
+  function xUnaryExpression($v) {
+    $sub_v = $v;
+    $op = '';
+    if ($sub_r = $this->x('(\!|\+|\-)', $sub_v)) {
+      $op = $sub_r[1];
+      $sub_v = $sub_r[2];
+    }
+    if ((list($sub_r, $sub_v) = $this->xPrimaryExpression($sub_v)) && $sub_r) {
+      if (!is_array($sub_r)) {
+        $sub_r = array('type' => 'unary', 'expression' => $sub_r);
+      }
+      elseif ($sub_op = $this->v1('operator', '', $sub_r)) {
+        $ops = array('!!' => '', '++' => '+', '--' => '+', '+-' => '-', '-+' => '-');
+        $op = isset($ops[$op . $sub_op]) ? $ops[$op . $sub_op] : $op . $sub_op;
+      }
+      $sub_r['operator'] = $op;
+      return array($sub_r, $sub_v);
+    }
+    return array(0, $v);
+  }
+
+  /* 55 */
+
+  function xPrimaryExpression($v) {
+    foreach (array('BrackettedExpression', 'BuiltInCall', 'IRIrefOrFunction', 'RDFLiteral', 'NumericLiteral', 'BooleanLiteral', 'Var', 'Placeholder') as $type) {
+      $m = 'x' . $type;
+      if ((list($sub_r, $sub_v) = $this->$m($v)) && $sub_r) {
+        return array($sub_r, $sub_v);
+      }
+    }
+    return array(0, $v);
+  }
+
+  /* 56 */
+
+  function xBrackettedExpression($v) {
+    if ($r = $this->x('\(', $v)) {
+      if ((list($r, $sub_v) = $this->xExpression($r[1])) && $r) {
+        if ($sub_r = $this->x('\)', $sub_v)) {
+          return array($r, $sub_r[1]);
+        }
+      }
+    }
+    return array(0, $v);
+  }
+
+  /* 57.., 58.. */
+
+  function xBuiltInCall($v) {
+    if ($sub_r = $this->x('(str|lang|langmatches|datatype|bound|sameterm|isiri|isuri|isblank|isliteral|regex)\s*\(', $v)) {
+      $r = array('type' => 'built_in_call', 'call' => strtolower($sub_r[1]));
+      if ((list($sub_r, $sub_v) = $this->xArgList('(' . $sub_r[2])) && is_array($sub_r)) {
+        $r['args'] = $sub_r;
+        return array($r, $sub_v);
+      }
+    }
+    return array(0, $v);
+  }
+
+  /* 59.. */
+
+  function xIRIrefOrFunction($v) {
+    if ((list($r, $v) = $this->xIRIref($v)) && $r) {
+      if ((list($sub_r, $sub_v) = $this->xArgList($v)) && is_array($sub_r)) {
+        return array(array('type' => 'function', 'uri' => $r, 'args' => $sub_r), $sub_v);
+      }
+      return array(array('type' => 'uri', 'uri' => $r), $sub_v);
+    }
+  }
+
+  /* 70.. @@sync with TurtleParser */
+
+  function xIRI_REF($v) {
+    if (($r = $this->x('\<(\$\{[^\>]*\})\>', $v)) && ($sub_r = $this->xPlaceholder($r[1]))) {
+      return array($r[1], $r[2]);
+    }
+    elseif ($r = $this->x('\<([^\<\>\s\"\|\^`]*)\>', $v)) {
+      return array($r[1] ? $r[1] : true, $r[2]);
+    }
+    /* allow reserved chars in obvious IRIs */
+    elseif ($r = $this->x('\<(https?\:[^\s][^\<\>]*)\>', $v)) {
+      return array($r[1] ? $r[1] : true, $r[2]);
+    }
+    return array(0, $v);
+  }
+
+}