Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/capabilities-syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ Tracking issues: [#58](https://github.com/PurHur/php-compiler/issues/58), [#145]
| Arrow functions `fn () =>` | no | no | no | [#142](https://github.com/PurHur/php-compiler/issues/142) | |
| Magic constants `__CLASS__`, `__METHOD__`, `__FUNCTION__` | yes | yes | yes | [#199](https://github.com/PurHur/php-compiler/issues/199) | Lowered at parse time via php-cfg MagicStringResolver; compliance PHPT |
| Magic constant `__NAMESPACE__` | yes | yes | yes | [#199](https://github.com/PurHur/php-compiler/issues/199) | Requires `namespace` declaration (#84); compliance PHPT |
| Magic constants `__DIR__`, `__FILE__` | yes | yes | yes | [#707](https://github.com/PurHur/php-compiler/issues/707) | VM script stack on include; JIT uses per-unit script path |
| Magic constant `__LINE__` | no | no | no | [#707](https://github.com/PurHur/php-compiler/issues/707) | Lowered at parse time via php-cfg MagicStringResolver (best effort) |

_Syntax AOT column reflects `Runtime::MODE_AOT` compile probes unless a row pins AOT (e.g. #568 native link)._
## Web north-star (`examples/003-MiniWebApp`)
Expand Down
37 changes: 37 additions & 0 deletions lib/Block.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
// used as a property type.
// @phan-suppress-next-line PhanUnreferencedUseNormal
use PHPCfg\Func;
use PHPCfg\Op;
use PHPCfg\Block as CfgBlock;
use PHPCfg\Operand;
use PHPCfg\Operand\Literal;
use PHPCfg\Operand\Temporary;
use PHPCfg\Operand\Variable as VarOperand;
use PHPCompiler\VM\Context;
use PHPCompiler\VM\ScriptStack;
use PHPCompiler\VM\Variable;
use PHPCompiler\Web\Superglobals;

Expand Down Expand Up @@ -69,12 +71,46 @@ class Block {
*/
public array $deployIncludePaths = [];

/** Absolute entry script path when CFG filename attribute is missing (issue #707). */
private string $scriptPathOverride = '';

public function __construct(?CfgBlock $block) {
$this->orig = $block;
$this->scope = new \SplObjectStorage;
$this->args = new \SplObjectStorage;
}

public function setScriptPath(string $path): void
{
$this->scriptPathOverride = ScriptStack::normalize($path);
}

/** Absolute path of the PHP source unit for this block (issue #707). */
public function scriptPath(): string
{
if ('' !== $this->scriptPathOverride) {
return $this->scriptPathOverride;
}
if (null !== $this->func) {
$file = $this->func->getFile();
if ('' !== $file && 'unknown' !== $file) {
return ScriptStack::normalize($file);
}
}
if (null !== $this->orig) {
foreach ($this->orig->children as $child) {
if ($child instanceof Op) {
$file = $child->getFile();
if ('' !== $file && 'unknown' !== $file) {
return ScriptStack::normalize($file);
}
}
}
}

return '';
}

public function getOperand(int $offset): Operand {
foreach ($this->scope as $operand) {
if ($this->scope[$operand] === $offset) {
Expand Down Expand Up @@ -285,6 +321,7 @@ public function getFrame(Context $context, ?Frame $frame = null): Frame {
}

$return = new Frame(null, $this, $frame, ...$scope);
$return->scriptPath = $this->scriptPath();
if (!is_null($frame) && !is_null($frame->returnVar)) {
$return->returnVar = $frame->returnVar;
}
Expand Down
7 changes: 7 additions & 0 deletions lib/Compiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,13 @@ protected function compileExpr(Op\Expr $expr, Block $block): array {
);
}
return $return;
case Op\Expr\MagicScriptConst::class:
return [new OpCode(
OpCode::TYPE_SCRIPT_MAGIC,
$this->compileOperand($expr->result, $block, false),
null,
$expr->kind,
)];
case Op\Expr\Include_::class:
return [$this->compileIncludeOp($expr, $block)];
case Op\Expr\Isset_::class:
Expand Down
3 changes: 3 additions & 0 deletions lib/Frame.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class Frame {
/** When true, finishing this frame resumes the caller instead of ending execution. */
public bool $ephemeral = false;

/** Absolute path of the script this frame executes (issue #707). */
public string $scriptPath = '';

/** VM context for nested builtin calls (set when invoking Internal handlers). */
public ?Context $vmContext = null;

Expand Down
9 changes: 9 additions & 0 deletions lib/JIT.php
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,15 @@ private function compileBlockInternal(
$value = JIT\IteratorHelper::compileValue($this->context, $array);
$this->assignOperand($block->getOperand($op->arg1), $value);
break;
case OpCode::TYPE_SCRIPT_MAGIC:
$magicStr = JIT\ScriptMagic::stringForBlock($block, (int) $op->arg3);
$lit = new Operand\Literal($magicStr);
$lit->type = \PHPTypes\Type::string();
$this->assignOperand(
$block->getOperand($op->arg1),
JIT\Variable::fromLiteral($this->context, $lit)
);
break;
case OpCode::TYPE_INCLUDE:
JIT\IncludeHelper::compileLiteral(
$this,
Expand Down
24 changes: 24 additions & 0 deletions lib/JIT/ScriptMagic.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\JIT;

use PHPCompiler\Block;
use PHPCompiler\OpCode;

/**
* Compile-time __DIR__ / __FILE__ for JIT using the unit's script path (#707).
*/
final class ScriptMagic
{
public static function stringForBlock(Block $block, int $kind): string
{
$path = $block->scriptPath();
if (OpCode::SCRIPT_MAGIC_DIR === $kind) {
return '' !== $path ? dirname($path) : '';
}

return $path;
}
}
6 changes: 6 additions & 0 deletions lib/OpCode.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ class OpCode {
const TYPE_CATCH = 93;
const TYPE_FINALLY = 94;
const TYPE_DECLARE_GLOBAL_CONST = 95;
/** Runtime __DIR__ / __FILE__ from script stack (issue #707). arg3 = SCRIPT_MAGIC_* kind. */
const TYPE_SCRIPT_MAGIC = 96;

public const SCRIPT_MAGIC_DIR = 1;

public const SCRIPT_MAGIC_FILE = 2;

public int $type;
public ?int $arg1;
Expand Down
2 changes: 2 additions & 0 deletions lib/OpCodeNames.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ function opcode_type_name(int $type): string
return 'TYPE_FINALLY';
case 95:
return 'TYPE_DECLARE_GLOBAL_CONST';
case 96:
return 'TYPE_SCRIPT_MAGIC';
default:
return 'unknown opcode';
}
Expand Down
19 changes: 17 additions & 2 deletions lib/Runtime.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,26 @@ public function standalone(?Block $block, string $outfile) {
}

public function parseAndCompile(string $code, string $filename): ?Block {
return $this->compile($this->parse($code, $filename));
$block = $this->compile($this->parse($code, $filename));
if (null !== $block) {
$block->setScriptPath($filename);
}

return $block;
}

public function parseAndCompileFile(string $filename): ?Block {
return $this->compile($this->parse(file_get_contents($filename), $filename));
$normalized = VM\ScriptStack::normalize($filename);
if ('' !== $normalized) {
$filename = $normalized;
}

$block = $this->compile($this->parse(file_get_contents($filename), $filename));
if (null !== $block) {
$block->setScriptPath($filename);
}

return $block;
}

/**
Expand Down
47 changes: 42 additions & 5 deletions lib/VM.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,29 @@ public function __construct(Context $context) {

public function run(Block $block): int {
if (!is_null($block->handler)) {
$block->handler->execute($block->getFrame($this->context));
$frame = $block->getFrame($this->context);
$this->seedScriptPath($frame);
$block->handler->execute($frame);
return self::SUCCESS;
}

$this->context->push($block->getFrame($this->context));
$frame = $block->getFrame($this->context);
$this->seedScriptPath($frame);
$this->context->push($frame);

$result = $this->runFrames();
if ('' !== $frame->scriptPath) {
$this->context->scriptStack->pop();
}

return $this->runFrames();
return $result;
}

private function seedScriptPath(Frame $frame): void
{
if ('' !== $frame->scriptPath) {
$this->context->scriptStack->push($frame->scriptPath);
}
}

private function runFrames(): int
Expand Down Expand Up @@ -460,9 +476,28 @@ private function runFrames(): int
&& Variable::TYPE_NULL !== $value->type
);
break;
case OpCode::TYPE_SCRIPT_MAGIC:
$script = '' !== $frame->scriptPath
? $frame->scriptPath
: $this->context->scriptStack->current();
if ('' === $script) {
return $this->raise('__DIR__/__FILE__ used without script context', $frame);
}
$dst = $frame->scope[$op->arg1];
if (OpCode::SCRIPT_MAGIC_DIR === $op->arg3) {
$dst->string(dirname($script));
} else {
$dst->string($script);
}
break;
case OpCode::TYPE_INCLUDE:
$file = $frame->scope[$op->arg1]->toString();
$parsed = $this->context->runtime->parseAndCompileFile($file);
$resolved = VM\ScriptStack::normalize($file);
if ('' === $resolved || !is_file($resolved)) {
return $this->raise('Failed opening required \''.$file.'\' for inclusion', $frame);
}
$this->context->scriptStack->push($resolved);
$parsed = $this->context->runtime->parseAndCompileFile($resolved);
$new = $parsed->getFrame($this->context, $frame);
$new->ephemeral = true;
$new->parent = $frame;
Expand Down Expand Up @@ -530,6 +565,7 @@ private function runFrames(): int
}
}
if ($frame->ephemeral) {
$this->context->scriptStack->pop();
if (null !== $frame->parent) {
$frame = $frame->parent;
goto restart;
Expand All @@ -541,7 +577,8 @@ private function runFrames(): int

protected function raise(string $message, Frame $frame): int
{
throw new \LogicException($message.' in '.$frame->block->getName());
$where = '' !== $frame->scriptPath ? $frame->scriptPath : 'script';
throw new \LogicException($message.' in '.$where);
}

protected function defineClass(ClassEntry $entry, Block $block): void {
Expand Down
3 changes: 3 additions & 0 deletions lib/VM/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ class Context {

public ErrorReporter $errors;

public ScriptStack $scriptStack;

public function __construct(Runtime $runtime) {
$this->runtime = $runtime;
$this->errors = new ErrorReporter();
$this->scriptStack = new ScriptStack();
}

public function constantFetch(string $name): ?Variable {
Expand Down
52 changes: 52 additions & 0 deletions lib/VM/ScriptStack.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\VM;

/**
* Tracks the currently executing script path for __DIR__ / __FILE__ (issue #707, #85).
*/
final class ScriptStack
{
/** @var list<string> absolute script paths */
private array $stack = [];

public function push(string $scriptPath): void
{
$normalized = self::normalize($scriptPath);
if ('' === $normalized) {
return;
}
$this->stack[] = $normalized;
}

public function pop(): void
{
if ([] !== $this->stack) {
array_pop($this->stack);
}
}

public function current(): string
{
if ([] === $this->stack) {
return '';
}

return $this->stack[count($this->stack) - 1];
}

public static function normalize(string $path): string
{
if ('' === $path) {
return '';
}
$resolved = realpath($path);
if (false !== $resolved) {
return $resolved;
}

return $path;
}
}
Loading