initial commit
This commit is contained in:
commit
872c878857
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
# Binaries
|
||||
php2go
|
||||
|
||||
# ..
|
48
README.md
Normal file
48
README.md
Normal file
@ -0,0 +1,48 @@
|
||||
# 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
|
||||
|
||||
[X] Convert some small programs
|
||||
[X] All functions return `(type, error)`
|
||||
- [X] Convert `throw` to err return
|
||||
- [ ] Elide error return for functions that cannot throw
|
||||
- ?? Could be a standalone Go refactoring tool
|
||||
- [ ] Non-leaf function calls need to check + bubble errors
|
||||
[ ] Comprehensive coverage of all AST node types
|
||||
- [ ] Expr
|
||||
- [ ] Binary
|
||||
- [ ] Scalar
|
||||
- [ ] ...
|
||||
- [ ] Convert instanceof for Exception types to Go1.13 `errors.Is`
|
||||
[ ] Array handling
|
||||
- [ ] Infer whether to use slice/map for PHP array
|
||||
- [ ] Infer whether map use should be order-preserving
|
||||
[ ] Multi-file programs
|
||||
- [ ] Include/Require
|
||||
[ ] Namespaces
|
||||
[ ] Generators
|
||||
[X] Convert top-level calls to `init()`/`main()`
|
||||
- *Currently always `init`*
|
||||
[ ] Class/object transformations
|
||||
- [X] Convert `new X` constructor calls to `NewX`
|
||||
- [ ] Convert class inheritance to interfaces with interface assertion
|
||||
- [ ] Traits / `use`
|
||||
- [ ] Abstract methods
|
||||
[ ] Type inference
|
||||
- [ ] Parse extra types from phpdoc blocks
|
||||
[ ] Infer whether to declare variable (`var` / `:=`) or reuse (`=`)
|
||||
- [ ] Track current visibility scope
|
||||
[ ] Standard library transformations
|
||||
- [ ] Track golang package imports
|
||||
- [ ] Handle conflicts between golang stdlib packages / local variable names
|
||||
[ ] PHP Superglobal transformations
|
||||
- [ ] `$_SERVER['argv']` to os.Args
|
||||
- [ ] `$_GET['name']` to r.FormValue('name')
|
||||
[ ] Closures
|
||||
- [ ] Handle value/reference captures
|
||||
[X] Variadic function parameters
|
||||
[ ] Convert wordpress / mediawiki / symfony
|
30
fixtures/0001.php
Normal file
30
fixtures/0001.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
class Bar {
|
||||
protected $mX = null;
|
||||
protected $mY = 3;
|
||||
|
||||
function __construct(string $x) {
|
||||
$this->mX = $x;
|
||||
}
|
||||
|
||||
function hello() {
|
||||
echo $this->mX . "\n";
|
||||
throw new Exception("asdf");
|
||||
}
|
||||
|
||||
function scalarThrower() {
|
||||
throw "str";
|
||||
}
|
||||
}
|
||||
|
||||
function foo($a, int $b): int {
|
||||
return 3 + $a + $b;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < 3; ++$i) {
|
||||
echo foo($i, 2)."\n";
|
||||
}
|
||||
|
||||
$bb = new Bar("hello");
|
||||
$bb->hello();
|
1168
fixtures/0001.php.parse.json
Normal file
1168
fixtures/0001.php.parse.json
Normal file
File diff suppressed because it is too large
Load Diff
21
fixtures/0002-loops.php
Normal file
21
fixtures/0002-loops.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
// Different types of loop statement
|
||||
for($i = 0; $i < 3; ++$i) {
|
||||
|
||||
foreach($foo as $k => $v) {
|
||||
}
|
||||
|
||||
foreach($foo2 as $v2) {
|
||||
}
|
||||
|
||||
while(true) {
|
||||
}
|
||||
|
||||
do {
|
||||
} while (true);
|
||||
|
||||
}
|
||||
|
||||
// Loop with no separate body statement
|
||||
while (true) echo "hello";
|
437
fixtures/0002-loops.php.parse.json
Normal file
437
fixtures/0002-loops.php.parse.json
Normal file
@ -0,0 +1,437 @@
|
||||
{
|
||||
"type": "*node.Root",
|
||||
"position": {
|
||||
"startPos": 44,
|
||||
"endPos": 254,
|
||||
"startLine": 4,
|
||||
"endLine": 21
|
||||
},
|
||||
"Stmts": [
|
||||
{
|
||||
"type": "*stmt.For",
|
||||
"position": {
|
||||
"startPos": 44,
|
||||
"endPos": 186,
|
||||
"startLine": 4,
|
||||
"endLine": 18
|
||||
},
|
||||
"Init": [
|
||||
{
|
||||
"type": "*assign.Assign",
|
||||
"position": {
|
||||
"startPos": 48,
|
||||
"endPos": 54,
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
},
|
||||
"Variable": {
|
||||
"type": "*expr.Variable",
|
||||
"position": {
|
||||
"startPos": 48,
|
||||
"endPos": 50,
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
},
|
||||
"VarName": {
|
||||
"type": "*node.Identifier",
|
||||
"position": {
|
||||
"startPos": 48,
|
||||
"endPos": 50,
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
},
|
||||
"Value": "i"
|
||||
}
|
||||
},
|
||||
"Expression": {
|
||||
"type": "*scalar.Lnumber",
|
||||
"position": {
|
||||
"startPos": 53,
|
||||
"endPos": 54,
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
},
|
||||
"Value": "0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Cond": [
|
||||
{
|
||||
"type": "*binary.Smaller",
|
||||
"position": {
|
||||
"startPos": 56,
|
||||
"endPos": 62,
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
},
|
||||
"Left": {
|
||||
"type": "*expr.Variable",
|
||||
"position": {
|
||||
"startPos": 56,
|
||||
"endPos": 58,
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
},
|
||||
"VarName": {
|
||||
"type": "*node.Identifier",
|
||||
"position": {
|
||||
"startPos": 56,
|
||||
"endPos": 58,
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
},
|
||||
"Value": "i"
|
||||
}
|
||||
},
|
||||
"Right": {
|
||||
"type": "*scalar.Lnumber",
|
||||
"position": {
|
||||
"startPos": 61,
|
||||
"endPos": 62,
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
},
|
||||
"Value": "3"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Loop": [
|
||||
{
|
||||
"type": "*expr.PreInc",
|
||||
"position": {
|
||||
"startPos": 64,
|
||||
"endPos": 68,
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
},
|
||||
"Variable": {
|
||||
"type": "*expr.Variable",
|
||||
"position": {
|
||||
"startPos": 66,
|
||||
"endPos": 68,
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
},
|
||||
"VarName": {
|
||||
"type": "*node.Identifier",
|
||||
"position": {
|
||||
"startPos": 66,
|
||||
"endPos": 68,
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
},
|
||||
"Value": "i"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"Stmt": {
|
||||
"type": "*stmt.StmtList",
|
||||
"position": {
|
||||
"startPos": 70,
|
||||
"endPos": 186,
|
||||
"startLine": 4,
|
||||
"endLine": 18
|
||||
},
|
||||
"Stmts": [
|
||||
{
|
||||
"type": "*stmt.Foreach",
|
||||
"position": {
|
||||
"startPos": 75,
|
||||
"endPos": 105,
|
||||
"startLine": 6,
|
||||
"endLine": 7
|
||||
},
|
||||
"Expr": {
|
||||
"type": "*expr.Variable",
|
||||
"position": {
|
||||
"startPos": 83,
|
||||
"endPos": 87,
|
||||
"startLine": 6,
|
||||
"endLine": 6
|
||||
},
|
||||
"VarName": {
|
||||
"type": "*node.Identifier",
|
||||
"position": {
|
||||
"startPos": 83,
|
||||
"endPos": 87,
|
||||
"startLine": 6,
|
||||
"endLine": 6
|
||||
},
|
||||
"Value": "foo"
|
||||
}
|
||||
},
|
||||
"Key": {
|
||||
"type": "*expr.Variable",
|
||||
"position": {
|
||||
"startPos": 91,
|
||||
"endPos": 93,
|
||||
"startLine": 6,
|
||||
"endLine": 6
|
||||
},
|
||||
"VarName": {
|
||||
"type": "*node.Identifier",
|
||||
"position": {
|
||||
"startPos": 91,
|
||||
"endPos": 93,
|
||||
"startLine": 6,
|
||||
"endLine": 6
|
||||
},
|
||||
"Value": "k"
|
||||
}
|
||||
},
|
||||
"Variable": {
|
||||
"type": "*expr.Variable",
|
||||
"position": {
|
||||
"startPos": 97,
|
||||
"endPos": 99,
|
||||
"startLine": 6,
|
||||
"endLine": 6
|
||||
},
|
||||
"VarName": {
|
||||
"type": "*node.Identifier",
|
||||
"position": {
|
||||
"startPos": 97,
|
||||
"endPos": 99,
|
||||
"startLine": 6,
|
||||
"endLine": 6
|
||||
},
|
||||
"Value": "v"
|
||||
}
|
||||
},
|
||||
"Stmt": {
|
||||
"type": "*stmt.StmtList",
|
||||
"position": {
|
||||
"startPos": 101,
|
||||
"endPos": 105,
|
||||
"startLine": 6,
|
||||
"endLine": 7
|
||||
},
|
||||
"Stmts": [
|
||||
|
||||
]
|
||||
}
|
||||
} {
|
||||
"type": "*stmt.Foreach",
|
||||
"position": {
|
||||
"startPos": 109,
|
||||
"endPos": 135,
|
||||
"startLine": 9,
|
||||
"endLine": 10
|
||||
},
|
||||
"Expr": {
|
||||
"type": "*expr.Variable",
|
||||
"position": {
|
||||
"startPos": 117,
|
||||
"endPos": 122,
|
||||
"startLine": 9,
|
||||
"endLine": 9
|
||||
},
|
||||
"VarName": {
|
||||
"type": "*node.Identifier",
|
||||
"position": {
|
||||
"startPos": 117,
|
||||
"endPos": 122,
|
||||
"startLine": 9,
|
||||
"endLine": 9
|
||||
},
|
||||
"Value": "foo2"
|
||||
}
|
||||
},
|
||||
"Variable": {
|
||||
"type": "*expr.Variable",
|
||||
"position": {
|
||||
"startPos": 126,
|
||||
"endPos": 129,
|
||||
"startLine": 9,
|
||||
"endLine": 9
|
||||
},
|
||||
"VarName": {
|
||||
"type": "*node.Identifier",
|
||||
"position": {
|
||||
"startPos": 126,
|
||||
"endPos": 129,
|
||||
"startLine": 9,
|
||||
"endLine": 9
|
||||
},
|
||||
"Value": "v2"
|
||||
}
|
||||
},
|
||||
"Stmt": {
|
||||
"type": "*stmt.StmtList",
|
||||
"position": {
|
||||
"startPos": 131,
|
||||
"endPos": 135,
|
||||
"startLine": 9,
|
||||
"endLine": 10
|
||||
},
|
||||
"Stmts": [
|
||||
|
||||
]
|
||||
}
|
||||
} {
|
||||
"type": "*stmt.While",
|
||||
"position": {
|
||||
"startPos": 139,
|
||||
"endPos": 155,
|
||||
"startLine": 12,
|
||||
"endLine": 13
|
||||
},
|
||||
"Cond": {
|
||||
"type": "*expr.ConstFetch",
|
||||
"position": {
|
||||
"startPos": 145,
|
||||
"endPos": 149,
|
||||
"startLine": 12,
|
||||
"endLine": 12
|
||||
},
|
||||
"Constant": {
|
||||
"type": "*name.Name",
|
||||
"position": {
|
||||
"startPos": 145,
|
||||
"endPos": 149,
|
||||
"startLine": 12,
|
||||
"endLine": 12
|
||||
},
|
||||
"Parts": [
|
||||
{
|
||||
"type": "*name.NamePart",
|
||||
"position": {
|
||||
"startPos": 145,
|
||||
"endPos": 149,
|
||||
"startLine": 12,
|
||||
"endLine": 12
|
||||
},
|
||||
"Value": "true"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"Stmt": {
|
||||
"type": "*stmt.StmtList",
|
||||
"position": {
|
||||
"startPos": 151,
|
||||
"endPos": 155,
|
||||
"startLine": 12,
|
||||
"endLine": 13
|
||||
},
|
||||
"Stmts": [
|
||||
|
||||
]
|
||||
}
|
||||
} {
|
||||
"type": "*stmt.Do",
|
||||
"position": {
|
||||
"startPos": 159,
|
||||
"endPos": 182,
|
||||
"startLine": 15,
|
||||
"endLine": 16
|
||||
},
|
||||
"Stmt": {
|
||||
"type": "*stmt.StmtList",
|
||||
"position": {
|
||||
"startPos": 162,
|
||||
"endPos": 168,
|
||||
"startLine": 15,
|
||||
"endLine": 16
|
||||
},
|
||||
"Stmts": [
|
||||
|
||||
]
|
||||
},
|
||||
"Cond": {
|
||||
"type": "*expr.ConstFetch",
|
||||
"position": {
|
||||
"startPos": 176,
|
||||
"endPos": 180,
|
||||
"startLine": 16,
|
||||
"endLine": 16
|
||||
},
|
||||
"Constant": {
|
||||
"type": "*name.Name",
|
||||
"position": {
|
||||
"startPos": 176,
|
||||
"endPos": 180,
|
||||
"startLine": 16,
|
||||
"endLine": 16
|
||||
},
|
||||
"Parts": [
|
||||
{
|
||||
"type": "*name.NamePart",
|
||||
"position": {
|
||||
"startPos": 176,
|
||||
"endPos": 180,
|
||||
"startLine": 16,
|
||||
"endLine": 16
|
||||
},
|
||||
"Value": "true"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "*stmt.While",
|
||||
"position": {
|
||||
"startPos": 228,
|
||||
"endPos": 254,
|
||||
"startLine": 21,
|
||||
"endLine": 21
|
||||
},
|
||||
"Cond": {
|
||||
"type": "*expr.ConstFetch",
|
||||
"position": {
|
||||
"startPos": 235,
|
||||
"endPos": 239,
|
||||
"startLine": 21,
|
||||
"endLine": 21
|
||||
},
|
||||
"Constant": {
|
||||
"type": "*name.Name",
|
||||
"position": {
|
||||
"startPos": 235,
|
||||
"endPos": 239,
|
||||
"startLine": 21,
|
||||
"endLine": 21
|
||||
},
|
||||
"Parts": [
|
||||
{
|
||||
"type": "*name.NamePart",
|
||||
"position": {
|
||||
"startPos": 235,
|
||||
"endPos": 239,
|
||||
"startLine": 21,
|
||||
"endLine": 21
|
||||
},
|
||||
"Value": "true"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"Stmt": {
|
||||
"type": "*stmt.Echo",
|
||||
"position": {
|
||||
"startPos": 241,
|
||||
"endPos": 254,
|
||||
"startLine": 21,
|
||||
"endLine": 21
|
||||
},
|
||||
"Exprs": [
|
||||
{
|
||||
"type": "*scalar.String",
|
||||
"position": {
|
||||
"startPos": 246,
|
||||
"endPos": 253,
|
||||
"startLine": 21,
|
||||
"endLine": 21
|
||||
},
|
||||
"Value": "\"hello\""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
39
fixtures_test.go
Normal file
39
fixtures_test.go
Normal file
@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const fixtureDirectory = `fixtures`
|
||||
|
||||
func TestConvertFixtures(t *testing.T) {
|
||||
fh, err := os.Open(fixtureDirectory)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
fixtures, err := fh.Readdirnames(-1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, fixture := range fixtures {
|
||||
if !strings.HasSuffix(fixture, `.php`) {
|
||||
continue
|
||||
}
|
||||
|
||||
ret, err := ConvertFile(filepath.Join(fixtureDirectory, fixture))
|
||||
if err != nil {
|
||||
t.Errorf("In test fixture %s:\n %s", fixture, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
// Success
|
||||
t.Logf("Successful test for fixture %s:\n%s", fixture, ret)
|
||||
}
|
||||
|
||||
}
|
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
||||
module php2go
|
||||
|
||||
go 1.13
|
||||
|
||||
require github.com/z7zmey/php-parser v0.7.0
|
25
go.sum
Normal file
25
go.sum
Normal file
@ -0,0 +1,25 @@
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.4.0 h1:uCmaf4vVbWAOZz36k1hrQD7ijGRzLwaME8Am/7a4jZI=
|
||||
github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE=
|
||||
github.com/yookoala/realpath v1.0.0 h1:7OA9pj4FZd+oZDsyvXWQvjn5oBdcHRTV44PpdMSuImQ=
|
||||
github.com/yookoala/realpath v1.0.0/go.mod h1:gJJMA9wuX7AcqLy1+ffPatSCySA1FQ2S8Ya9AIoYBpE=
|
||||
github.com/z7zmey/php-parser v0.7.0 h1:3pVIdijGn0OtIExr1IxjHxUq+/Es8Nulb89J+VNoy08=
|
||||
github.com/z7zmey/php-parser v0.7.0/go.mod h1:r03mwVJvNhQKrTqKFzK0MIepU1uO62Z0p9ES3A7KTu4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200308013534-11ec41452d41/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
21
literal.go
Normal file
21
literal.go
Normal file
@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/z7zmey/php-parser/freefloating"
|
||||
"github.com/z7zmey/php-parser/node"
|
||||
"github.com/z7zmey/php-parser/position"
|
||||
"github.com/z7zmey/php-parser/walker"
|
||||
)
|
||||
|
||||
type Literal struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
func (l Literal) Walk(v walker.Visitor) {}
|
||||
func (l Literal) Attributes() map[string]interface{} { return nil }
|
||||
func (l Literal) SetPosition(p *position.Position) {}
|
||||
func (l Literal) GetPosition() *position.Position { return nil }
|
||||
func (l Literal) GetFreeFloating() *freefloating.Collection { return nil }
|
||||
|
||||
// interface assertion
|
||||
var _ node.Node = Literal{}
|
71
main.go
Normal file
71
main.go
Normal file
@ -0,0 +1,71 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/z7zmey/php-parser/parser"
|
||||
"github.com/z7zmey/php-parser/visitor"
|
||||
)
|
||||
|
||||
func ConvertFile(filename string) (string, error) {
|
||||
|
||||
inputFile, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
namespaces := visitor.NewNamespaceResolver()
|
||||
// scope := NewScope()
|
||||
|
||||
p, err := parser.NewParser([]byte(inputFile), "7.4")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
p.Parse()
|
||||
for _, err := range p.GetErrors() {
|
||||
return "", errors.New(err.String())
|
||||
}
|
||||
|
||||
// Walk and print JSON...
|
||||
if fh, err := os.OpenFile(filename+`.parse.json`, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err == nil {
|
||||
v := visitor.NewPrettyJsonDumper(fh, namespaces)
|
||||
p.GetRootNode().Walk(v)
|
||||
fh.Close()
|
||||
}
|
||||
|
||||
// Walk and print (converted)
|
||||
ret, err := convert(p.GetRootNode())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Gofmt output
|
||||
// TODO pass flags to get -s/-r equivalent for more agressive simplification
|
||||
formatted, err := format.Source([]byte(ret))
|
||||
if err != nil {
|
||||
ret += "// Gofmt failed: " + err.Error()
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
return string(formatted), nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
filename := flag.String("InFile", "", "Select file to convert")
|
||||
flag.Parse()
|
||||
|
||||
ret, err := ConvertFile(*filename)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println(ret)
|
||||
}
|
700
node.go
Normal file
700
node.go
Normal file
@ -0,0 +1,700 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
//"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/z7zmey/php-parser/node"
|
||||
"github.com/z7zmey/php-parser/node/expr"
|
||||
"github.com/z7zmey/php-parser/node/expr/assign"
|
||||
"github.com/z7zmey/php-parser/node/expr/binary"
|
||||
"github.com/z7zmey/php-parser/node/name"
|
||||
"github.com/z7zmey/php-parser/node/scalar"
|
||||
"github.com/z7zmey/php-parser/node/stmt"
|
||||
)
|
||||
|
||||
func nodeTypeString(n node.Node) string {
|
||||
return reflect.TypeOf(n).String()
|
||||
}
|
||||
|
||||
type parseErr struct {
|
||||
n node.Node
|
||||
childErr error
|
||||
}
|
||||
|
||||
func (pe parseErr) Error() string {
|
||||
return fmt.Sprintf("Parsing %s on line %d: %s", nodeTypeString(pe.n), pe.n.GetPosition().StartLine, pe.childErr)
|
||||
}
|
||||
|
||||
func (pe parseErr) Unwrap() error {
|
||||
return pe.childErr
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
func convert(n_ node.Node) (string, error) {
|
||||
switch n := n_.(type) {
|
||||
|
||||
//
|
||||
// node
|
||||
//
|
||||
|
||||
case *node.Root:
|
||||
ret := "package main\n\n"
|
||||
|
||||
// Hoist all declarations first, and put any top-level code into a generated main() function
|
||||
statements := []string{}
|
||||
|
||||
for _, s := range n.Stmts {
|
||||
sm, err := convert(s)
|
||||
if err != nil {
|
||||
return "", parseErr{s, err}
|
||||
}
|
||||
|
||||
switch s.(type) {
|
||||
case *stmt.Class, *stmt.Function:
|
||||
// Declaration - emit immediately (hoist)
|
||||
ret += sm + "\n"
|
||||
|
||||
default:
|
||||
// Top-level function code - deter emission
|
||||
statements = append(statements, sm)
|
||||
}
|
||||
}
|
||||
|
||||
// Emit deferred statements
|
||||
if len(statements) > 0 {
|
||||
ret += "func init() {\n"
|
||||
ret += "\t" + strings.Join(statements, "\n\t") + "\n"
|
||||
ret += "}\n"
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
|
||||
//
|
||||
// stmt
|
||||
//
|
||||
|
||||
case *stmt.StmtList:
|
||||
// TODO keep track of variable types within this scope
|
||||
|
||||
ret := "{\n" // new variable scope
|
||||
for _, s := range n.Stmts {
|
||||
line, err := convert(s)
|
||||
if err != nil {
|
||||
return "", parseErr{s, err}
|
||||
}
|
||||
|
||||
ret += line + "\n"
|
||||
}
|
||||
return ret + "}\n", nil
|
||||
|
||||
case *stmt.Class:
|
||||
ret := ""
|
||||
|
||||
className := n.ClassName.(*node.Identifier).Value
|
||||
memberVars := []string{}
|
||||
memberFuncs := []string{}
|
||||
|
||||
// Walk all child nodes of the class
|
||||
for _, s_ := range n.Stmts {
|
||||
switch s := s_.(type) {
|
||||
|
||||
case *stmt.PropertyList:
|
||||
// Class member variable
|
||||
|
||||
// Doc comment
|
||||
// TODO scan for `@var {type}` strings
|
||||
|
||||
// Name
|
||||
prop, ok := s.Properties[0].(*stmt.Property)
|
||||
if !ok {
|
||||
return "", parseErr{s, fmt.Errorf("unexpected propertylist structure")}
|
||||
}
|
||||
name := prop.Variable.(*expr.Variable).VarName.(*node.Identifier).Value
|
||||
|
||||
// Type (unknown)
|
||||
memberType := unknownVarType
|
||||
|
||||
// 'Modifiers' - protected public readonly ...
|
||||
// prop.Modifiers
|
||||
|
||||
memberVars = append(memberVars, name+" "+memberType)
|
||||
|
||||
case *stmt.ClassMethod:
|
||||
// Function name
|
||||
// If function is public/private/protected, set the first character to upper/lowercase
|
||||
funcName, err := applyVisibilityModifier(s.MethodName.(*node.Identifier).Value, s.Modifiers)
|
||||
if err != nil {
|
||||
return "", parseErr{s, err}
|
||||
}
|
||||
|
||||
// Doc comment
|
||||
// TODO scan for `@param {type}` strings
|
||||
|
||||
isConstructor := (strings.ToLower(funcName) == `__construct` || strings.ToLower(funcName) == strings.ToLower(className))
|
||||
|
||||
if isConstructor {
|
||||
// Constructor functions get transformed to NewFoo() (*Foo, error)
|
||||
|
||||
// We need to force the return type
|
||||
returnType := name.NewName([]node.Node{name.NewNamePart(className)})
|
||||
|
||||
// We also need prefix + suffix statements
|
||||
allStmts := make([]node.Node, 0, 2+len(s.Stmt.(*stmt.StmtList).Stmts))
|
||||
allStmts = append(allStmts, Literal{`this := &` + className + `{}`}) // TODO also insert variable type into the scope
|
||||
allStmts = append(allStmts, s.Stmt.(*stmt.StmtList).Stmts...)
|
||||
allStmts = append(allStmts, Literal{`return this, nil`})
|
||||
|
||||
// Method body
|
||||
funcStmt, err := convertFunctionCommon(s.Params, returnType, true /* always use ptr return */, allStmts)
|
||||
if err != nil {
|
||||
return "", parseErr{s, err}
|
||||
}
|
||||
memberFuncStmt := "func New" + className + funcStmt + "\n"
|
||||
memberFuncs = append(memberFuncs, memberFuncStmt)
|
||||
|
||||
} else {
|
||||
|
||||
// Method body
|
||||
funcStmt, err := convertFunctionCommon(s.Params, s.ReturnType, s.ReturnsRef, s.Stmt.(*stmt.StmtList).Stmts)
|
||||
if err != nil {
|
||||
return "", parseErr{s, err}
|
||||
}
|
||||
memberFuncStmt := "func (this *" + className + ") " + funcName + funcStmt + "\n"
|
||||
memberFuncs = append(memberFuncs, memberFuncStmt)
|
||||
|
||||
}
|
||||
|
||||
default:
|
||||
return "", parseErr{s, fmt.Errorf("Class '%s' contained unexpected AST node; expected PropertyList / ClassMethod", className)}
|
||||
}
|
||||
}
|
||||
|
||||
// Create struct typedef containing all explicit properties
|
||||
ret += "type " + className + " struct {\n"
|
||||
ret += "\t" + strings.Join(memberVars, "\n\t") + "\n"
|
||||
ret += "}\n"
|
||||
|
||||
// Create all member functions
|
||||
ret += strings.Join(memberFuncs, "\n\n")
|
||||
|
||||
// Done
|
||||
return ret, nil
|
||||
|
||||
case *stmt.Function:
|
||||
// Top-level function
|
||||
// TODO parse doc comment
|
||||
// FIXME is this the same as a closure?
|
||||
funcName := n.FunctionName.(*node.Identifier).Value
|
||||
|
||||
// All top-level functions like this are public; ensure function name starts
|
||||
// with an uppercase letter
|
||||
funcName = toPublic(funcName)
|
||||
|
||||
// Convert body
|
||||
funcStmt, err := convertFunctionCommon(n.Params, n.ReturnType, n.ReturnsRef, n.Stmts)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
ret := "func " + funcName + funcStmt + "\n"
|
||||
return ret, nil
|
||||
|
||||
case *stmt.Return:
|
||||
child, err := convert(n.Expr)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
ret := "return " + child + ", nil\n"
|
||||
return ret, nil
|
||||
|
||||
case *stmt.Throw:
|
||||
// throw (expr);
|
||||
// Treat as an err return
|
||||
// FIXME we don't know the default return type for the function we're in
|
||||
|
||||
// If the expr is a string literal, we can convert it to errors.New()
|
||||
// Although we probably can't do this in general for stringly-typed expressions
|
||||
|
||||
if str, ok := n.Expr.(*scalar.String); ok {
|
||||
return "return nil, errors.New(" + str.Value + ")\n", nil
|
||||
}
|
||||
|
||||
child, err := convert(n.Expr)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
return "return nil, " + child + "\n", nil
|
||||
|
||||
case *stmt.For:
|
||||
if len(n.Init) != 1 {
|
||||
// We can handle the case of multiple init statements by hoisting them
|
||||
// above the loop. There is no negative impact on PHP scoping rules, but
|
||||
// it may cause an extra local variable after the loop that may result
|
||||
// in type mismatch (can be fixed by using an extra scope).
|
||||
return "", parseErr{n, fmt.Errorf("for loop can only have 1 init clause, found %d", len(n.Init))}
|
||||
}
|
||||
finit, err := convert(n.Init[0])
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
if len(n.Init) != 1 {
|
||||
return "", parseErr{n, fmt.Errorf("for loop can only have 1 cond clause, found %d", len(n.Cond))}
|
||||
}
|
||||
fcond, err := convert(n.Cond[0])
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
if len(n.Init) != 1 {
|
||||
return "", parseErr{n, fmt.Errorf("for loop can only have 1 loop clause, found %d", len(n.Loop))}
|
||||
}
|
||||
loopStmt := n.Loop[0]
|
||||
if preinc, ok := loopStmt.(*expr.PreInc); ok {
|
||||
// It's idiomatic to do for (,, ++i) but preincrement doesn't exist in Go
|
||||
// Luckily for the case of a for loop, we can just swap it to postincrement
|
||||
loopStmt = expr.NewPostInc(preinc.Variable)
|
||||
} else if predec, ok := loopStmt.(*expr.PreDec); ok { // Likewise
|
||||
loopStmt = expr.NewPostDec(predec.Variable)
|
||||
}
|
||||
|
||||
floop, err := convert(loopStmt)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
body, err := convert(convertToStmtList(n.Stmt))
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
return "for " + finit + "; " + fcond + "; " + floop + " " + body + "\n", nil
|
||||
|
||||
case *stmt.Foreach:
|
||||
iterand, err := convert(n.Expr)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
valueReceiver, err := convert(n.Variable)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
keyReceiver := `_`
|
||||
if n.Key != nil {
|
||||
keyReceiver, err = convert(n.Key)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
}
|
||||
|
||||
body, err := convert(convertToStmtList(n.Stmt))
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
return "for " + keyReceiver + ", " + valueReceiver + " := range " + iterand + " " + body + "\n", nil
|
||||
|
||||
case *stmt.While:
|
||||
cond, err := convert(n.Cond)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
body, err := convert(convertToStmtList(n.Stmt))
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
return "for " + cond + " " + body + "\n", nil
|
||||
|
||||
case *stmt.Do:
|
||||
cond, err := convert(n.Cond)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
bodyStmts := convertToStmtList(n.Stmt)
|
||||
bodyStmts.Stmts = append(bodyStmts.Stmts, Literal{"if " + cond + "{\nbreak\n}"})
|
||||
|
||||
body, err := convert(bodyStmts)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
return "for " + cond + " " + body + "\n", nil
|
||||
|
||||
case *stmt.Expression:
|
||||
child, err := convert(n.Expr)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
ret := child + "\n" // standalone expression statement
|
||||
return ret, nil
|
||||
|
||||
case *stmt.Echo:
|
||||
// Convert into fmt.Print
|
||||
args := make([]string, 0, len(n.Exprs))
|
||||
for _, expr := range n.Exprs {
|
||||
exprGo, err := convert(expr)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
args = append(args, exprGo)
|
||||
}
|
||||
return "fmt.Print(" + strings.Join(args, ", ") + ")\n", nil // newline - standalone statement
|
||||
|
||||
//
|
||||
// assign
|
||||
//
|
||||
|
||||
case *assign.Assign:
|
||||
lvalue, err := convert(n.Variable) // might be a more complicated lvalue
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
rvalue, err := convert(n.Expression)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
// TODO this may need to use `:=`
|
||||
return lvalue + " = " + rvalue, nil
|
||||
|
||||
//
|
||||
// special literals
|
||||
//
|
||||
|
||||
case Literal:
|
||||
return n.Value, nil
|
||||
|
||||
case *node.Identifier:
|
||||
return n.Value, nil
|
||||
|
||||
//
|
||||
// expr
|
||||
//
|
||||
|
||||
case *expr.FunctionCall:
|
||||
// All our generated functions return err, but this AST node may be in a single-rvalue context
|
||||
// TODO do something more intelligent here
|
||||
// We can't necessarily hoist the whole call, in case we are on the right-hand side of a && operator
|
||||
funcName, err := resolveName(n.Function)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
callParams, err := convertFuncCallArgsCommon(n.ArgumentList)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
return funcName + callParams, nil // expr only, no semicolon/newline
|
||||
|
||||
case *expr.New:
|
||||
// new foo(xx)
|
||||
// Transparently convert to calling constructor function.
|
||||
nn, err := resolveName(n.Class)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
// FIXME if there is a package specifier embedded in the result name,
|
||||
// the `New` will appear in the wrong place
|
||||
nn = `New` + nn
|
||||
|
||||
// Convert resolved back to node.Name
|
||||
transparentNameNode := name.NewName([]node.Node{name.NewNamePart(nn)})
|
||||
|
||||
return convert(expr.NewFunctionCall(transparentNameNode, n.ArgumentList))
|
||||
|
||||
case *expr.PreInc:
|
||||
// """In Go, i++ is a statement, not an expression. So you can't use its value in another expression such as a function call."""
|
||||
v, err := convert(n.Variable)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
return "++" + v, nil
|
||||
|
||||
case *expr.PostInc:
|
||||
// """In Go, i++ is a statement, not an expression. So you can't use its value in another expression such as a function call."""
|
||||
v, err := convert(n.Variable)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
return v + "++", nil
|
||||
|
||||
case *expr.MethodCall:
|
||||
// Foo->Bar(Baz)
|
||||
parent, err := convert(n.Variable)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
child, err := convert(n.Method)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
args, err := convertFuncCallArgsCommon(n.ArgumentList)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
return parent + "." + child + args, nil
|
||||
|
||||
case *expr.PropertyFetch:
|
||||
// Foo->Bar
|
||||
parent, err := convert(n.Variable)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
child, err := convert(n.Property)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
return parent + "." + child, nil
|
||||
|
||||
case *expr.Variable:
|
||||
return n.VarName.(*node.Identifier).Value, nil
|
||||
|
||||
case *expr.ConstFetch:
|
||||
return resolveName(n.Constant)
|
||||
|
||||
//
|
||||
// binary
|
||||
//
|
||||
|
||||
case *binary.Plus:
|
||||
// PHP uses + for numbers, `.` for strings; Go uses `+` in both cases
|
||||
return convertBinaryCommon(n.Left, n.Right, `+`)
|
||||
|
||||
case *binary.Smaller:
|
||||
return convertBinaryCommon(n.Left, n.Right, `<`)
|
||||
|
||||
case *binary.SmallerOrEqual:
|
||||
return convertBinaryCommon(n.Left, n.Right, `<=`)
|
||||
|
||||
case *binary.Greater:
|
||||
return convertBinaryCommon(n.Left, n.Right, `>`)
|
||||
|
||||
case *binary.GreaterOrEqual:
|
||||
return convertBinaryCommon(n.Left, n.Right, `>=`)
|
||||
|
||||
case *binary.Equal:
|
||||
return convertBinaryCommon(n.Left, n.Right, `==`)
|
||||
|
||||
case *binary.Identical: // PHP triple-equals
|
||||
return convertBinaryCommon(n.Left, n.Right, `===`)
|
||||
|
||||
case *binary.Concat:
|
||||
// PHP uses + for numbers, `.` for strings; Go uses `+` in both cases
|
||||
return convertBinaryCommon(n.Left, n.Right, `+`)
|
||||
|
||||
//
|
||||
// scalar
|
||||
//
|
||||
|
||||
case *scalar.Lnumber:
|
||||
return n.Value, nil // number formats are compatible
|
||||
|
||||
case *scalar.String:
|
||||
return n.Value, nil // It's already quoted in PHP format
|
||||
// return strconv.Quote(n.Value), nil // Go source code quoting format
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported node type %s", nodeTypeString(n))
|
||||
}
|
||||
}
|
||||
|
||||
// applyVisibilityModifier renames a function to use an upper/lowercase first
|
||||
// letter based on PHP visibility modifiers.
|
||||
func applyVisibilityModifier(funcName string, modifiers []node.Node) (string, error) {
|
||||
isPublic := true
|
||||
|
||||
for _, mod := range modifiers {
|
||||
ident, ok := mod.(*node.Identifier)
|
||||
if !ok {
|
||||
return "", parseErr{mod, fmt.Errorf("expected node.Identifier")}
|
||||
}
|
||||
|
||||
switch ident.Value {
|
||||
case "public":
|
||||
isPublic = true
|
||||
case "private", "protected":
|
||||
isPublic = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if isPublic {
|
||||
return toPublic(funcName), nil
|
||||
} else {
|
||||
return toPrivate(funcName), nil
|
||||
}
|
||||
}
|
||||
|
||||
func toPublic(name string) string {
|
||||
nFirst := name[0:1]
|
||||
uFirst := strings.ToUpper(nFirst)
|
||||
if nFirst == uFirst {
|
||||
return name // avoid making more heap garbage
|
||||
}
|
||||
|
||||
return uFirst + name[1:]
|
||||
}
|
||||
|
||||
func toPrivate(name string) string {
|
||||
nFirst := name[0:1]
|
||||
lFirst := strings.ToLower(nFirst)
|
||||
if nFirst == lFirst {
|
||||
return name // avoid making more heap garbage
|
||||
}
|
||||
|
||||
return lFirst + name[1:]
|
||||
}
|
||||
|
||||
// resolveName turns a `*name.Name` node into a Go string.
|
||||
func resolveName(n node.Node) (string, error) {
|
||||
// TODO support namespace lookups
|
||||
|
||||
paramType := unknownVarType
|
||||
if vt, ok := n.(*name.Name); ok {
|
||||
if len(vt.Parts) != 1 {
|
||||
return "", parseErr{n, fmt.Errorf("name has %d parts, expected 1", len(vt.Parts))}
|
||||
}
|
||||
paramType = vt.Parts[0].(*name.NamePart).Value
|
||||
}
|
||||
|
||||
return paramType, nil
|
||||
}
|
||||
|
||||
// convertToStmtList asserts that the node is either a StmtList or wraps it in a
|
||||
// single-stmt StmtList if not.
|
||||
// Loop bodies may be a StmtList if it is wrapped in {}, or a single statement
|
||||
// if it is not; we want to enforce the use of {} for all loop bodies
|
||||
func convertToStmtList(n node.Node) *stmt.StmtList {
|
||||
if sl, ok := n.(*stmt.StmtList); ok {
|
||||
return sl // It's already a StmtList
|
||||
}
|
||||
|
||||
return stmt.NewStmtList([]node.Node{n})
|
||||
}
|
||||
|
||||
func convertBinaryCommon(left, right node.Node, goBinaryOperator string) (string, error) {
|
||||
|
||||
// PHP uses + for numbers, `.` for strings; Go uses `+` in both cases
|
||||
// Assume PHP/Go have the same associativity here
|
||||
lhs, err := convert(left)
|
||||
if err != nil {
|
||||
return "", parseErr{left, err}
|
||||
}
|
||||
rhs, err := convert(right)
|
||||
if err != nil {
|
||||
return "", parseErr{right, err}
|
||||
}
|
||||
|
||||
return "(" + lhs + " " + goBinaryOperator + " " + rhs + ")", nil
|
||||
}
|
||||
|
||||
func convertFuncCallArgsCommon(args *node.ArgumentList) (string, error) {
|
||||
|
||||
callParams := make([]string, 0, len(args.Arguments))
|
||||
for _, arg_ := range args.Arguments {
|
||||
arg, ok := arg_.(*node.Argument)
|
||||
if !ok {
|
||||
return "", parseErr{arg_, fmt.Errorf("expected node.Argument")}
|
||||
}
|
||||
|
||||
rvalue, err := convert(arg.Expr)
|
||||
if err != nil {
|
||||
return "", parseErr{arg, err}
|
||||
}
|
||||
if arg.IsReference {
|
||||
rvalue = "&" + rvalue
|
||||
}
|
||||
if arg.Variadic {
|
||||
rvalue = "..." + rvalue
|
||||
}
|
||||
|
||||
callParams = append(callParams, rvalue)
|
||||
}
|
||||
|
||||
return "(" + strings.Join(callParams, `, `) + ")", nil // expr only, no semicolon/newline
|
||||
}
|
||||
|
||||
func convertFunctionCommon(params []node.Node, returnType node.Node, returnsRef bool, bodyStmts []node.Node) (string, error) {
|
||||
|
||||
// TODO scan function and see if it contains any return statements at all
|
||||
// If not, then we only need an err return parameter, not anything else
|
||||
|
||||
funcParams := []string{}
|
||||
for _, param := range params {
|
||||
param, ok := param.(*node.Parameter) // shadow
|
||||
if !ok {
|
||||
return "", parseErr{param, fmt.Errorf("expected node.Parameter")}
|
||||
}
|
||||
|
||||
// VariableType: might be nil for untyped parameters
|
||||
paramType, err := resolveName(param.VariableType)
|
||||
if err != nil {
|
||||
return "", parseErr{param, err}
|
||||
}
|
||||
if param.ByRef {
|
||||
paramType = "*" + paramType
|
||||
}
|
||||
if param.Variadic {
|
||||
paramType = "..." + paramType
|
||||
}
|
||||
|
||||
// Name
|
||||
paramName := param.Variable.(*expr.Variable).VarName.(*node.Identifier).Value
|
||||
|
||||
funcParams = append(funcParams, paramName+" "+paramType)
|
||||
}
|
||||
|
||||
// ReturnType
|
||||
funcReturn, err := resolveName(returnType)
|
||||
if err != nil {
|
||||
return "", parseErr{returnType, err}
|
||||
}
|
||||
if returnsRef {
|
||||
funcReturn = "*" + funcReturn
|
||||
}
|
||||
|
||||
// Build function prototype
|
||||
ret := "(" + strings.Join(funcParams, ", ") + ") (" + funcReturn + ", error) {\n"
|
||||
|
||||
// Recurse through body statements
|
||||
for _, s := range bodyStmts {
|
||||
bodyStmt, err := convert(s)
|
||||
if err != nil {
|
||||
return "", parseErr{s, err}
|
||||
}
|
||||
|
||||
ret += bodyStmt + "\n"
|
||||
}
|
||||
|
||||
// Done
|
||||
// No extra trailing newline in case this is part of a large expression
|
||||
ret += "}"
|
||||
return ret, nil
|
||||
}
|
60
scope.go
Normal file
60
scope.go
Normal file
@ -0,0 +1,60 @@
|
||||
package main
|
||||
|
||||
const (
|
||||
unknownVarType string = `unknown` // placeholder
|
||||
mixedVarType string = `mixed` // when setting an incompatible type
|
||||
)
|
||||
|
||||
type LocalVar struct {
|
||||
Name string
|
||||
Type string
|
||||
}
|
||||
|
||||
type Scope struct {
|
||||
parent *Scope
|
||||
locals []LocalVar
|
||||
}
|
||||
|
||||
func NewScope() *Scope {
|
||||
return &Scope{}
|
||||
}
|
||||
|
||||
func (this *Scope) NewScope() *Scope {
|
||||
return &Scope{
|
||||
parent: this,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Scope) Has(varName string) *LocalVar {
|
||||
for idx := range this.locals {
|
||||
if this.locals[idx].Name == varName {
|
||||
return &this.locals[idx] // Mutable
|
||||
}
|
||||
}
|
||||
|
||||
if this.parent != nil {
|
||||
return this.parent.Has(varName)
|
||||
}
|
||||
|
||||
return nil // not found
|
||||
}
|
||||
|
||||
func (this *Scope) Set(Name, Type string) *LocalVar {
|
||||
|
||||
if lv := this.Has(Name); lv != nil {
|
||||
// Update known type for existing variable
|
||||
if lv.Type == unknownVarType {
|
||||
lv.Type = Type
|
||||
} else if lv.Type == Type {
|
||||
// no-op, more evidence for the same type
|
||||
} else if lv.Type != Type {
|
||||
// conflicting type information
|
||||
lv.Type = mixedVarType
|
||||
}
|
||||
return lv
|
||||
}
|
||||
|
||||
// Insert new
|
||||
this.locals = append(this.locals, LocalVar{Name: Name, Type: Type})
|
||||
return &this.locals[len(this.locals)-1]
|
||||
}
|
Loading…
Reference in New Issue
Block a user