From feb7fc43ddb5d138b221753a8a1e7ea7f4487f16 Mon Sep 17 00:00:00 2001 From: tuqqu Date: Tue, 19 Mar 2024 22:26:09 +0100 Subject: [PATCH] Callstack collection and dump & positions for invokable --- .php-cs-fixer.php | 10 +-- README.md | 6 +- bin/go-php | 29 ++++++--- composer.json | 7 ++- examples/helloworld/main.php | 2 +- examples/sorting/main.php | 2 +- src/Builtin/BuiltinFunc/Recover.php | 6 +- src/CallStackCollectorDebugger.php | 44 ------------- src/Debug/CallStack.php | 49 +++++++++++++++ src/Debug/CallStackCollectorDebugger.php | 43 +++++++++++++ src/Debug/CallTrace.php | 15 +++++ src/Debug/Debugger.php | 16 +++++ src/Debug/utils.php | 44 +++++++++++++ src/Debugger.php | 17 ------ src/DeferredStack.php | 2 +- src/Env/EnvValue.php | 18 ++++-- src/Env/Environment.php | 2 +- src/Error/InterfaceTypeError.php | 2 +- src/Error/RuntimeError.php | 22 +++---- .../{OutputToStream.php => StreamWriter.php} | 6 +- src/GoType/FuncType.php | 2 +- src/GoValue/AddressableTrait.php | 15 ++++- src/GoValue/AddressableValue.php | 7 ++- src/GoValue/Map/MapValue.php | 2 +- src/ImportHandler.php | 12 ++-- src/Interpreter.php | 61 +++++++++++++------ src/InvokableCall.php | 4 +- src/PanicPointer.php | 17 +++++- src/PositionAwareTrait.php | 22 +++++++ src/RuntimeResult.php | 1 + src/RuntimeResultBuilder.php | 1 + src/Stream/ResourceInputStream.php | 6 +- src/Stream/ResourceOutputStream.php | 2 +- src/Stream/StringOutputStream.php | 2 +- tests/Functional/InterpreterTest.php | 6 +- 35 files changed, 363 insertions(+), 139 deletions(-) delete mode 100644 src/CallStackCollectorDebugger.php create mode 100644 src/Debug/CallStack.php create mode 100644 src/Debug/CallStackCollectorDebugger.php create mode 100644 src/Debug/CallTrace.php create mode 100644 src/Debug/Debugger.php create mode 100644 src/Debug/utils.php delete mode 100644 src/Debugger.php rename src/ErrorHandler/{OutputToStream.php => StreamWriter.php} (65%) create mode 100644 src/PositionAwareTrait.php diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index d70f07a77..bda97e2d4 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -18,10 +18,12 @@ ]; config->setRules(rules); -config->setFinder(PhpCsFixer\Finder::create() - ->in(__DIR__ . '/bin') - ->in(__DIR__ . '/src') - ->in(__DIR__ . '/tests') +config->setRiskyAllowed(true); +config->setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__ . '/bin') + ->in(__DIR__ . '/src') + ->in(__DIR__ . '/tests'), ); return config; diff --git a/README.md b/README.md index 08fa5018c..ef99b2572 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ php main.php ## WIP -This is a toy project, currently work-in-progress. +This is a toy project, not intended for production use. To see what is already implemented, refer to [tests](tests/Functional/files/). @@ -61,3 +61,7 @@ make test ``` run `make help` for more commands. + +## Differences from the Go compiler + +- No support for real goroutines, go statements run sequentially diff --git a/bin/go-php b/bin/go-php index ad367337b..4ffa2dc2c 100755 --- a/bin/go-php +++ b/bin/go-php @@ -4,12 +4,15 @@ declare(strict_types=1); use GoPhp\EnvVarSet; -use GoPhp\ErrorHandler\OutputToStream; +use GoPhp\ErrorHandler\StreamWriter; +use GoPhp\ExitCode; use GoPhp\Interpreter; use GoPhp\Stream\ResourceOutputStream; use const GoPhp\VERSION; +use function GoPhp\Debug\dump_call_stack; + const AUTOLOAD_PATHS = [ __DIR__ . '/../vendor/autoload.php', __DIR__ . '/../../../autoload.php', @@ -55,6 +58,7 @@ function print_help(): void -e, --eval Evaluate given code --goroot Set GOROOT environment variable --gopath Set GOPATH environment variable + --dump-stack-trace Dump stack trace on error HELP, VERSION)); } @@ -71,6 +75,7 @@ enum Flag: string case Eval = 'eval'; case Goroot = 'goroot'; case Gopath = 'gopath'; + case DumpStackTrace = 'dump-stack-trace'; private const array FLAGS_WITH_VALUE = [ self::Goroot, @@ -81,6 +86,7 @@ enum Flag: string self::Help, self::Version, self::Eval, + self::DumpStackTrace, ]; /** @@ -139,6 +145,7 @@ function main(array $argv): never $gopath = EnvVarSet::DEFAULT_GOPATH; $eval = false; $script = null; + $dumpTrace = false; foreach ($argv as $arg) { if ($eval) { @@ -166,6 +173,9 @@ function main(array $argv): never case Flag::Eval: $eval = true; break; + case Flag::DumpStackTrace: + $dumpTrace = true; + break; default: $file = $arg; break 2; @@ -177,25 +187,30 @@ function main(array $argv): never exit(0); } - $src = $script !== null - ? $script - : file_get_contents($file); - $stderr = new ResourceOutputStream(STDERR); - $errorHandler = new OutputToStream($stderr); + $errorHandler = new StreamWriter($stderr); $runtime = Interpreter::create( - source: $src, + source: $script !== null ? $script : null, errorHandler: $errorHandler, envVars: new EnvVarSet( goroot: $goroot, gopath: $gopath, ), + filename: $file, toplevel: $script !== null, + debug: $dumpTrace, ); $result = $runtime->run(); + if ($dumpTrace && $result->exitCode === ExitCode::Failure) { + $callStack = $result->debugger?->getCallStack(); + + fwrite(STDERR, "\n"); + dump_call_stack($callStack); + } + exit($result->exitCode->value); } diff --git a/composer.json b/composer.json index 9f6c148e1..d64e3e35a 100644 --- a/composer.json +++ b/composer.json @@ -14,10 +14,10 @@ "tuqqu/go-parser": "^0.5.1" }, "require-dev": { - "symfony/var-dumper": "^6", - "friendsofphp/php-cs-fixer": "^3.48", + "symfony/var-dumper": "^7", + "friendsofphp/php-cs-fixer": "^3.59", "phpunit/phpunit": "^10.5", - "vimeo/psalm": "^5.20" + "vimeo/psalm": "^5.25" }, "bin": [ "bin/go-php" @@ -29,6 +29,7 @@ "files": [ "src/asserts.php", "src/utils.php", + "src/Debug/utils.php", "src/GoValue/utils.php" ] }, diff --git a/examples/helloworld/main.php b/examples/helloworld/main.php index dfac332ac..042e9c79b 100644 --- a/examples/helloworld/main.php +++ b/examples/helloworld/main.php @@ -22,4 +22,4 @@ $result = $interp->run(); print "Output:\n$stdout\n"; -print "Exit code: $result->exitCode->value\n"; +print "Exit code: {$result->exitCode->value}\n"; diff --git a/examples/sorting/main.php b/examples/sorting/main.php index 078b814c7..17d0d195e 100644 --- a/examples/sorting/main.php +++ b/examples/sorting/main.php @@ -22,4 +22,4 @@ $result = $interp->run(); print "Output:\n$stdout\n"; -print "Exit code: $result->exitCode->value\n"; +print "Exit code: {$result->exitCode->value}\n"; diff --git a/src/Builtin/BuiltinFunc/Recover.php b/src/Builtin/BuiltinFunc/Recover.php index e142c7e46..2b9deab52 100644 --- a/src/Builtin/BuiltinFunc/Recover.php +++ b/src/Builtin/BuiltinFunc/Recover.php @@ -24,10 +24,10 @@ public function __construct( public function __invoke(Argv $argv): InterfaceValue { assert_argc($this, $argv, 0); + $lastPanic = $this->panicPointer->pointsTo(); - if ($this->panicPointer->panic !== null) { - $lastPanic = $this->panicPointer->panic; - $this->panicPointer->panic = null; + if ($lastPanic !== null) { + $this->panicPointer->clear(); return new InterfaceValue($lastPanic->panicValue); } diff --git a/src/CallStackCollectorDebugger.php b/src/CallStackCollectorDebugger.php deleted file mode 100644 index f1b128367..000000000 --- a/src/CallStackCollectorDebugger.php +++ /dev/null @@ -1,44 +0,0 @@ - */ - private array $stackTrace = []; - - public function __construct( - private readonly bool $enableDebug = true, - private readonly int $maxTraceDepth = self::DEFAULT_STACK_TRACE_DEPTH, - ) {} - - public function addStackTrace(InvokableCall|PanicError $call): void - { - if (!$this->enableDebug) { - return; - } - - $this->stackTrace[] = $call; - - if (count($this->stackTrace) > $this->maxTraceDepth) { - array_shift($this->stackTrace); - } - } - - /** - * @return list - */ - public function getStackTrace(): array - { - return $this->stackTrace; - } -} diff --git a/src/Debug/CallStack.php b/src/Debug/CallStack.php new file mode 100644 index 000000000..2da69c202 --- /dev/null +++ b/src/Debug/CallStack.php @@ -0,0 +1,49 @@ + + */ + private array $callTraces = []; + + public function __construct( + private readonly int $limit, + ) {} + + public function add(CallTrace $callTrace): void + { + if (count($this->callTraces) > $this->limit) { + array_shift($this->callTraces); + } + + $this->callTraces[] = $callTrace; + } + + public function pop(): void + { + array_pop($this->callTraces); + } + + /** + * @return list + */ + public function getCallTraces(): array + { + return array_reverse($this->callTraces); + } + + public function count(): int + { + return count($this->callTraces); + } +} diff --git a/src/Debug/CallStackCollectorDebugger.php b/src/Debug/CallStackCollectorDebugger.php new file mode 100644 index 000000000..9c10330b8 --- /dev/null +++ b/src/Debug/CallStackCollectorDebugger.php @@ -0,0 +1,43 @@ +callStack = new CallStack($maxTraceDepth); + } + + public function addStackTrace(InvokableCall $call): void + { + $name = match (true) { + $call->func instanceof AddressableValue => $call->func->getQualifiedName(), + $call->func instanceof BuiltinFuncValue => $call->func->getName(), + default => throw new InternalError(sprintf('unknown call stack value %s', $call->func::class)), + }; + + $this->callStack->add(new CallTrace($name, $call->getPosition())); + } + + public function releaseLastStackTrace(): void + { + $this->callStack->pop(); + } + + public function getCallStack(): CallStack + { + return $this->callStack; + } +} diff --git a/src/Debug/CallTrace.php b/src/Debug/CallTrace.php new file mode 100644 index 000000000..9b5d90902 --- /dev/null +++ b/src/Debug/CallTrace.php @@ -0,0 +1,15 @@ + $position === null + ? 'unknown' + : sprintf( + "%s:%d:%d", + $position->filename === null + ? '' + : realpath($position->filename), + $position->line, + $position->offset, + ); + + $callTraces = $callStack->getCallTraces(); + $length = $callStack->count(); + + for ($i = 0; $i < $length; $i++) { + if ($i === 0) { + continue; + } + + fwrite(STDERR, sprintf( + "%s(...)\n\t%s\n", + $callTraces[$i]->name, + $positionDumper($callTraces[$i - 1]->position), + )); + } +} diff --git a/src/Debugger.php b/src/Debugger.php deleted file mode 100644 index ce66593b2..000000000 --- a/src/Debugger.php +++ /dev/null @@ -1,17 +0,0 @@ - - */ - public function getStackTrace(): array; -} diff --git a/src/DeferredStack.php b/src/DeferredStack.php index fd53b5b6e..ad5cc2966 100644 --- a/src/DeferredStack.php +++ b/src/DeferredStack.php @@ -32,7 +32,7 @@ public function iter(): iterable } yield from array_reverse( - $this->stack[$this->context - 1]->get() + $this->stack[$this->context - 1]->get(), ); unset($this->stack[$this->context - 1]); diff --git a/src/Env/EnvValue.php b/src/Env/EnvValue.php index 418ae3b05..e67d9b1c6 100644 --- a/src/Env/EnvValue.php +++ b/src/Env/EnvValue.php @@ -22,10 +22,15 @@ final class EnvValue { public readonly string $name; + public readonly ?string $namespace; private readonly GoValue $value; - public function __construct(string $name, GoValue $value, ?GoType $type = null) - { + public function __construct( + string $name, + GoValue $value, + ?GoType $type = null, + ?string $namespace = null, + ) { if ($type instanceof UntypedNilType) { throw RuntimeError::untypedNilInVarDecl(); } @@ -51,14 +56,19 @@ public function __construct(string $name, GoValue $value, ?GoType $type = null) } } + if ($value instanceof AddressableValue) { + $value->addressedWithName($name, $namespace); + } + $this->name = $name; + $this->namespace = $namespace; $this->value = $value; } public function unwrap(): GoValue { if ($this->value instanceof AddressableValue) { - $this->value->addressedWithName($this->name); + $this->value->addressedWithName($this->name, $this->namespace); } return $this->value; @@ -66,7 +76,7 @@ public function unwrap(): GoValue public function copy(): self { - return new self($this->name, $this->value->copy()); + return new self($this->name, $this->value->copy(), namespace: $this->namespace); } public function equals(self $other): bool diff --git a/src/Env/Environment.php b/src/Env/Environment.php index cb4f206f0..aa9245d6a 100644 --- a/src/Env/Environment.php +++ b/src/Env/Environment.php @@ -135,7 +135,7 @@ private function defineAddressableValue( string $namespace, ): void { $value->makeAddressable(); - $envValue = new EnvValue($name, $value, $type); + $envValue = new EnvValue($name, $value, $type, $namespace); if ($name === $this->blankIdent) { return; diff --git a/src/Error/InterfaceTypeError.php b/src/Error/InterfaceTypeError.php index 6ec9acdf0..bca03de54 100644 --- a/src/Error/InterfaceTypeError.php +++ b/src/Error/InterfaceTypeError.php @@ -20,7 +20,7 @@ public static function cannotUseAsInterfaceType(WrappedValue|WrappedType $value, $missingMethod = (string) $interfaceType->tryGetMissingMethod( $value instanceof WrappedType ? $value - : $value->type() + : $value->type(), ); $error = self::cannotUseAsType($value, $interfaceType, $missingMethod); diff --git a/src/Error/RuntimeError.php b/src/Error/RuntimeError.php index a253777dc..680e9b6d9 100644 --- a/src/Error/RuntimeError.php +++ b/src/Error/RuntimeError.php @@ -96,7 +96,7 @@ public static function cannotUseValue(GoValue $value, GoType $type, ?string $con self::valueToString($value), $type->name(), $context === null ? '' : ' ' . $context, - ) + ), ); } @@ -196,21 +196,21 @@ public static function undefinedOperator(Operator $op, AddressableValue $value, 'invalid operation: operator %s not defined on %s', $op->value, self::valueToString($value), - ) + ), ); } public static function cannotIndex(GoType $type): self { return new self( - sprintf('invalid operation: cannot index (%s)', $type->name()) + sprintf('invalid operation: cannot index (%s)', $type->name()), ); } public static function cannotSlice(GoType $type): self { return new self( - sprintf('invalid operation: cannot slice (%s)', $type->name()) + sprintf('invalid operation: cannot slice (%s)', $type->name()), ); } @@ -245,7 +245,7 @@ public static function cannotTakeAddressOfMapValue(GoType $type): self sprintf( 'invalid operation: cannot take address of value (map index expression of type %s)', $type->name(), - ) + ), ); } @@ -255,7 +255,7 @@ public static function cannotTakeAddressOfValue(GoValue $value): self sprintf( 'invalid operation: cannot take address of %s', self::valueToString($value), - ) + ), ); } @@ -266,7 +266,7 @@ public static function unsupportedOperation(string $operation, GoValue $value): 'Value of type "%s" does not support "%s" operation', $value->type()->name(), $operation, - ) + ), ); } @@ -281,7 +281,7 @@ public static function nonFunctionCall(GoValue $value): self sprintf( 'invalid operation: cannot call non-function %s', self::valueToString($value), - ) + ), ); } @@ -291,7 +291,7 @@ public static function expectedAssignmentOperator(Operator $op): self sprintf( 'Unexpected operator "%s" in assignment', $op->value, - ) + ), ); } @@ -314,7 +314,7 @@ public static function wrongArgumentType(Arg $arg, string|GoType $expectedType): $arg->pos, $arg->value->type()->name(), is_string($expectedType) ? $expectedType : $expectedType->name(), - ) + ), ); } @@ -357,7 +357,7 @@ public static function indexOfWrongType(GoValue $index, string $type, string $wh $index->type()->name(), $type, $where, - ) + ), ); } diff --git a/src/ErrorHandler/OutputToStream.php b/src/ErrorHandler/StreamWriter.php similarity index 65% rename from src/ErrorHandler/OutputToStream.php rename to src/ErrorHandler/StreamWriter.php index 5daac1a04..e7c5ef9b1 100644 --- a/src/ErrorHandler/OutputToStream.php +++ b/src/ErrorHandler/StreamWriter.php @@ -8,12 +8,12 @@ use GoPhp\Stream\OutputStream; /** - * Error handler that outputs error messages to a stream. + * Error handler that writes error messages to a stream. */ -final class OutputToStream implements ErrorHandler +final class StreamWriter implements ErrorHandler { public function __construct( - private readonly OutputStream $stream + private readonly OutputStream $stream, ) {} public function onError(GoError $error): void diff --git a/src/GoType/FuncType.php b/src/GoType/FuncType.php index 668a2e668..e94f02792 100644 --- a/src/GoType/FuncType.php +++ b/src/GoType/FuncType.php @@ -40,7 +40,7 @@ public function name(): string 0 => '', 1 => sprintf(' %s', $this->returns), default => sprintf(' (%s)', $this->returns), - } + }, ); } diff --git a/src/GoValue/AddressableTrait.php b/src/GoValue/AddressableTrait.php index 8e6331c4d..d05c2a68f 100644 --- a/src/GoValue/AddressableTrait.php +++ b/src/GoValue/AddressableTrait.php @@ -4,13 +4,20 @@ namespace GoPhp\GoValue; +use GoPhp\Env\EnvMap; + +use function GoPhp\construct_qualified_name; + /** * Default implementation of AddressableValue interface. */ trait AddressableTrait { + private const string NULL_NAMESPACE = EnvMap::NAMESPACE_TOP; + protected bool $addressable = false; protected ?string $name = null; + protected string $namespace = self::NULL_NAMESPACE; public function makeAddressable(): void { @@ -22,13 +29,19 @@ public function isAddressable(): bool return $this->addressable; } - public function addressedWithName(string $name): void + public function addressedWithName(string $name, ?string $namespace = null): void { $this->name = $name; + $this->namespace = $namespace ?? self::NULL_NAMESPACE; } public function getName(): string { return $this->name ?? ''; } + + public function getQualifiedName(): string + { + return construct_qualified_name($this->getName(), $this->namespace); + } } diff --git a/src/GoValue/AddressableValue.php b/src/GoValue/AddressableValue.php index 66720ddd4..612d0e033 100644 --- a/src/GoValue/AddressableValue.php +++ b/src/GoValue/AddressableValue.php @@ -33,10 +33,15 @@ public function isAddressable(): bool; */ public function getName(): string; + /** + * Returns the fully qualified name. + */ + public function getQualifiedName(): string; + /** * Sets the name with which current value has been addressed. */ - public function addressedWithName(string $name): void; + public function addressedWithName(string $name, ?string $namespace = null): void; /** * Copying an addressable value results in an addressable value. diff --git a/src/GoValue/Map/MapValue.php b/src/GoValue/Map/MapValue.php index e00b52d13..34145db62 100644 --- a/src/GoValue/Map/MapValue.php +++ b/src/GoValue/Map/MapValue.php @@ -64,7 +64,7 @@ public function toString(): string $str[] = sprintf( '%s:%s', $key->toString(), - $value->toString() + $value->toString(), ); } diff --git a/src/ImportHandler.php b/src/ImportHandler.php index 79fc4df4b..4d1c570d0 100644 --- a/src/ImportHandler.php +++ b/src/ImportHandler.php @@ -31,6 +31,11 @@ public function __construct( $this->extensions = self::EXTENSIONS + $customExtensions; } + public function importFromFile(string $path): string + { + return file_get_contents($path); + } + /** * @return iterable */ @@ -48,7 +53,7 @@ public function importFromPath(string $path): iterable $file = $path . $extension; if (is_file($file)) { - yield self::importFromFile($file); + yield $this->importFromFile($file); return; } @@ -74,9 +79,4 @@ protected function importFromDirectory(string $path): iterable // fixme add go.mod support } } - - protected static function importFromFile(string $path): string - { - return file_get_contents($path); - } } diff --git a/src/Interpreter.php b/src/Interpreter.php index 165cf00a9..d125af965 100644 --- a/src/Interpreter.php +++ b/src/Interpreter.php @@ -64,6 +64,8 @@ use GoPhp\Builtin\BuiltinProvider; use GoPhp\Builtin\Iota; use GoPhp\Builtin\StdBuiltinProvider; +use GoPhp\Debug\CallStackCollectorDebugger; +use GoPhp\Debug\Debugger; use GoPhp\Env\Environment; use GoPhp\Error\AbortExecutionError; use GoPhp\Error\InternalError; @@ -71,7 +73,7 @@ use GoPhp\Error\ParserError; use GoPhp\Error\RuntimeError; use GoPhp\ErrorHandler\ErrorHandler; -use GoPhp\ErrorHandler\OutputToStream; +use GoPhp\ErrorHandler\StreamWriter; use GoPhp\GoType\ArrayType; use GoPhp\GoType\GoType; use GoPhp\GoType\InterfaceType; @@ -129,13 +131,14 @@ final class Interpreter private readonly ScopeResolver $scopeResolver; private readonly InvokableCallList $initializers; private readonly TypeResolver $typeResolver; - private Ast $ast; private bool $constContext = false; private int $switchContext = 0; private ?FuncValue $entryPoint = null; + private Ast $ast; public function __construct( private readonly string $source, + private readonly ?string $filename, private readonly Argv $argv, private readonly ErrorHandler $errorHandler, private readonly StreamProvider $streams, @@ -147,6 +150,7 @@ public function __construct( private Environment $env, private readonly ?Debugger $debugger, ) { + self::init(); $this->jumpStack = new JumpStack(); $this->deferredStack = new DeferredStack(); $this->scopeResolver = new ScopeResolver(); @@ -161,7 +165,7 @@ public function __construct( /** * Creates an interpreter instance * - * @param string $source Source code to execute + * @param string|null $source Source code to execute * @param list $argv Command line arguments * @param BuiltinProvider|null $builtin Builtin package provider * @param ErrorHandler|null $errorHandler Error handler @@ -169,6 +173,7 @@ public function __construct( * @param FuncTypeValidator $entryPointValidator Validator for entry point function * @param FuncTypeValidator $initValidator Validator for package initializer functions * @param EnvVarSet $envVars Environment variables + * @param string|null $filename Filename of the source code * @param bool $toplevel Whether the source is a top level code or not * @param bool $debug Whether to enable debug mode or not * @param Debugger|null $debugger Debugger, if $debug is set to false, this is ignored @@ -177,7 +182,7 @@ public function __construct( * @return self */ public static function create( - string $source, + ?string $source, array $argv = [], ?BuiltinProvider $builtin = null, ?ErrorHandler $errorHandler = null, @@ -188,18 +193,15 @@ public static function create( ), FuncTypeValidator $initValidator = new ZeroArityValidator(INITIALIZER_FUNC), EnvVarSet $envVars = new EnvVarSet(), + ?string $filename = null, bool $toplevel = false, bool $debug = false, ?Debugger $debugger = null, array $customFileExtensions = [], ): self { - static $init = false; - if (!$init) { - $init = true; - self::$noneJump = new None(); - } + self::init(); - $errorHandler ??= new OutputToStream($streams->stderr()); + $errorHandler ??= new StreamWriter($streams->stderr()); $builtin ??= new StdBuiltinProvider($streams->stderr()); $importHandler = new ImportHandler($envVars, $customFileExtensions); @@ -209,8 +211,17 @@ public static function create( $debugger = null; } + if ($source === null) { + if ($filename === null) { + throw new InternalError('source or filename must be provided'); + } + + $source = $importHandler->importFromFile($filename); + } + return new self( source: $toplevel ? self::wrapSource($source, $entryPointValidator) : $source, + filename: $filename, argv: (new ArgvBuilder($argv))->build(), errorHandler: $errorHandler, streams: $streams, @@ -353,7 +364,7 @@ private function setAst(Ast $ast): void private function parseSourceToAst(string $source): Ast { - $parser = new Parser($source); + $parser = new Parser($source, $this->filename); /** @var Ast $ast */ $ast = $parser->parse(); @@ -665,13 +676,16 @@ private function evalCallExprWithoutCall(CallExpr $expr): InvokableCall $argvBuilder->markUnpacked($func); } - return new InvokableCall($func, $argvBuilder->build()); + $call = new InvokableCall($func, $argvBuilder->build()); + $call->setPosition($expr->lParen->pos); + + return $call; } private function evalCallExpr(CallExpr $expr): GoValue { return $this->callFunc( - $this->evalCallExprWithoutCall($expr) + $this->evalCallExprWithoutCall($expr), ); } @@ -679,18 +693,19 @@ private function callFunc(InvokableCall $fn): GoValue { $this->jumpStack->push(new JumpHandler()); $this->deferredStack->newContext(); + $this->debugger?->addStackTrace($fn); try { $value = $fn(); - $this->debugger?->addStackTrace($fn); $this->releaseDeferredStack(); + $this->debugger?->releaseLastStackTrace(); return $value; } catch (PanicError $panic) { - $this->panicPointer->panic = $panic; + $this->panicPointer->set($panic); $this->releaseDeferredStack(); - if ($this->panicPointer->panic === null && ($recover = $fn->tryRecover()) !== null) { + if ($this->panicPointer->pointsTo() === null && ($recover = $fn->tryRecover()) !== null) { return $recover; } @@ -1500,7 +1515,7 @@ private function evalIdent(Ident $ident): GoValue { $value = $this->env->get( $ident->name, - $this->scopeResolver->currentPackage + $this->scopeResolver->currentPackage, )->unwrap(); if ($value === $this->iota && !$this->constContext) { @@ -1578,4 +1593,16 @@ private static function wrapSource(string $source, FuncTypeValidator $entryPoint } GO; } + + private static function init(): void + { + static $init = false; + + if ($init) { + return; + } + + $init = true; + self::$noneJump = new None(); + } } diff --git a/src/InvokableCall.php b/src/InvokableCall.php index 1eb6c44fc..e1eef0077 100644 --- a/src/InvokableCall.php +++ b/src/InvokableCall.php @@ -10,8 +10,10 @@ final class InvokableCall { + use PositionAwareTrait; + public function __construct( - private readonly Invokable $func, + public readonly Invokable $func, private readonly Argv $argv, ) {} diff --git a/src/PanicPointer.php b/src/PanicPointer.php index 82fb20e70..93a55c1b9 100644 --- a/src/PanicPointer.php +++ b/src/PanicPointer.php @@ -8,5 +8,20 @@ final class PanicPointer { - public ?PanicError $panic = null; + private ?PanicError $panic = null; + + public function set(PanicError $panic): void + { + $this->panic = $panic; + } + + public function clear(): void + { + $this->panic = null; + } + + public function pointsTo(): ?PanicError + { + return $this->panic; + } } diff --git a/src/PositionAwareTrait.php b/src/PositionAwareTrait.php new file mode 100644 index 000000000..9e7a6907b --- /dev/null +++ b/src/PositionAwareTrait.php @@ -0,0 +1,22 @@ +position; + } + + public function setPosition(Position $position): void + { + $this->position = $position; + } +} diff --git a/src/RuntimeResult.php b/src/RuntimeResult.php index a0a6d8d65..667232829 100644 --- a/src/RuntimeResult.php +++ b/src/RuntimeResult.php @@ -4,6 +4,7 @@ namespace GoPhp; +use GoPhp\Debug\Debugger; use GoPhp\Error\GoError; use GoPhp\GoValue\GoValue; diff --git a/src/RuntimeResultBuilder.php b/src/RuntimeResultBuilder.php index b21b5191d..a6280e05b 100644 --- a/src/RuntimeResultBuilder.php +++ b/src/RuntimeResultBuilder.php @@ -4,6 +4,7 @@ namespace GoPhp; +use GoPhp\Debug\Debugger; use GoPhp\Error\GoError; use GoPhp\Error\InternalError; use GoPhp\GoValue\GoValue; diff --git a/src/Stream/ResourceInputStream.php b/src/Stream/ResourceInputStream.php index 2e6eba0ab..cc7f3e296 100644 --- a/src/Stream/ResourceInputStream.php +++ b/src/Stream/ResourceInputStream.php @@ -13,7 +13,7 @@ class ResourceInputStream implements InputStream * @param resource $stream */ public function __construct( - private readonly mixed $stream + private readonly mixed $stream, ) {} public function getChar(): ?string @@ -25,8 +25,8 @@ public function getChar(): ?string public function getLine(): ?string { - $char = fgets($this->stream); + $line = fgets($this->stream); - return $char === false ? null : $char; + return $line === false ? null : $line; } } diff --git a/src/Stream/ResourceOutputStream.php b/src/Stream/ResourceOutputStream.php index 30f7f8adb..af99c2835 100644 --- a/src/Stream/ResourceOutputStream.php +++ b/src/Stream/ResourceOutputStream.php @@ -12,7 +12,7 @@ class ResourceOutputStream implements OutputStream * @param resource $stream */ public function __construct( - private readonly mixed $stream + private readonly mixed $stream, ) {} public function write(string $str): void diff --git a/src/Stream/StringOutputStream.php b/src/Stream/StringOutputStream.php index a1e819c30..52f0aa30a 100644 --- a/src/Stream/StringOutputStream.php +++ b/src/Stream/StringOutputStream.php @@ -7,7 +7,7 @@ class StringOutputStream implements OutputStream { public function __construct( - private string &$buffer + private string &$buffer, ) {} public function write(string $str): void diff --git a/tests/Functional/InterpreterTest.php b/tests/Functional/InterpreterTest.php index ece3ec2b1..69dcd604a 100644 --- a/tests/Functional/InterpreterTest.php +++ b/tests/Functional/InterpreterTest.php @@ -17,8 +17,8 @@ final class InterpreterTest extends TestCase { - private const SRC_FILES_PATH = __DIR__ . '/files'; - private const OUTPUT_FILES_PATH = __DIR__ . '/output'; + private const string SRC_FILES_PATH = __DIR__ . '/files'; + private const string OUTPUT_FILES_PATH = __DIR__ . '/output'; #[DataProvider('sourceFileProvider')] public function testSourceFiles(string $goProgram, string $expectedOutput): void @@ -53,7 +53,7 @@ public static function sourceFileProvider(): iterable foreach ($files as $file) { $goProgram = file_get_contents($file); $expectedOutput = file_get_contents( - sprintf('%s/%s.out', self::OUTPUT_FILES_PATH, basename($file, '.go')) + sprintf('%s/%s.out', self::OUTPUT_FILES_PATH, basename($file, '.go')), ); yield $file => [$goProgram, $expectedOutput];