1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
|
<?php
require_once('Destructable.php');
class Scope extends Destructable {
protected $_parent;
protected $definitions = array();
protected $assignments = array();
public function __destruct() {
$this->mem_flush('_parent', 'definitions', 'assignments');
}
/**
* Turns a name symbol into a variable symbol
*
* @param Symbol $symbol A name object occurring in the current scope
*/
public function define(&$symbol) {
if ($token = $this->definitions[$symbol->value] && $token->reserved) {
throw new Exception("Already defined: {$symbol->value}");
}
$this->definitions[$symbol->value] = $symbol;
$symbol->reserved = FALSE;
$symbol->nud = 'nud_itself';
$symbol->led = NULL;
$symbol->std = NULL;
$symbol->lbp = 0;
$symbol->scope = $this;
$symbol->global_scope = empty($this->_parent);
return $symbol;
}
/**
* Mark an assignment made in the current scope between two symbols
*
* @param Symbol $to_expression The expression $expression is assigned to
* @param Symbol $expression The expression being assigned
*/
public function assignment($to_expression, $expression) {
// Only let through assignments to actual lookups (foo or foo.bar.baz)
// where the assignment is also a lookup, or an object (which may contain lookups)
if (is_object($to_expression) && $to_expression->is_lookup()) {
if ($this !== $to_expression->scope) {
// The assignment might be referencing a higher scope (e.g. without var)
return $to_expression->scope->assignment($to_expression, $expression);
}
$this->assignments[$to_expression->value] = array();
$expressions = array($expression);
if ($possibilities = $this->possibilities($expression)) {
$expressions = $possibilities;
}
foreach ($expressions as $expression) {
if (is_object($expression) && ($expression->is_lookup() || $expression->id == '{')) {
$this->assignments[$to_expression->value][] = $expression;
}
}
}
}
protected function possibilities($symbol, $possibilities=array()) {
$symbols = $symbol;
if (!is_array($symbols)) {
$symbols = array($symbols);
}
foreach ($symbols as $symbol) {
if (is_array($symbol)) {
$possibilities = $this->possibilities($symbol, $possibilities);
}
elseif ($symbol->is_option()) {
$firsts = $symbol->first;
if (!is_array($firsts)) {
$firsts = array($firsts);
}
foreach ($firsts as $first) {
if (is_array($first)) {
$possibilities = $this->possibilities($first, $possibilities);
}
elseif ($first->possible_variable()) {
$possibilities[] = $first;
}
}
$seconds = $symbol->second;
if (!is_array($seconds)) {
$seconds = array($seconds);
}
foreach ($seconds as $second) {
if (is_array($second) || $second->is_option()) {
$possibilities = $this->possibilities($second, $possibilities);
}
elseif ($second->possible_variable()) {
$possibilities[] = $second;
}
}
}
}
return $possibilities;
}
public function assigned($variable, $as_array=FALSE) {
if (isset($this->assignments[$variable])) {
return $as_array ? $this->assignments[$variable] : $this->assignments[$variable][0];
}
if (isset($this->parent)) {
return $this->_parent->assigned($variable, $as_array);
}
}
/**
* Sets the current scope's parent
*
* @param Scope $parent
*/
public function setParent($parent) {
if ($parent instanceof Scope) {
return ($this->_parent = $parent);
}
}
/**
* Returns the current parent
*/
public function parent() {
// This is how pop() will work as well
return $this->_parent;
}
public function definition($name) {
return $this->definitions[$name];
}
/**
* Tries to look up through each scope
* to find a symbol with the same name
* and returns the global symbol or empty
* (name) symbol instead
*/
public function find ($name, $symbol_table) {
if ($symbol_table[$name]) {
return clone $symbol_table[$name];
}
$scope = $this;
while (1) {
if ($symbol = $scope->definition($name)) {
return clone $symbol;
}
if (!$scope->parent()) {
if (array_key_exists($name, $symbol_table)) {
return $symbol_table[$name];
}
$symbol = $symbol_table['(name)'];
$s = clone $symbol;
$s->global_scope = TRUE;
$s->reserved = FALSE;
$s->nud = 'nud_itself';
$s->led = NULL;
$s->std = NULL;
$s->lbp = 0;
$s->scope = $scope;
return $s;
}
$scope = $scope->parent();
}
}
/**
* Marks a variable symbol as being reserved in the current scope
*
* @param Symbol @symbol The variable symbol to mark reserved
*/
public function reserve($symbol) {
if ($symbol->arity != 'name' || $symbol->reserved) {
return;
}
if ($token = $this->definitions[$symbol->value]) {
if ($token->reserved) {
$symbol->reserved = TRUE;
if (!$this->parent()) {
$symbol->global_scope = TRUE;
}
return;
}
if ($token->arity == 'name') {
throw new Exception("Already defined: {$symbol->value}");
}
}
$symbol->reserved = TRUE;
if (!$this->parent()) {
$symbol->global_scope = TRUE;
}
$this->definitions[$symbol->value] = $symbol;
}
}
|