protected function compileChild($child, $out)
{
$this->sourcePos = isset($child[-1]) ? $child[-1] : -1;
$this->sourceParser = isset($child[-2]) ? $child[-2] : $this->parser;
switch ($child[0]) {
case "import":
list(, $rawPath) = $child;
$rawPath = $this->reduce($rawPath);
if (!$this->compileImport($rawPath, $out)) {
$out->lines[] = "@import " . $this->compileValue($rawPath) . ";";
}
break;
case "directive":
list(, $directive) = $child;
$s = "@" . $directive->name;
if (!empty($directive->value)) {
$s .= " " . $this->compileValue($directive->value);
}
$this->compileNestedBlock($directive, array($s));
break;
case "media":
$this->compileMedia($child[1]);
break;
case "block":
$this->compileBlock($child[1]);
break;
case "charset":
$out->lines[] = "@charset " . $this->compileValue($child[1]) . ";";
break;
case "assign":
list(, $name, $value) = $child;
if ($name[0] == "var") {
$isDefault = !empty($child[3]);
if ($isDefault) {
$existingValue = $this->get($name[1], true);
$shouldSet = $existingValue === true || $existingValue == self::$null;
}
if (!$isDefault || $shouldSet) {
$this->set($name[1], $this->reduce($value));
}
break;
}
// if the value reduces to null from something else then
// the property should be discarded
if ($value[0] != "null") {
$value = $this->reduce($value);
if ($value[0] == "null") {
break;
}
}
$compiledValue = $this->compileValue($value);
$out->lines[] = $this->formatter->property($this->compileValue($name), $compiledValue);
break;
case "comment":
$out->lines[] = $child[1];
break;
case "mixin":
case "function":
list(, $block) = $child;
$this->set(self::$namespaces[$block->type] . $block->name, $block);
break;
case "extend":
list(, $selectors) = $child;
foreach ($selectors as $sel) {
// only use the first one
$sel = current($this->evalSelector($sel));
$this->pushExtends($sel, $out->selectors);
}
break;
case "if":
list(, $if) = $child;
if ($this->isTruthy($this->reduce($if->cond, true))) {
return $this->compileChildren($if->children, $out);
} else {
foreach ($if->cases as $case) {
if ($case->type == "else" || $case->type == "elseif" && $this->isTruthy($this->reduce($case->cond))) {
return $this->compileChildren($case->children, $out);
}
}
}
break;
case "return":
return $this->reduce($child[1], true);
case "each":
list(, $each) = $child;
$list = $this->coerceList($this->reduce($each->list));
foreach ($list[2] as $item) {
$this->pushEnv();
$this->set($each->var, $item);
// TODO: allow return from here
$this->compileChildren($each->children, $out);
$this->popEnv();
}
break;
case "while":
list(, $while) = $child;
while ($this->isTruthy($this->reduce($while->cond, true))) {
$ret = $this->compileChildren($while->children, $out);
if ($ret) {
return $ret;
}
}
break;
case "for":
list(, $for) = $child;
$start = $this->reduce($for->start, true);
$start = $start[1];
$end = $this->reduce($for->end, true);
$end = $end[1];
$d = $start < $end ? 1 : -1;
while (true) {
if (!$for->until && $start - $d == $end || $for->until && $start == $end) {
break;
}
$this->set($for->var, array("number", $start, ""));
$start += $d;
$ret = $this->compileChildren($for->children, $out);
if ($ret) {
return $ret;
}
}
break;
case "nestedprop":
list(, $prop) = $child;
$prefixed = array();
$prefix = $this->compileValue($prop->prefix) . "-";
foreach ($prop->children as $child) {
if ($child[0] == "assign") {
array_unshift($child[1][2], $prefix);
}
if ($child[0] == "nestedprop") {
array_unshift($child[1]->prefix[2], $prefix);
}
$prefixed[] = $child;
}
$this->compileChildren($prefixed, $out);
break;
case "include":
// including a mixin
list(, $name, $argValues, $content) = $child;
$mixin = $this->get(self::$namespaces["mixin"] . $name, false);
if (!$mixin) {
$this->throwError("Undefined mixin {$name}");
}
$callingScope = $this->env;
// push scope, apply args
$this->pushEnv();
if ($this->env->depth > 0) {
$this->env->depth--;
}
if (!is_null($content)) {
$content->scope = $callingScope;
$this->setRaw(self::$namespaces["special"] . "content", $content);
}
if (!is_null($mixin->args)) {
$this->applyArguments($mixin->args, $argValues);
}
foreach ($mixin->children as $child) {
$this->compileChild($child, $out);
}
$this->popEnv();
break;
case "mixin_content":
$content = $this->get(self::$namespaces["special"] . "content");
if (is_null($content)) {
$this->throwError("Expected @content inside of mixin");
}
$strongTypes = array('include', 'block', 'for', 'while');
foreach ($content->children as $child) {
$this->storeEnv = in_array($child[0], $strongTypes) ? null : $content->scope;
$this->compileChild($child, $out);
}
unset($this->storeEnv);
break;
case "debug":
list(, $value, $pos) = $child;
$line = $this->parser->getLineNo($pos);
$value = $this->compileValue($this->reduce($value, true));
fwrite(STDERR, "Line {$line} DEBUG: {$value}\n");
break;
default:
$this->throwError("unknown child type: {$child['0']}");
}
}