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

  • [X] Convert some small programs
  • [X] Error handling
    • [X] All functions return (type, error)
    • [X] Convert throw to err return
    • [X] Non-leaf function calls need to check + bubble errors
  • [X] Array handling
    • [X] Infer whether to use slice/map for PHP array
  • [-] Namespaces
    • [X] Basic support for namespace -> package name transformation
    • [ ] Resolve namespace names in calls
    • [ ] Resolve use statements
  • [X] if
  • [X] for/foreach
  • [X] switch
  • [ ] Generators
  • [ ] Goto
  • [X] null -> nil
  • [X] Consts and define()
    • [X] Magic constants (__LINE__,__FILE__ etc)
    • [ ] Predefined constants (PHP_VERSION, PHP_EOL, PHP_OS, PHP_INT_MAX etc)
  • [ ] isset/unset
  • [ ] instanceof
  • [X] Ternary expressions
    • Implemented as inline closure
  • [X] die
  • [X] try/catch/finally
    • [X] 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
  • [X] Abandon upon sight of highly dynamic constructs
    • [X] eval
    • [X] extract
    • [ ] variable-variables
    • [ ] Attempts to enable magic_quotes or register_globals
  • [X] 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)
    • [X] Add subtree walk + catch error for this case
  • [X] Closures
    • [ ] Handle value/reference captures
  • [X] Convert top-level calls to init()/main()
    • [X] Wrap in function
    • [X] Determine init/main based on package name
  • [ ] Class/object transformations
    • [X] Convert new X constructor calls to NewX
    • [X] Visibility
      • [X] 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
    • [X] Static methods
    • [X] Inheritance partial
    • [X] Interfaces
    • [X] Class constants
    • [X] super
      • [X] Need to track current conversion state through into function generator, to select the concrete parent
    • [X] parent::
    • [X] self::
    • [ ] static::
    • [ ] Traits / use
    • [ ] Abstract methods
  • [X] Track golang package imports
  • [X] Variadic function parameters
  • [X] 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
    • [X] Create full-coverage interface to implement
    • [ ] Node
    • [ ] Stmt
    • [ ] Expr
    • [ ] Assign
    • [X] Binary
    • [X] Scalar
      • [X] 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
  • [X] Hoisting pass for rvalue expressions
    • [X] 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
    • [X] common string/array functions
    • [ ] More string/array functions
      • [ ] Top 100 from https://www.exakat.io/top-100-php-functions/
    • [X] 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
      • [X] 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