[#82] Arrow function and assign coalesce

This commit is contained in:
z7zmey 2019-12-28 22:26:54 +02:00
parent 0e55cb3b25
commit 7b4c72a3af
17 changed files with 8117 additions and 7467 deletions

View File

@ -4,9 +4,9 @@ package freefloating
import "strconv" import "strconv"
const _Position_name = "StartEndSlashColonSemiColonAltEndDollarAmpersandNamePrefixKeyVarUseTypeReturnTypeOptionalTypeCaseSeparatorLexicalVarsParamsRefCastExprInitExprCondExprIncExprTrueCondHaltCompillerNamespaceStaticClassUseWhileForSwitchBreakForeachDeclareLabelFinallyListDefaultIfElseIfElseVariadicFunctionAliasAsEqualExitArrayIssetEmptyEvalEchoTryCatchUnsetStmtsVarListConstListNameListParamListModifierListArrayPairListCaseListStartCaseListEndArgumentListPropertyListParameterListAdaptationListLexicalVarListUseDeclarationListOpenParenthesisTokenCloseParenthesisToken" const _Position_name = "StartEndSlashColonSemiColonAltEndDollarAmpersandNamePrefixKeyVarUseTypeReturnTypeOptionalTypeCaseSeparatorLexicalVarsParamsRefCastExprInitExprCondExprIncExprTrueCondHaltCompillerNamespaceStaticClassUseWhileForSwitchBreakForeachDeclareLabelFinallyListDefaultIfElseIfElseVariadicFunctionDoubleArrowAliasAsEqualExitArrayIssetEmptyEvalEchoTryCatchUnsetStmtsVarListConstListNameListParamListModifierListArrayPairListCaseListStartCaseListEndArgumentListPropertyListParameterListAdaptationListLexicalVarListUseDeclarationListOpenParenthesisTokenCloseParenthesisToken"
var _Position_index = [...]uint16{0, 5, 8, 13, 18, 27, 33, 39, 48, 52, 58, 61, 64, 71, 81, 93, 106, 117, 123, 126, 130, 134, 142, 150, 157, 161, 165, 178, 187, 193, 198, 201, 206, 209, 215, 220, 227, 234, 239, 246, 250, 257, 259, 265, 269, 277, 285, 290, 292, 297, 301, 306, 311, 316, 320, 324, 327, 332, 337, 342, 349, 358, 366, 375, 387, 400, 413, 424, 436, 448, 461, 475, 489, 507, 527, 548} var _Position_index = [...]uint16{0, 5, 8, 13, 18, 27, 33, 39, 48, 52, 58, 61, 64, 71, 81, 93, 106, 117, 123, 126, 130, 134, 142, 150, 157, 161, 165, 178, 187, 193, 198, 201, 206, 209, 215, 220, 227, 234, 239, 246, 250, 257, 259, 265, 269, 277, 285, 296, 301, 303, 308, 312, 317, 322, 327, 331, 335, 338, 343, 348, 353, 360, 369, 377, 386, 398, 411, 424, 435, 447, 459, 472, 486, 500, 518, 538, 559}
func (i Position) String() string { func (i Position) String() string {
if i < 0 || i >= Position(len(_Position_index)-1) { if i < 0 || i >= Position(len(_Position_index)-1) {

View File

@ -61,6 +61,7 @@ const (
Else Else
Variadic Variadic
Function Function
DoubleArrow
Alias Alias
As As
Equal Equal

View File

@ -0,0 +1,66 @@
package assign
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"
)
// Coalesce node
type Coalesce struct {
FreeFloating freefloating.Collection
Position *position.Position
Variable node.Node
Expression node.Node
}
// NewCoalesce node constructor
func NewCoalesce(Variable node.Node, Expression node.Node) *Coalesce {
return &Coalesce{
FreeFloating: nil,
Variable: Variable,
Expression: Expression,
}
}
// SetPosition sets node position
func (n *Coalesce) SetPosition(p *position.Position) {
n.Position = p
}
// GetPosition returns node positions
func (n *Coalesce) GetPosition() *position.Position {
return n.Position
}
func (n *Coalesce) GetFreeFloating() *freefloating.Collection {
return &n.FreeFloating
}
// Attributes returns node attributes as map
func (n *Coalesce) Attributes() map[string]interface{} {
return nil
}
// Walk traverses nodes
// Walk is invoked recursively until v.EnterNode returns true
func (n *Coalesce) Walk(v walker.Visitor) {
if v.EnterNode(n) == false {
return
}
if n.Variable != nil {
v.EnterChildNode("Variable", n)
n.Variable.Walk(v)
v.LeaveChildNode("Variable", n)
}
if n.Expression != nil {
v.EnterChildNode("Expression", n)
n.Expression.Walk(v)
v.LeaveChildNode("Expression", n)
}
v.LeaveNode(n)
}

View File

@ -1272,3 +1272,73 @@ func TestShiftRight(t *testing.T) {
actual = php5parser.GetRootNode() actual = php5parser.GetRootNode()
assert.DeepEqual(t, expected, actual) assert.DeepEqual(t, expected, actual)
} }
func TestCoalesce(t *testing.T) {
src := `<? $a ??= $b;`
expected := &node.Root{
Position: &position.Position{
StartLine: 1,
EndLine: 1,
StartPos: 3,
EndPos: 13,
},
Stmts: []node.Node{
&stmt.Expression{
Position: &position.Position{
StartLine: 1,
EndLine: 1,
StartPos: 3,
EndPos: 13,
},
Expr: &assign.Coalesce{
Position: &position.Position{
StartLine: 1,
EndLine: 1,
StartPos: 3,
EndPos: 12,
},
Variable: &expr.Variable{
Position: &position.Position{
StartLine: 1,
EndLine: 1,
StartPos: 3,
EndPos: 5,
},
VarName: &node.Identifier{
Position: &position.Position{
StartLine: 1,
EndLine: 1,
StartPos: 3,
EndPos: 5,
},
Value: "a",
},
},
Expression: &expr.Variable{
Position: &position.Position{
StartLine: 1,
EndLine: 1,
StartPos: 10,
EndPos: 12,
},
VarName: &node.Identifier{
Position: &position.Position{
StartLine: 1,
EndLine: 1,
StartPos: 10,
EndPos: 12,
},
Value: "b",
},
},
},
},
},
}
php7parser := php7.NewParser([]byte(src), "7.4")
php7parser.Parse()
actual := php7parser.GetRootNode()
assert.DeepEqual(t, expected, actual)
}

View File

@ -41,6 +41,9 @@ var nodes = []node.Node{
&assign.BitwiseXor{ &assign.BitwiseXor{
FreeFloating: expected, FreeFloating: expected,
}, },
&assign.Coalesce{
FreeFloating: expected,
},
&assign.Concat{ &assign.Concat{
FreeFloating: expected, FreeFloating: expected,
}, },

View File

@ -56,6 +56,14 @@ var nodesToTest = []struct {
[]string{"Variable", "Expression"}, []string{"Variable", "Expression"},
nil, nil,
}, },
{
&assign.Coalesce{
Variable: &expr.Variable{},
Expression: &expr.Variable{},
},
[]string{"Variable", "Expression"},
nil,
},
{ {
&assign.Concat{ &assign.Concat{
Variable: &expr.Variable{}, Variable: &expr.Variable{},

View File

@ -0,0 +1,88 @@
package expr
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"
)
// ArrowFunction node
type ArrowFunction struct {
FreeFloating freefloating.Collection
Position *position.Position
ReturnsRef bool
Static bool
PhpDocComment string
Params []node.Node
ReturnType node.Node
Expr node.Node
}
// NewArrowFunction node constructor
func NewArrowFunction(Params []node.Node, ReturnType node.Node, Stmt node.Node, Static bool, ReturnsRef bool, PhpDocComment string) *ArrowFunction {
return &ArrowFunction{
FreeFloating: nil,
ReturnsRef: ReturnsRef,
Static: Static,
PhpDocComment: PhpDocComment,
Params: Params,
ReturnType: ReturnType,
Expr: Stmt,
}
}
// SetPosition sets node position
func (n *ArrowFunction) SetPosition(p *position.Position) {
n.Position = p
}
// GetPosition returns node positions
func (n *ArrowFunction) GetPosition() *position.Position {
return n.Position
}
func (n *ArrowFunction) GetFreeFloating() *freefloating.Collection {
return &n.FreeFloating
}
// Attributes returns node attributes as map
func (n *ArrowFunction) Attributes() map[string]interface{} {
return map[string]interface{}{
"ReturnsRef": n.ReturnsRef,
"Static": n.Static,
"PhpDocComment": n.PhpDocComment,
}
}
// Walk traverses nodes
// Walk is invoked recursively until v.EnterNode returns true
func (n *ArrowFunction) Walk(v walker.Visitor) {
if v.EnterNode(n) == false {
return
}
if n.Params != nil {
v.EnterChildList("Params", n)
for _, nn := range n.Params {
if nn != nil {
nn.Walk(v)
}
}
v.LeaveChildList("Params", n)
}
if n.ReturnType != nil {
v.EnterChildNode("ReturnType", n)
n.ReturnType.Walk(v)
v.LeaveChildNode("ReturnType", n)
}
if n.Expr != nil {
v.EnterChildNode("Expr", n)
n.Expr.Walk(v)
v.LeaveChildNode("Expr", n)
}
v.LeaveNode(n)
}

View File

@ -0,0 +1,144 @@
package expr_test
import (
"testing"
"github.com/z7zmey/php-parser/node"
"github.com/z7zmey/php-parser/node/expr"
"github.com/z7zmey/php-parser/node/name"
"github.com/z7zmey/php-parser/node/stmt"
"github.com/z7zmey/php-parser/php7"
"github.com/z7zmey/php-parser/position"
"gotest.tools/assert"
)
func TestArrowFunction(t *testing.T) {
src := `<? fn() => $a;`
expected := &node.Root{
Position: &position.Position{
StartLine: 1,
EndLine: 1,
StartPos: 3,
EndPos: 14,
},
Stmts: []node.Node{
&stmt.Expression{
Position: &position.Position{
StartLine: 1,
EndLine: 1,
StartPos: 3,
EndPos: 14,
},
Expr: &expr.ArrowFunction{
Position: &position.Position{
StartLine: 1,
EndLine: 1,
StartPos: 3,
EndPos: 13,
},
ReturnsRef: false,
Static: false,
PhpDocComment: "",
Expr: &expr.Variable{
Position: &position.Position{
StartLine: 1,
EndLine: 1,
StartPos: 11,
EndPos: 13,
},
VarName: &node.Identifier{
Position: &position.Position{
StartLine: 1,
EndLine: 1,
StartPos: 11,
EndPos: 13,
},
Value: "a",
},
},
},
},
},
}
php7parser := php7.NewParser([]byte(src), "7.4")
php7parser.Parse()
actual := php7parser.GetRootNode()
assert.DeepEqual(t, expected, actual)
}
func TestArrowFunctionReturnType(t *testing.T) {
src := `<? fn & () : foo => $a;`
expected := &node.Root{
Position: &position.Position{
StartLine: 1,
EndLine: 1,
StartPos: 3,
EndPos: 23,
},
Stmts: []node.Node{
&stmt.Expression{
Position: &position.Position{
StartLine: 1,
EndLine: 1,
StartPos: 3,
EndPos: 23,
},
Expr: &expr.ArrowFunction{
Position: &position.Position{
StartLine: 1,
EndLine: 1,
StartPos: 3,
EndPos: 22,
},
Static: false,
PhpDocComment: "",
ReturnsRef: true,
ReturnType: &name.Name{
Position: &position.Position{
StartLine: 1,
EndLine: 1,
StartPos: 13,
EndPos: 16,
},
Parts: []node.Node{
&name.NamePart{
Position: &position.Position{
StartLine: 1,
EndLine: 1,
StartPos: 13,
EndPos: 16,
},
Value: "foo",
},
},
},
Expr: &expr.Variable{
Position: &position.Position{
StartLine: 1,
EndLine: 1,
StartPos: 20,
EndPos: 22,
},
VarName: &node.Identifier{
Position: &position.Position{
StartLine: 1,
EndLine: 1,
StartPos: 20,
EndPos: 22,
},
Value: "a",
},
},
},
},
},
}
php7parser := php7.NewParser([]byte(src), "7.4")
php7parser.Parse()
actual := php7parser.GetRootNode()
assert.DeepEqual(t, expected, actual)
}

View File

@ -35,6 +35,9 @@ var nodes = []node.Node{
&expr.Array{ &expr.Array{
FreeFloating: expected, FreeFloating: expected,
}, },
&expr.ArrowFunction{
FreeFloating: expected,
},
&expr.BitwiseNot{ &expr.BitwiseNot{
FreeFloating: expected, FreeFloating: expected,
}, },

View File

@ -97,6 +97,18 @@ var nodesToTest = []struct {
[]string{"Params", "ClosureUse", "ReturnType", "Stmts"}, []string{"Params", "ClosureUse", "ReturnType", "Stmts"},
map[string]interface{}{"ReturnsRef": true, "Static": false, "PhpDocComment": ""}, map[string]interface{}{"ReturnsRef": true, "Static": false, "PhpDocComment": ""},
}, },
{
&expr.ArrowFunction{
ReturnsRef: true,
Static: false,
PhpDocComment: "",
Params: []node.Node{&node.Parameter{}},
ReturnType: &name.Name{},
Expr: &expr.Variable{},
},
[]string{"Params", "ReturnType", "Expr"},
map[string]interface{}{"ReturnsRef": true, "Static": false, "PhpDocComment": ""},
},
{ {
&expr.ConstFetch{ &expr.ConstFetch{
Constant: &node.Identifier{Value: "foo"}, Constant: &node.Identifier{Value: "foo"},

File diff suppressed because it is too large Load Diff

View File

@ -271,6 +271,7 @@ import (
%type <node> switch_case_list %type <node> switch_case_list
%type <node> method_body %type <node> method_body
%type <node> foreach_statement for_statement while_statement %type <node> foreach_statement for_statement while_statement
%type <node> inline_function
%type <ClassExtends> extends_from %type <ClassExtends> extends_from
%type <ClassImplements> implements_list %type <ClassImplements> implements_list
%type <InterfaceExtends> interface_extends_list %type <InterfaceExtends> interface_extends_list
@ -318,7 +319,7 @@ reserved_non_modifiers:
| T_THROW {$$=$1} | T_USE {$$=$1} | T_INSTEADOF {$$=$1} | T_GLOBAL {$$=$1} | T_VAR {$$=$1} | T_UNSET {$$=$1} | T_ISSET {$$=$1} | T_EMPTY {$$=$1} | T_CONTINUE {$$=$1} | T_GOTO {$$=$1} | T_THROW {$$=$1} | T_USE {$$=$1} | T_INSTEADOF {$$=$1} | T_GLOBAL {$$=$1} | T_VAR {$$=$1} | T_UNSET {$$=$1} | T_ISSET {$$=$1} | T_EMPTY {$$=$1} | T_CONTINUE {$$=$1} | T_GOTO {$$=$1}
| T_FUNCTION {$$=$1} | T_CONST {$$=$1} | T_RETURN {$$=$1} | T_PRINT {$$=$1} | T_YIELD {$$=$1} | T_LIST {$$=$1} | T_SWITCH {$$=$1} | T_ENDSWITCH {$$=$1} | T_CASE {$$=$1} | T_DEFAULT {$$=$1} | T_BREAK {$$=$1} | T_FUNCTION {$$=$1} | T_CONST {$$=$1} | T_RETURN {$$=$1} | T_PRINT {$$=$1} | T_YIELD {$$=$1} | T_LIST {$$=$1} | T_SWITCH {$$=$1} | T_ENDSWITCH {$$=$1} | T_CASE {$$=$1} | T_DEFAULT {$$=$1} | T_BREAK {$$=$1}
| T_ARRAY {$$=$1} | T_CALLABLE {$$=$1} | T_EXTENDS {$$=$1} | T_IMPLEMENTS {$$=$1} | T_NAMESPACE {$$=$1} | T_TRAIT {$$=$1} | T_INTERFACE {$$=$1} | T_CLASS {$$=$1} | T_ARRAY {$$=$1} | T_CALLABLE {$$=$1} | T_EXTENDS {$$=$1} | T_IMPLEMENTS {$$=$1} | T_NAMESPACE {$$=$1} | T_TRAIT {$$=$1} | T_INTERFACE {$$=$1} | T_CLASS {$$=$1}
| T_CLASS_C {$$=$1} | T_TRAIT_C {$$=$1} | T_FUNC_C {$$=$1} | T_METHOD_C {$$=$1} | T_LINE {$$=$1} | T_FILE {$$=$1} | T_DIR {$$=$1} | T_NS_C {$$=$1} | T_CLASS_C {$$=$1} | T_TRAIT_C {$$=$1} | T_FUNC_C {$$=$1} | T_METHOD_C {$$=$1} | T_LINE {$$=$1} | T_FILE {$$=$1} | T_DIR {$$=$1} | T_NS_C {$$=$1} | T_FN {$$=$1}
; ;
semi_reserved: semi_reserved:
@ -3398,6 +3399,19 @@ expr_without_variable:
yylex.(*Parser).MoveFreeFloating($1, $$) yylex.(*Parser).MoveFreeFloating($1, $$)
yylex.(*Parser).setFreeFloating($$, freefloating.Var, $2.FreeFloating) yylex.(*Parser).setFreeFloating($$, freefloating.Var, $2.FreeFloating)
yylex.(*Parser).returnTokenToPool(yyDollar, &yyVAL)
}
| variable T_COALESCE_EQUAL expr
{
$$ = assign.NewCoalesce($1, $3)
// save position
$$.SetPosition(yylex.(*Parser).positionBuilder.NewNodesPosition($1, $3))
// save comments
yylex.(*Parser).MoveFreeFloating($1, $$)
yylex.(*Parser).setFreeFloating($$, freefloating.Var, $2.FreeFloating)
yylex.(*Parser).returnTokenToPool(yyDollar, &yyVAL) yylex.(*Parser).returnTokenToPool(yyDollar, &yyVAL)
} }
| variable T_INC | variable T_INC
@ -4122,7 +4136,36 @@ expr_without_variable:
yylex.(*Parser).returnTokenToPool(yyDollar, &yyVAL) yylex.(*Parser).returnTokenToPool(yyDollar, &yyVAL)
} }
| T_FUNCTION returns_ref backup_doc_comment '(' parameter_list ')' lexical_vars return_type '{' inner_statement_list '}' | inline_function
{
$$ = $1;
yylex.(*Parser).returnTokenToPool(yyDollar, &yyVAL)
}
| T_STATIC inline_function
{
$$ = $2;
switch n := $$.(type) {
case *expr.Closure :
n.Static = true;
case *expr.ArrowFunction :
n.Static = true;
};
// save position
$$.SetPosition(yylex.(*Parser).positionBuilder.NewTokenNodePosition($1, $2))
// save comments
yylex.(*Parser).setFreeFloating($$, freefloating.Static, (*$$.GetFreeFloating())[freefloating.Start]); delete((*$$.GetFreeFloating()), freefloating.Start)
yylex.(*Parser).setFreeFloating($$, freefloating.Start, $1.FreeFloating);
yylex.(*Parser).returnTokenToPool(yyDollar, &yyVAL)
}
;
inline_function:
T_FUNCTION returns_ref backup_doc_comment '(' parameter_list ')' lexical_vars return_type '{' inner_statement_list '}'
{ {
$$ = expr.NewClosure($5, $7, $8, $10, false, $2 != nil, $3) $$ = expr.NewClosure($5, $7, $8, $10, false, $2 != nil, $3)
@ -4154,36 +4197,31 @@ expr_without_variable:
yylex.(*Parser).returnTokenToPool(yyDollar, &yyVAL) yylex.(*Parser).returnTokenToPool(yyDollar, &yyVAL)
} }
| T_STATIC T_FUNCTION returns_ref backup_doc_comment '(' parameter_list ')' lexical_vars return_type '{' inner_statement_list '}' | T_FN returns_ref '(' parameter_list ')' return_type backup_doc_comment T_DOUBLE_ARROW expr
{ {
$$ = expr.NewClosure($6, $8, $9, $11, true, $3 != nil, $4) $$ = expr.NewArrowFunction($4, $6, $9, false, $2 != nil, $7)
// save position // save position
$$.SetPosition(yylex.(*Parser).positionBuilder.NewTokensPosition($1, $12)) $$.SetPosition(yylex.(*Parser).positionBuilder.NewTokenNodePosition($1, $9))
// save comments // save comments
yylex.(*Parser).setFreeFloating($$, freefloating.Start, $1.FreeFloating) yylex.(*Parser).setFreeFloating($$, freefloating.Start, $1.FreeFloating)
yylex.(*Parser).setFreeFloating($$, freefloating.Static, $2.FreeFloating) if $2 == nil {
if $3 == nil {
yylex.(*Parser).setFreeFloating($$, freefloating.Function, $5.FreeFloating)
} else {
yylex.(*Parser).setFreeFloating($$, freefloating.Function, $3.FreeFloating) yylex.(*Parser).setFreeFloating($$, freefloating.Function, $3.FreeFloating)
yylex.(*Parser).setFreeFloating($$, freefloating.Ampersand, $5.FreeFloating) } else {
} yylex.(*Parser).setFreeFloating($$, freefloating.Function, $2.FreeFloating)
yylex.(*Parser).setFreeFloating($$, freefloating.ParameterList, $7.FreeFloating) yylex.(*Parser).setFreeFloating($$, freefloating.Ampersand, $3.FreeFloating)
if $9 != nil { };
yylex.(*Parser).setFreeFloating($$, freefloating.LexicalVars, (*$9.GetFreeFloating())[freefloating.Colon]); delete((*$9.GetFreeFloating()), freefloating.Colon) yylex.(*Parser).setFreeFloating($$, freefloating.ParameterList, $5.FreeFloating)
} if $6 != nil {
yylex.(*Parser).setFreeFloating($$, freefloating.ReturnType, $10.FreeFloating) yylex.(*Parser).setFreeFloating($$, freefloating.Params, (*$6.GetFreeFloating())[freefloating.Colon]); delete((*$6.GetFreeFloating()), freefloating.Colon)
yylex.(*Parser).setFreeFloating($$, freefloating.Stmts, $12.FreeFloating) };
yylex.(*Parser).setFreeFloating($$, freefloating.ReturnType, $8.FreeFloating)
// normalize // normalize
if $9 == nil { if $6 == nil {
yylex.(*Parser).setFreeFloating($$, freefloating.LexicalVars, (*$$.GetFreeFloating())[freefloating.ReturnType]); delete((*$$.GetFreeFloating()), freefloating.ReturnType) yylex.(*Parser).setFreeFloating($$, freefloating.Params, (*$$.GetFreeFloating())[freefloating.ReturnType]); delete((*$$.GetFreeFloating()), freefloating.ReturnType)
} };
if $8 == nil {
yylex.(*Parser).setFreeFloating($$, freefloating.Params, (*$$.GetFreeFloating())[freefloating.LexicalVarList]); delete((*$$.GetFreeFloating()), freefloating.LexicalVarList)
}
yylex.(*Parser).returnTokenToPool(yyDollar, &yyVAL) yylex.(*Parser).returnTokenToPool(yyDollar, &yyVAL)
} }

View File

@ -135,6 +135,8 @@ func (p *Printer) printNode(n node.Node) {
p.printAssignBitwiseOr(n) p.printAssignBitwiseOr(n)
case *assign.BitwiseXor: case *assign.BitwiseXor:
p.printAssignBitwiseXor(n) p.printAssignBitwiseXor(n)
case *assign.Coalesce:
p.printAssignCoalesce(n)
case *assign.Concat: case *assign.Concat:
p.printAssignConcat(n) p.printAssignConcat(n)
case *assign.Div: case *assign.Div:
@ -236,6 +238,8 @@ func (p *Printer) printNode(n node.Node) {
p.printExprArrayItem(n) p.printExprArrayItem(n)
case *expr.Array: case *expr.Array:
p.printExprArray(n) p.printExprArray(n)
case *expr.ArrowFunction:
p.printExprArrowFunction(n)
case *expr.BitwiseNot: case *expr.BitwiseNot:
p.printExprBitwiseNot(n) p.printExprBitwiseNot(n)
case *expr.BooleanNot: case *expr.BooleanNot:
@ -712,6 +716,17 @@ func (p *Printer) printAssignBitwiseXor(n node.Node) {
p.printFreeFloating(nn, freefloating.End) p.printFreeFloating(nn, freefloating.End)
} }
func (p *Printer) printAssignCoalesce(n node.Node) {
nn := n.(*assign.Coalesce)
p.printFreeFloating(nn, freefloating.Start)
p.Print(nn.Variable)
p.printFreeFloating(nn, freefloating.Var)
io.WriteString(p.w, "??=")
p.Print(nn.Expression)
p.printFreeFloating(nn, freefloating.End)
}
func (p *Printer) printAssignConcat(n node.Node) { func (p *Printer) printAssignConcat(n node.Node) {
nn := n.(*assign.Concat) nn := n.(*assign.Concat)
p.printFreeFloating(nn, freefloating.Start) p.printFreeFloating(nn, freefloating.Start)
@ -1313,6 +1328,45 @@ func (p *Printer) printExprArray(n node.Node) {
p.printFreeFloating(nn, freefloating.End) p.printFreeFloating(nn, freefloating.End)
} }
func (p *Printer) printExprArrowFunction(n node.Node) {
nn := n.(*expr.ArrowFunction)
p.printFreeFloating(nn, freefloating.Start)
if nn.Static {
io.WriteString(p.w, "static")
}
p.printFreeFloating(nn, freefloating.Static)
if nn.Static && n.GetFreeFloating().IsEmpty() {
io.WriteString(p.w, " ")
}
io.WriteString(p.w, "fn")
p.printFreeFloating(nn, freefloating.Function)
if nn.ReturnsRef {
io.WriteString(p.w, "&")
}
p.printFreeFloating(nn, freefloating.Ampersand)
io.WriteString(p.w, "(")
p.joinPrint(",", nn.Params)
p.printFreeFloating(nn, freefloating.ParameterList)
io.WriteString(p.w, ")")
p.printFreeFloating(nn, freefloating.Params)
if nn.ReturnType != nil {
io.WriteString(p.w, ":")
p.Print(nn.ReturnType)
}
p.printFreeFloating(nn, freefloating.ReturnType)
io.WriteString(p.w, "=>")
p.printNode(nn.Expr)
p.printFreeFloating(nn, freefloating.End)
}
func (p *Printer) printExprBitwiseNot(n node.Node) { func (p *Printer) printExprBitwiseNot(n node.Node) {
nn := n.(*expr.BitwiseNot) nn := n.(*expr.BitwiseNot)
p.printFreeFloating(nn, freefloating.Start) p.printFreeFloating(nn, freefloating.Start)

View File

@ -270,6 +270,7 @@ func TestParseAndPrintAssign(t *testing.T) {
$a &= $b ; $a &= $b ;
$a |= $b ; $a |= $b ;
$a ^= $b ; $a ^= $b ;
$a ??= $b ;
$a .= $b ; $a .= $b ;
$a /= $b ; $a /= $b ;
$a -= $b ; $a -= $b ;
@ -482,6 +483,18 @@ func TestParseAndPrintClosure(t *testing.T) {
} }
} }
func TestParseAndPrintArrowFunction(t *testing.T) {
src := `<?php
$a = static fn & ( $b ) : void => $c ;
`
actual := print(parse(src))
if src != actual {
t.Errorf("\nexpected: %s\ngot: %s\n", src, actual)
}
}
func TestParseAndPrintConstFetch(t *testing.T) { func TestParseAndPrintConstFetch(t *testing.T) {
src := `<?php src := `<?php
null ; null ;

View File

@ -586,6 +586,27 @@ func TestPrinterPrintAssignBitwiseXor(t *testing.T) {
} }
} }
func TestPrinterPrintAssignCoalesce(t *testing.T) {
o := bytes.NewBufferString("")
p := printer.NewPrinter(o)
p.Print(&assign.Coalesce{
Variable: &expr.Variable{
VarName: &node.Identifier{Value: "a"},
},
Expression: &expr.Variable{
VarName: &node.Identifier{Value: "b"},
},
})
expected := `$a??=$b`
actual := o.String()
if expected != actual {
t.Errorf("\nexpected: %s\ngot: %s\n", expected, actual)
}
}
func TestPrinterPrintAssignConcat(t *testing.T) { func TestPrinterPrintAssignConcat(t *testing.T) {
o := bytes.NewBufferString("") o := bytes.NewBufferString("")
@ -1706,6 +1727,40 @@ func TestPrinterPrintExprClosure(t *testing.T) {
} }
} }
func TestPrinterPrintExprArrowFunction(t *testing.T) {
o := bytes.NewBufferString("")
p := printer.NewPrinter(o)
p.Print(&stmt.Expression{
Expr: &expr.ArrowFunction{
Static: true,
ReturnsRef: true,
Params: []node.Node{
&node.Parameter{
ByRef: true,
Variadic: false,
Variable: &expr.Variable{
VarName: &node.Identifier{Value: "var"},
},
},
},
ReturnType: &name.FullyQualified{
Parts: []node.Node{&name.NamePart{Value: "Foo"}},
},
Expr: &expr.Variable{
VarName: &node.Identifier{Value: "a"},
},
},
})
expected := `static fn&(&$var):\Foo=>$a;`
actual := o.String()
if expected != actual {
t.Errorf("\nexpected: %s\ngot: %s\n", expected, actual)
}
}
func TestPrinterPrintExprConstFetch(t *testing.T) { func TestPrinterPrintExprConstFetch(t *testing.T) {
o := bytes.NewBufferString("") o := bytes.NewBufferString("")

File diff suppressed because it is too large Load Diff

View File

@ -231,6 +231,7 @@ func (lex *Lexer) Lex(lval Lval) int {
'for'i => {lex.setTokenPosition(token); tok = T_FOR; fbreak;}; 'for'i => {lex.setTokenPosition(token); tok = T_FOR; fbreak;};
'foreach'i => {lex.setTokenPosition(token); tok = T_FOREACH; fbreak;}; 'foreach'i => {lex.setTokenPosition(token); tok = T_FOREACH; fbreak;};
'function'i | 'cfunction'i => {lex.setTokenPosition(token); tok = T_FUNCTION; fbreak;}; 'function'i | 'cfunction'i => {lex.setTokenPosition(token); tok = T_FUNCTION; fbreak;};
'fn'i => {lex.setTokenPosition(token); tok = T_FN; fbreak;};
'global'i => {lex.setTokenPosition(token); tok = T_GLOBAL; fbreak;}; 'global'i => {lex.setTokenPosition(token); tok = T_GLOBAL; fbreak;};
'goto'i => {lex.setTokenPosition(token); tok = T_GOTO; fbreak;}; 'goto'i => {lex.setTokenPosition(token); tok = T_GOTO; fbreak;};
'if'i => {lex.setTokenPosition(token); tok = T_IF; fbreak;}; 'if'i => {lex.setTokenPosition(token); tok = T_IF; fbreak;};
@ -305,6 +306,7 @@ func (lex *Lexer) Lex(lval Lval) int {
'<<' => {lex.setTokenPosition(token); tok = T_SL; fbreak;}; '<<' => {lex.setTokenPosition(token); tok = T_SL; fbreak;};
'>>' => {lex.setTokenPosition(token); tok = T_SR; fbreak;}; '>>' => {lex.setTokenPosition(token); tok = T_SR; fbreak;};
'??' => {lex.setTokenPosition(token); tok = T_COALESCE; fbreak;}; '??' => {lex.setTokenPosition(token); tok = T_COALESCE; fbreak;};
'??=' => {lex.setTokenPosition(token); tok = T_COALESCE_EQUAL; fbreak;};
'(' whitespace* 'array'i whitespace* ')' => {lex.setTokenPosition(token); tok = T_ARRAY_CAST; fbreak;}; '(' whitespace* 'array'i whitespace* ')' => {lex.setTokenPosition(token); tok = T_ARRAY_CAST; fbreak;};
'(' whitespace* ('bool'i|'boolean'i) whitespace* ')' => {lex.setTokenPosition(token); tok = T_BOOL_CAST; fbreak;}; '(' whitespace* ('bool'i|'boolean'i) whitespace* ')' => {lex.setTokenPosition(token); tok = T_BOOL_CAST; fbreak;};