protected function parseChunk()
{
$s = $this->seek();
// the directives
if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] === '@') {
if ($this->literal('@at-root') && ($this->selectors($selector) || true) && ($this->map($with) || true) && $this->literal('{')) {
$atRoot = $this->pushSpecialBlock(Type::T_AT_ROOT, $s);
$atRoot->selector = $selector;
$atRoot->with = $with;
return true;
}
$this->seek($s);
if ($this->literal('@media') && $this->mediaQueryList($mediaQueryList) && $this->literal('{')) {
$media = $this->pushSpecialBlock(Type::T_MEDIA, $s);
$media->queryList = $mediaQueryList[2];
return true;
}
$this->seek($s);
if ($this->literal('@mixin') && $this->keyword($mixinName) && ($this->argumentDef($args) || true) && $this->literal('{')) {
$mixin = $this->pushSpecialBlock(Type::T_MIXIN, $s);
$mixin->name = $mixinName;
$mixin->args = $args;
return true;
}
$this->seek($s);
if ($this->literal('@include') && $this->keyword($mixinName) && ($this->literal('(') && ($this->argValues($argValues) || true) && $this->literal(')') || true) && ($this->end() || $this->literal('{') && ($hasBlock = true))) {
$child = [Type::T_INCLUDE, $mixinName, isset($argValues) ? $argValues : null, null];
if (!empty($hasBlock)) {
$include = $this->pushSpecialBlock(Type::T_INCLUDE, $s);
$include->child = $child;
} else {
$this->append($child, $s);
}
return true;
}
$this->seek($s);
if ($this->literal('@scssphp-import-once') && $this->valueList($importPath) && $this->end()) {
$this->append([Type::T_SCSSPHP_IMPORT_ONCE, $importPath], $s);
return true;
}
$this->seek($s);
if ($this->literal('@import') && $this->valueList($importPath) && $this->end()) {
$this->append([Type::T_IMPORT, $importPath], $s);
return true;
}
$this->seek($s);
if ($this->literal('@import') && $this->url($importPath) && $this->end()) {
$this->append([Type::T_IMPORT, $importPath], $s);
return true;
}
$this->seek($s);
if ($this->literal('@extend') && $this->selectors($selectors) && $this->end()) {
// check for '!flag'
$optional = $this->stripOptionalFlag($selectors);
$this->append([Type::T_EXTEND, $selectors, $optional], $s);
return true;
}
$this->seek($s);
if ($this->literal('@function') && $this->keyword($fnName) && $this->argumentDef($args) && $this->literal('{')) {
$func = $this->pushSpecialBlock(Type::T_FUNCTION, $s);
$func->name = $fnName;
$func->args = $args;
return true;
}
$this->seek($s);
if ($this->literal('@break') && $this->end()) {
$this->append([Type::T_BREAK], $s);
return true;
}
$this->seek($s);
if ($this->literal('@continue') && $this->end()) {
$this->append([Type::T_CONTINUE], $s);
return true;
}
$this->seek($s);
if ($this->literal('@return') && ($this->valueList($retVal) || true) && $this->end()) {
$this->append([Type::T_RETURN, isset($retVal) ? $retVal : [Type::T_NULL]], $s);
return true;
}
$this->seek($s);
if ($this->literal('@each') && $this->genericList($varNames, 'variable', ',', false) && $this->literal('in') && $this->valueList($list) && $this->literal('{')) {
$each = $this->pushSpecialBlock(Type::T_EACH, $s);
foreach ($varNames[2] as $varName) {
$each->vars[] = $varName[1];
}
$each->list = $list;
return true;
}
$this->seek($s);
if ($this->literal('@while') && $this->expression($cond) && $this->literal('{')) {
$while = $this->pushSpecialBlock(Type::T_WHILE, $s);
$while->cond = $cond;
return true;
}
$this->seek($s);
if ($this->literal('@for') && $this->variable($varName) && $this->literal('from') && $this->expression($start) && ($this->literal('through') || ($forUntil = true && $this->literal('to'))) && $this->expression($end) && $this->literal('{')) {
$for = $this->pushSpecialBlock(Type::T_FOR, $s);
$for->var = $varName[1];
$for->start = $start;
$for->end = $end;
$for->until = isset($forUntil);
return true;
}
$this->seek($s);
if ($this->literal('@if') && $this->valueList($cond) && $this->literal('{')) {
$if = $this->pushSpecialBlock(Type::T_IF, $s);
$if->cond = $cond;
$if->cases = [];
return true;
}
$this->seek($s);
if ($this->literal('@debug') && $this->valueList($value) && $this->end()) {
$this->append([Type::T_DEBUG, $value], $s);
return true;
}
$this->seek($s);
if ($this->literal('@warn') && $this->valueList($value) && $this->end()) {
$this->append([Type::T_WARN, $value], $s);
return true;
}
$this->seek($s);
if ($this->literal('@error') && $this->valueList($value) && $this->end()) {
$this->append([Type::T_ERROR, $value], $s);
return true;
}
$this->seek($s);
if ($this->literal('@content') && $this->end()) {
$this->append([Type::T_MIXIN_CONTENT], $s);
return true;
}
$this->seek($s);
$last = $this->last();
if (isset($last) && $last[0] === Type::T_IF) {
list(, $if) = $last;
if ($this->literal('@else')) {
if ($this->literal('{')) {
$else = $this->pushSpecialBlock(Type::T_ELSE, $s);
} elseif ($this->literal('if') && $this->valueList($cond) && $this->literal('{')) {
$else = $this->pushSpecialBlock(Type::T_ELSEIF, $s);
$else->cond = $cond;
}
if (isset($else)) {
$else->dontAppend = true;
$if->cases[] = $else;
return true;
}
}
$this->seek($s);
}
// only retain the first @charset directive encountered
if ($this->literal('@charset') && $this->valueList($charset) && $this->end()) {
if (!isset($this->charset)) {
$statement = [Type::T_CHARSET, $charset];
list($line, $column) = $this->getSourcePosition($s);
$statement[static::SOURCE_LINE] = $line;
$statement[static::SOURCE_COLUMN] = $column;
$statement[static::SOURCE_INDEX] = $this->sourceIndex;
$this->charset = $statement;
}
return true;
}
$this->seek($s);
// doesn't match built in directive, do generic one
if ($this->literal('@', false) && $this->keyword($dirName) && ($this->variable($dirValue) || $this->openString('{', $dirValue) || true) && $this->literal('{')) {
if ($dirName === 'media') {
$directive = $this->pushSpecialBlock(Type::T_MEDIA, $s);
} else {
$directive = $this->pushSpecialBlock(Type::T_DIRECTIVE, $s);
$directive->name = $dirName;
}
if (isset($dirValue)) {
$directive->value = $dirValue;
}
return true;
}
$this->seek($s);
return false;
}
// property shortcut
// captures most properties before having to parse a selector
if ($this->keyword($name, false) && $this->literal(': ') && $this->valueList($value) && $this->end()) {
$name = [Type::T_STRING, '', [$name]];
$this->append([Type::T_ASSIGN, $name, $value], $s);
return true;
}
$this->seek($s);
// variable assigns
if ($this->variable($name) && $this->literal(':') && $this->valueList($value) && $this->end()) {
// check for '!flag'
$assignmentFlags = $this->stripAssignmentFlags($value);
$this->append([Type::T_ASSIGN, $name, $value, $assignmentFlags], $s);
return true;
}
$this->seek($s);
// misc
if ($this->literal('-->')) {
return true;
}
// opening css block
if ($this->selectors($selectors) && $this->literal('{')) {
$this->pushBlock($selectors, $s);
return true;
}
$this->seek($s);
// property assign, or nested assign
if ($this->propertyName($name) && $this->literal(':')) {
$foundSomething = false;
if ($this->valueList($value)) {
$this->append([Type::T_ASSIGN, $name, $value], $s);
$foundSomething = true;
}
if ($this->literal('{')) {
$propBlock = $this->pushSpecialBlock(Type::T_NESTED_PROPERTY, $s);
$propBlock->prefix = $name;
$foundSomething = true;
} elseif ($foundSomething) {
$foundSomething = $this->end();
}
if ($foundSomething) {
return true;
}
}
$this->seek($s);
// closing a block
if ($this->literal('}')) {
$block = $this->popBlock();
if (isset($block->type) && $block->type === Type::T_INCLUDE) {
$include = $block->child;
unset($block->child);
$include[3] = $block;
$this->append($include, $s);
} elseif (empty($block->dontAppend)) {
$type = isset($block->type) ? $block->type : Type::T_BLOCK;
$this->append([$type, $block], $s);
}
return true;
}
// extra stuff
if ($this->literal(';') || $this->literal('<!--')) {
return true;
}
return false;
}