Module refinery.lib.scripts.php.synth

Expand source code Browse git
from __future__ import annotations

from refinery.lib.scripts import Node, Statement, Synthesizer
from refinery.lib.scripts.php.deobfuscation.helpers import escape_php_string
from refinery.lib.scripts.php.model import (
    PhpArg,
    PhpArray,
    PhpArrayDimFetch,
    PhpArrayItem,
    PhpArrowFunction,
    PhpAssignment,
    PhpAttribute,
    PhpAttributeGroup,
    PhpBinaryExpression,
    PhpBlock,
    PhpBooleanLiteral,
    PhpBreak,
    PhpCase,
    PhpCastExpression,
    PhpCatch,
    PhpClass,
    PhpClassConst,
    PhpClassConstFetch,
    PhpClassMethod,
    PhpClone,
    PhpClosure,
    PhpClosureUse,
    PhpConst,
    PhpConstDeclaration,
    PhpConstFetch,
    PhpContinue,
    PhpDeclare,
    PhpDeclareDirective,
    PhpDoWhile,
    PhpEcho,
    PhpEchoTagStatement,
    PhpEmpty,
    PhpEnumCase,
    PhpErrorNode,
    PhpErrorSuppress,
    PhpEval,
    PhpExit,
    PhpExpressionStatement,
    PhpFloatLiteral,
    PhpFor,
    PhpForeach,
    PhpFunctionCall,
    PhpFunctionDeclaration,
    PhpGlobal,
    PhpGoto,
    PhpGroupUse,
    PhpHaltCompiler,
    PhpHeredoc,
    PhpIdentifier,
    PhpIf,
    PhpInclude,
    PhpInlineHTML,
    PhpInstanceof,
    PhpIntLiteral,
    PhpInterpolatedString,
    PhpIntersectionType,
    PhpIsset,
    PhpLabel,
    PhpList,
    PhpMagicConstant,
    PhpMatch,
    PhpMatchArm,
    PhpMethodCall,
    PhpName,
    PhpNameKind,
    PhpNamespace,
    PhpNew,
    PhpNewAnonymous,
    PhpNop,
    PhpNullableType,
    PhpNullLiteral,
    PhpParam,
    PhpParenExpression,
    PhpPrint,
    PhpProperty,
    PhpPropertyDeclaration,
    PhpPropertyFetch,
    PhpReturn,
    PhpScript,
    PhpShellExec,
    PhpStaticCall,
    PhpStaticPropertyFetch,
    PhpStaticVar,
    PhpStaticVarDeclaration,
    PhpStringLiteral,
    PhpSwitch,
    PhpTernary,
    PhpThrowExpression,
    PhpThrowStatement,
    PhpTraitAdaptation,
    PhpTraitUse,
    PhpTry,
    PhpUnaryExpression,
    PhpUnionType,
    PhpUnset,
    PhpUpdateExpression,
    PhpUse,
    PhpUseItem,
    PhpUseKind,
    PhpVariable,
    PhpVariableVariable,
    PhpWhile,
    PhpYield,
    PhpYieldFrom,
)
from refinery.lib.scripts.php.precedence import needs_parens


class PhpSynthesizer(Synthesizer):
    """
    Synthesize PHP source from a `PhpScript` AST. Correctness of expression nesting is provided by
    `precedence.needs_parens` rather than by the presence of `PhpParenExpression` nodes, so a tree
    produced by a deobfuscation pass that dropped explicit parentheses still prints correctly.
    """

    def __init__(
        self,
        indent: str = '    ',
        line_length: int = 140,
        unescape_strings: bool = False,
        strip_comments: bool = False,
    ):
        super().__init__(indent, line_length)
        self._unescape_strings = unescape_strings
        self._strip_comments = strip_comments
        self._in_php = True

    def _emit_leading_comments(self, node: Node):
        if self._strip_comments or not node.leading_comments:
            return
        for comment in node.leading_comments:
            self._write(comment)
            self._newline()

    def _emit_child(self, child: Node | None, parent: Node):
        if child is None:
            return
        if needs_parens(child, parent):
            self._write('(')
            self.visit(child)
            self._write(')')
        else:
            self.visit(child)

    def _comma_list(self, nodes: list):
        for i, node in enumerate(nodes):
            if i > 0:
                self._write(', ')
            if node is None:
                continue
            self.visit(node)

    def _enter_php(self):
        if not self._in_php:
            self._write('<?php')
            self._in_php = True

    def _leave_php(self):
        if self._in_php:
            self._write(' ?>\n')
            self._in_php = False

    def _emit_statement(self, stmt: Statement):
        if isinstance(stmt, (PhpInlineHTML, PhpEchoTagStatement)):
            self._leave_php()
            self.visit(stmt)
        else:
            self._enter_php()
            self._newline()
            self._emit_leading_comments(stmt)
            self.visit(stmt)

    def _emit_statements(self, body: list[Statement]):
        self._depth += 1
        for stmt in body:
            self._emit_statement(stmt)
        self._depth -= 1
        if body:
            self._enter_php()
            self._newline()

    def _emit_brace_block(self, body: list[Statement]):
        self._write('{')
        self._emit_statements(body)
        self._write('}')

    def visit_PhpIntLiteral(self, node: PhpIntLiteral):
        self._write(node.raw)

    def visit_PhpFloatLiteral(self, node: PhpFloatLiteral):
        self._write(node.raw)

    def visit_PhpStringLiteral(self, node: PhpStringLiteral):
        if self._unescape_strings:
            quote = node.raw[:1] if node.raw[:1] in ('"', '\'') else '\''
            self._write(F'{quote}{escape_php_string(node.value, quote)}{quote}')
        else:
            self._write(node.raw)

    def visit_PhpInterpolatedString(self, node: PhpInterpolatedString):
        self._write(node.raw)

    def visit_PhpHeredoc(self, node: PhpHeredoc):
        self._write(node.raw)

    def visit_PhpShellExec(self, node: PhpShellExec):
        self._write(node.raw)

    def visit_PhpBooleanLiteral(self, node: PhpBooleanLiteral):
        self._write(node.raw)

    def visit_PhpNullLiteral(self, node: PhpNullLiteral):
        self._write(node.raw)

    def visit_PhpMagicConstant(self, node: PhpMagicConstant):
        self._write(node.name)

    def visit_PhpVariable(self, node: PhpVariable):
        self._write(node.name)

    def visit_PhpVariableVariable(self, node: PhpVariableVariable):
        self._write('$')
        if isinstance(node.expression, PhpVariable):
            self.visit(node.expression)
        else:
            self._write('{')
            if node.expression:
                self.visit(node.expression)
            self._write('}')

    def visit_PhpIdentifier(self, node: PhpIdentifier):
        self._write(node.name)

    def visit_PhpName(self, node: PhpName):
        if node.kind is PhpNameKind.FULLY_QUALIFIED:
            self._write('\\')
        elif node.kind is PhpNameKind.RELATIVE:
            self._write('namespace\\')
        self._write('\\'.join(node.parts))

    def visit_PhpConstFetch(self, node: PhpConstFetch):
        if node.name:
            self.visit(node.name)

    def visit_PhpErrorNode(self, node: PhpErrorNode):
        self._write(node.text)

    def visit_PhpParenExpression(self, node: PhpParenExpression):
        self._write('(')
        if node.expression:
            self.visit(node.expression)
        self._write(')')

    def _emit_binary(self, node: PhpBinaryExpression):
        self._emit_child(node.left, node)
        self._write(F' {node.operator} ')
        self._emit_child(node.right, node)

    visit_PhpBinaryExpression = _emit_binary

    def visit_PhpAssignment(self, node: PhpAssignment):
        self._emit_child(node.target, node)
        self._write(F' {node.operator} ')
        if node.by_ref:
            self._write('&')
        self._emit_child(node.value, node)

    def visit_PhpUnaryExpression(self, node: PhpUnaryExpression):
        self._write(node.operator)
        self._emit_child(node.operand, node)

    def visit_PhpUpdateExpression(self, node: PhpUpdateExpression):
        if node.prefix:
            self._write(node.operator)
            self._emit_child(node.operand, node)
        else:
            self._emit_child(node.operand, node)
            self._write(node.operator)

    def visit_PhpCastExpression(self, node: PhpCastExpression):
        self._write(F'({node.cast}) ')
        self._emit_child(node.operand, node)

    def visit_PhpErrorSuppress(self, node: PhpErrorSuppress):
        self._write('@')
        self._emit_child(node.operand, node)

    def visit_PhpClone(self, node: PhpClone):
        self._write('clone ')
        self._emit_child(node.operand, node)

    def visit_PhpInstanceof(self, node: PhpInstanceof):
        self._emit_child(node.operand, node)
        self._write(' instanceof ')
        self._emit_child(node.class_name, node)

    def visit_PhpTernary(self, node: PhpTernary):
        self._emit_child(node.condition, node)
        if node.consequent is None:
            self._write(' ?: ')
        else:
            self._write(' ? ')
            self._emit_child(node.consequent, node)
            self._write(' : ')
        self._emit_child(node.alternate, node)

    def visit_PhpPrint(self, node: PhpPrint):
        self._write('print ')
        self._emit_child(node.operand, node)

    def visit_PhpInclude(self, node: PhpInclude):
        self._write(F'{node.kind} ')
        self._emit_child(node.operand, node)

    def visit_PhpThrowExpression(self, node: PhpThrowExpression):
        self._write('throw ')
        self._emit_child(node.operand, node)

    def visit_PhpYield(self, node: PhpYield):
        self._write('yield')
        if node.value is not None:
            self._write(' ')
            if node.key is not None:
                self._emit_child(node.key, node)
                self._write(' => ')
            self._emit_child(node.value, node)

    def visit_PhpYieldFrom(self, node: PhpYieldFrom):
        self._write('yield from ')
        self._emit_child(node.operand, node)

    def _emit_args(self, args: list[PhpArg], first_class_callable: bool):
        self._write('(')
        if first_class_callable:
            self._write('...')
        else:
            self._comma_list(args)
        self._write(')')

    def visit_PhpArg(self, node: PhpArg):
        if node.name is not None:
            self._write(F'{node.name}: ')
        if node.spread:
            self._write('...')
        if node.by_ref:
            self._write('&')
        if node.value:
            self.visit(node.value)

    def visit_PhpFunctionCall(self, node: PhpFunctionCall):
        self._emit_child(node.callee, node)
        self._emit_args(node.args, node.first_class_callable)

    def visit_PhpMethodCall(self, node: PhpMethodCall):
        self._emit_child(node.receiver, node)
        self._write('?->' if node.nullsafe else '->')
        self._emit_member_name(node.method)
        self._emit_args(node.args, node.first_class_callable)

    def visit_PhpStaticCall(self, node: PhpStaticCall):
        self._emit_child(node.class_name, node)
        self._write('::')
        self._emit_member_name(node.method)
        self._emit_args(node.args, node.first_class_callable)

    def visit_PhpPropertyFetch(self, node: PhpPropertyFetch):
        self._emit_child(node.receiver, node)
        self._write('?->' if node.nullsafe else '->')
        self._emit_member_name(node.name)

    def visit_PhpStaticPropertyFetch(self, node: PhpStaticPropertyFetch):
        self._emit_child(node.class_name, node)
        self._write('::')
        self._emit_member_name(node.name)

    def visit_PhpClassConstFetch(self, node: PhpClassConstFetch):
        self._emit_child(node.class_name, node)
        self._write('::')
        self._emit_member_name(node.name)

    def _emit_member_name(self, name: Node | None):
        if name is None:
            return
        if isinstance(name, (PhpIdentifier, PhpVariable, PhpVariableVariable)):
            self.visit(name)
        else:
            self._write('{')
            self.visit(name)
            self._write('}')

    def visit_PhpArrayDimFetch(self, node: PhpArrayDimFetch):
        self._emit_child(node.receiver, node)
        self._write('[')
        if node.index is not None:
            self.visit(node.index)
        self._write(']')

    def visit_PhpArray(self, node: PhpArray):
        if node.short:
            self._write('[')
            self._comma_list(node.items)
            self._write(']')
        else:
            self._write('array(')
            self._comma_list(node.items)
            self._write(')')

    def visit_PhpList(self, node: PhpList):
        if node.short:
            self._write('[')
            self._comma_list(node.items)
            self._write(']')
        else:
            self._write('list(')
            self._comma_list(node.items)
            self._write(')')

    def visit_PhpArrayItem(self, node: PhpArrayItem):
        if node.spread:
            self._write('...')
        if node.key is not None:
            self._emit_child(node.key, node)
            self._write(' => ')
        if node.by_ref:
            self._write('&')
        if node.value is not None:
            self._emit_child(node.value, node)

    def visit_PhpMatch(self, node: PhpMatch):
        self._write('match (')
        if node.subject:
            self.visit(node.subject)
        self._write(') {')
        self._depth += 1
        for arm in node.arms:
            self._newline()
            self.visit(arm)
            self._write(',')
        self._depth -= 1
        if node.arms:
            self._newline()
        self._write('}')

    def visit_PhpMatchArm(self, node: PhpMatchArm):
        if node.is_default:
            self._write('default')
        else:
            self._comma_list(node.conditions)
        self._write(' => ')
        if node.body:
            self._emit_child(node.body, node)

    def visit_PhpIsset(self, node: PhpIsset):
        self._write('isset(')
        self._comma_list(node.variables)
        self._write(')')

    def visit_PhpEmpty(self, node: PhpEmpty):
        self._write('empty(')
        if node.operand:
            self.visit(node.operand)
        self._write(')')

    def visit_PhpEval(self, node: PhpEval):
        self._write('eval(')
        if node.operand:
            self.visit(node.operand)
        self._write(')')

    def visit_PhpExit(self, node: PhpExit):
        self._write(node.keyword)
        if node.operand is not None:
            self._write('(')
            self.visit(node.operand)
            self._write(')')

    def _emit_params(self, params: list[PhpParam]):
        self._write('(')
        self._comma_list(params)
        self._write(')')

    def visit_PhpParam(self, node: PhpParam):
        for group in node.attributes:
            self.visit(group)
            self._write(' ')
        if node.visibility is not None:
            self._write(F'{node.visibility.value} ')
        if node.readonly:
            self._write('readonly ')
        if node.type is not None:
            self.visit(node.type)
            self._write(' ')
        if node.by_ref:
            self._write('&')
        if node.variadic:
            self._write('...')
        self._write(node.name)
        if node.default is not None:
            self._write(' = ')
            self._emit_child(node.default, node)

    def visit_PhpNullableType(self, node: PhpNullableType):
        self._write('?')
        if node.type:
            self.visit(node.type)

    def visit_PhpUnionType(self, node: PhpUnionType):
        for i, t in enumerate(node.types):
            if i > 0:
                self._write('|')
            if isinstance(t, PhpIntersectionType):
                self._write('(')
                self.visit(t)
                self._write(')')
            else:
                self.visit(t)

    def visit_PhpIntersectionType(self, node: PhpIntersectionType):
        for i, t in enumerate(node.types):
            if i > 0:
                self._write('&')
            self.visit(t)

    def visit_PhpClosure(self, node: PhpClosure):
        if node.is_static:
            self._write('static ')
        self._write('function ')
        if node.by_ref:
            self._write('&')
        self._emit_params(node.params)
        if node.uses:
            self._write(' use (')
            self._comma_list(node.uses)
            self._write(')')
        if node.return_type is not None:
            self._write(': ')
            self.visit(node.return_type)
        self._write(' ')
        if node.body:
            self._emit_brace_block(node.body.body)

    def visit_PhpClosureUse(self, node: PhpClosureUse):
        if node.by_ref:
            self._write('&')
        if node.variable:
            self.visit(node.variable)

    def visit_PhpArrowFunction(self, node: PhpArrowFunction):
        if node.is_static:
            self._write('static ')
        self._write('fn')
        if node.by_ref:
            self._write('&')
        self._emit_params(node.params)
        if node.return_type is not None:
            self._write(': ')
            self.visit(node.return_type)
        self._write(' => ')
        if node.body:
            self._emit_child(node.body, node)

    def visit_PhpNew(self, node: PhpNew):
        self._write('new ')
        self._emit_child(node.class_name, node)
        if node.has_parens or node.args:
            self._write('(')
            self._comma_list(node.args)
            self._write(')')

    def visit_PhpNewAnonymous(self, node: PhpNewAnonymous):
        self._write('new ')
        if node.declaration:
            for modifier in node.declaration.modifiers:
                self._write(F'{modifier} ')
        self._write('class')
        if node.has_parens or node.args:
            self._write('(')
            self._comma_list(node.args)
            self._write(')')
        if node.declaration:
            self._emit_class_heritage(node.declaration)
            self._write(' ')
            self._emit_brace_block(node.declaration.members)

    def visit_PhpScript(self, node: PhpScript):
        self._emit_top_level(node.body)

    def _emit_top_level(self, body: list[Statement]):
        """
        Emit the top-level statement run, starting in HTML mode and toggling `<?php` / `?>` islands
        around inline HTML through the shared `_emit_statement`. The trailing newline is suppressed
        after a `__halt_compiler` whose `remainder` is verbatim data that must not grow per pass.
        """
        self._in_php = False
        for stmt in body:
            self._emit_statement(stmt)
        if self._in_php and not (body and isinstance(body[-1], PhpHaltCompiler)):
            self._newline()

    def visit_PhpInlineHTML(self, node: PhpInlineHTML):
        self._write(node.value)

    def visit_PhpEchoTagStatement(self, node: PhpEchoTagStatement):
        self._write('<?= ')
        self._comma_list(node.expressions)
        self._write(' ?>')

    def visit_PhpNop(self, node: PhpNop):
        self._write(';')

    def visit_PhpBlock(self, node: PhpBlock):
        self._emit_brace_block(node.body)

    def visit_PhpExpressionStatement(self, node: PhpExpressionStatement):
        if node.expression is not None:
            self.visit(node.expression)
        self._write(';')

    def visit_PhpEcho(self, node: PhpEcho):
        self._write('echo ')
        self._comma_list(node.expressions)
        self._write(';')

    def _emit_body_or_alt(
        self,
        body: list[Statement],
        alternative_syntax: bool,
        end_keyword: str,
    ):
        if alternative_syntax:
            self._write(':')
            self._emit_statements(body)
            self._write(end_keyword)
            self._write(';')
        else:
            self._write(' ')
            self._emit_brace_block(body)

    def visit_PhpIf(self, node: PhpIf):
        self._write('if (')
        if node.condition:
            self.visit(node.condition)
        self._write(')')
        alt = node.alternative_syntax
        self._emit_if_body(node.consequent, alt)
        for elseif in node.elseifs:
            if alt:
                self._write('elseif (')
            else:
                self._write(' elseif (')
            if elseif.condition:
                self.visit(elseif.condition)
            self._write(')')
            self._emit_if_body(elseif.body, alt)
        if node.alternate is not None:
            self._write('else' if alt else ' else')
            self._emit_if_body(node.alternate, alt)
        if alt:
            self._write('endif;')

    def _emit_if_body(self, body: list[Statement], alt: bool):
        if alt:
            self._write(':')
            self._emit_statements(body)
            if not body:
                self._newline()
        else:
            self._write(' ')
            self._emit_brace_block(body)

    def visit_PhpWhile(self, node: PhpWhile):
        self._write('while (')
        if node.condition:
            self.visit(node.condition)
        self._write(')')
        self._emit_body_or_alt(node.body, node.alternative_syntax, 'endwhile')

    def visit_PhpDoWhile(self, node: PhpDoWhile):
        self._write('do ')
        self._emit_brace_block(node.body)
        self._write(' while (')
        if node.condition:
            self.visit(node.condition)
        self._write(');')

    def visit_PhpFor(self, node: PhpFor):
        self._write('for (')
        self._comma_list(node.init)
        self._write('; ')
        self._comma_list(node.condition)
        self._write('; ')
        self._comma_list(node.update)
        self._write(')')
        self._emit_body_or_alt(node.body, node.alternative_syntax, 'endfor')

    def visit_PhpForeach(self, node: PhpForeach):
        self._write('foreach (')
        if node.subject:
            self.visit(node.subject)
        self._write(' as ')
        if node.key is not None:
            self.visit(node.key)
            self._write(' => ')
        if node.by_ref:
            self._write('&')
        if node.value:
            self.visit(node.value)
        self._write(')')
        self._emit_body_or_alt(node.body, node.alternative_syntax, 'endforeach')

    def visit_PhpSwitch(self, node: PhpSwitch):
        self._write('switch (')
        if node.subject:
            self.visit(node.subject)
        self._write(')')
        if node.alternative_syntax:
            self._write(':')
        else:
            self._write(' {')
        self._depth += 1
        for case in node.cases:
            self._newline()
            self.visit(case)
        self._depth -= 1
        if node.cases:
            self._newline()
        elif node.alternative_syntax:
            self._newline()
        if node.alternative_syntax:
            self._write('endswitch;')
        else:
            self._write('}')

    def visit_PhpCase(self, node: PhpCase):
        if node.test is not None:
            self._write('case ')
            self.visit(node.test)
            self._write(':')
        else:
            self._write('default:')
        self._depth += 1
        for stmt in node.body:
            self._emit_statement(stmt)
        self._depth -= 1
        self._enter_php()

    def visit_PhpBreak(self, node: PhpBreak):
        self._write('break')
        if node.level is not None:
            self._write(' ')
            self.visit(node.level)
        self._write(';')

    def visit_PhpContinue(self, node: PhpContinue):
        self._write('continue')
        if node.level is not None:
            self._write(' ')
            self.visit(node.level)
        self._write(';')

    def visit_PhpReturn(self, node: PhpReturn):
        self._write('return')
        if node.value is not None:
            self._write(' ')
            self.visit(node.value)
        self._write(';')

    def visit_PhpThrowStatement(self, node: PhpThrowStatement):
        self._write('throw ')
        if node.operand:
            self.visit(node.operand)
        self._write(';')

    def visit_PhpTry(self, node: PhpTry):
        self._write('try ')
        self._emit_brace_block(node.body)
        for catch in node.catches:
            self._write(' ')
            self.visit(catch)
        if node.finally_body is not None:
            self._write(' finally ')
            self._emit_brace_block(node.finally_body)

    def visit_PhpCatch(self, node: PhpCatch):
        self._write('catch (')
        for i, t in enumerate(node.types):
            if i > 0:
                self._write(' | ')
            self.visit(t)
        if node.variable is not None:
            self._write(' ')
            self.visit(node.variable)
        self._write(') ')
        self._emit_brace_block(node.body)

    def visit_PhpUnset(self, node: PhpUnset):
        self._write('unset(')
        self._comma_list(node.variables)
        self._write(');')

    def visit_PhpGlobal(self, node: PhpGlobal):
        self._write('global ')
        self._comma_list(node.variables)
        self._write(';')

    def visit_PhpStaticVar(self, node: PhpStaticVar):
        self._write('static ')
        self._comma_list(node.declarations)
        self._write(';')

    def visit_PhpStaticVarDeclaration(self, node: PhpStaticVarDeclaration):
        if node.variable:
            self.visit(node.variable)
        if node.default is not None:
            self._write(' = ')
            self._emit_child(node.default, node)

    def visit_PhpGoto(self, node: PhpGoto):
        self._write(F'goto {node.label};')

    def visit_PhpLabel(self, node: PhpLabel):
        self._write(F'{node.name}:')

    def visit_PhpHaltCompiler(self, node: PhpHaltCompiler):
        self._write('__halt_compiler();')
        self._write(node.remainder)

    def visit_PhpNamespace(self, node: PhpNamespace):
        self._write('namespace')
        if node.name is not None:
            self._write(' ')
            self.visit(node.name)
        if node.body is not None:
            self._write(' ')
            self._emit_brace_block(node.body)
        else:
            self._write(';')

    def visit_PhpUse(self, node: PhpUse):
        self._write('use ')
        self._emit_use_kind(node.kind)
        self._comma_list(node.uses)
        self._write(';')

    def visit_PhpGroupUse(self, node: PhpGroupUse):
        self._write('use ')
        self._emit_use_kind(node.kind)
        if node.prefix:
            self.visit(node.prefix)
        self._write('\\{')
        self._comma_list(node.uses)
        self._write('};')

    def _emit_use_kind(self, kind: PhpUseKind):
        if kind is PhpUseKind.FUNCTION:
            self._write('function ')
        elif kind is PhpUseKind.CONSTANT:
            self._write('const ')

    def visit_PhpUseItem(self, node: PhpUseItem):
        if node.kind is PhpUseKind.FUNCTION:
            self._write('function ')
        elif node.kind is PhpUseKind.CONSTANT:
            self._write('const ')
        if node.name:
            self.visit(node.name)
        if node.alias is not None:
            self._write(F' as {node.alias}')

    def visit_PhpConst(self, node: PhpConst):
        self._write('const ')
        self._comma_list(node.consts)
        self._write(';')

    def visit_PhpConstDeclaration(self, node: PhpConstDeclaration):
        self._write(node.name)
        self._write(' = ')
        if node.value is not None:
            self._emit_child(node.value, node)

    def visit_PhpDeclare(self, node: PhpDeclare):
        self._write('declare(')
        self._comma_list(node.directives)
        self._write(')')
        if node.body is not None:
            if node.alternative_syntax:
                self._write(':')
                self._emit_statements(node.body)
                self._write('enddeclare;')
            else:
                self._write(' ')
                self._emit_brace_block(node.body)
        else:
            self._write(';')

    def visit_PhpDeclareDirective(self, node: PhpDeclareDirective):
        self._write(F'{node.name}=')
        if node.value is not None:
            self.visit(node.value)

    def visit_PhpAttributeGroup(self, node: PhpAttributeGroup):
        self._write('#[')
        self._comma_list(node.attributes)
        self._write(']')

    def visit_PhpAttribute(self, node: PhpAttribute):
        if node.name:
            self.visit(node.name)
        if node.args:
            self._write('(')
            self._comma_list(node.args)
            self._write(')')

    def _emit_attribute_groups(self, groups: list[PhpAttributeGroup]):
        for group in groups:
            self.visit(group)
            self._newline()

    def visit_PhpFunctionDeclaration(self, node: PhpFunctionDeclaration):
        self._emit_attribute_groups(node.attributes)
        self._write('function ')
        if node.by_ref:
            self._write('&')
        self._write(node.name)
        self._emit_params(node.params)
        if node.return_type is not None:
            self._write(': ')
            self.visit(node.return_type)
        if node.body is not None:
            self._write(' ')
            self._emit_brace_block(node.body.body)
        else:
            self._write(';')

    def _emit_class_heritage(self, node: PhpClass):
        if node.extends:
            self._write(' extends ')
            for i, name in enumerate(node.extends):
                if i > 0:
                    self._write(', ')
                self.visit(name)
        if node.implements:
            self._write(' implements ')
            for i, name in enumerate(node.implements):
                if i > 0:
                    self._write(', ')
                self.visit(name)

    def visit_PhpClass(self, node: PhpClass):
        self._emit_attribute_groups(node.attributes)
        for modifier in node.modifiers:
            self._write(F'{modifier} ')
        self._write(F'{node.kind.value} {node.name}')
        if node.enum_backing_type is not None:
            self._write(': ')
            self.visit(node.enum_backing_type)
        self._emit_class_heritage(node)
        self._write(' ')
        self._emit_brace_block(node.members)

    def _emit_modifiers(self, modifiers: list[str]):
        for modifier in modifiers:
            self._write(F'{modifier} ')

    def visit_PhpClassMethod(self, node: PhpClassMethod):
        self._emit_attribute_groups(node.attributes)
        self._emit_modifiers(node.modifiers)
        self._write('function ')
        if node.by_ref:
            self._write('&')
        self._write(node.name)
        self._emit_params(node.params)
        if node.return_type is not None:
            self._write(': ')
            self.visit(node.return_type)
        if node.body is not None:
            self._write(' ')
            self._emit_brace_block(node.body.body)
        else:
            self._write(';')

    def visit_PhpProperty(self, node: PhpProperty):
        self._emit_attribute_groups(node.attributes)
        self._emit_modifiers(node.modifiers)
        if node.type is not None:
            self.visit(node.type)
            self._write(' ')
        self._comma_list(node.props)
        self._write(';')

    def visit_PhpPropertyDeclaration(self, node: PhpPropertyDeclaration):
        if node.variable:
            self.visit(node.variable)
        if node.default is not None:
            self._write(' = ')
            self._emit_child(node.default, node)

    def visit_PhpClassConst(self, node: PhpClassConst):
        self._emit_attribute_groups(node.attributes)
        self._emit_modifiers(node.modifiers)
        self._write('const ')
        if node.type is not None:
            self.visit(node.type)
            self._write(' ')
        self._comma_list(node.consts)
        self._write(';')

    def visit_PhpEnumCase(self, node: PhpEnumCase):
        self._emit_attribute_groups(node.attributes)
        self._write(F'case {node.name}')
        if node.value is not None:
            self._write(' = ')
            self.visit(node.value)
        self._write(';')

    def visit_PhpTraitUse(self, node: PhpTraitUse):
        self._write('use ')
        for i, name in enumerate(node.traits):
            if i > 0:
                self._write(', ')
            self.visit(name)
        if not node.adaptations:
            self._write(';')
            return
        self._write(' {')
        self._depth += 1
        for adaptation in node.adaptations:
            self._newline()
            self.visit(adaptation)
        self._depth -= 1
        if node.adaptations:
            self._newline()
        self._write('}')

    def visit_PhpTraitAdaptation(self, node: PhpTraitAdaptation):
        if node.trait is not None:
            self.visit(node.trait)
            self._write('::')
        self._write(node.method)
        if node.kind == 'insteadof':
            self._write(' insteadof ')
            for i, name in enumerate(node.insteadof):
                if i > 0:
                    self._write(', ')
                self.visit(name)
        else:
            self._write(' as')
            if node.new_modifier is not None:
                self._write(F' {node.new_modifier}')
            if node.new_name is not None:
                self._write(F' {node.new_name}')
        self._write(';')

Classes

class PhpSynthesizer (indent=' ', line_length=140, unescape_strings=False, strip_comments=False)

Synthesize PHP source from a PhpScript AST. Correctness of expression nesting is provided by precedence.needs_parens rather than by the presence of PhpParenExpression nodes, so a tree produced by a deobfuscation pass that dropped explicit parentheses still prints correctly.

Expand source code Browse git
class PhpSynthesizer(Synthesizer):
    """
    Synthesize PHP source from a `PhpScript` AST. Correctness of expression nesting is provided by
    `precedence.needs_parens` rather than by the presence of `PhpParenExpression` nodes, so a tree
    produced by a deobfuscation pass that dropped explicit parentheses still prints correctly.
    """

    def __init__(
        self,
        indent: str = '    ',
        line_length: int = 140,
        unescape_strings: bool = False,
        strip_comments: bool = False,
    ):
        super().__init__(indent, line_length)
        self._unescape_strings = unescape_strings
        self._strip_comments = strip_comments
        self._in_php = True

    def _emit_leading_comments(self, node: Node):
        if self._strip_comments or not node.leading_comments:
            return
        for comment in node.leading_comments:
            self._write(comment)
            self._newline()

    def _emit_child(self, child: Node | None, parent: Node):
        if child is None:
            return
        if needs_parens(child, parent):
            self._write('(')
            self.visit(child)
            self._write(')')
        else:
            self.visit(child)

    def _comma_list(self, nodes: list):
        for i, node in enumerate(nodes):
            if i > 0:
                self._write(', ')
            if node is None:
                continue
            self.visit(node)

    def _enter_php(self):
        if not self._in_php:
            self._write('<?php')
            self._in_php = True

    def _leave_php(self):
        if self._in_php:
            self._write(' ?>\n')
            self._in_php = False

    def _emit_statement(self, stmt: Statement):
        if isinstance(stmt, (PhpInlineHTML, PhpEchoTagStatement)):
            self._leave_php()
            self.visit(stmt)
        else:
            self._enter_php()
            self._newline()
            self._emit_leading_comments(stmt)
            self.visit(stmt)

    def _emit_statements(self, body: list[Statement]):
        self._depth += 1
        for stmt in body:
            self._emit_statement(stmt)
        self._depth -= 1
        if body:
            self._enter_php()
            self._newline()

    def _emit_brace_block(self, body: list[Statement]):
        self._write('{')
        self._emit_statements(body)
        self._write('}')

    def visit_PhpIntLiteral(self, node: PhpIntLiteral):
        self._write(node.raw)

    def visit_PhpFloatLiteral(self, node: PhpFloatLiteral):
        self._write(node.raw)

    def visit_PhpStringLiteral(self, node: PhpStringLiteral):
        if self._unescape_strings:
            quote = node.raw[:1] if node.raw[:1] in ('"', '\'') else '\''
            self._write(F'{quote}{escape_php_string(node.value, quote)}{quote}')
        else:
            self._write(node.raw)

    def visit_PhpInterpolatedString(self, node: PhpInterpolatedString):
        self._write(node.raw)

    def visit_PhpHeredoc(self, node: PhpHeredoc):
        self._write(node.raw)

    def visit_PhpShellExec(self, node: PhpShellExec):
        self._write(node.raw)

    def visit_PhpBooleanLiteral(self, node: PhpBooleanLiteral):
        self._write(node.raw)

    def visit_PhpNullLiteral(self, node: PhpNullLiteral):
        self._write(node.raw)

    def visit_PhpMagicConstant(self, node: PhpMagicConstant):
        self._write(node.name)

    def visit_PhpVariable(self, node: PhpVariable):
        self._write(node.name)

    def visit_PhpVariableVariable(self, node: PhpVariableVariable):
        self._write('$')
        if isinstance(node.expression, PhpVariable):
            self.visit(node.expression)
        else:
            self._write('{')
            if node.expression:
                self.visit(node.expression)
            self._write('}')

    def visit_PhpIdentifier(self, node: PhpIdentifier):
        self._write(node.name)

    def visit_PhpName(self, node: PhpName):
        if node.kind is PhpNameKind.FULLY_QUALIFIED:
            self._write('\\')
        elif node.kind is PhpNameKind.RELATIVE:
            self._write('namespace\\')
        self._write('\\'.join(node.parts))

    def visit_PhpConstFetch(self, node: PhpConstFetch):
        if node.name:
            self.visit(node.name)

    def visit_PhpErrorNode(self, node: PhpErrorNode):
        self._write(node.text)

    def visit_PhpParenExpression(self, node: PhpParenExpression):
        self._write('(')
        if node.expression:
            self.visit(node.expression)
        self._write(')')

    def _emit_binary(self, node: PhpBinaryExpression):
        self._emit_child(node.left, node)
        self._write(F' {node.operator} ')
        self._emit_child(node.right, node)

    visit_PhpBinaryExpression = _emit_binary

    def visit_PhpAssignment(self, node: PhpAssignment):
        self._emit_child(node.target, node)
        self._write(F' {node.operator} ')
        if node.by_ref:
            self._write('&')
        self._emit_child(node.value, node)

    def visit_PhpUnaryExpression(self, node: PhpUnaryExpression):
        self._write(node.operator)
        self._emit_child(node.operand, node)

    def visit_PhpUpdateExpression(self, node: PhpUpdateExpression):
        if node.prefix:
            self._write(node.operator)
            self._emit_child(node.operand, node)
        else:
            self._emit_child(node.operand, node)
            self._write(node.operator)

    def visit_PhpCastExpression(self, node: PhpCastExpression):
        self._write(F'({node.cast}) ')
        self._emit_child(node.operand, node)

    def visit_PhpErrorSuppress(self, node: PhpErrorSuppress):
        self._write('@')
        self._emit_child(node.operand, node)

    def visit_PhpClone(self, node: PhpClone):
        self._write('clone ')
        self._emit_child(node.operand, node)

    def visit_PhpInstanceof(self, node: PhpInstanceof):
        self._emit_child(node.operand, node)
        self._write(' instanceof ')
        self._emit_child(node.class_name, node)

    def visit_PhpTernary(self, node: PhpTernary):
        self._emit_child(node.condition, node)
        if node.consequent is None:
            self._write(' ?: ')
        else:
            self._write(' ? ')
            self._emit_child(node.consequent, node)
            self._write(' : ')
        self._emit_child(node.alternate, node)

    def visit_PhpPrint(self, node: PhpPrint):
        self._write('print ')
        self._emit_child(node.operand, node)

    def visit_PhpInclude(self, node: PhpInclude):
        self._write(F'{node.kind} ')
        self._emit_child(node.operand, node)

    def visit_PhpThrowExpression(self, node: PhpThrowExpression):
        self._write('throw ')
        self._emit_child(node.operand, node)

    def visit_PhpYield(self, node: PhpYield):
        self._write('yield')
        if node.value is not None:
            self._write(' ')
            if node.key is not None:
                self._emit_child(node.key, node)
                self._write(' => ')
            self._emit_child(node.value, node)

    def visit_PhpYieldFrom(self, node: PhpYieldFrom):
        self._write('yield from ')
        self._emit_child(node.operand, node)

    def _emit_args(self, args: list[PhpArg], first_class_callable: bool):
        self._write('(')
        if first_class_callable:
            self._write('...')
        else:
            self._comma_list(args)
        self._write(')')

    def visit_PhpArg(self, node: PhpArg):
        if node.name is not None:
            self._write(F'{node.name}: ')
        if node.spread:
            self._write('...')
        if node.by_ref:
            self._write('&')
        if node.value:
            self.visit(node.value)

    def visit_PhpFunctionCall(self, node: PhpFunctionCall):
        self._emit_child(node.callee, node)
        self._emit_args(node.args, node.first_class_callable)

    def visit_PhpMethodCall(self, node: PhpMethodCall):
        self._emit_child(node.receiver, node)
        self._write('?->' if node.nullsafe else '->')
        self._emit_member_name(node.method)
        self._emit_args(node.args, node.first_class_callable)

    def visit_PhpStaticCall(self, node: PhpStaticCall):
        self._emit_child(node.class_name, node)
        self._write('::')
        self._emit_member_name(node.method)
        self._emit_args(node.args, node.first_class_callable)

    def visit_PhpPropertyFetch(self, node: PhpPropertyFetch):
        self._emit_child(node.receiver, node)
        self._write('?->' if node.nullsafe else '->')
        self._emit_member_name(node.name)

    def visit_PhpStaticPropertyFetch(self, node: PhpStaticPropertyFetch):
        self._emit_child(node.class_name, node)
        self._write('::')
        self._emit_member_name(node.name)

    def visit_PhpClassConstFetch(self, node: PhpClassConstFetch):
        self._emit_child(node.class_name, node)
        self._write('::')
        self._emit_member_name(node.name)

    def _emit_member_name(self, name: Node | None):
        if name is None:
            return
        if isinstance(name, (PhpIdentifier, PhpVariable, PhpVariableVariable)):
            self.visit(name)
        else:
            self._write('{')
            self.visit(name)
            self._write('}')

    def visit_PhpArrayDimFetch(self, node: PhpArrayDimFetch):
        self._emit_child(node.receiver, node)
        self._write('[')
        if node.index is not None:
            self.visit(node.index)
        self._write(']')

    def visit_PhpArray(self, node: PhpArray):
        if node.short:
            self._write('[')
            self._comma_list(node.items)
            self._write(']')
        else:
            self._write('array(')
            self._comma_list(node.items)
            self._write(')')

    def visit_PhpList(self, node: PhpList):
        if node.short:
            self._write('[')
            self._comma_list(node.items)
            self._write(']')
        else:
            self._write('list(')
            self._comma_list(node.items)
            self._write(')')

    def visit_PhpArrayItem(self, node: PhpArrayItem):
        if node.spread:
            self._write('...')
        if node.key is not None:
            self._emit_child(node.key, node)
            self._write(' => ')
        if node.by_ref:
            self._write('&')
        if node.value is not None:
            self._emit_child(node.value, node)

    def visit_PhpMatch(self, node: PhpMatch):
        self._write('match (')
        if node.subject:
            self.visit(node.subject)
        self._write(') {')
        self._depth += 1
        for arm in node.arms:
            self._newline()
            self.visit(arm)
            self._write(',')
        self._depth -= 1
        if node.arms:
            self._newline()
        self._write('}')

    def visit_PhpMatchArm(self, node: PhpMatchArm):
        if node.is_default:
            self._write('default')
        else:
            self._comma_list(node.conditions)
        self._write(' => ')
        if node.body:
            self._emit_child(node.body, node)

    def visit_PhpIsset(self, node: PhpIsset):
        self._write('isset(')
        self._comma_list(node.variables)
        self._write(')')

    def visit_PhpEmpty(self, node: PhpEmpty):
        self._write('empty(')
        if node.operand:
            self.visit(node.operand)
        self._write(')')

    def visit_PhpEval(self, node: PhpEval):
        self._write('eval(')
        if node.operand:
            self.visit(node.operand)
        self._write(')')

    def visit_PhpExit(self, node: PhpExit):
        self._write(node.keyword)
        if node.operand is not None:
            self._write('(')
            self.visit(node.operand)
            self._write(')')

    def _emit_params(self, params: list[PhpParam]):
        self._write('(')
        self._comma_list(params)
        self._write(')')

    def visit_PhpParam(self, node: PhpParam):
        for group in node.attributes:
            self.visit(group)
            self._write(' ')
        if node.visibility is not None:
            self._write(F'{node.visibility.value} ')
        if node.readonly:
            self._write('readonly ')
        if node.type is not None:
            self.visit(node.type)
            self._write(' ')
        if node.by_ref:
            self._write('&')
        if node.variadic:
            self._write('...')
        self._write(node.name)
        if node.default is not None:
            self._write(' = ')
            self._emit_child(node.default, node)

    def visit_PhpNullableType(self, node: PhpNullableType):
        self._write('?')
        if node.type:
            self.visit(node.type)

    def visit_PhpUnionType(self, node: PhpUnionType):
        for i, t in enumerate(node.types):
            if i > 0:
                self._write('|')
            if isinstance(t, PhpIntersectionType):
                self._write('(')
                self.visit(t)
                self._write(')')
            else:
                self.visit(t)

    def visit_PhpIntersectionType(self, node: PhpIntersectionType):
        for i, t in enumerate(node.types):
            if i > 0:
                self._write('&')
            self.visit(t)

    def visit_PhpClosure(self, node: PhpClosure):
        if node.is_static:
            self._write('static ')
        self._write('function ')
        if node.by_ref:
            self._write('&')
        self._emit_params(node.params)
        if node.uses:
            self._write(' use (')
            self._comma_list(node.uses)
            self._write(')')
        if node.return_type is not None:
            self._write(': ')
            self.visit(node.return_type)
        self._write(' ')
        if node.body:
            self._emit_brace_block(node.body.body)

    def visit_PhpClosureUse(self, node: PhpClosureUse):
        if node.by_ref:
            self._write('&')
        if node.variable:
            self.visit(node.variable)

    def visit_PhpArrowFunction(self, node: PhpArrowFunction):
        if node.is_static:
            self._write('static ')
        self._write('fn')
        if node.by_ref:
            self._write('&')
        self._emit_params(node.params)
        if node.return_type is not None:
            self._write(': ')
            self.visit(node.return_type)
        self._write(' => ')
        if node.body:
            self._emit_child(node.body, node)

    def visit_PhpNew(self, node: PhpNew):
        self._write('new ')
        self._emit_child(node.class_name, node)
        if node.has_parens or node.args:
            self._write('(')
            self._comma_list(node.args)
            self._write(')')

    def visit_PhpNewAnonymous(self, node: PhpNewAnonymous):
        self._write('new ')
        if node.declaration:
            for modifier in node.declaration.modifiers:
                self._write(F'{modifier} ')
        self._write('class')
        if node.has_parens or node.args:
            self._write('(')
            self._comma_list(node.args)
            self._write(')')
        if node.declaration:
            self._emit_class_heritage(node.declaration)
            self._write(' ')
            self._emit_brace_block(node.declaration.members)

    def visit_PhpScript(self, node: PhpScript):
        self._emit_top_level(node.body)

    def _emit_top_level(self, body: list[Statement]):
        """
        Emit the top-level statement run, starting in HTML mode and toggling `<?php` / `?>` islands
        around inline HTML through the shared `_emit_statement`. The trailing newline is suppressed
        after a `__halt_compiler` whose `remainder` is verbatim data that must not grow per pass.
        """
        self._in_php = False
        for stmt in body:
            self._emit_statement(stmt)
        if self._in_php and not (body and isinstance(body[-1], PhpHaltCompiler)):
            self._newline()

    def visit_PhpInlineHTML(self, node: PhpInlineHTML):
        self._write(node.value)

    def visit_PhpEchoTagStatement(self, node: PhpEchoTagStatement):
        self._write('<?= ')
        self._comma_list(node.expressions)
        self._write(' ?>')

    def visit_PhpNop(self, node: PhpNop):
        self._write(';')

    def visit_PhpBlock(self, node: PhpBlock):
        self._emit_brace_block(node.body)

    def visit_PhpExpressionStatement(self, node: PhpExpressionStatement):
        if node.expression is not None:
            self.visit(node.expression)
        self._write(';')

    def visit_PhpEcho(self, node: PhpEcho):
        self._write('echo ')
        self._comma_list(node.expressions)
        self._write(';')

    def _emit_body_or_alt(
        self,
        body: list[Statement],
        alternative_syntax: bool,
        end_keyword: str,
    ):
        if alternative_syntax:
            self._write(':')
            self._emit_statements(body)
            self._write(end_keyword)
            self._write(';')
        else:
            self._write(' ')
            self._emit_brace_block(body)

    def visit_PhpIf(self, node: PhpIf):
        self._write('if (')
        if node.condition:
            self.visit(node.condition)
        self._write(')')
        alt = node.alternative_syntax
        self._emit_if_body(node.consequent, alt)
        for elseif in node.elseifs:
            if alt:
                self._write('elseif (')
            else:
                self._write(' elseif (')
            if elseif.condition:
                self.visit(elseif.condition)
            self._write(')')
            self._emit_if_body(elseif.body, alt)
        if node.alternate is not None:
            self._write('else' if alt else ' else')
            self._emit_if_body(node.alternate, alt)
        if alt:
            self._write('endif;')

    def _emit_if_body(self, body: list[Statement], alt: bool):
        if alt:
            self._write(':')
            self._emit_statements(body)
            if not body:
                self._newline()
        else:
            self._write(' ')
            self._emit_brace_block(body)

    def visit_PhpWhile(self, node: PhpWhile):
        self._write('while (')
        if node.condition:
            self.visit(node.condition)
        self._write(')')
        self._emit_body_or_alt(node.body, node.alternative_syntax, 'endwhile')

    def visit_PhpDoWhile(self, node: PhpDoWhile):
        self._write('do ')
        self._emit_brace_block(node.body)
        self._write(' while (')
        if node.condition:
            self.visit(node.condition)
        self._write(');')

    def visit_PhpFor(self, node: PhpFor):
        self._write('for (')
        self._comma_list(node.init)
        self._write('; ')
        self._comma_list(node.condition)
        self._write('; ')
        self._comma_list(node.update)
        self._write(')')
        self._emit_body_or_alt(node.body, node.alternative_syntax, 'endfor')

    def visit_PhpForeach(self, node: PhpForeach):
        self._write('foreach (')
        if node.subject:
            self.visit(node.subject)
        self._write(' as ')
        if node.key is not None:
            self.visit(node.key)
            self._write(' => ')
        if node.by_ref:
            self._write('&')
        if node.value:
            self.visit(node.value)
        self._write(')')
        self._emit_body_or_alt(node.body, node.alternative_syntax, 'endforeach')

    def visit_PhpSwitch(self, node: PhpSwitch):
        self._write('switch (')
        if node.subject:
            self.visit(node.subject)
        self._write(')')
        if node.alternative_syntax:
            self._write(':')
        else:
            self._write(' {')
        self._depth += 1
        for case in node.cases:
            self._newline()
            self.visit(case)
        self._depth -= 1
        if node.cases:
            self._newline()
        elif node.alternative_syntax:
            self._newline()
        if node.alternative_syntax:
            self._write('endswitch;')
        else:
            self._write('}')

    def visit_PhpCase(self, node: PhpCase):
        if node.test is not None:
            self._write('case ')
            self.visit(node.test)
            self._write(':')
        else:
            self._write('default:')
        self._depth += 1
        for stmt in node.body:
            self._emit_statement(stmt)
        self._depth -= 1
        self._enter_php()

    def visit_PhpBreak(self, node: PhpBreak):
        self._write('break')
        if node.level is not None:
            self._write(' ')
            self.visit(node.level)
        self._write(';')

    def visit_PhpContinue(self, node: PhpContinue):
        self._write('continue')
        if node.level is not None:
            self._write(' ')
            self.visit(node.level)
        self._write(';')

    def visit_PhpReturn(self, node: PhpReturn):
        self._write('return')
        if node.value is not None:
            self._write(' ')
            self.visit(node.value)
        self._write(';')

    def visit_PhpThrowStatement(self, node: PhpThrowStatement):
        self._write('throw ')
        if node.operand:
            self.visit(node.operand)
        self._write(';')

    def visit_PhpTry(self, node: PhpTry):
        self._write('try ')
        self._emit_brace_block(node.body)
        for catch in node.catches:
            self._write(' ')
            self.visit(catch)
        if node.finally_body is not None:
            self._write(' finally ')
            self._emit_brace_block(node.finally_body)

    def visit_PhpCatch(self, node: PhpCatch):
        self._write('catch (')
        for i, t in enumerate(node.types):
            if i > 0:
                self._write(' | ')
            self.visit(t)
        if node.variable is not None:
            self._write(' ')
            self.visit(node.variable)
        self._write(') ')
        self._emit_brace_block(node.body)

    def visit_PhpUnset(self, node: PhpUnset):
        self._write('unset(')
        self._comma_list(node.variables)
        self._write(');')

    def visit_PhpGlobal(self, node: PhpGlobal):
        self._write('global ')
        self._comma_list(node.variables)
        self._write(';')

    def visit_PhpStaticVar(self, node: PhpStaticVar):
        self._write('static ')
        self._comma_list(node.declarations)
        self._write(';')

    def visit_PhpStaticVarDeclaration(self, node: PhpStaticVarDeclaration):
        if node.variable:
            self.visit(node.variable)
        if node.default is not None:
            self._write(' = ')
            self._emit_child(node.default, node)

    def visit_PhpGoto(self, node: PhpGoto):
        self._write(F'goto {node.label};')

    def visit_PhpLabel(self, node: PhpLabel):
        self._write(F'{node.name}:')

    def visit_PhpHaltCompiler(self, node: PhpHaltCompiler):
        self._write('__halt_compiler();')
        self._write(node.remainder)

    def visit_PhpNamespace(self, node: PhpNamespace):
        self._write('namespace')
        if node.name is not None:
            self._write(' ')
            self.visit(node.name)
        if node.body is not None:
            self._write(' ')
            self._emit_brace_block(node.body)
        else:
            self._write(';')

    def visit_PhpUse(self, node: PhpUse):
        self._write('use ')
        self._emit_use_kind(node.kind)
        self._comma_list(node.uses)
        self._write(';')

    def visit_PhpGroupUse(self, node: PhpGroupUse):
        self._write('use ')
        self._emit_use_kind(node.kind)
        if node.prefix:
            self.visit(node.prefix)
        self._write('\\{')
        self._comma_list(node.uses)
        self._write('};')

    def _emit_use_kind(self, kind: PhpUseKind):
        if kind is PhpUseKind.FUNCTION:
            self._write('function ')
        elif kind is PhpUseKind.CONSTANT:
            self._write('const ')

    def visit_PhpUseItem(self, node: PhpUseItem):
        if node.kind is PhpUseKind.FUNCTION:
            self._write('function ')
        elif node.kind is PhpUseKind.CONSTANT:
            self._write('const ')
        if node.name:
            self.visit(node.name)
        if node.alias is not None:
            self._write(F' as {node.alias}')

    def visit_PhpConst(self, node: PhpConst):
        self._write('const ')
        self._comma_list(node.consts)
        self._write(';')

    def visit_PhpConstDeclaration(self, node: PhpConstDeclaration):
        self._write(node.name)
        self._write(' = ')
        if node.value is not None:
            self._emit_child(node.value, node)

    def visit_PhpDeclare(self, node: PhpDeclare):
        self._write('declare(')
        self._comma_list(node.directives)
        self._write(')')
        if node.body is not None:
            if node.alternative_syntax:
                self._write(':')
                self._emit_statements(node.body)
                self._write('enddeclare;')
            else:
                self._write(' ')
                self._emit_brace_block(node.body)
        else:
            self._write(';')

    def visit_PhpDeclareDirective(self, node: PhpDeclareDirective):
        self._write(F'{node.name}=')
        if node.value is not None:
            self.visit(node.value)

    def visit_PhpAttributeGroup(self, node: PhpAttributeGroup):
        self._write('#[')
        self._comma_list(node.attributes)
        self._write(']')

    def visit_PhpAttribute(self, node: PhpAttribute):
        if node.name:
            self.visit(node.name)
        if node.args:
            self._write('(')
            self._comma_list(node.args)
            self._write(')')

    def _emit_attribute_groups(self, groups: list[PhpAttributeGroup]):
        for group in groups:
            self.visit(group)
            self._newline()

    def visit_PhpFunctionDeclaration(self, node: PhpFunctionDeclaration):
        self._emit_attribute_groups(node.attributes)
        self._write('function ')
        if node.by_ref:
            self._write('&')
        self._write(node.name)
        self._emit_params(node.params)
        if node.return_type is not None:
            self._write(': ')
            self.visit(node.return_type)
        if node.body is not None:
            self._write(' ')
            self._emit_brace_block(node.body.body)
        else:
            self._write(';')

    def _emit_class_heritage(self, node: PhpClass):
        if node.extends:
            self._write(' extends ')
            for i, name in enumerate(node.extends):
                if i > 0:
                    self._write(', ')
                self.visit(name)
        if node.implements:
            self._write(' implements ')
            for i, name in enumerate(node.implements):
                if i > 0:
                    self._write(', ')
                self.visit(name)

    def visit_PhpClass(self, node: PhpClass):
        self._emit_attribute_groups(node.attributes)
        for modifier in node.modifiers:
            self._write(F'{modifier} ')
        self._write(F'{node.kind.value} {node.name}')
        if node.enum_backing_type is not None:
            self._write(': ')
            self.visit(node.enum_backing_type)
        self._emit_class_heritage(node)
        self._write(' ')
        self._emit_brace_block(node.members)

    def _emit_modifiers(self, modifiers: list[str]):
        for modifier in modifiers:
            self._write(F'{modifier} ')

    def visit_PhpClassMethod(self, node: PhpClassMethod):
        self._emit_attribute_groups(node.attributes)
        self._emit_modifiers(node.modifiers)
        self._write('function ')
        if node.by_ref:
            self._write('&')
        self._write(node.name)
        self._emit_params(node.params)
        if node.return_type is not None:
            self._write(': ')
            self.visit(node.return_type)
        if node.body is not None:
            self._write(' ')
            self._emit_brace_block(node.body.body)
        else:
            self._write(';')

    def visit_PhpProperty(self, node: PhpProperty):
        self._emit_attribute_groups(node.attributes)
        self._emit_modifiers(node.modifiers)
        if node.type is not None:
            self.visit(node.type)
            self._write(' ')
        self._comma_list(node.props)
        self._write(';')

    def visit_PhpPropertyDeclaration(self, node: PhpPropertyDeclaration):
        if node.variable:
            self.visit(node.variable)
        if node.default is not None:
            self._write(' = ')
            self._emit_child(node.default, node)

    def visit_PhpClassConst(self, node: PhpClassConst):
        self._emit_attribute_groups(node.attributes)
        self._emit_modifiers(node.modifiers)
        self._write('const ')
        if node.type is not None:
            self.visit(node.type)
            self._write(' ')
        self._comma_list(node.consts)
        self._write(';')

    def visit_PhpEnumCase(self, node: PhpEnumCase):
        self._emit_attribute_groups(node.attributes)
        self._write(F'case {node.name}')
        if node.value is not None:
            self._write(' = ')
            self.visit(node.value)
        self._write(';')

    def visit_PhpTraitUse(self, node: PhpTraitUse):
        self._write('use ')
        for i, name in enumerate(node.traits):
            if i > 0:
                self._write(', ')
            self.visit(name)
        if not node.adaptations:
            self._write(';')
            return
        self._write(' {')
        self._depth += 1
        for adaptation in node.adaptations:
            self._newline()
            self.visit(adaptation)
        self._depth -= 1
        if node.adaptations:
            self._newline()
        self._write('}')

    def visit_PhpTraitAdaptation(self, node: PhpTraitAdaptation):
        if node.trait is not None:
            self.visit(node.trait)
            self._write('::')
        self._write(node.method)
        if node.kind == 'insteadof':
            self._write(' insteadof ')
            for i, name in enumerate(node.insteadof):
                if i > 0:
                    self._write(', ')
                self.visit(name)
        else:
            self._write(' as')
            if node.new_modifier is not None:
                self._write(F' {node.new_modifier}')
            if node.new_name is not None:
                self._write(F' {node.new_name}')
        self._write(';')

Ancestors

Methods

def visit_PhpIntLiteral(self, node)
Expand source code Browse git
def visit_PhpIntLiteral(self, node: PhpIntLiteral):
    self._write(node.raw)
def visit_PhpFloatLiteral(self, node)
Expand source code Browse git
def visit_PhpFloatLiteral(self, node: PhpFloatLiteral):
    self._write(node.raw)
def visit_PhpStringLiteral(self, node)
Expand source code Browse git
def visit_PhpStringLiteral(self, node: PhpStringLiteral):
    if self._unescape_strings:
        quote = node.raw[:1] if node.raw[:1] in ('"', '\'') else '\''
        self._write(F'{quote}{escape_php_string(node.value, quote)}{quote}')
    else:
        self._write(node.raw)
def visit_PhpInterpolatedString(self, node)
Expand source code Browse git
def visit_PhpInterpolatedString(self, node: PhpInterpolatedString):
    self._write(node.raw)
def visit_PhpHeredoc(self, node)
Expand source code Browse git
def visit_PhpHeredoc(self, node: PhpHeredoc):
    self._write(node.raw)
def visit_PhpShellExec(self, node)
Expand source code Browse git
def visit_PhpShellExec(self, node: PhpShellExec):
    self._write(node.raw)
def visit_PhpBooleanLiteral(self, node)
Expand source code Browse git
def visit_PhpBooleanLiteral(self, node: PhpBooleanLiteral):
    self._write(node.raw)
def visit_PhpNullLiteral(self, node)
Expand source code Browse git
def visit_PhpNullLiteral(self, node: PhpNullLiteral):
    self._write(node.raw)
def visit_PhpMagicConstant(self, node)
Expand source code Browse git
def visit_PhpMagicConstant(self, node: PhpMagicConstant):
    self._write(node.name)
def visit_PhpVariable(self, node)
Expand source code Browse git
def visit_PhpVariable(self, node: PhpVariable):
    self._write(node.name)
def visit_PhpVariableVariable(self, node)
Expand source code Browse git
def visit_PhpVariableVariable(self, node: PhpVariableVariable):
    self._write('$')
    if isinstance(node.expression, PhpVariable):
        self.visit(node.expression)
    else:
        self._write('{')
        if node.expression:
            self.visit(node.expression)
        self._write('}')
def visit_PhpIdentifier(self, node)
Expand source code Browse git
def visit_PhpIdentifier(self, node: PhpIdentifier):
    self._write(node.name)
def visit_PhpName(self, node)
Expand source code Browse git
def visit_PhpName(self, node: PhpName):
    if node.kind is PhpNameKind.FULLY_QUALIFIED:
        self._write('\\')
    elif node.kind is PhpNameKind.RELATIVE:
        self._write('namespace\\')
    self._write('\\'.join(node.parts))
def visit_PhpConstFetch(self, node)
Expand source code Browse git
def visit_PhpConstFetch(self, node: PhpConstFetch):
    if node.name:
        self.visit(node.name)
def visit_PhpErrorNode(self, node)
Expand source code Browse git
def visit_PhpErrorNode(self, node: PhpErrorNode):
    self._write(node.text)
def visit_PhpParenExpression(self, node)
Expand source code Browse git
def visit_PhpParenExpression(self, node: PhpParenExpression):
    self._write('(')
    if node.expression:
        self.visit(node.expression)
    self._write(')')
def visit_PhpBinaryExpression(self, node)
Expand source code Browse git
def _emit_binary(self, node: PhpBinaryExpression):
    self._emit_child(node.left, node)
    self._write(F' {node.operator} ')
    self._emit_child(node.right, node)
def visit_PhpAssignment(self, node)
Expand source code Browse git
def visit_PhpAssignment(self, node: PhpAssignment):
    self._emit_child(node.target, node)
    self._write(F' {node.operator} ')
    if node.by_ref:
        self._write('&')
    self._emit_child(node.value, node)
def visit_PhpUnaryExpression(self, node)
Expand source code Browse git
def visit_PhpUnaryExpression(self, node: PhpUnaryExpression):
    self._write(node.operator)
    self._emit_child(node.operand, node)
def visit_PhpUpdateExpression(self, node)
Expand source code Browse git
def visit_PhpUpdateExpression(self, node: PhpUpdateExpression):
    if node.prefix:
        self._write(node.operator)
        self._emit_child(node.operand, node)
    else:
        self._emit_child(node.operand, node)
        self._write(node.operator)
def visit_PhpCastExpression(self, node)
Expand source code Browse git
def visit_PhpCastExpression(self, node: PhpCastExpression):
    self._write(F'({node.cast}) ')
    self._emit_child(node.operand, node)
def visit_PhpErrorSuppress(self, node)
Expand source code Browse git
def visit_PhpErrorSuppress(self, node: PhpErrorSuppress):
    self._write('@')
    self._emit_child(node.operand, node)
def visit_PhpClone(self, node)
Expand source code Browse git
def visit_PhpClone(self, node: PhpClone):
    self._write('clone ')
    self._emit_child(node.operand, node)
def visit_PhpInstanceof(self, node)
Expand source code Browse git
def visit_PhpInstanceof(self, node: PhpInstanceof):
    self._emit_child(node.operand, node)
    self._write(' instanceof ')
    self._emit_child(node.class_name, node)
def visit_PhpTernary(self, node)
Expand source code Browse git
def visit_PhpTernary(self, node: PhpTernary):
    self._emit_child(node.condition, node)
    if node.consequent is None:
        self._write(' ?: ')
    else:
        self._write(' ? ')
        self._emit_child(node.consequent, node)
        self._write(' : ')
    self._emit_child(node.alternate, node)
def visit_PhpPrint(self, node)
Expand source code Browse git
def visit_PhpPrint(self, node: PhpPrint):
    self._write('print ')
    self._emit_child(node.operand, node)
def visit_PhpInclude(self, node)
Expand source code Browse git
def visit_PhpInclude(self, node: PhpInclude):
    self._write(F'{node.kind} ')
    self._emit_child(node.operand, node)
def visit_PhpThrowExpression(self, node)
Expand source code Browse git
def visit_PhpThrowExpression(self, node: PhpThrowExpression):
    self._write('throw ')
    self._emit_child(node.operand, node)
def visit_PhpYield(self, node)
Expand source code Browse git
def visit_PhpYield(self, node: PhpYield):
    self._write('yield')
    if node.value is not None:
        self._write(' ')
        if node.key is not None:
            self._emit_child(node.key, node)
            self._write(' => ')
        self._emit_child(node.value, node)
def visit_PhpYieldFrom(self, node)
Expand source code Browse git
def visit_PhpYieldFrom(self, node: PhpYieldFrom):
    self._write('yield from ')
    self._emit_child(node.operand, node)
def visit_PhpArg(self, node)
Expand source code Browse git
def visit_PhpArg(self, node: PhpArg):
    if node.name is not None:
        self._write(F'{node.name}: ')
    if node.spread:
        self._write('...')
    if node.by_ref:
        self._write('&')
    if node.value:
        self.visit(node.value)
def visit_PhpFunctionCall(self, node)
Expand source code Browse git
def visit_PhpFunctionCall(self, node: PhpFunctionCall):
    self._emit_child(node.callee, node)
    self._emit_args(node.args, node.first_class_callable)
def visit_PhpMethodCall(self, node)
Expand source code Browse git
def visit_PhpMethodCall(self, node: PhpMethodCall):
    self._emit_child(node.receiver, node)
    self._write('?->' if node.nullsafe else '->')
    self._emit_member_name(node.method)
    self._emit_args(node.args, node.first_class_callable)
def visit_PhpStaticCall(self, node)
Expand source code Browse git
def visit_PhpStaticCall(self, node: PhpStaticCall):
    self._emit_child(node.class_name, node)
    self._write('::')
    self._emit_member_name(node.method)
    self._emit_args(node.args, node.first_class_callable)
def visit_PhpPropertyFetch(self, node)
Expand source code Browse git
def visit_PhpPropertyFetch(self, node: PhpPropertyFetch):
    self._emit_child(node.receiver, node)
    self._write('?->' if node.nullsafe else '->')
    self._emit_member_name(node.name)
def visit_PhpStaticPropertyFetch(self, node)
Expand source code Browse git
def visit_PhpStaticPropertyFetch(self, node: PhpStaticPropertyFetch):
    self._emit_child(node.class_name, node)
    self._write('::')
    self._emit_member_name(node.name)
def visit_PhpClassConstFetch(self, node)
Expand source code Browse git
def visit_PhpClassConstFetch(self, node: PhpClassConstFetch):
    self._emit_child(node.class_name, node)
    self._write('::')
    self._emit_member_name(node.name)
def visit_PhpArrayDimFetch(self, node)
Expand source code Browse git
def visit_PhpArrayDimFetch(self, node: PhpArrayDimFetch):
    self._emit_child(node.receiver, node)
    self._write('[')
    if node.index is not None:
        self.visit(node.index)
    self._write(']')
def visit_PhpArray(self, node)
Expand source code Browse git
def visit_PhpArray(self, node: PhpArray):
    if node.short:
        self._write('[')
        self._comma_list(node.items)
        self._write(']')
    else:
        self._write('array(')
        self._comma_list(node.items)
        self._write(')')
def visit_PhpList(self, node)
Expand source code Browse git
def visit_PhpList(self, node: PhpList):
    if node.short:
        self._write('[')
        self._comma_list(node.items)
        self._write(']')
    else:
        self._write('list(')
        self._comma_list(node.items)
        self._write(')')
def visit_PhpArrayItem(self, node)
Expand source code Browse git
def visit_PhpArrayItem(self, node: PhpArrayItem):
    if node.spread:
        self._write('...')
    if node.key is not None:
        self._emit_child(node.key, node)
        self._write(' => ')
    if node.by_ref:
        self._write('&')
    if node.value is not None:
        self._emit_child(node.value, node)
def visit_PhpMatch(self, node)
Expand source code Browse git
def visit_PhpMatch(self, node: PhpMatch):
    self._write('match (')
    if node.subject:
        self.visit(node.subject)
    self._write(') {')
    self._depth += 1
    for arm in node.arms:
        self._newline()
        self.visit(arm)
        self._write(',')
    self._depth -= 1
    if node.arms:
        self._newline()
    self._write('}')
def visit_PhpMatchArm(self, node)
Expand source code Browse git
def visit_PhpMatchArm(self, node: PhpMatchArm):
    if node.is_default:
        self._write('default')
    else:
        self._comma_list(node.conditions)
    self._write(' => ')
    if node.body:
        self._emit_child(node.body, node)
def visit_PhpIsset(self, node)
Expand source code Browse git
def visit_PhpIsset(self, node: PhpIsset):
    self._write('isset(')
    self._comma_list(node.variables)
    self._write(')')
def visit_PhpEmpty(self, node)
Expand source code Browse git
def visit_PhpEmpty(self, node: PhpEmpty):
    self._write('empty(')
    if node.operand:
        self.visit(node.operand)
    self._write(')')
def visit_PhpEval(self, node)
Expand source code Browse git
def visit_PhpEval(self, node: PhpEval):
    self._write('eval(')
    if node.operand:
        self.visit(node.operand)
    self._write(')')
def visit_PhpExit(self, node)
Expand source code Browse git
def visit_PhpExit(self, node: PhpExit):
    self._write(node.keyword)
    if node.operand is not None:
        self._write('(')
        self.visit(node.operand)
        self._write(')')
def visit_PhpParam(self, node)
Expand source code Browse git
def visit_PhpParam(self, node: PhpParam):
    for group in node.attributes:
        self.visit(group)
        self._write(' ')
    if node.visibility is not None:
        self._write(F'{node.visibility.value} ')
    if node.readonly:
        self._write('readonly ')
    if node.type is not None:
        self.visit(node.type)
        self._write(' ')
    if node.by_ref:
        self._write('&')
    if node.variadic:
        self._write('...')
    self._write(node.name)
    if node.default is not None:
        self._write(' = ')
        self._emit_child(node.default, node)
def visit_PhpNullableType(self, node)
Expand source code Browse git
def visit_PhpNullableType(self, node: PhpNullableType):
    self._write('?')
    if node.type:
        self.visit(node.type)
def visit_PhpUnionType(self, node)
Expand source code Browse git
def visit_PhpUnionType(self, node: PhpUnionType):
    for i, t in enumerate(node.types):
        if i > 0:
            self._write('|')
        if isinstance(t, PhpIntersectionType):
            self._write('(')
            self.visit(t)
            self._write(')')
        else:
            self.visit(t)
def visit_PhpIntersectionType(self, node)
Expand source code Browse git
def visit_PhpIntersectionType(self, node: PhpIntersectionType):
    for i, t in enumerate(node.types):
        if i > 0:
            self._write('&')
        self.visit(t)
def visit_PhpClosure(self, node)
Expand source code Browse git
def visit_PhpClosure(self, node: PhpClosure):
    if node.is_static:
        self._write('static ')
    self._write('function ')
    if node.by_ref:
        self._write('&')
    self._emit_params(node.params)
    if node.uses:
        self._write(' use (')
        self._comma_list(node.uses)
        self._write(')')
    if node.return_type is not None:
        self._write(': ')
        self.visit(node.return_type)
    self._write(' ')
    if node.body:
        self._emit_brace_block(node.body.body)
def visit_PhpClosureUse(self, node)
Expand source code Browse git
def visit_PhpClosureUse(self, node: PhpClosureUse):
    if node.by_ref:
        self._write('&')
    if node.variable:
        self.visit(node.variable)
def visit_PhpArrowFunction(self, node)
Expand source code Browse git
def visit_PhpArrowFunction(self, node: PhpArrowFunction):
    if node.is_static:
        self._write('static ')
    self._write('fn')
    if node.by_ref:
        self._write('&')
    self._emit_params(node.params)
    if node.return_type is not None:
        self._write(': ')
        self.visit(node.return_type)
    self._write(' => ')
    if node.body:
        self._emit_child(node.body, node)
def visit_PhpNew(self, node)
Expand source code Browse git
def visit_PhpNew(self, node: PhpNew):
    self._write('new ')
    self._emit_child(node.class_name, node)
    if node.has_parens or node.args:
        self._write('(')
        self._comma_list(node.args)
        self._write(')')
def visit_PhpNewAnonymous(self, node)
Expand source code Browse git
def visit_PhpNewAnonymous(self, node: PhpNewAnonymous):
    self._write('new ')
    if node.declaration:
        for modifier in node.declaration.modifiers:
            self._write(F'{modifier} ')
    self._write('class')
    if node.has_parens or node.args:
        self._write('(')
        self._comma_list(node.args)
        self._write(')')
    if node.declaration:
        self._emit_class_heritage(node.declaration)
        self._write(' ')
        self._emit_brace_block(node.declaration.members)
def visit_PhpScript(self, node)
Expand source code Browse git
def visit_PhpScript(self, node: PhpScript):
    self._emit_top_level(node.body)
def visit_PhpInlineHTML(self, node)
Expand source code Browse git
def visit_PhpInlineHTML(self, node: PhpInlineHTML):
    self._write(node.value)
def visit_PhpEchoTagStatement(self, node)
Expand source code Browse git
def visit_PhpEchoTagStatement(self, node: PhpEchoTagStatement):
    self._write('<?= ')
    self._comma_list(node.expressions)
    self._write(' ?>')
def visit_PhpNop(self, node)
Expand source code Browse git
def visit_PhpNop(self, node: PhpNop):
    self._write(';')
def visit_PhpBlock(self, node)
Expand source code Browse git
def visit_PhpBlock(self, node: PhpBlock):
    self._emit_brace_block(node.body)
def visit_PhpExpressionStatement(self, node)
Expand source code Browse git
def visit_PhpExpressionStatement(self, node: PhpExpressionStatement):
    if node.expression is not None:
        self.visit(node.expression)
    self._write(';')
def visit_PhpEcho(self, node)
Expand source code Browse git
def visit_PhpEcho(self, node: PhpEcho):
    self._write('echo ')
    self._comma_list(node.expressions)
    self._write(';')
def visit_PhpIf(self, node)
Expand source code Browse git
def visit_PhpIf(self, node: PhpIf):
    self._write('if (')
    if node.condition:
        self.visit(node.condition)
    self._write(')')
    alt = node.alternative_syntax
    self._emit_if_body(node.consequent, alt)
    for elseif in node.elseifs:
        if alt:
            self._write('elseif (')
        else:
            self._write(' elseif (')
        if elseif.condition:
            self.visit(elseif.condition)
        self._write(')')
        self._emit_if_body(elseif.body, alt)
    if node.alternate is not None:
        self._write('else' if alt else ' else')
        self._emit_if_body(node.alternate, alt)
    if alt:
        self._write('endif;')
def visit_PhpWhile(self, node)
Expand source code Browse git
def visit_PhpWhile(self, node: PhpWhile):
    self._write('while (')
    if node.condition:
        self.visit(node.condition)
    self._write(')')
    self._emit_body_or_alt(node.body, node.alternative_syntax, 'endwhile')
def visit_PhpDoWhile(self, node)
Expand source code Browse git
def visit_PhpDoWhile(self, node: PhpDoWhile):
    self._write('do ')
    self._emit_brace_block(node.body)
    self._write(' while (')
    if node.condition:
        self.visit(node.condition)
    self._write(');')
def visit_PhpFor(self, node)
Expand source code Browse git
def visit_PhpFor(self, node: PhpFor):
    self._write('for (')
    self._comma_list(node.init)
    self._write('; ')
    self._comma_list(node.condition)
    self._write('; ')
    self._comma_list(node.update)
    self._write(')')
    self._emit_body_or_alt(node.body, node.alternative_syntax, 'endfor')
def visit_PhpForeach(self, node)
Expand source code Browse git
def visit_PhpForeach(self, node: PhpForeach):
    self._write('foreach (')
    if node.subject:
        self.visit(node.subject)
    self._write(' as ')
    if node.key is not None:
        self.visit(node.key)
        self._write(' => ')
    if node.by_ref:
        self._write('&')
    if node.value:
        self.visit(node.value)
    self._write(')')
    self._emit_body_or_alt(node.body, node.alternative_syntax, 'endforeach')
def visit_PhpSwitch(self, node)
Expand source code Browse git
def visit_PhpSwitch(self, node: PhpSwitch):
    self._write('switch (')
    if node.subject:
        self.visit(node.subject)
    self._write(')')
    if node.alternative_syntax:
        self._write(':')
    else:
        self._write(' {')
    self._depth += 1
    for case in node.cases:
        self._newline()
        self.visit(case)
    self._depth -= 1
    if node.cases:
        self._newline()
    elif node.alternative_syntax:
        self._newline()
    if node.alternative_syntax:
        self._write('endswitch;')
    else:
        self._write('}')
def visit_PhpCase(self, node)
Expand source code Browse git
def visit_PhpCase(self, node: PhpCase):
    if node.test is not None:
        self._write('case ')
        self.visit(node.test)
        self._write(':')
    else:
        self._write('default:')
    self._depth += 1
    for stmt in node.body:
        self._emit_statement(stmt)
    self._depth -= 1
    self._enter_php()
def visit_PhpBreak(self, node)
Expand source code Browse git
def visit_PhpBreak(self, node: PhpBreak):
    self._write('break')
    if node.level is not None:
        self._write(' ')
        self.visit(node.level)
    self._write(';')
def visit_PhpContinue(self, node)
Expand source code Browse git
def visit_PhpContinue(self, node: PhpContinue):
    self._write('continue')
    if node.level is not None:
        self._write(' ')
        self.visit(node.level)
    self._write(';')
def visit_PhpReturn(self, node)
Expand source code Browse git
def visit_PhpReturn(self, node: PhpReturn):
    self._write('return')
    if node.value is not None:
        self._write(' ')
        self.visit(node.value)
    self._write(';')
def visit_PhpThrowStatement(self, node)
Expand source code Browse git
def visit_PhpThrowStatement(self, node: PhpThrowStatement):
    self._write('throw ')
    if node.operand:
        self.visit(node.operand)
    self._write(';')
def visit_PhpTry(self, node)
Expand source code Browse git
def visit_PhpTry(self, node: PhpTry):
    self._write('try ')
    self._emit_brace_block(node.body)
    for catch in node.catches:
        self._write(' ')
        self.visit(catch)
    if node.finally_body is not None:
        self._write(' finally ')
        self._emit_brace_block(node.finally_body)
def visit_PhpCatch(self, node)
Expand source code Browse git
def visit_PhpCatch(self, node: PhpCatch):
    self._write('catch (')
    for i, t in enumerate(node.types):
        if i > 0:
            self._write(' | ')
        self.visit(t)
    if node.variable is not None:
        self._write(' ')
        self.visit(node.variable)
    self._write(') ')
    self._emit_brace_block(node.body)
def visit_PhpUnset(self, node)
Expand source code Browse git
def visit_PhpUnset(self, node: PhpUnset):
    self._write('unset(')
    self._comma_list(node.variables)
    self._write(');')
def visit_PhpGlobal(self, node)
Expand source code Browse git
def visit_PhpGlobal(self, node: PhpGlobal):
    self._write('global ')
    self._comma_list(node.variables)
    self._write(';')
def visit_PhpStaticVar(self, node)
Expand source code Browse git
def visit_PhpStaticVar(self, node: PhpStaticVar):
    self._write('static ')
    self._comma_list(node.declarations)
    self._write(';')
def visit_PhpStaticVarDeclaration(self, node)
Expand source code Browse git
def visit_PhpStaticVarDeclaration(self, node: PhpStaticVarDeclaration):
    if node.variable:
        self.visit(node.variable)
    if node.default is not None:
        self._write(' = ')
        self._emit_child(node.default, node)
def visit_PhpGoto(self, node)
Expand source code Browse git
def visit_PhpGoto(self, node: PhpGoto):
    self._write(F'goto {node.label};')
def visit_PhpLabel(self, node)
Expand source code Browse git
def visit_PhpLabel(self, node: PhpLabel):
    self._write(F'{node.name}:')
def visit_PhpHaltCompiler(self, node)
Expand source code Browse git
def visit_PhpHaltCompiler(self, node: PhpHaltCompiler):
    self._write('__halt_compiler();')
    self._write(node.remainder)
def visit_PhpNamespace(self, node)
Expand source code Browse git
def visit_PhpNamespace(self, node: PhpNamespace):
    self._write('namespace')
    if node.name is not None:
        self._write(' ')
        self.visit(node.name)
    if node.body is not None:
        self._write(' ')
        self._emit_brace_block(node.body)
    else:
        self._write(';')
def visit_PhpUse(self, node)
Expand source code Browse git
def visit_PhpUse(self, node: PhpUse):
    self._write('use ')
    self._emit_use_kind(node.kind)
    self._comma_list(node.uses)
    self._write(';')
def visit_PhpGroupUse(self, node)
Expand source code Browse git
def visit_PhpGroupUse(self, node: PhpGroupUse):
    self._write('use ')
    self._emit_use_kind(node.kind)
    if node.prefix:
        self.visit(node.prefix)
    self._write('\\{')
    self._comma_list(node.uses)
    self._write('};')
def visit_PhpUseItem(self, node)
Expand source code Browse git
def visit_PhpUseItem(self, node: PhpUseItem):
    if node.kind is PhpUseKind.FUNCTION:
        self._write('function ')
    elif node.kind is PhpUseKind.CONSTANT:
        self._write('const ')
    if node.name:
        self.visit(node.name)
    if node.alias is not None:
        self._write(F' as {node.alias}')
def visit_PhpConst(self, node)
Expand source code Browse git
def visit_PhpConst(self, node: PhpConst):
    self._write('const ')
    self._comma_list(node.consts)
    self._write(';')
def visit_PhpConstDeclaration(self, node)
Expand source code Browse git
def visit_PhpConstDeclaration(self, node: PhpConstDeclaration):
    self._write(node.name)
    self._write(' = ')
    if node.value is not None:
        self._emit_child(node.value, node)
def visit_PhpDeclare(self, node)
Expand source code Browse git
def visit_PhpDeclare(self, node: PhpDeclare):
    self._write('declare(')
    self._comma_list(node.directives)
    self._write(')')
    if node.body is not None:
        if node.alternative_syntax:
            self._write(':')
            self._emit_statements(node.body)
            self._write('enddeclare;')
        else:
            self._write(' ')
            self._emit_brace_block(node.body)
    else:
        self._write(';')
def visit_PhpDeclareDirective(self, node)
Expand source code Browse git
def visit_PhpDeclareDirective(self, node: PhpDeclareDirective):
    self._write(F'{node.name}=')
    if node.value is not None:
        self.visit(node.value)
def visit_PhpAttributeGroup(self, node)
Expand source code Browse git
def visit_PhpAttributeGroup(self, node: PhpAttributeGroup):
    self._write('#[')
    self._comma_list(node.attributes)
    self._write(']')
def visit_PhpAttribute(self, node)
Expand source code Browse git
def visit_PhpAttribute(self, node: PhpAttribute):
    if node.name:
        self.visit(node.name)
    if node.args:
        self._write('(')
        self._comma_list(node.args)
        self._write(')')
def visit_PhpFunctionDeclaration(self, node)
Expand source code Browse git
def visit_PhpFunctionDeclaration(self, node: PhpFunctionDeclaration):
    self._emit_attribute_groups(node.attributes)
    self._write('function ')
    if node.by_ref:
        self._write('&')
    self._write(node.name)
    self._emit_params(node.params)
    if node.return_type is not None:
        self._write(': ')
        self.visit(node.return_type)
    if node.body is not None:
        self._write(' ')
        self._emit_brace_block(node.body.body)
    else:
        self._write(';')
def visit_PhpClass(self, node)
Expand source code Browse git
def visit_PhpClass(self, node: PhpClass):
    self._emit_attribute_groups(node.attributes)
    for modifier in node.modifiers:
        self._write(F'{modifier} ')
    self._write(F'{node.kind.value} {node.name}')
    if node.enum_backing_type is not None:
        self._write(': ')
        self.visit(node.enum_backing_type)
    self._emit_class_heritage(node)
    self._write(' ')
    self._emit_brace_block(node.members)
def visit_PhpClassMethod(self, node)
Expand source code Browse git
def visit_PhpClassMethod(self, node: PhpClassMethod):
    self._emit_attribute_groups(node.attributes)
    self._emit_modifiers(node.modifiers)
    self._write('function ')
    if node.by_ref:
        self._write('&')
    self._write(node.name)
    self._emit_params(node.params)
    if node.return_type is not None:
        self._write(': ')
        self.visit(node.return_type)
    if node.body is not None:
        self._write(' ')
        self._emit_brace_block(node.body.body)
    else:
        self._write(';')
def visit_PhpProperty(self, node)
Expand source code Browse git
def visit_PhpProperty(self, node: PhpProperty):
    self._emit_attribute_groups(node.attributes)
    self._emit_modifiers(node.modifiers)
    if node.type is not None:
        self.visit(node.type)
        self._write(' ')
    self._comma_list(node.props)
    self._write(';')
def visit_PhpPropertyDeclaration(self, node)
Expand source code Browse git
def visit_PhpPropertyDeclaration(self, node: PhpPropertyDeclaration):
    if node.variable:
        self.visit(node.variable)
    if node.default is not None:
        self._write(' = ')
        self._emit_child(node.default, node)
def visit_PhpClassConst(self, node)
Expand source code Browse git
def visit_PhpClassConst(self, node: PhpClassConst):
    self._emit_attribute_groups(node.attributes)
    self._emit_modifiers(node.modifiers)
    self._write('const ')
    if node.type is not None:
        self.visit(node.type)
        self._write(' ')
    self._comma_list(node.consts)
    self._write(';')
def visit_PhpEnumCase(self, node)
Expand source code Browse git
def visit_PhpEnumCase(self, node: PhpEnumCase):
    self._emit_attribute_groups(node.attributes)
    self._write(F'case {node.name}')
    if node.value is not None:
        self._write(' = ')
        self.visit(node.value)
    self._write(';')
def visit_PhpTraitUse(self, node)
Expand source code Browse git
def visit_PhpTraitUse(self, node: PhpTraitUse):
    self._write('use ')
    for i, name in enumerate(node.traits):
        if i > 0:
            self._write(', ')
        self.visit(name)
    if not node.adaptations:
        self._write(';')
        return
    self._write(' {')
    self._depth += 1
    for adaptation in node.adaptations:
        self._newline()
        self.visit(adaptation)
    self._depth -= 1
    if node.adaptations:
        self._newline()
    self._write('}')
def visit_PhpTraitAdaptation(self, node)
Expand source code Browse git
def visit_PhpTraitAdaptation(self, node: PhpTraitAdaptation):
    if node.trait is not None:
        self.visit(node.trait)
        self._write('::')
    self._write(node.method)
    if node.kind == 'insteadof':
        self._write(' insteadof ')
        for i, name in enumerate(node.insteadof):
            if i > 0:
                self._write(', ')
            self.visit(name)
    else:
        self._write(' as')
        if node.new_modifier is not None:
            self._write(F' {node.new_modifier}')
        if node.new_name is not None:
            self._write(F' {node.new_name}')
    self._write(';')