<?php

namespace CoffeeScript;

Init::init();

abstract class yy_Base
{
  public $as_key = FALSE;
  public $children = array();
  public $ctor = NULL;
  public $exclusive = NULL;
  public $expression = NULL;
  public $from = NULL;
  public $front = NULL;
  public $namespaced = FALSE;
  public $negated = FALSE;
  public $no_return = FALSE;
  public $proto = FALSE;
  public $soak = FALSE;
  public $this = NULL;
  public $to = NULL;

  function __construct() {}

  function __toString()
  {
    return ''.$this->to_string();
  }

  function cache($options, $level = NULL, $reused = NULL)
  {
    if ( ! $this->is_complex())
    {
      $ref = $level ? $this->compile($options, $level) : $this;
      return array($ref, $ref);
    }
    else
    {
      $ref = yy('Literal', $reused ? $reused : $options['scope']->free_variable('ref'));
      $sub = yy('Assign', $ref, $this);

      if ($level)
      {
        return array($sub->compile($options, $level), $ref->value);
      }
      else
      {
        return array($sub, $ref);
      }
    }
  }

  function compile($options, $level = NULL)
  {
    if (isset($level))
    {
      $options['level'] = $level;
    }

    if ( ! ($node = $this->unfold_soak($options)))
    {
      $node = $this;
    }

    $node->tab = $options['indent'];

    if ($options['level'] === LEVEL_TOP || ! $node->is_statement($options))
    {
      return $node->compile_node($options);
    }

    return $node->compile_closure($options);
  }

  function compile_closure($options)
  {
    if ($this->jumps())
    {
      throw new SyntaxError('cannot use a pure statement in an expression.');
    }

    $options['sharedScope'] = TRUE;

    $closure = yy_Closure::wrap($this);

    return $closure->compile_node($options);
  }

  function compile_loop_reference($options, $name)
  {
    $src = $tmp = $this->compile($options, LEVEL_LIST);

    if ( ! ( ($src === 0 || $src === '') || preg_match(IDENTIFIER, $src) && 
      $options['scope']->check($src, TRUE)))
    {
      $src = ($tmp = $options['scope']->free_variable($name)).' = '.$src;
    }

    return array($src, $tmp);
  }

  function contains($pred)
  {
    $contains = FALSE;

    if (is_string($pred))
    {
      $tmp = __NAMESPACE__.'\\'.$pred;

      $pred = function($node) use ($tmp)
      {
        return call_user_func($tmp, $node);
      };
    }

    $this->traverse_children(FALSE, function($node) use ( & $contains, & $pred)
    {
      if ($pred($node))
      {
        $contains = TRUE;
        return FALSE;
      }
    });

    return $contains;
  }

  function contains_type($type)
  {
    return ($this instanceof $type) || $this->contains(function($node) use ( & $type)
    {
      return $node instanceof $type;
    });
  }

  function each_child($func)
  {
    if ( ! ($this->children))
    {
      return $this;
    }

    foreach ($this->children as $i => $attr)
    {
      if (isset($this->{$attr}) && $this->{$attr})
      {
        foreach (flatten(array($this->{$attr})) as $i => $child)
        {
          if ($func($child) === FALSE)
          {
            break 2;
          }
        }
      }
    }

    return $this;
  }

  function invert()
  {
    return yy('Op', '!', $this);
  }

  function is_assignable()
  {
    return FALSE;
  }

  function is_complex()
  {
    return TRUE;
  }

  function is_chainable()
  {
    return FALSE;
  }

  function is_object()
  {
    return FALSE;
  }

  function is_statement($options = NULL)
  {
    return FALSE;
  }

  function is_undefined()
  {
    return FALSE;
  }

  function jumps()
  {
    return FALSE;
  }

  function last_non_comment($list)
  {
    $i = count($list);

    while ($i--)
    {
      if ( ! ($list[$i] instanceof yy_Comment))
      {
        return $list[$i];
      }
    }

    return NULL;
  }

  function make_return($res = NULL)
  {
    $me = $this->unwrap_all();

    if ($res)
    {
      return yy('Call', yy('Literal', "{$res}.push"), array($me));
    }
    else
    {
      return yy('Return', $me);
    }
  }

  function to_string($idt = '', $name = NULL)
  {
    if ($name === NULL)
    {
      $name = get_class($this);
    }

    $tree = "\n{$idt}{$name}";

    if ($this->soak)
    {
      $tree .= '?';
    }

    $this->each_child(function($node) use ($idt, & $tree)
    {
      $tree .= $node->to_string($idt.TAB);
    });

    return $tree;
  }

  function traverse_children($cross_scope, $func)
  {
    $this->each_child(function($child) use ($cross_scope, & $func)
    {
      if ($func($child) === FALSE)
      {
        return FALSE;
      }

      return $child->traverse_children($cross_scope, $func);
    });
  }

  function unfold_soak($options = NULL)
  {
    return FALSE;
  }

  function unwrap()
  {
    return $this;
  }

  function unwrap_all()
  {
    $node = $this;

    while ($node !== ($tmp = $node->unwrap()))
    {
      $node = $tmp;
    }

    return $node;
  }
}

?>
