Leafo\ScssPhp\Compiler::compileChild PHP Method

compileChild() protected method

Compile child; returns a value to halt execution
protected compileChild ( array $child, Leafo\ScssPhp\Formatter\OutputBlock $out ) : array
$child array
$out Leafo\ScssPhp\Formatter\OutputBlock
return array
    protected function compileChild($child, OutputBlock $out)
    {
        $this->sourceIndex = isset($child[Parser::SOURCE_INDEX]) ? $child[Parser::SOURCE_INDEX] : null;
        $this->sourceLine = isset($child[Parser::SOURCE_LINE]) ? $child[Parser::SOURCE_LINE] : -1;
        $this->sourceColumn = isset($child[Parser::SOURCE_COLUMN]) ? $child[Parser::SOURCE_COLUMN] : -1;
        switch ($child[0]) {
            case Type::T_SCSSPHP_IMPORT_ONCE:
                list(, $rawPath) = $child;
                $rawPath = $this->reduce($rawPath);
                if (!$this->compileImport($rawPath, $out, true)) {
                    $out->lines[] = '@import ' . $this->compileValue($rawPath) . ';';
                }
                break;
            case Type::T_IMPORT:
                list(, $rawPath) = $child;
                $rawPath = $this->reduce($rawPath);
                if (!$this->compileImport($rawPath, $out)) {
                    $out->lines[] = '@import ' . $this->compileValue($rawPath) . ';';
                }
                break;
            case Type::T_DIRECTIVE:
                $this->compileDirective($child[1]);
                break;
            case Type::T_AT_ROOT:
                $this->compileAtRoot($child[1]);
                break;
            case Type::T_MEDIA:
                $this->compileMedia($child[1]);
                break;
            case Type::T_BLOCK:
                $this->compileBlock($child[1]);
                break;
            case Type::T_CHARSET:
                if (!$this->charsetSeen) {
                    $this->charsetSeen = true;
                    $out->lines[] = '@charset ' . $this->compileValue($child[1]) . ';';
                }
                break;
            case Type::T_ASSIGN:
                list(, $name, $value) = $child;
                if ($name[0] === Type::T_VARIABLE) {
                    $flags = isset($child[3]) ? $child[3] : [];
                    $isDefault = in_array('!default', $flags);
                    $isGlobal = in_array('!global', $flags);
                    if ($isGlobal) {
                        $this->set($name[1], $this->reduce($value), false, $this->rootEnv);
                        break;
                    }
                    $shouldSet = $isDefault && (($result = $this->get($name[1], false)) === null || $result === static::$null);
                    if (!$isDefault || $shouldSet) {
                        $this->set($name[1], $this->reduce($value));
                    }
                    break;
                }
                $compiledName = $this->compileValue($name);
                // handle shorthand syntax: size / line-height
                if ($compiledName === 'font') {
                    if ($value[0] === Type::T_EXPRESSION && $value[1] === '/') {
                        $value = $this->expToString($value);
                    } elseif ($value[0] === Type::T_LIST) {
                        foreach ($value[2] as &$item) {
                            if ($item[0] === Type::T_EXPRESSION && $item[1] === '/') {
                                $item = $this->expToString($item);
                            }
                        }
                    }
                }
                // if the value reduces to null from something else then
                // the property should be discarded
                if ($value[0] !== Type::T_NULL) {
                    $value = $this->reduce($value);
                    if ($value[0] === Type::T_NULL || $value === static::$nullString) {
                        break;
                    }
                }
                $compiledValue = $this->compileValue($value);
                $out->lines[] = $this->formatter->property($compiledName, $compiledValue);
                break;
            case Type::T_COMMENT:
                if ($out->type === Type::T_ROOT) {
                    $this->compileComment($child);
                    break;
                }
                $out->lines[] = $child[1];
                break;
            case Type::T_MIXIN:
            case Type::T_FUNCTION:
                list(, $block) = $child;
                $this->set(static::$namespaces[$block->type] . $block->name, $block);
                break;
            case Type::T_EXTEND:
                list(, $selectors) = $child;
                foreach ($selectors as $sel) {
                    $results = $this->evalSelectors([$sel]);
                    foreach ($results as $result) {
                        // only use the first one
                        $result = current($result);
                        $this->pushExtends($result, $out->selectors, $child);
                    }
                }
                break;
            case Type::T_IF:
                list(, $if) = $child;
                if ($this->isTruthy($this->reduce($if->cond, true))) {
                    return $this->compileChildren($if->children, $out);
                }
                foreach ($if->cases as $case) {
                    if ($case->type === Type::T_ELSE || $case->type === Type::T_ELSEIF && $this->isTruthy($this->reduce($case->cond))) {
                        return $this->compileChildren($case->children, $out);
                    }
                }
                break;
            case Type::T_EACH:
                list(, $each) = $child;
                $list = $this->coerceList($this->reduce($each->list));
                $this->pushEnv();
                foreach ($list[2] as $item) {
                    if (count($each->vars) === 1) {
                        $this->set($each->vars[0], $item, true);
                    } else {
                        list(, , $values) = $this->coerceList($item);
                        foreach ($each->vars as $i => $var) {
                            $this->set($var, isset($values[$i]) ? $values[$i] : static::$null, true);
                        }
                    }
                    $ret = $this->compileChildren($each->children, $out);
                    if ($ret) {
                        if ($ret[0] !== Type::T_CONTROL) {
                            $this->popEnv();
                            return $ret;
                        }
                        if ($ret[1]) {
                            break;
                        }
                    }
                }
                $this->popEnv();
                break;
            case Type::T_WHILE:
                list(, $while) = $child;
                while ($this->isTruthy($this->reduce($while->cond, true))) {
                    $ret = $this->compileChildren($while->children, $out);
                    if ($ret) {
                        if ($ret[0] !== Type::T_CONTROL) {
                            return $ret;
                        }
                        if ($ret[1]) {
                            break;
                        }
                    }
                }
                break;
            case Type::T_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;
                for (;;) {
                    if (!$for->until && $start - $d == $end || $for->until && $start == $end) {
                        break;
                    }
                    $this->set($for->var, new Node\Number($start, ''));
                    $start += $d;
                    $ret = $this->compileChildren($for->children, $out);
                    if ($ret) {
                        if ($ret[0] !== Type::T_CONTROL) {
                            return $ret;
                        }
                        if ($ret[1]) {
                            break;
                        }
                    }
                }
                break;
            case Type::T_BREAK:
                return [Type::T_CONTROL, true];
            case Type::T_CONTINUE:
                return [Type::T_CONTROL, false];
            case Type::T_RETURN:
                return $this->reduce($child[1], true);
            case Type::T_NESTED_PROPERTY:
                list(, $prop) = $child;
                $prefixed = [];
                $prefix = $this->compileValue($prop->prefix) . '-';
                foreach ($prop->children as $child) {
                    switch ($child[0]) {
                        case Type::T_ASSIGN:
                            array_unshift($child[1][2], $prefix);
                            break;
                        case Type::T_NESTED_PROPERTY:
                            array_unshift($child[1]->prefix[2], $prefix);
                            break;
                    }
                    $prefixed[] = $child;
                }
                $this->compileChildrenNoReturn($prefixed, $out);
                break;
            case Type::T_INCLUDE:
                // including a mixin
                list(, $name, $argValues, $content) = $child;
                $mixin = $this->get(static::$namespaces['mixin'] . $name, false);
                if (!$mixin) {
                    $this->throwError("Undefined mixin {$name}");
                    break;
                }
                $callingScope = $this->getStoreEnv();
                // push scope, apply args
                $this->pushEnv();
                $this->env->depth--;
                $storeEnv = $this->storeEnv;
                $this->storeEnv = $this->env;
                if (isset($content)) {
                    $content->scope = $callingScope;
                    $this->setRaw(static::$namespaces['special'] . 'content', $content, $this->env);
                }
                if (isset($mixin->args)) {
                    $this->applyArguments($mixin->args, $argValues);
                }
                $this->env->marker = 'mixin';
                $this->compileChildrenNoReturn($mixin->children, $out);
                $this->storeEnv = $storeEnv;
                $this->popEnv();
                break;
            case Type::T_MIXIN_CONTENT:
                $content = $this->get(static::$namespaces['special'] . 'content', false, $this->getStoreEnv()) ?: $this->get(static::$namespaces['special'] . 'content', false, $this->env);
                if (!$content) {
                    $content = new \stdClass();
                    $content->scope = new \stdClass();
                    $content->children = $this->storeEnv->parent->block->children;
                    break;
                }
                $storeEnv = $this->storeEnv;
                $this->storeEnv = $content->scope;
                $this->compileChildrenNoReturn($content->children, $out);
                $this->storeEnv = $storeEnv;
                break;
            case Type::T_DEBUG:
                list(, $value) = $child;
                $line = $this->sourceLine;
                $value = $this->compileValue($this->reduce($value, true));
                fwrite($this->stderr, "Line {$line} DEBUG: {$value}\n");
                break;
            case Type::T_WARN:
                list(, $value) = $child;
                $line = $this->sourceLine;
                $value = $this->compileValue($this->reduce($value, true));
                fwrite($this->stderr, "Line {$line} WARN: {$value}\n");
                break;
            case Type::T_ERROR:
                list(, $value) = $child;
                $line = $this->sourceLine;
                $value = $this->compileValue($this->reduce($value, true));
                $this->throwError("Line {$line} ERROR: {$value}\n");
                break;
            case Type::T_CONTROL:
                $this->throwError('@break/@continue not permitted in this scope');
                break;
            default:
                $this->throwError("unknown child type: {$child['0']}");
        }
    }
Compiler