runner

package
v0.3.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 26, 2026 License: MIT Imports: 35 Imported by: 0

Documentation

Index

Examples

Constants

View Source
const (
	UploadErrOK        = 0
	UploadErrIniSize   = 1
	UploadErrNoFile    = 4
	UploadErrNoTmpDir  = 6
	UploadErrCantWrite = 7
)

PHP's UPLOAD_ERR_* codes, as they appear in a $_FILES entry. Only the ones this runtime can produce are named; 2, 3 and 8 come from a per-form limit and from extensions that phpscript has no equivalent of.

View Source
const DefaultMaxCacheSize = 10000

DefaultMaxCacheSize is the default upper bound on cached entries.

View Source
const DefaultUploadFileMode = FileMode(0o644)

DefaultUploadFileMode is the mode move_uploaded_file() gives a stored upload when the configuration does not name one. The temporary copy an upload arrives in is private to this process, so a mode has to be applied on the way out or nothing else could read what the script stored.

View Source
const InfrastructurePrefix = "PLATFORM_"

InfrastructurePrefix names the variables that configure phpscript and the platform it runs on: connection strings, the listen address, the telemetry block. They are the host's configuration, not the script's environment, and a script never sees them.

The rule already held for what a configuration file declares: a PLATFORM_DB_* entry registers a connection and is not added to PHP variables. It holds for the process environment for the same reason, and it matters more now that one process serves several sites: a tenant reading the operator's connection strings out of getenv() would make the per-site database boundary pointless.

Variables

This section is empty.

Functions

func AcceptsHTML added in v0.3.3

func AcceptsHTML(accept string) bool

AcceptsHTML reports whether an Accept header explicitly names HTML.

Explicitly is the whole point. "*/*" is what curl and fetch() send and it matches every type there is, so reading it as a request for HTML would put a website's error page in front of every program that talks to the server. Only text/html and application/xhtml+xml, written out and not weighted to zero, count; so does neither a "text/*" wildcard nor an absent header.

func Bindings added in v0.2.4

func Bindings() []func(*Runtime)

Bindings returns the contributed installers in registration order.

func DeepSize added in v0.3.3

func DeepSize(v any, visited visitedSet) int64

DeepSize estimates the bytes held by a PHP value, recursing into script-owned containers. A container already in visited costs nothing. Native Go values returned by bindings are host-owned and get a shallow size, the same ownership rule flatHost.SetEntry applies.

func EnvFromContext added in v0.2.0

func EnvFromContext(ctx context.Context) (map[string]string, bool)

EnvFromContext returns the request-scoped environment attached to contexts auto-injected into registered Go callables.

func EstimateValueSize added in v0.3.3

func EstimateValueSize(v any) int64

EstimateValueSize calculates a shallow estimate of memory consumed by a PHP/Go value. It is closed and non-recursive: - scalars / strings: direct size + string length - byte slices: slice header + length - arrays: header + keys + shallow one-level elements - objects: header + property names + shallow one-level properties - pointers/structs: shallow sizeof

func RegisterBinding added in v0.2.4

func RegisterBinding(installer func(*Runtime))

RegisterBinding contributes a runtime installer, which stdlib.Register runs on every Runtime it sets up. Binding packages call it from their init() (see stdlib/core/defer.go), so a package is wired in by importing it, the way a program imports a database/sql driver. The registry lives here, in the leaf package that owns Runtime, so that stdlib can blank-import its subpackages without the two importing each other.

Installers run in registration order, before the bindings a host passes to stdlib.Register directly.

func ScriptEnvironment added in v0.3.1

func ScriptEnvironment(environment []string) map[string]string

ScriptEnvironment returns the environment scripts read with getenv().

A nil environment means the process environment, which is what a CLI run has. A host that configured one passes it instead, and a virtual host always does, so that a site sees the variables it declared rather than everything the operator's process happens to carry. Either way the infrastructure variables are held back.

func WantsErrorPage added in v0.3.3

func WantsErrorPage(r *http.Request) bool

WantsErrorPage reports whether a request is a browser navigation, the only kind of request a site's HTML error page is meant for. A HEAD request is never one: there is no body to render a page into.

A URL prefix cannot answer this, because an API and a website share one URL space and one catch-all handler; the request answers it itself. Sec-Fetch-Dest is decisive when a browser sends it: fetch() and XHR send "empty", an image "image", a stylesheet "style", and only a navigation "document". A client that sends none is judged by whether its Accept header explicitly names HTML.

Types

type ArgumentCountError added in v0.3.0

type ArgumentCountError struct {
	Name string
	Want int
	Got  int
}

ArgumentCountError reports a call that passed more arguments than the callable declares. PHP raises the same condition under the same name, so it names that class: `catch (ArgumentCountError $e)` and `catch (Error $e)` match it and `catch (Exception $e)` does not, as in PHP.

func (*ArgumentCountError) Error added in v0.3.0

func (e *ArgumentCountError) Error() string

func (*ArgumentCountError) ThrowableClass added in v0.3.4

func (e *ArgumentCountError) ThrowableClass() string

ThrowableClass names the PHP class, implementing Throwable.

type ArithmeticError added in v0.3.3

type ArithmeticError struct {
	Message string
}

ArithmeticError reports an operation PHP 8 rejects with the class of the same name: a shift by a negative number is the one this runtime raises. It names its PHP class, and a clause matches on that name, so `catch (ArithmeticError $e)` matches it exactly and `catch (Error $e)` matches it on the name's suffix, while `catch (Exception $e)` does not, as in PHP.

func (*ArithmeticError) Error added in v0.3.3

func (e *ArithmeticError) Error() string

Error renders the message PHP carries on the same operation.

func (*ArithmeticError) ThrowableClass added in v0.3.4

func (e *ArithmeticError) ThrowableClass() string

ThrowableClass names the PHP class, implementing Throwable.

type Context

type Context struct {
	Get     map[string]string
	Post    map[string]string
	Path    map[string]string
	Cookie  map[string]string
	Server  map[string]string
	Env     map[string]string
	Headers map[string]string
	Argv    []string

	// Files holds the file parts of a multipart body keyed by form field name,
	// in the order they were sent. A field carries more than one file when the
	// form repeats it, which HTML spells as a "name[]" field.
	Files map[string][]*UploadedFile
	// contains filtered or unexported fields
}

Context carries HTTP request data exposed to PHP as superglobals, header functions, and staged response headers.

Example
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"

	"github.com/titpetric/phpscript/runner"
)

func main() {
	req := httptest.NewRequest(http.MethodGet, "/users/42?tab=profile", nil)
	req.Header.Set("X-Request-Id", "abc123")
	req.Pattern = "GET /users/{id}"
	req.SetPathValue("id", "42")

	ctx := runner.FromRequest(req)
	ctx.Header("X-Powered-By: phpscript")

	fmt.Println(ctx.Get["tab"])
	fmt.Println(ctx.Path["id"])
	fmt.Println(ctx.Headers["X-Request-Id"])
	fmt.Println(ctx.ResponseHeaders().Get("X-Powered-By"))

}
Output:
profile
42
abc123
phpscript

func FromRequest

func FromRequest(r *http.Request) Context

FromRequest builds a Context from an HTTP request with no size limits on the body. A host that has runtime options, which is every host that reads a configuration file, uses FromRequestOptions instead.

func FromRequestOptions added in v0.3.1

func FromRequestOptions(r *http.Request, opts Options) Context

FromRequestOptions builds a Context from an HTTP request. Query and form values are flattened to one value per key (PHP's scalar superglobal shape); path values are pulled out of the matched ServeMux pattern via r.PathValue.

A key sent more than once keeps the last value, which is what PHP's own parser does: each repetition assigns over the one before it.

The upload_max_filesize and post_max_size options limit what the body may carry; see enforcement in the form-body section below.

func NewContext

func NewContext() Context

NewContext returns an allocated empty Context value.

func RequestContext added in v0.2.2

func RequestContext(ctx context.Context) (Context, bool)

RequestContext returns the request data registered on a runtime context. It is intended for request-aware Go bindings such as session management.

func (Context) AddResponseHeader added in v0.2.2

func (c Context) AddResponseHeader(name, value string)

AddResponseHeader appends a response header without replacing existing values. It is useful for headers such as Set-Cookie that may occur more than once in a response.

func (Context) Answered added in v0.3.3

func (c Context) Answered(body []byte) bool

Answered reports whether the script answered this request itself, which a host takes as the script refusing whatever error page the site put up.

A body counts, because a script that echoed something has said what the response is: an endpoint returning 404 with a JSON payload keeps it. So does a Content-Type, because a script that declared what it answers with has declared that it is not answering in HTML, which is a one line opt-out for an endpoint with nothing to put in the body. Neither has anything to do with where the script sits in the URL space, which is the point.

func (Context) Cleanup added in v0.3.1

func (c Context) Cleanup()

Cleanup removes the temporary files created for the uploaded parts of a request. A host handler defers it for the lifetime of one request; a file the script moved with move_uploaded_file is already gone and is skipped.

func (Context) Errors added in v0.3.1

func (c Context) Errors() []error

Errors returns what the request got wrong before any script ran, which today is a body or a file part refused for its size. A script has no way to catch these: they happened outside it, and the only sign of them it gets is the empty superglobal or the UPLOAD_ERR_INI_SIZE entry they left behind. Register reports each one to the runtime through Runtime.RecordError, so a Go host sees them on the request trace or through Runtime.OnError.

func (Context) GetAllHeaders

func (c Context) GetAllHeaders() *model.Array

GetAllHeaders implements PHP getallheaders(): an associative array of the incoming request headers keyed by canonical header name.

func (Context) HTTPResponseCode added in v0.3.3

func (c Context) HTTPResponseCode(sapi string, opts ...any) any

HTTPResponseCode implements PHP http_response_code([$response_code]).

With no argument it reports the status this response will be sent with, and with one it stages that status and reports the one it replaced.

PHP answers false rather than a number while no status has been chosen, and true rather than a number for the first one set, having none to hand back. On a web SAPI neither happens: a request starts out answering 200. sapi is the SAPI name the runtime runs under, and only the command line, or a host that named no SAPI at all, starts without a status.

func (Context) Header

func (c Context) Header(header string, opts ...any)

Header implements PHP header($header[, $replace[, $code]]): it parses a "Name: value" line and stages it on the response header set. replace controls whether an existing header of the same name is overwritten (default true).

func (Context) IsUpload added in v0.3.1

func (c Context) IsUpload(path string) bool

IsUpload reports whether path is the temporary file of a part of this request. It backs is_uploaded_file() and move_uploaded_file(), which in PHP refuse any path the request did not produce. A path the script has already moved away is no longer one of them, so the copy has to still be there.

func (Context) Register

func (c Context) Register(rt *Runtime)

Register installs the request-aware PHP functions onto rt and seeds the request superglobals. After this, transpiled PHP can call getallheaders() / header() and read $_GET, $_POST, $_PATH, all backed by this Context.

func (Context) ResponseHeaders

func (c Context) ResponseHeaders() http.Header

ResponseHeaders returns the headers staged by the PHP header() function so a host handler can copy them onto the http.ResponseWriter after execution.

func (Context) ResponseStatus added in v0.2.0

func (c Context) ResponseStatus() int

ResponseStatus returns the status staged by header(), or zero when the host should retain its default status. A Location header defaults to 302 like PHP.

func (Context) SetDefaultHeader added in v0.3.3

func (c Context) SetDefaultHeader(name, value string)

SetDefaultHeader stages a header only if the script did not set one itself. It is how a host applies a default, such as PHP's text/html content type, without overriding what the script said.

func (Context) StatusFor added in v0.3.3

func (c Context) StatusFor(err error) int

StatusFor resolves the HTTP status a finished script ends on, given whatever error it returned. Zero means nothing chose one and the host's default stands.

An uncaught exception is the interesting case. Its code is the script's own number and usually not a status at all, so only a code in the 4xx and 5xx range is taken as one; anything else is an application code and the request failed with a 500. exit() and die() are not failures: a script that ended early still answers with the status it staged, as it does in PHP.

func (Context) WriteResponse added in v0.3.3

func (c Context) WriteResponse(w http.ResponseWriter, status int, body []byte)

WriteResponse flushes one response: the headers the script staged with header(), the status, and the body it produced. Nothing reaches the ResponseWriter before this, which is what lets a host look at a finished response and answer with something else instead.

A status of zero writes none, leaving net/http its 200. So does a status net/http will not send: it panics on anything outside 100 to 999, and http_response_code() takes whatever number a script hands it.

type DivisionByZeroError added in v0.3.4

type DivisionByZeroError struct {
	Message string
}

DivisionByZeroError reports a division or modulo by zero that PHP rejects with the class of the same name: intdiv() and `%` raise it. It names its PHP class, so `catch (DivisionByZeroError $e)` matches it exactly and `catch (Error $e)` matches it on the name's suffix, as in PHP.

func (*DivisionByZeroError) Error added in v0.3.4

func (e *DivisionByZeroError) Error() string

Error renders the message PHP carries for the same operation.

func (*DivisionByZeroError) ThrowableClass added in v0.3.4

func (e *DivisionByZeroError) ThrowableClass() string

ThrowableClass names the PHP class, implementing Throwable.

type ExitError

type ExitError struct {
	Code int
}

ExitError is returned when PHP die()/exit() interrupts script execution.

func IsExit

func IsExit(err error) (*ExitError, bool)

IsExit reports whether err was caused by PHP die()/exit().

func (*ExitError) Error

func (e *ExitError) Error() string

Error returns a formatted PHP exit status.

func (*ExitError) ScriptExit added in v0.3.3

func (e *ExitError) ScriptExit() int

ScriptExit reports the status a script ended with, and marks this error as an ending rather than a failure.

It exists so a package that cannot name *ExitError can still recognise one: runner imports flatstack/engine, so engine cannot import runner back, and the VM has to know an exit when it unwinds one past a catch clause. Asking the error what it is keeps that seam an interface rather than a string comparison on the message.

type ExprCache

type ExprCache struct {
	// contains filtered or unexported fields
}

ExprCache stores immutable compiled expression programs by transpiled source and optional flat bytecode by parsed program identity. Expression AST metadata stays runtime-local; flat bytecode retains its source Program for the lifetime of the explicitly shared cache. Cache capacity is bounded to prevent memory leaks.

func NewExprCache

func NewExprCache() *ExprCache

NewExprCache returns an empty compiled expression cache with default capacity (10,000 entries).

func NewExprCacheWithCapacity added in v0.2.1

func NewExprCacheWithCapacity(maxEntries int) *ExprCache

NewExprCacheWithCapacity returns an empty expression cache bounded to maxEntries.

func (*ExprCache) Clear added in v0.2.1

func (c *ExprCache) Clear()

Clear resets the cached compiled expressions.

func (*ExprCache) GetSource

func (c *ExprCache) GetSource(src string) (*vm.Program, bool)

GetSource returns the compiled expression cached for src, if any.

func (*ExprCache) Len added in v0.2.1

func (c *ExprCache) Len() int

Len returns the number of currently cached source expressions.

func (*ExprCache) SetSource added in v0.1.4

func (c *ExprCache) SetSource(src string, prog *vm.Program)

SetSource stores a compiled program for transpiled source. Evicts one item if max capacity is reached.

type FileMode added in v0.3.1

type FileMode uint32

FileMode is a Unix file mode written the way chmod writes it, in octal, with or without the leading zero: 0644 and 644 are the same mode. It is octal whether or not it says so, because a mode is never anything else.

The zero value means "not configured", which is a caller's cue to use its own default rather than a mode of 0000.

func ParseFileMode added in v0.3.1

func ParseFileMode(s string) (FileMode, error)

ParseFileMode reads an octal file mode. An empty value is the zero value.

func (FileMode) MarshalYAML added in v0.3.1

func (m FileMode) MarshalYAML() ([]byte, error)

MarshalYAML writes the mode back as a quoted octal string, so a round trip through a configuration file cannot come back as a decimal number.

func (FileMode) Mode added in v0.3.1

func (m FileMode) Mode() os.FileMode

Mode converts to the Go representation, where the three special bits live outside the permission bits rather than above them.

func (FileMode) String added in v0.3.1

func (m FileMode) String() string

String writes the mode back as four octal digits, the spelling a configuration file and chmod() both use.

func (*FileMode) UnmarshalYAML added in v0.3.1

func (m *FileMode) UnmarshalYAML(data []byte) error

UnmarshalYAML reads a mode from a configuration file, where it is written as octal, quoted or not.

type FilenameObserver added in v0.2.1

type FilenameObserver interface {
	UpdateFilename(context.Context, string)
}

FilenameObserver optionally receives the entrypoint passed to LoadFile.

type HostPanicError added in v0.2.0

type HostPanicError struct {
	Callable string
	Value    any
}

HostPanicError converts a panic raised by a registered Go constructor, function, or method into the runtime error path. PHP try/catch can therefore handle it like an error returned by the same host callable.

func (*HostPanicError) Error added in v0.2.0

func (e *HostPanicError) Error() string

type IncludeCache

type IncludeCache struct {
	// contains filtered or unexported fields
}

IncludeCache stores parsed include/require programs by cleaned filesystem path. Parsed programs are treated as immutable by Runtime.Run/exec: hoisting copies declarations into runtime maps, while statement execution only reads the AST, so cached *model.Program values can be shared safely by callers that do not mutate ASTs themselves. Cache size is bounded to prevent memory leaks.

func NewIncludeCache

func NewIncludeCache() *IncludeCache

NewIncludeCache returns an empty parsed include cache with default capacity (10,000 entries).

func NewIncludeCacheWithCapacity added in v0.2.1

func NewIncludeCacheWithCapacity(maxEntries int) *IncludeCache

NewIncludeCacheWithCapacity returns an empty include cache bounded to maxEntries.

func (*IncludeCache) Clear added in v0.2.1

func (c *IncludeCache) Clear()

Clear resets the cached entries.

func (*IncludeCache) Get

func (c *IncludeCache) Get(path string) (*model.Program, bool)

Get returns the parsed program cached for path, if any.

func (*IncludeCache) Len added in v0.2.1

func (c *IncludeCache) Len() int

Len returns the number of currently cached programs.

func (*IncludeCache) Set

func (c *IncludeCache) Set(path string, prog *model.Program)

Set stores prog for path. Evicts one item if max capacity is reached.

type IncludeFunc

type IncludeFunc func(path string) (*model.Program, error)

IncludeFunc resolves an include/require path to a parsed program. Wiring this from the host keeps the runner free of file-system and parser dependencies.

type IncludeObserver added in v0.2.1

type IncludeObserver interface {
	UpdateIncludedFiles(context.Context, int)
}

IncludeObserver optionally receives the number of files included so far.

type Observer added in v0.2.1

type Observer interface {
	UpdateStatus(context.Context, telemetry.State)
	Trace(context.Context, string, ...telemetry.Kind) *telemetry.Span
}

Observer receives lifecycle updates for a Runtime. Implementations must be safe for use by concurrent runtimes. A span is returned so the interpreter can measure the region it just reported; a nil span is valid and every method on it does nothing, which is what an observer with no trace in the context returns.

type Options

type Options struct {
	// RootFS is the filesystem used to load PHP entrypoints and includes.
	RootFS fs.FS `yaml:"-"`

	// Database resolves the named connections the Database and
	// Database\Migrate bindings open. Nil leaves the choice to the binding,
	// which falls back to the process environment. A virtual host sets its
	// own, so that the connections a site can name are only the ones it
	// configured.
	Database model.DatabaseProvider `yaml:"-"`

	// Stdin is exposed to scripts as the STDIN stream. A nil reader produces an
	// empty stream; CLI hosts should pass os.Stdin explicitly.
	Stdin io.Reader `yaml:"-"`

	// SAPI provides output for `php_sapi_name`.
	SAPI string `yaml:"-"`

	// WorkDir is the directory inside RootFS used as the script working directory.
	// Empty means the RootFS root.
	WorkDir string `yaml:"work_dir"`

	// WritablePaths optionally restricts filesystem writes. When empty, writes are
	// left to normal OS/user permissions. Enforcement is done by filesystem shims.
	WritablePaths []string `yaml:"writable_paths"`

	// UploadMaxFilesize is the largest file part a request may carry, PHP's
	// upload_max_filesize. A part over it is reported to the script as
	// UPLOAD_ERR_INI_SIZE and is not stored. Zero is no limit.
	UploadMaxFilesize Size `yaml:"upload_max_filesize"`

	// PostMaxSize is the largest request body that is parsed at all, PHP's
	// post_max_size. A body over it leaves both $_POST and $_FILES empty, as it
	// does in PHP. Zero is no limit.
	PostMaxSize Size `yaml:"post_max_size"`

	// UploadFileMode is the mode move_uploaded_file() gives a stored upload.
	// Zero means DefaultUploadFileMode; a host that serves uploads to nobody
	// but itself sets something tighter, 0600 or 0640.
	UploadFileMode FileMode `yaml:"upload_file_mode"`

	// Env is the environment scripts read with getenv(). Nil means the
	// process environment, minus what ScriptEnvironment holds back. A host
	// that configured an env of its own passes it here, and a virtual host
	// always does.
	Env []string `yaml:"-"`

	// MemoryLimit is the memory one script may allocate, PHP's memory_limit.
	// Zero is no limit.
	MemoryLimit Size `yaml:"memory_limit"`

	// TimeLimit is how long one script may run before it is stopped, in
	// seconds, PHP's max_execution_time. Zero is no limit.
	//
	// NOT ENFORCED YET. The key is accepted and carried so a configuration
	// written today keeps working when enforcement lands, rather than
	// failing to parse.
	TimeLimit int `yaml:"time_limit"`

	// ConcurrencyLimit is how many scripts may execute at once. Zero is no
	// limit. It has no equivalent in php.ini, where the SAPI owns it; here
	// one process serves several sites and each gets its own share.
	//
	// NOT ENFORCED YET. See TimeLimit.
	ConcurrencyLimit int `yaml:"concurrency_limit"`
}

Options configures a Runtime.

type Runtime

type Runtime struct {

	// Env is the environment visible to PHP for this Runtime. New snapshots the
	// host environment so mutations remain local to a single request/runtime.
	Env map[string]string
	// contains filtered or unexported fields
}

Runtime executes parsed PHP statements and evaluates transpiled expressions with registered functions, classes, constructors, and runtime state.

func New

func New(w io.Writer, opts Options) *Runtime

New returns a Runtime that writes echo output to w (defaults to os.Stdout).

func NewFlatStack added in v0.2.0

func NewFlatStack(w io.Writer, opts Options) *Runtime

NewFlatStack returns a Runtime that executes supported programs through the flat bytecode backend and atomically falls back to the interpreter otherwise. Most callers should use flatstack.New, which preserves runner's public API.

func (*Runtime) AccountRequest added in v0.3.3

func (rt *Runtime) AccountRequest(values ...any)

AccountRequest folds the size of host-owned request-lifetime values into the baseline the memory walk starts from: the request Context, the parsed *http.Request, the response writer. It is called once per value at the point a request crosses into the runtime; the walk then adds live script values on top. It is an estimate of what the request costs before any PHP evaluates, not an audit of every host allocation.

func (*Runtime) Callable added in v0.3.0

func (rt *Runtime) Callable(v any) (func(...any) (any, error), bool)

Callable resolves a PHP `callable` value into the uniform func(...any) (any, error) signature the runtime invokes everywhere.

PHP accepts several spellings of a callable and library code written for stock PHP uses all of them, so `call_user_func`, `usort` and friends have to understand each one:

  • a closure or any Go func already registered with the runtime,
  • "function_name", naming a free function,
  • "Class::method", naming a static method,
  • array($object, "method"), the bound-method form,
  • array("Class", "method"), the static form.

The second return reports whether v was callable at all; callers turn that into PHP's "not a valid callback" error with their own function name.

func (*Runtime) ClassExists added in v0.1.0

func (rt *Runtime) ClassExists(name string, autoload bool) (bool, error)

ClassExists reports whether a PHP or host-backed class exists. If autoload is true, registered autoloaders are given a chance to define a missing class.

func (*Runtime) Const

func (rt *Runtime) Const(name string) (any, bool)

Const returns a registered constant value and whether it is defined.

func (*Runtime) Context added in v0.0.5

func (rt *Runtime) Context() context.Context

Context returns the configured lifecycle context.

func (*Runtime) Database added in v0.3.1

func (rt *Runtime) Database() model.DatabaseProvider

Database returns the provider named connections resolve through, or nil when the host configured none. The binding decides what nil falls back to.

func (*Runtime) DeclaredClasses added in v0.1.0

func (rt *Runtime) DeclaredClasses() []string

DeclaredClasses returns the names of PHP classes and host-backed constructor classes currently available to the runtime. PHP does not guarantee ordering; phpscript sorts the snapshot for deterministic diagnostics.

func (*Runtime) DefinedConstants added in v0.1.0

func (rt *Runtime) DefinedConstants() map[string]any

DefinedConstants returns a stable snapshot of all runtime constants.

func (*Runtime) DefinedFunctions added in v0.1.0

func (rt *Runtime) DefinedFunctions() (internal, user []string)

DefinedFunctions returns stable snapshots of registered host/internal and PHP user-defined function names.

func (*Runtime) Eval

func (rt *Runtime) Eval(e model.Expr, scope *Scope) (any, error)

Eval transpiles e, binds the referenced variables from scope, and runs the resulting program through the expr-lang VM.

func (*Runtime) Exit

func (rt *Runtime) Exit(code int) error

Exit interrupts execution with a PHP exit status.

func (*Runtime) FS

func (rt *Runtime) FS() fs.FS

FS returns the configured source root (or nil).

func (*Runtime) FreezeStdlib added in v0.3.0

func (rt *Runtime) FreezeStdlib()

FreezeStdlib snapshots constants after host bindings are registered so ResetSession can drop script-defined constants without losing the stdlib.

func (*Runtime) FunctionExists added in v0.3.0

func (rt *Runtime) FunctionExists(name string) bool

FunctionExists reports whether name resolves to a host shim or a PHP user-defined function, backing `function_exists`. Template engines guard their generated block functions with it, so an always-false answer would redeclare them on every include.

func (*Runtime) IncludeFile added in v0.3.0

func (rt *Runtime) IncludeFile(path string) (any, error)

IncludeFile evaluates path as PHP in a fresh scope, the same way an `include` statement would. Hosts that resolve classes outside the interpreter (the composer autoloader) use it to pull a declaration file into the runtime.

func (*Runtime) IncludePath added in v0.1.0

func (rt *Runtime) IncludePath() string

IncludePath returns the current SPL include path.

func (*Runtime) IncludedFiles

func (rt *Runtime) IncludedFiles() []string

IncludedFiles returns the cleaned dirFS filenames included by this runtime.

func (*Runtime) Load

func (rt *Runtime) Load(src string) (*model.Program, error)

Load parses PHP source into a program.

func (*Runtime) LoadFile

func (rt *Runtime) LoadFile(path string) (*model.Program, error)

LoadFile reads and parses a PHP file from the runtime source FS.

func (*Runtime) LookupConstructor added in v0.3.0

func (rt *Runtime) LookupConstructor(name string) (any, bool)

LookupConstructor returns the host constructor registered for name.

func (*Runtime) LookupFunc added in v0.3.3

func (rt *Runtime) LookupFunc(name string) (any, bool)

LookupFunc returns the host function registered for name. Introspection tooling uses it to reflect over a binding's Go signature; a PHP user-defined function resolves too, as whatever callable the runtime stored for it.

func (*Runtime) MemoryLimit added in v0.3.3

func (rt *Runtime) MemoryLimit() Size

MemoryLimit returns the configured memory limit.

func (*Runtime) MemoryPeak added in v0.3.3

func (rt *Runtime) MemoryPeak() int64

MemoryPeak returns the high-water usage mark. Peak is sampled at walk points (memory_get_usage calls and limit checkpoints), so an allocation both made and released between walks does not raise it, unlike PHP's allocator-level peak.

func (*Runtime) MemoryUsage added in v0.3.3

func (rt *Runtime) MemoryUsage() int64

MemoryUsage returns the current request-scoped memory estimation in bytes, computed by a fresh walk of the live roots.

func (*Runtime) MemoryWalk added in v0.3.3

func (rt *Runtime) MemoryWalk() int64

MemoryWalk recomputes live usage from the roots: the interpreter frame stack, globals, class statics, and any running flat VM's live values. A visited set keyed on container identity counts a value reachable through several variables once and terminates cycles. The result refreshes the cached usage and the peak.

func (*Runtime) MethodExists added in v0.3.0

func (rt *Runtime) MethodExists(class, method string) bool

MethodExists reports whether a declared PHP class has a method by that name, using PHP's case-insensitive method lookup. It answers only for interpreted classes; a host-backed one is reflected over by its caller.

func (*Runtime) Observe added in v0.2.1

func (rt *Runtime) Observe(observer Observer)

Observe registers an observer and reports that this Runtime is starting.

func (*Runtime) OnError

func (rt *Runtime) OnError(fn func(error))

OnError installs an error handler (register_error_handler). When set, runtime evaluation errors are routed here instead of aborting the caller.

func (*Runtime) Output added in v0.3.0

func (rt *Runtime) Output() io.Writer

Output returns the writer script output goes to: echo statements, inline HTML, and any builtin that emits text of its own, such as die() with a message, so their text joins the response body in the order the script produced it.

While a redirection is active (PushOutput, which output buffering is built on) this is the innermost writer, so everything the script emits is captured rather than sent on.

func (*Runtime) OutputDepth added in v0.3.0

func (rt *Runtime) OutputDepth() int

OutputDepth reports how many redirections are active.

func (*Runtime) PHPInfo added in v0.1.0

func (rt *Runtime) PHPInfo() error

PHPInfo prints a compact phpinfo-style text report for the phpscript runtime. It intentionally reports runtime facts rather than PHP extensions that phpscript does not provide.

func (*Runtime) PopOutput added in v0.3.0

func (rt *Runtime) PopOutput() bool

PopOutput ends the innermost redirection, reporting whether one was active. Output resumes going to the enclosing writer, which is what makes nested captures compose.

func (*Runtime) PushOutput added in v0.3.0

func (rt *Runtime) PushOutput(w io.Writer)

PushOutput redirects script output to w until the matching PopOutput. Pushes nest: the innermost writer receives the output, so a captured region can itself capture.

func (*Runtime) RecordError added in v0.3.1

func (rt *Runtime) RecordError(err error)

RecordError reports err on the trace of the request this runtime is serving, as the failure of the script it is running. Run calls it for the error a script ends with; a host calls it for a failure that happened outside the script, before or around it, which the script therefore has no throw to catch: a request body refused for its size, say.

It records and returns. It does not unwind PHP execution, so nothing about it is visible to a script through try/catch; a Go host observes it on the trace, or through the handler installed with OnError.

func (*Runtime) RegisterAutoloader added in v0.1.0

func (rt *Runtime) RegisterAutoloader(callback any, prepend bool)

RegisterAutoloader appends or prepends a callback to the SPL autoload queue. The callback receives a fully-qualified class name without a leading slash.

func (*Runtime) RegisterClass

func (rt *Runtime) RegisterClass(c *model.Class)

RegisterClass adds a resolved class to the class table so `new Name` works.

func (*Runtime) RegisterConstructor

func (rt *Runtime) RegisterConstructor(name string, ctor any)

RegisterConstructor binds a class name to a Go constructor so `new Name` in PHP instantiates a native Go value. The constructor may take a leading context.Context (auto-injected) and may return a trailing error, which is surfaced to the interpreter as a thrown error. When a direct variable assignment receives a constructed value that implements SetID(string), the runtime passes it the PHP variable name without the leading dollar sign. Example:

rt.RegisterConstructor("Storage", func(ctx context.Context) (Storage, error) { ... }).
// PHP:  $storage = new Storage;   // == storage, err := NewStorage(ctx).

func (*Runtime) RegisterFunc

func (rt *Runtime) RegisterFunc(name string, fn any)

RegisterFunc forwards a Go function (or any callable) into the VM under name. This is the shim mechanism: e.g. rt.RegisterFunc("strlen", func(s string) int { return len(s) }) makes `strlen($x)` work in transpiled code.

func (*Runtime) RegisterInclude added in v0.3.0

func (rt *Runtime) RegisterInclude(path string, fn func() (any, error))

RegisterInclude installs a host implementation of one include target: an include or require of path runs fn instead of parsing the file, and the script sees fn's return value where PHP would see the file's.

This is for files the runtime reimplements in Go. composer's generated vendor/autoload.php is the motivating case: it bootstraps a class loader through PHP features the interpreter does not support, while phpscript can read the same composer metadata natively. Binding it here rather than installing the loader at startup keeps the PHP semantics intact: nothing is autoloadable until the script has actually included the autoloader.

func (*Runtime) RegisterShutdown added in v0.2.1

func (rt *Runtime) RegisterShutdown(callback any)

RegisterShutdown appends a callback to run when the current program exits. Shutdown callbacks run in registration order, including after exit or error.

func (*Runtime) ResetSession added in v0.3.0

func (rt *Runtime) ResetSession(out io.Writer, stdin io.Reader)

ResetSession prepares the runtime to execute another program: new output and stdin, empty globals and user declarations. Host functions, constructors and the expression/bytecode caches stay.

func (*Runtime) Run

func (rt *Runtime) Run(p *model.Program) (err error)

Run executes a whole program in the global scope.

func (*Runtime) SAPI

func (rt *Runtime) SAPI() string

SAPI returns the configured SAPI string.

func (*Runtime) SPLAutoload added in v0.1.0

func (rt *Runtime) SPLAutoload(class string) error

SPLAutoload implements PHP's default autoloader: lowercase the qualified class name and search each include_path entry for class.php.

func (*Runtime) SetConst

func (rt *Runtime) SetConst(name string, val any)

SetConst registers a PHP constant (e.g. define("FOO", 1) or a built-in like T_VARIABLE). Constants are visible in every scope, including inside functions and methods, unlike globals, which PHP confines to the global scope.

func (*Runtime) SetContext

func (rt *Runtime) SetContext(ctx context.Context)

SetContext installs the lifecycle context auto-injected into registered Go callables whose first parameter is a context.Context (constructors, methods, functions). Defaults to context.Background().

func (*Runtime) SetExprCache

func (rt *Runtime) SetExprCache(cache *ExprCache)

SetExprCache installs a source-keyed compiled-program cache that is safe to share across runtimes. AST-specific expression metadata remains runtime-local. Passing nil disables cross-runtime expression caching.

func (*Runtime) SetGlobal

func (rt *Runtime) SetGlobal(name string, val any)

SetGlobal seeds a variable into the global scope before execution. Useful for injecting request data (the README's $_SERVER gray area) or, in tests, an input value.

func (*Runtime) SetIncludeCache

func (rt *Runtime) SetIncludeCache(cache *IncludeCache)

SetIncludeCache installs a shared include cache. A cache must only be shared by runtimes whose include paths resolve within the same source-root namespace. Passing nil disables include caching.

func (*Runtime) SetIncludePath added in v0.1.0

func (rt *Runtime) SetIncludePath(value string) string

SetIncludePath sets the path list used by the default SPL autoloader and returns its previous value.

func (*Runtime) SetIncludeResolver

func (rt *Runtime) SetIncludeResolver(fn IncludeFunc)

SetIncludeResolver installs the include/require resolver.

func (*Runtime) Stdin added in v0.2.0

func (rt *Runtime) Stdin() io.Reader

Stdin returns the input stream configured by the runtime host.

func (*Runtime) Trace added in v0.2.1

func (rt *Runtime) Trace(message string, kind ...telemetry.Kind) *telemetry.Span

Trace publishes a trace span to registered observers and returns the first mutable span provided by one of them.

func (*Runtime) UnregisterAutoloader added in v0.3.0

func (rt *Runtime) UnregisterAutoloader(callback any) bool

UnregisterAutoloader removes a callback from the SPL autoload queue, matching it the way PHP does: by the function, object and method it names rather than by identity, since each `array($this, "loadClass")` is a fresh array.

func (*Runtime) UpdateFilename added in v0.2.1

func (rt *Runtime) UpdateFilename(filename string)

UpdateFilename publishes the PHP entrypoint to observers that support filename updates.

func (*Runtime) UpdateIncludedFiles added in v0.2.1

func (rt *Runtime) UpdateIncludedFiles(count int)

UpdateIncludedFiles publishes the current number of included files.

func (*Runtime) UpdateStatus added in v0.2.1

func (rt *Runtime) UpdateStatus(state telemetry.State)

UpdateStatus publishes a lifecycle phase to all registered observers. It is also available to hosts for phases that occur outside PHP execution.

func (*Runtime) UploadFileMode added in v0.3.1

func (rt *Runtime) UploadFileMode() FileMode

UploadFileMode returns the mode move_uploaded_file() gives a stored upload, which is DefaultUploadFileMode unless the host configured one.

func (*Runtime) WorkDir

func (rt *Runtime) WorkDir() string

WorkDir returns the configured working directory inside the source root.

func (*Runtime) WritablePaths

func (rt *Runtime) WritablePaths() []string

WritablePaths returns the configured writable path whitelist.

type RuntimeException added in v0.3.3

type RuntimeException struct {
	Message string `json:"message"`
	Code    int    `json:"code"`
}

RuntimeException represents a PHP RuntimeException raised by the runner.

func NewRuntimeException added in v0.3.3

func NewRuntimeException(message string, code int) *RuntimeException

NewRuntimeException creates a new RuntimeException instance.

func (*RuntimeException) Error added in v0.3.3

func (e *RuntimeException) Error() string

func (*RuntimeException) GetCode added in v0.3.3

func (e *RuntimeException) GetCode() int

func (*RuntimeException) GetMessage added in v0.3.3

func (e *RuntimeException) GetMessage() string

func (*RuntimeException) ThrowableClass added in v0.3.4

func (e *RuntimeException) ThrowableClass() string

ThrowableClass names the PHP class, implementing Throwable, so a catch filters on it the way it filters on one a script constructed.

type Scope

type Scope struct {
	// contains filtered or unexported fields
}

Scope is a flat variable table for one execution frame.

PHP has no block scoping: variables introduced inside if/for/foreach bodies live in the enclosing function scope. Each function call gets a fresh Scope; the file body runs in the global Scope.

There is intentionally no `global` keyword implemented.

func NewScope

func NewScope() *Scope

NewScope returns an empty scope.

func ScopeFromContext added in v0.0.6

func ScopeFromContext(ctx context.Context) (*Scope, bool)

ScopeFromContext returns the active PHP execution frame attached to a context auto-injected into a registered free function.

func (*Scope) Defer added in v0.1.3

func (s *Scope) Defer(callback any)

Defer registers a callback to run when the current PHP execution frame returns. Callbacks run in last-in, first-out order.

func (*Scope) DefinedVars added in v0.1.0

func (s *Scope) DefinedVars() map[string]any

DefinedVars returns a snapshot of PHP-visible variables in this frame. Interpreter bookkeeping slots use a double-underscore prefix and are not PHP variables, so they are omitted.

func (*Scope) Get

func (s *Scope) Get(name string) (any, bool)

Get returns the value of name and whether it is set.

func (*Scope) Set

func (s *Scope) Set(name string, val any)

Set stores name=val.

func (*Scope) Unset added in v0.3.0

func (s *Scope) Unset(name string)

Unset removes name from the frame (PHP's unset). Removing a name that was never set is not an error.

type Size added in v0.3.1

type Size int64

Size is a byte count written the way a php.ini size is: a bare number of bytes, or a number with an M suffix for megabytes. PHP's K and G shorthands are rejected rather than guessed at, so a configuration file says what it means in one of two spellings and nothing else parses.

The zero value means no limit, which is what 0 means in php.ini.

func ParseSize added in v0.3.1

func ParseSize(s string) (Size, error)

ParseSize reads a php.ini-style size. An empty value is no limit.

func (Size) Bytes added in v0.3.1

func (s Size) Bytes() int64

Bytes returns the limit as a byte count. Zero is no limit; a caller has to decide what that means for it.

func (Size) Exceeds added in v0.3.1

func (s Size) Exceeds(n int64) bool

Exceeds reports whether n is over the limit. A zero Size is no limit, so nothing exceeds it.

func (Size) MarshalYAML added in v0.3.1

func (s Size) MarshalYAML() ([]byte, error)

MarshalYAML writes the size back as a string, so a round trip through a configuration file keeps the M suffix.

func (Size) String added in v0.3.1

func (s Size) String() string

String writes the size back in the spelling it was read in: megabytes when it is a whole number of them, bytes otherwise.

func (*Size) UnmarshalYAML added in v0.3.1

func (s *Size) UnmarshalYAML(data []byte) error

UnmarshalYAML reads a size from a configuration file, where it is written either as a bare number or as a quoted or unquoted "8M".

type Throwable added in v0.3.4

type Throwable interface {
	error

	// ThrowableClass returns the PHP class name, such as "InvalidArgumentException".
	ThrowableClass() string
}

Throwable is implemented by a value that knows which PHP class it was constructed as.

The class is asked for rather than read off the Go type, because every SPL name is one Go type: reflection would answer "Exception" for an InvalidArgumentException. It is also the predicate that separates a PHP throwable from an error a Go binding returned, which a Go type name cannot do; a driver type called Error would otherwise be read as PHP's Error class and fall out of the `catch (Exception $e)` a script wrote around its query.

type Transpiler

type Transpiler struct {
	// contains filtered or unexported fields
}

Transpiler lowers expression AST nodes into type-agnostic expr-lang source that delegates PHP-specific behavior to runtime helpers.

Example
package main

import (
	"fmt"

	"github.com/titpetric/phpscript/model"
	"github.com/titpetric/phpscript/runner"
)

func main() {
	t := runner.NewTranspiler()

	src, vars, err := t.Transpile(&model.Binary{
		Op: ".",
		Left: &model.Var{
			Name: "greeting",
		},
		Right: &model.Lit{
			Value: " world",
		},
	})
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println(src)
	fmt.Println(vars)

}
Output:
__concat(v_greeting, " world")
[greeting]

func NewTranspiler

func NewTranspiler() *Transpiler

NewTranspiler returns a fresh transpiler.

func (*Transpiler) Calls added in v0.2.5

func (t *Transpiler) Calls() []string

Calls returns the function names the last Transpile emitted as bare env identifiers, deduplicated and in first-use order. Like Idents, it aliases the transpiler's own storage.

func (*Transpiler) Closures

func (t *Transpiler) Closures() map[string]*model.Closure

Closures returns the anonymous functions collected during the last Transpile, keyed by their env identifier. It is nil when the expression has none.

func (*Transpiler) Exprs added in v0.0.5

func (t *Transpiler) Exprs() map[string]model.Expr

Exprs returns the sub-expressions marked for deferred evaluation during the last Transpile. It is nil when the expression has none.

func (*Transpiler) Idents added in v0.2.5

func (t *Transpiler) Idents() []string

Idents returns the expr identifiers of the variables collected during the last Transpile, positionally matching its vars result.

func (*Transpiler) Reset added in v0.2.5

func (t *Transpiler) Reset()

Reset clears the state collected by the last Transpile, keeping the backing arrays of the variable slices.

func (*Transpiler) Transpile

func (t *Transpiler) Transpile(e model.Expr) (src string, vars []string, err error)

Transpile converts e into expr-lang source and returns the source plus the set of variable names it references, in first-use order.

The returned slice aliases the transpiler's own storage; copy it (or use Idents, which is kept in step with it) before the transpiler is reused.

type TypeError added in v0.3.3

type TypeError struct {
	Name     string
	Position int
	Want     string
	Got      string
}

TypeError reports an argument that cannot be converted to the type the callable's parameter declares. PHP raises TypeError for the same call, so it names that class: `catch (TypeError $e)` and `catch (Error $e)` match it and `catch (Exception $e)` does not, as in PHP.

func (*TypeError) Error added in v0.3.3

func (e *TypeError) Error() string

Error renders the message PHP's TypeError carries for the same call.

func (*TypeError) ThrowableClass added in v0.3.4

func (e *TypeError) ThrowableClass() string

ThrowableClass names the PHP class, implementing Throwable.

type UploadedFile added in v0.3.1

type UploadedFile struct {
	Name string // client-supplied file name, without any directory part
	// FullPath is the file name as the client sent it, directory part and all,
	// which a directory upload uses to say where in the tree a file sat. PHP
	// 8.1 added it; like Name, it is the client's word and not a path on this
	// host.
	FullPath string
	Type     string // client-supplied content type
	TmpName  string // path of the temporary copy, empty when Error is set
	Size     int64
	Error    int // an UPLOAD_ERR_* code, UploadErrOK when the part was stored
}

UploadedFile is one file part of a multipart request body, in the shape a PHP $_FILES entry exposes. TmpName is the absolute path of the server-side copy, which lives until Cleanup runs or the script moves it away.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL