protected function __construct($file, $target, $included = false)
{
$this->file = $file;
$this->target = $target;
$this->revision = ++Object::$lastRevision;
$this->data = file_get_contents($file);
if (substr($this->data, 0, 2) === '#!') {
if (!is_executable($file)) {
$this->raiseError('Shebang (#!) detected in the first line, but file hasn\'t +x mode.');
return;
}
$this->data = shell_exec($file);
}
$this->data = str_replace("\r", '', $this->data);
$this->length = mb_orig_strlen($this->data);
$this->state[] = [static::T_ALL, $this->target];
$this->tokens = [static::T_COMMENT => function ($c) {
if ($c === "\n") {
array_pop($this->state);
}
}, static::T_STRING_DOUBLE => function ($q) {
$str = '';
++$this->p;
for (; $this->p < $this->length; ++$this->p) {
$c = $this->getCurrentChar();
if ($c === $q) {
++$this->p;
break;
} elseif ($c === '\\') {
next:
$n = $this->getNextChar();
if ($n === $q) {
$str .= $q;
++$this->p;
} elseif (ctype_digit($n)) {
$def = $n;
++$this->p;
for (; $this->p < min($this->length, $this->p + 2); ++$this->p) {
$n = $this->getNextChar();
if (!ctype_digit($n)) {
break;
}
$def .= $n;
}
$str .= chr((int) $def);
} elseif ($n === 'x' || $n === 'X') {
$def = $n;
++$this->p;
for (; $this->p < min($this->length, $this->p + 2); ++$this->p) {
$n = $this->getNextChar();
if (!ctype_xdigit($n)) {
break;
}
$def .= $n;
}
$str .= chr((int) hexdec($def));
} else {
$str .= $c;
}
} else {
$str .= $c;
}
}
if ($this->p >= $this->length) {
$this->raiseError('Unexpected End-Of-File.');
}
return $str;
}, static::T_STRING => function ($q) {
$str = '';
++$this->p;
for (; $this->p < $this->length; ++$this->p) {
$c = $this->getCurrentChar();
if ($c === $q) {
++$this->p;
break;
} elseif ($c === '\\') {
if ($this->getNextChar() === $q) {
$str .= $q;
++$this->p;
} else {
$str .= $c;
}
} else {
$str .= $c;
}
}
if ($this->p >= $this->length) {
$this->raiseError('Unexpected End-Of-File.');
}
return $str;
}, static::T_ALL => function ($c) {
if (ctype_space($c)) {
} elseif ($c === '#') {
$this->state[] = [static::T_COMMENT];
} elseif ($c === '}') {
if (sizeof($this->state) > 1) {
$this->purgeScope($this->getCurrentScope());
array_pop($this->state);
} else {
$this->raiseError('Unexpected \'}\'');
}
} elseif (ctype_alnum($c) || $c === '\\') {
$elements = [''];
$elTypes = [null];
$i = 0;
$tokenType = 0;
$newLineDetected = null;
for (; $this->p < $this->length; ++$this->p) {
$prePoint = [$this->line, $this->col - 1];
$c = $this->getCurrentChar();
if (ctype_space($c) || $c === '=' || $c === ',') {
if ($c === "\n") {
$newLineDetected = $prePoint;
}
if ($elTypes[$i] !== null) {
++$i;
$elTypes[$i] = null;
}
} elseif ($c === '\'') {
if ($elTypes[$i] !== null) {
$this->raiseError('Unexpected T_STRING.');
}
$string = $this->token(static::T_STRING, $c);
--$this->p;
if ($elTypes[$i] === null) {
$elements[$i] = $string;
$elTypes[$i] = static::T_STRING;
}
} elseif ($c === '"') {
if ($elTypes[$i] !== null) {
$this->raiseError('Unexpected T_STRING_DOUBLE.');
}
$string = $this->token(static::T_STRING_DOUBLE, $c);
--$this->p;
if ($elTypes[$i] === null) {
$elements[$i] = $string;
$elTypes[$i] = static::T_STRING_DOUBLE;
}
} elseif ($c === '}') {
$this->raiseError('Unexpected \'}\' instead of \';\' or \'{\'');
} elseif ($c === ';') {
if ($newLineDetected) {
$this->raiseError('Unexpected new-line instead of \';\'', 'notice', $newLineDetected[0], $newLineDetected[1]);
}
$tokenType = static::T_VAR;
break;
} elseif ($c === '{') {
$tokenType = static::T_BLOCK;
break;
} else {
if ($elTypes[$i] === static::T_STRING) {
$this->raiseError('Unexpected T_CVALUE.');
} else {
if (!isset($elements[$i])) {
$elements[$i] = '';
}
$elements[$i] .= $c;
$elTypes[$i] = static::T_CVALUE;
}
}
}
foreach ($elTypes as $k => $v) {
if (static::T_CVALUE === $v) {
if (ctype_digit($elements[$k])) {
$elements[$k] = (int) $elements[$k];
} elseif (is_numeric($elements[$k])) {
$elements[$k] = (double) $elements[$k];
} else {
$l = strtolower($elements[$k]);
if ($l === 'true' || $l === 'on') {
$elements[$k] = true;
} elseif ($l === 'false' || $l === 'off') {
$elements[$k] = false;
} elseif ($l === 'null') {
$elements[$k] = null;
}
}
}
}
if ($tokenType === 0) {
$this->raiseError('Expected \';\' or \'{\'');
} elseif ($tokenType === static::T_VAR) {
$name = str_replace('-', '', strtolower($elements[0]));
if (sizeof($elements) > 2) {
$value = array_slice($elements, 1);
} else {
$value = isset($elements[1]) ? $elements[1] : null;
}
$scope = $this->getCurrentScope();
if ($name === 'include') {
if (!is_array($value)) {
$value = [$value];
}
foreach ($value as $path) {
if (substr($path, 0, 1) !== '/') {
$path = 'conf/' . $path;
}
$files = glob($path);
if ($files) {
foreach ($files as $fn) {
try {
static::parse($fn, $scope, true);
} catch (InfiniteRecursion $e) {
$this->raiseError('Cannot include \'' . $fn . '\' as a part of itself, it may cause an infinite recursion.');
}
}
}
}
} else {
if (sizeof($elements) === 1) {
$value = true;
$elements[1] = true;
$elTypes[1] = static::T_CVALUE;
} elseif ($value === null) {
$value = null;
$elements[1] = null;
$elTypes[1] = static::T_CVALUE;
}
if (isset($scope->{$name})) {
if ($scope->{$name}->source !== 'cmdline') {
if ($elTypes[1] === static::T_CVALUE && is_string($value)) {
$scope->{$name}->pushHumanValue($value);
} else {
$scope->{$name}->pushValue($value);
}
$scope->{$name}->source = 'config';
$scope->{$name}->revision = $this->revision;
}
} elseif ($scope instanceof Section) {
$scope->{$name} = new Generic();
$scope->{$name}->source = 'config';
$scope->{$name}->revision = $this->revision;
$scope->{$name}->pushValue($value);
$scope->{$name}->setValueType($value);
} else {
$this->raiseError('Unrecognized parameter \'' . $name . '\'');
}
}
} elseif ($tokenType === static::T_BLOCK) {
$scope = $this->getCurrentScope();
$sectionName = implode('-', $elements);
$sectionName = strtr($sectionName, '-. ', ':::');
if (!isset($scope->{$sectionName})) {
$scope->{$sectionName} = new Section();
}
$scope->{$sectionName}->source = 'config';
$scope->{$sectionName}->revision = $this->revision;
$this->state[] = [static::T_ALL, $scope->{$sectionName}];
}
} else {
$this->raiseError('Unexpected char \'' . Debug::exportBytes($c) . '\'');
}
}];
for (; $this->p < $this->length; ++$this->p) {
$c = $this->getCurrentChar();
$e = end($this->state);
$this->token($e[0], $c);
}
if (!$included) {
$this->purgeScope($this->target);
}
if (Daemon::$config->verbosetty->value) {
Daemon::log('Loaded config file: ' . escapeshellarg($file));
}
}