arity == 'name') ? $statement : $statement->first;
$second = $statement->second;
$global_scope = FALSE;
if ($first->id == '.' || $first->id == '[') {
list($is_global, $name) = $this->resolve($statement->first);
if ($name->id == '{') {
foreach($name->first as $value) {
if ($value->key == $second->value) {
return $this->resolve($value);
}
}
}
}
else {
if ($first->arity == 'name' && ($assigned = $first->scope->assigned($first->value))) {
if ($assigned->id == '{') {
// foo.bar.baz.qoo is (.(.(. foo bar) baz) qoo)
foreach ($assigned->first as $value) {
if ($value->key == $second->value) {
return array($value->is_global, $value);
}
}
return array(FALSE, $first->value);
}
elseif ($assigned->id != '.' && $assigned->id != '[' && $assigned->arity != 'name') {
return array($first->global_scope, $first->value);
}
list($is_global, $name) = $this->resolve($assigned);
}
else {
$is_global = $first->global_scope;
$name = $first->value;
}
}
if ($second) {
$name .= '.' . $second->value;
}
if ($is_global) {
$global_scope = TRUE;
}
return array($global_scope, $name);
}
/**
* Guesses the type of the current symbol
*/
public function type() {
switch($this->id){
case '{':
return 'Object';
case '[':
return 'Array';
case 'function':
return 'Function';
}
}
public function convert($recursing = FALSE) {
if ($this->arity == 'literal') {
switch($this->type){
case 'string':
return new JavaScriptString($this->value);
case 'number':
return new JavaScriptNumber($this->value);
case 'regex':
return new JavaScriptRegExp($this->value);
default:
return new JavaScriptLiteral($this->value);
}
}
else {
switch($this->id){
case '?':
return new JavaScriptTernary($this->first, $this->second, $this->third);
case '(':
return new JavaScriptFunctionCall($this->first, $this->second);
case 'function':
return new JavaScriptFunction($this);
case '{':
return new JavaScriptObject($this->first);
case '[':
case '.':
if ($this->arity == 'unary') {
return new JavaScriptArray($this->first);
}else{
return new JavaScriptVariable($this);
}
case '=':
return new JavaScriptAssignment($this->first, $this->second);
case '||':
$output = array();
$first = $this->first->convert(TRUE);
if (is_array($first)) {
$output = array_merge($output, $first);
}
else {
$output[] = $first;
}
$second = $this->second->convert(TRUE);
if (is_array($second)) {
$output = array_merge($output, $second);
}
else {
$output[] = $second;
}
return $recursing ? $output : new JavaScriptOr($output);
}
switch ($this->arity) {
case 'name':
return new JavaScriptVariable($this);
case 'literal':
case 'operator':
return new JavaScriptLiteral($this);
}
}
throw new Exception("No class for {$this->id}:{$this->arity}");
}
// led/nud/lbp functions
public function led_bracket($parser, $left) {
$this->first = $left;
$this->second = $parser->expression();
$this->arity = 'binary';
$parser->advance(']');
return $this;
}
public function nud_bracket($parser) {
$items = array();
if (!$parser->peek(']')) {
while (1) {
$items[] = $parser->expression();
if (!$parser->peek(',')) {
break;
}
$parser->advance(',');
}
}
$parser->advance(']');
$this->first = $items;
$this->arity = 'unary';
return $this;
}
public function std_break($parser) {
if ($parser->peek(';')) {
$parser->advance(';');
}
$this->arity = 'statement';
return $this;
}
public function lbp_colon($parser, $left) {
if ($parser->peek2(array('for', 'while', 'do'))) {
return 100;
}
return 0;
}
public function led_colon($parser, $left) {
$this->first = $left;
if ($parser->token->arity != 'name') {
throw new Exception('Expected a property name');
}
$parser->token->arity = 'literal';
$this->second = $parser->expression();
$this->arity ='binary';
$parser->advance();
return $this;
}
public function lbp_crement($parser, $left) {
if ($left->id == '.' || $left->id == '[' || $left->arity == 'name') {
return 100;
}
return 0;
}
public function led_crement($parser, $left) {
// Show that the in/decrementer is on the right
$this->first = $left;
$this->arity = 'unary';
return $this;
}
public function nud_crement($parser) {
// Show that the in/decrement is before the expression
$this->first = NULL;
$this->second = $parser->expression(75);
return $this;
}
public function nud_curly($parser) {
$values = array();
$this->comments = array();
if (!$parser->peek('}')) {
while (1) {
$token = $parser->token;
if ($token->arity != 'name' && $token->arity != 'literal') {
throw new Exception("Bad key: {$token->id}");
}
$comments = $parser->comments_before($token);
if (!empty($comments)) {
if (!empty($this->comments)) {
$this->comments[] = "\n";
}
$this->comments = array_merge($this->comments, $comments);
}
$parser->advance();
$parser->advance(':');
$expression = $parser->expression();
if (is_array($expression)) {
$expression = $expression[0];
}
$expression->key = $token->value;
$values[] = $expression;
if (!$parser->peek(',')) {
break;
}
$parser->advance(',');
}
}
$parser->advance('}');
$this->first = $values;
$this->arity = 'unary';
return $this;
}
public function std_curly($parser) {
$statements = $parser->statements(array('}'));
$parser->advance('}');
return $statements;
}
public function std_do($parser) {
if ($parser->peek('{')) {
$this->first = $this->block($parser);
}
else {
$this->first = $parser->expression($parser);
if ($parser->peek(';')) {
$parser->advance(';');
}
}
$parser->advance('while');
$parser->advance('(');
$this->second = $parser->expression();
$parser->advance(')');
if ($parser->peek(';')) {
$parser->advance(';');
}
return $this;
}
public function std_for($parser) {
$parser->advance('(');
if($parser->peek('var')) {
$token = $parser->token;
$parser->advance('var');
$this->first = $token->std($parser);
if ($parser->peek('in')) {
$parser->advance('in');
$this->second = $parser->expression();
}
}
else {
// Don't forget that expressionless for(;;) loops are valid
$this->first = $parser->peek(';') ? NULL : $parser->statements(array(')', ';'));
}
if (!$parser->peek(')')) {
// var swallows the ;
if ($parser->peek(';')) {
$parser->advance(';');
}
$this->second = $parser->peek(';') ? NULL : $parser->statements(array(';'));
$parser->advance(';');
$this->thid = $parser->peek(')') ? NULL : $parser->statements(array(')'));
}
$parser->advance(')');
if ($parser->peek('{')) {
$this->block = $this->block($parser);
}
elseif (!$parser->peek(';')) {
$this->block = $parser->expression();
}
if ($parser->peek(';')) {
$parser->advance(';');
}
$this->arity = 'statement';
return $this;
}
public function nud_function($parser) {
$arguments = array();
$parser->new_scope();
$this->scope = $parser->scope;
if ($parser->token->arity == 'name') {
$parser->scope->define($parser->token);
$this->name = $parser->token->value;
$parser->advance();
}
$parser->advance('(');
if (!$parser->peek(')')) {
while (1) {
if ($parser->token->arity != 'name') {
throw new Exception('Expected a parameter name');
}
$parser->scope->define($parser->token);
$argument = $parser->token;
$parser->advance();
$argument->comments = array_merge($parser->comments_before($argument), $parser->comments_after($argument));
$arguments[] = $argument;
if (!$parser->peek(',')) {
break;
}
$parser->advance(',');
}
}
$this->first = $arguments;
$parser->advance(')');
$parser->peek('{');
$this->comments = $parser->comments_after($parser->token);
$parser->advance('{');
$this->second = $parser->statements(array('}'));
$parser->advance('}');
$this->arity = 'function';
$parser->scope_pop();
return $this;
}
public function std_if($parser) {
$parser->advance('(');
$this->first = $parser->expression();
$parser->advance(')');
if ($parser->peek('{')) {
$this->second = $this->block($parser);
}
elseif (!$parser->peek(';')) {
$this->second = $parser->expression();
}
if ($parser->peek(';')) {
$parser->advance(';');
}
if ($parser->peek('else')) {
$parser->advance('else');
if ($parser->peek('if')) {
$this->third = $parser->statement;
}
elseif ($parser->peek('{')) {
$this->third = $this->block($parser);
}
elseif (!$parser->peek(';')) {
$this->third = $parser->expression();
}
if ($parser->peek(';')) {
$parser->advance(';');
}
}
else {
$this->third = NULL;
}
$this->arity = 'statement';
return $this;
}
public function led_parenthesis($parser, $left) {
if ($left->id == 'function') {
return $this->executed_function($left, $parser);
}
if ($left->id == '{') {
$expression = $parser->expression();
$parser->advance(')');
return $expression;
}
if ($left->id != '.' && $left->id != '[') {
if (($left->arity != 'unary' || $left->id != 'function') && $left->arity != 'this' && $left->arity != 'name' && $left->id != '(' && $left->id != '&&' && $left->id != '||' && $left->id != '?') {
throw new Exception("Line {$parser->token->line_number}, char {$parser->token->char_pos}: Expected a variable name");
}
}
$arguments = array();
if (!$parser->peek(')')) {
while (1) {
$arguments[] = $parser->expression();
if (!$parser->peek(',')) {
break;
}
$parser->advance(',');
}
}
// e.g. foo(bar) has a foo first, bar second
$this->arity = 'binary';
$this->first = $left;
$this->second = $arguments;
$parser->advance(')');
return $this;
}
public function executed_function($function, $parser) {
// The function gets executed
if ($parser->peek('(')) {
// led_parenthesis might have already swallowed it
$parser->advance('(');
}
$arguments = array();
if (!$parser->peek(')')) {
while (1) {
$arguments[] = $parser->expression();
if (!$parser->peek(',')) {
break;
}
$parser->advance(',');
}
}
$parser->advance(')');
if ($parser->peek(';')) {
$parser->advance(';');
}
// Make assignments within the function scope (in $function)
// between the arguments in the expression and the passed arguments
foreach ($function->first as $i => $parameter) {
if ($arguments[$i]) {
// The passed argument is assigned immediately to the matching parameter
$function->scope->assignment($parameter, $arguments[$i]);
}
}
$this->first = $function;
$this->second = $arguments;
$this->arity = 'execution';
return $this;
}
public function nud_parenthesis($parser) {
// '(' can mean function call, or executed function
$is_function = $parser->peek('function');
$expressions = $parser->statements(array(')'));
$parser->advance(')');
if ($is_function && $parser->peek('(')) {
return $this->executed_function($expressions[0], $parser);
}
return $expressions;
}
public function led_period($parser, $left) {
$this->first = $left;
if ($parser->token->arity != 'name') {
throw new Exception('Expected a property name');
}
$parser->token->arity = 'literal';
$this->second = $parser->token;
$this->arity ='binary';
$parser->advance();
return $this;
}
public function led_questionmark($parser, $left) {
$this->first = $left;
$this->second = $parser->expression();
$parser->advance(':');
$this->third = $parser->expression();
$this->arity = 'ternary';
return $this;
}
public function nud_this($parser) {
$parser->scope->reserve($this);
$this->arity = 'this';
return $this;
}
public function std_try($parser) {
$this->first = $this->block($parser);
if ($parser->peek('catch')) {
$parser->advance('catch');
$catch = $parser->new_symbol('catch');
$parser->advance('(');
$catch->first = $parser->expression();
$parser->advance(')');
$catch->second = $this->block($parser);
$this->second = $catch;
}
if ($parser->peek('finally')) {
$parser->advance('finally');
$this->third = $this->block($parser);
}
$this->arity = 'statement';
return $this;
}
public function std_return($parser) {
if (!$parser->peek("\n") && !$parser->peek(';')) {
$this->first = $parser->expression();
}
if ($parser->peek(';')) {
$parser->advance(';');
}
$this->arity = 'statement';
return $this;
}
public function std_switch($parser) {
// switch statements can have multiple
// levels of passthrough and expressions
// need to be aggregated for each current
// case statement until a break is reached
$branches = array();
$parser->advance('(');
$this->first = $parser->expression();
$parser->advance(')');
$parser->advance('{');
$this->second = array();
$cases = array();
while (1) {
if ($parser->peek('}')) {
break;
}
if ($parser->peek('default')) {
$cases[] = $parser->token;
$switch = 'default';
$parser->advance('default');
}
else {
$cases[] = $parser->token;
$parser->advance('case');
$switch = 'case';
$cases[] = $parser->expression();
}
$parser->advance(':');
$statements = $parser->statements(array('default', 'case', '}'));
if ($switch == 'default') {
$default = $parser->new_symbol('default');
$default->first = $statements;
$cases[] = $default;
}
elseif ($switch == 'case' && !empty($statements)) {
$case = $parser->new_symbol('case');
$case->first = $statements;
$cases[] = $case;
}
}
$this->second = $cases;
$parser->advance('}');
$this->arity = 'statement';
return $this;
}
public function std_var($parser) {
$assignments = array();
while (1) {
$token = $parser->token;
if ($token->arity != 'name') {
throw new Exception('Expected a new variable name');
}
$parser->scope->define($token);
$parser->advance();
if ($parser->peek('=')) {
$t = $parser->token;
$parser->advance('=');
$t->first = $token;
$t->second = $parser->expression();
$parser->scope->assignment($t->first, $t->second);
$t->arity = 'binary';
$assignments[] = $t;
}
else {
$t = $parser->new_symbol('=');
$t->first = $token;
$t->second = NULL;
$assignments[] = $t;
}
if (!$parser->peek(',')) {
break;
}
$parser->advance(',');
}
if ($parser->peek(';')) {
$parser->advance(';');
}
return $assignments;
}
public function std_while($parser) {
$parser->advance('(');
$this->first = $parser->expression();
$parser->advance(')');
if ($parser->peek('{')) {
$this->second = $this->block($parser);
}
else {
$this->second = $parser->expression();
}
$this->arity = 'statement';
return $this;
}
}