Convert PHP source code to Go by AST walking
fixtures | ||
parseutil | ||
vendor | ||
.gitignore | ||
fixtures_test.go | ||
go.mod | ||
go.sum | ||
gotype.go | ||
hoistpass.go | ||
literal.go | ||
main.go | ||
node.go | ||
normalisealts.go | ||
quote.go | ||
README.md | ||
scope.go |
php2go
Convert PHP source code to Go by AST walking.
The goal is to produce idiomatic, maintainable Go code as part of a one-off conversion. This is not generally possible for highly dynamic PHP code, that may require manual fixups.
Progress
Phase 1
- Convert some small programs
- Error handling
- All functions return
(type, error)
- Convert
throw
to err return - Non-leaf function calls need to check + bubble errors
- All functions return
- Array handling
- Infer whether to use slice/map for PHP array
- [-] Namespaces
- Basic support for
namespace
-> package name transformation - Resolve namespace names in calls
- Resolve
use
statements
- Basic support for
if
for
/foreach
switch
- Generators
- Goto
null
->nil
- Consts and
define()
- Magic constants (
__LINE__
,__FILE__
etc) - Predefined constants (
PHP_VERSION
,PHP_EOL
,PHP_OS
,PHP_INT_MAX
etc)
- Magic constants (
isset
/unset
instanceof
- Ternary expressions
- Implemented as inline closure
die
try
/catch
/finally
- Implement using err checks, not panic/recover
- Any statements within the try block should capture the finally/catch to run within any interior err-check blocks
finally
- Runs before any return statement inside the try block or catch block; also runs after the try block is complete
- Maybe
finally
would be more concisely implemented as defer inside an IIFE, but that is not idiomatic, maintainable Go code
- Implement using err checks, not panic/recover
- Abandon upon sight of highly dynamic constructs
eval
extract
- variable-variables
- Attempts to enable magic_quotes or register_globals
- Detect assignment expressions
- Go doesn't support assignment in rvalues, only as a statement
- When walking below stmt level, need to first fully walk and check for any function calls + assignments that may need hoisting (and we can probably only do that correctly if there is no short-circuiting)
- Add subtree walk + catch error for this case
- Closures
- Handle value/reference captures
- Convert top-level calls to
init()
/main()
- Wrap in function
- Determine init/main based on package name
- Class/object transformations
- Convert
new X
constructor calls toNewX
- Visibility
- Apply PHP visibility modifiers as Go case change
- Call mangled function name at call sites
- PHP variable names are case-sensitive, function names are case-insensitive
- Static methods
- Inheritance partial
- Interfaces
- Class constants
- super
- Need to track current conversion state through into function generator, to select the concrete parent
- parent::
- self::
- static::
- Traits /
use
- Abstract methods
- Convert
- Track golang package imports
- Variadic function parameters
- Preserve comments
Productionize
- Multi-file programs
- Include/Require
- Infer whether to declare variable (
var
/:=
) or reuse (=
)- Track current visibility scope
- Comprehensive coverage of all AST node types
- Create full-coverage interface to implement
- Node
- Stmt
- Expr
- Assign
- Binary
- Scalar
- Heredocs/nowdocs
- Numbered break/continue
- Type inference
- Type declarations for literals (string, slice/map with constant initializer, etc)
- Convert known PHP typenames to Go equivalents
- Parse extra types from phpdoc blocks
- Hoisting pass for rvalue expressions
- Preincrement statements
- Behaviour of function calls in rvalue cases where err cannot be checked
- Assignment expressions
- Alternately - we can support the form
if foo := bar(); foo {
- Alternately - we can support the form
- Simple standard library transformations
- common string/array functions
- More string/array functions
- Top 100 from https://www.exakat.io/top-100-php-functions/
- substr -> slice index operator
- This has the same value semantics for
string literals, but not variablesall cases
- This has the same value semantics for
- error_reporting(E_ALL) --> just remove it
- ini_set('display_errors', 'On') --> remove it when the parameter is known
- Elide error return for functions that cannot throw
- ?? Could be a standalone Go refactoring tool
Moonshots
- Extended library/environment transformations
- Standard library transformations
- Track golang package imports
- Handle conflicts between golang stdlib packages / local variable names
- uasort -> sort.Slice
- preg -> regexp
- json -> encoding/json
- Output buffering (
ob_start
/ob_get_clean
)- Can push/pop os.Stdout onto a private stack, then we can keep using
fmt.Print
- Can push/pop os.Stdout onto a private stack, then we can keep using
- PHP Superglobal transformations
$_SERVER['argv']
to os.Args$_GET['name']
to r.FormValue('name')
- Common 3rd party package transformations to popular equivalents (optional)
- Guzzle
- AWS SDK
- PHPUnit -> Go Test
- Replace Composer/PSR-4 autoloading with Go imports
- Standard library transformations
- Preserve rough line spacing
- Somehow detect whether map use should be order-preserving
- Support scoped namespace (
namespace { ... }
) and multiple namespaces in file - Validation on imported comment formats
- Convert wordpress / mediawiki / symfony
- Option to convert with preset=cli, preset=web, webroot path
- Option to generate built-in web server
- Generate source maps
- The parser does have full positional information - exporting at the stmt level would be reasonable
- But the gofmt pass may lose this information
- Command-line tool option to
go run