code.ivysaur.me

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
  • 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
  • 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)
  • 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
  • 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 to NewX
    • 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
  • 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 {
  • Simple standard library transformations
    • common string/array functions
    • More string/array functions
    • substr -> slice index operator
      • This has the same value semantics for string literals, but not variables all cases
    • 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
    • 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
  • 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

Reference