PHP code example of yangweijie / perry-php

1. Go to this page and download the library: Download yangweijie/perry-php library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

yangweijie / perry-php example snippets




use Perry\App;
use Perry\UI\Binding;
use Perry\UI\Styling\Style;
use Perry\UI\Widget\Button;
use Perry\UI\Widget\HStack;
use Perry\UI\Widget\Text;
use Perry\UI\Widget\VStack;

$count = new Binding('count', 0);

$app = new App();
$app->setRoot(
    new VStack(
        (new Text($count))->style(Style::make()->fontSize(48)),
        new HStack(
            (new Button('-', function () use ($count) {
                $count -= 1;
            }))->style(Style::make()->fontSize(24)->padding(16)),
            (new Button('+', function () use ($count) {
                $count += 1;
            }))->style(Style::make()->fontSize(24)->padding(16)),
        ),
    )
);

// Generate for any platform
echo $app->generateCode('swiftui');   // macOS/iOS → SwiftUI Swift
echo $app->generateCode('html');      // Web → HTML/CSS/JS
echo $app->generateCode('compose');   // Android → Jetpack Compose



use Perry\App;
use Perry\UI\Binding;
use Perry\UI\Styling\Style;
use Perry\UI\Widget\Button;
use Perry\UI\Widget\HStack;
use Perry\UI\Widget\Text;
use Perry\UI\Widget\VStack;

$app = new App();

$root = new VStack(
    (new Text('Hello, Perry!'))->style(Style::make()->fontSize(24)),
    new HStack(
        (new Button('Click Me'))
            ->style(Style::make()->backgroundColor('#007AFF')->foregroundColor('#fff')),
        (new Button('Cancel'))
            ->style(Style::make()->backgroundColor('#f0f0f0')),
    ),
);

$app->setRoot($root);

echo $app->generateCode('swiftui');

use Perry\UI\Binding;
use Perry\UI\Widget\Text;

// Static text — renders literal string
$title = new Text('Hello, World!');

// Reactive text — renders binding value, auto-updates when state changes
$display = new Binding('display', '0');
$counter = new Text($display);

use Perry\App;
use Perry\UI\AppContainer;
use Perry\UI\Binding;
use Perry\UI\Widget\Text;
use Perry\UI\Widget\VStack;

$time = new Binding('time', '00:00:00');
$date = new Binding('date', '2024-01-01');

$app = new App();
$app->setRoot(
    new AppContainer(
        new VStack(
            (new Text($time))->style(
                \Perry\UI\Styling\Style::make()->fontSize(32)->textAlignment('center')
            ),
            (new Text($date))->style(
                \Perry\UI\Styling\Style::make()->fontSize(16)->foregroundColor('#888')
            ),
        ),
        320, 200,  // window size
        $date,     // extra binding (not attached to a Text widget)
    )
);

echo $app->generateCode('html');
// Generates: const state = { time: "00:00:00", date: "2024-01-01" };
//            function render() { el_time.textContent = state.time; ... }

use Perry\UI\Action;
use Perry\UI\Binding;
use Perry\UI\Widget\Button;
use Perry\UI\Styling\Style;

$display = new Binding('display', '0');

// 1. Static button — no action
$ok = new Button('OK');

// 2. Simple action — set binding to value
$setZero = new Button('Reset', Action::set($display, '0'));

// 3. Append action — append string to binding
$addDigit = new Button('1', Action::append($display, '1'));

// 4. Closure action — full PHP logic → cross-platform code
$toggleSign = new Button('±', Action::fromClosure(function () use ($display) {
    if ($display[0] === '-') {
        $display = substr($display, 1);
    } else {
        $display = '-' . $display;
    }
}));

// 5. Closure with bindings — pass external values into closure
$button = new Button('×', Action::fromClosure(
    function () use ($display, $operand1, $operation) {
        $operand1 = floatval($display);
        $operation = '×';
        $display .= '×';
    },
    compact('operand1', 'operation')  // external bindings for replacement
));

// Styled button
$styled = (new Button('Submit', $toggleSign))
    ->style(Style::make()
        ->backgroundColor('#007AFF')
        ->foregroundColor('#ffffff')
        ->fontSize(18)
        ->padding(12)
        ->cornerRadius(8)
    );

use Perry\UI\Widget\VStack;
use Perry\UI\Widget\Text;
use Perry\UI\Widget\Button;

// Pass children as constructor arguments
$layout = new VStack(
    new Text('Header'),
    new Text('Body content goes here'),
    new Text('Footer'),
);

use Perry\UI\Styling\Style;

$spaced = (new VStack(
    new Text('A'),
    new Text('B'),
    new Text('C'),
))->style(Style::make()->padding(16));  // 16px spacing between children

use Perry\UI\Widget\HStack;
use Perry\UI\Widget\Button;
use Perry\UI\Widget\Spacer;

// Button row
$toolbar = new HStack(
    (new Button('Bold'))->style(Style::make()->fontSize(14)),
    (new Button('Italic'))->style(Style::make()->fontSize(14)),
    (new Button('Underline'))->style(Style::make()->fontSize(14)),
);

// Left-right layout with Spacer
$navbar = new HStack(
    new Text('Logo'),
    new Spacer(),  // pushes "Menu" to the right
    new Text('Menu'),
);

use Perry\UI\Widget\HStack;
use Perry\UI\Widget\Text;
use Perry\UI\Widget\Spacer;

// "Left" is on the left, "Right" is on the right, Spacer fills the gap
$row = new HStack(
    new Text('Left'),
    new Spacer(),
    new Text('Right'),
);

// Vertical: pushes "Top" and "Bottom" apart
$column = new VStack(
    new Text('Top'),
    new Spacer(),
    new Text('Bottom'),
);

use Perry\UI\Widget\Image;

// Local file
$logo = new Image('logo.png');

// Named asset
$avatar = new Image('avatar');

use Perry\UI\Widget\ScrollView;
use Perry\UI\Widget\VStack;
use Perry\UI\Widget\Text;

$list = new ScrollView(
    new VStack(
        new Text('Item 1'),
        new Text('Item 2'),
        new Text('Item 3'),
        // ... many items
    )
);

use Perry\UI\State;
use Perry\UI\Widget\TextInput;

$state = new State();
$name = $state->create('');  // initial value: empty string

$input = new TextInput($name, 'Enter your name...');

use Perry\UI\State;
use Perry\UI\Widget\Toggle;

$state = new State();
$darkMode = $state->create(false);

$toggle = new Toggle($darkMode, 'Dark Mode');

use Perry\UI\AppContainer;
use Perry\UI\Binding;
use Perry\UI\Widget\VStack;
use Perry\UI\Widget\Text;
use Perry\UI\Widget\Button;
use Perry\UI\Action;
use Perry\UI\Styling\Style;

$count = new Binding('count', 0);
$label = new Binding('label', 'Clicks: 0');

$app = new AppContainer(
    // 1. Content widget tree
    new VStack(
        (new Text($label))->style(Style::make()->fontSize(24)),
        (new Button('Increment', function () use ($count, $label) {
            $count += 1;
            $label = 'Clicks: ' . strval($count);
        }))->style(Style::make()->backgroundColor('#007AFF')->foregroundColor('#fff')),
    ),
    // 2. Window size (optional)
    320,
    480,
    // 3. Extra bindings not attached to any Text widget
    $count,
);

$app2 = new App();
$app2->setRoot($app);
echo $app2->generateCode('html');

use Perry\UI\Binding;

$count = new Binding('count', 0);         // int
$display = new Binding('display', '0');    // string
$visible = new Binding('visible', true);   // bool
$opacity = new Binding('opacity', 1.0);    // float

$count = new Binding('count', 0);

// In a closure action, $count refers to the binding name in generated code
// The closure gets parsed → IR → target language assignment
$action = Action::fromClosure(function () use ($count) {
    $count += 1;
});

// Generated Swift: count = count + 1
// Generated JS:    state.count = state.count + 1
// Generated Kotlin: count.value = count.value + 1

use Perry\UI\State;

$state = new State();

// Create state entries
$name = $state->create('');           // StateId
$darkMode = $state->create(false);    // StateId
$speed = $state->create(1.0);         // StateId

// Read values
$currentName = $state->get($name);    // ''

// Update values
$state->set($name, 'Alice');

// Subscribe to changes (runtime only, not code-generated)
$state->subscribe($name, function (mixed $newValue) {
    echo "Name changed to: $newValue\n";
});

use Perry\UI\Action;
use Perry\UI\Binding;

$display = new Binding('display', '0');

// SetValue — assign a value
$action = Action::set($display, '42');
$action = Action::set($display, true);     // bool
$action = Action::set($display, 3.14);    // float

// Append — concatenate a string
$action = Action::append($display, '1');   // display += "1"

// Clear — reset to initial value
$action = Action::clear($display);         // display = "0"

// Custom — raw platform-specific code
$action = Action::custom('display.text = ""');  // passed through as-is

use Perry\UI\Action;
use Perry\UI\Binding;
use Perry\UI\Widget\Button;
use Perry\UI\Widget\Slider;
use Perry\UI\Widget\TextInput;
use Perry\UI\Widget\Toggle;

$display = new Binding('display', '0');
$operand1 = new Binding('operand1', 0.0);
$operation = new Binding('operation', '');

// Button — action on click
$btn = new Button('7', Action::append($display, '7'));

// Slider — action on value change
$slider = new Slider(0, 100, $operand1, onChange: Action::set($operand1, 50));

// TextInput — action on text change
$input = new TextInput($display, onChange: Action::set($display, ''));

// Toggle — action on toggle
$toggle = new Toggle(true, onToggle: Action::set($display, 'toggled'));

use Perry\UI\Action;
use Perry\UI\Binding;

$display = new Binding('display', '0');
$operand1 = new Binding('operand1', 0.0);
$operation = new Binding('operation', '');

$action = Action::fromClosure(
    function () use ($display, $operand1, $operation) {
        $operand1 = floatval($display);
        $operation = '+';
        $display .= '+';
    }
);

// Pass external values into the closure at definition time
$action = Action::fromClosure(
    function () use ($display, $digit) {
        $display .= $digit;
    },
    ['digit' => '5']  // $digit is replaced with "5" in generated code
);

// Generated Swift: display = display + "5"
// Generated JS:    state.display = state.display + "5"

function numBtn(string $digit, Binding $display): Button {
    return new Button($digit, Action::fromClosure(
        function () use ($digit, $display) {
            $display .= $digit;
        },
        compact('digit')
    ));
}

$row = new HStack(
    numBtn('1', $display),
    numBtn('2', $display),
    numBtn('3', $display),
);

use Perry\UI\Styling\Style;

$cardStyle = Style::make()
    ->backgroundColor('#1a1a1a')
    ->foregroundColor('#ffffff')
    ->fontSize(16)
    ->fontWeight('bold')
    ->padding(16)
    ->paddingAll(8, 8, 12, 12)    // top, bottom, leading, trailing
    ->width(300)
    ->height(200)
    ->cornerRadius(12)
    ->border(1, '#333333')
    ->shadow('#000000', 4, 2, 2)
    ->opacity(0.9)
    ->textAlignment('center');

// Merge styles
$base = Style::make()->fontSize(14)->foregroundColor('#333');
$highlight = Style::make()->backgroundColor('#ffff00');
$merged = $base->merge($highlight);

use Perry\UI\Styling\StyleMatrix;
use Perry\UI\Styling\StyleProperty;

$matrix = new StyleMatrix();

// Check a specific property on a platform
$support = $matrix->getSupport('macos', StyleProperty::CornerRadius);
// PlatformSupport::Wired (fully supported)

// Get all supported properties for a platform
$wired = $matrix->getWiredProperties('macos');

// Check if a platform has full support
$full = $matrix->isFullySupported('macos'); // bool

// Get missing properties
$missing = $matrix->getMissingProperties('android');

use Perry\App;
use Perry\Build\Target;

$app = new App();
$app->setRoot($widgetTree);

// By name
echo $app->generateCode('swiftui');
echo $app->generateCode('html');

// Auto-detect from target
$app = new App(Target::fromString('macos'));
echo $app->generateForTarget();

// Write to file
$backend = $app->codegen()->get('html');
$backend->generateToFile($widgetTree, 'build/output.html');

use Perry\Generator\SwiftGenerator;
use Perry\IR\Assignment;
use Perry\IR\Literal;

$gen = new SwiftGenerator(stateVars: ['display', 'result']);
$ir = new Assignment('display', new Literal('Hello'));
echo $gen->generateAssignment($ir);
// Output: display = "Hello"

$ir2 = new Assignment('count', new Literal(42));
echo $gen->generateAssignment($ir2);
// Output: var count = 42  (new variable, not a state var)

use Perry\Build\Target;

$target = Target::detect();            // auto-detect current platform
$target = Target::fromString('macos'); // from string

$target->isApple();    // true for macOS, iOS, tvOS, visionOS, watchOS
$target->isDesktop();  // true for macOS, Linux, Windows
$target->isMobile();   // true for iOS, Android, watchOS



use Perry\App;
use Perry\Build\Target;
use Perry\UI\Action;
use Perry\UI\AppContainer;
use Perry\UI\Binding;
use Perry\UI\Styling\Style;
use Perry\UI\Widget\Button;
use Perry\UI\Widget\HStack;
use Perry\UI\Widget\Text;
use Perry\UI\Widget\VStack;

// State
$display = new Binding('display', '0');
$result = new Binding('result', '');
$operand1 = new Binding('operand1', 0.0);
$operand2 = new Binding('operand2', 0.0);
$operation = new Binding('operation', '');
$isTyping = new Binding('isTyping', false);
$typed = new Binding('typed', '');

// Styles
$numberBtn = Style::make()->fontSize(24)->backgroundColor('#f0f0f0')->padding(20)->cornerRadius(8);
$opBtn = Style::make()->fontSize(24)->backgroundColor('#FF9500')->foregroundColor('#fff')->padding(20)->cornerRadius(8);

// Digit button factory
function numBtn(string $d, Binding $display, Binding $op2, Binding $typing, Binding $typed, Binding $op): Button {
    return (new Button($d, Action::fromClosure(
        function () use ($d, $display, $op2, $typing, $typed, $op) {
            if ($typing) {
                $typed .= $d;
                $display .= $d;
            } else {
                $typed = $d;
                $display = $d;
                $typing = true;
            }
            $op2 = floatval($typed);
        },
        compact('d')
    )))->style(Style::make()->fontSize(24)->backgroundColor('#f0f0f0')->padding(20)->cornerRadius(8));
}

// Build
$app = new App(Target::fromString('macos'));
$app->setRoot(
    new AppContainer(
        new VStack(
            (new Text($result))->style(Style::make()->fontSize(16)->foregroundColor('#888')),
            (new Text($display))->style(Style::make()->fontSize(32)->padding(16)),
            new HStack(
                numBtn('7', $display, $operand2, $isTyping, $typed, $operation),
                numBtn('8', $display, $operand2, $isTyping, $typed, $operation),
                numBtn('9', $display, $operand2, $isTyping, $typed, $operation),
                (new Button('×', Action::fromClosure(function () use ($display, $operand1, $operation, $typed) {
                    $operand1 = floatval($typed);
                    $operation = '×';
                    $display .= '×';
                    $typed = '';
                })))->style($opBtn),
            ),
            // ... more rows
        ),
        320, 480,
        $operand1, $operand2, $operation, $isTyping, $typed
    )
);

echo $app->generateForTarget();



use Perry\App;
use Perry\UI\Action;
use Perry\UI\Binding;
use Perry\UI\Styling\Style;
use Perry\UI\Widget\Button;
use Perry\UI\Widget\HStack;
use Perry\UI\Widget\Text;
use Perry\UI\Widget\VStack;

$count = new Binding('count', 0);

$app = new App();
$app->setRoot(
    new VStack(
        (new Text($count))->style(Style::make()->fontSize(48)),
        new HStack(
            (new Button('-', Action::fromClosure(function () use ($count) {
                $count -= 1;
            })))->style(Style::make()->fontSize(24)->padding(16)),
            (new Button('+', Action::fromClosure(function () use ($count) {
                $count += 1;
            })))->style(Style::make()->fontSize(24)->padding(16)),
        ),
    )
);

echo $app->generateCode('html');



use Perry\App;
use Perry\UI\Action;
use Perry\UI\Binding;
use Perry\UI\Styling\Style;
use Perry\UI\Widget\Button;
use Perry\UI\Widget\HStack;
use Perry\UI\Widget\ScrollView;
use Perry\UI\Widget\Text;
use Perry\UI\Widget\VStack;
use Perry\UI\Widget\AppContainer;

$items = new Binding('items', 'Buy milk');
$ newItem = new Binding('newItem', '');

$app = new App();
$app->setRoot(
    new AppContainer(
        new VStack(
            (new Text($items))->style(Style::make()->fontSize(16)),
            new HStack(
                (new Button('Add', Action::fromClosure(function () use ($items, $newItem) {
                    $items .= "\n" . $newItem;
                    $newItem = '';
                })))->style(Style::make()->backgroundColor('#007AFF')->foregroundColor('#fff')),
                (new Button('Clear', Action::fromClosure(function () use ($items) {
                    $items = '';
                })))->style(Style::make()->backgroundColor('#ff3b30')->foregroundColor('#fff')),
            ),
        ),
        320, 480,
        $newItem
    )
);

echo $app->generateCode('swiftui');



declare(strict_types=1);

namespace Perry\UI\Widget;

use Perry\UI\Widget;
use Perry\UI\WidgetKind;

final class Slider extends Widget
{
    public function __construct(
        private float $min = 0.0,
        private float $max = 1.0,
        private float $step = 0.1,
        private ?\Perry\UI\Binding $value = null,
    ) {
        parent::__construct();
    }

    public function kind(): WidgetKind
    {
        return WidgetKind::Slider;  // must add this enum case first
    }

    public function min(): float { return $this->min; }
    public function max(): float { return $this->max; }
    public function step(): float { return $this->step; }
    public function getValue(): ?\Perry\UI\Binding { return $this->value; }
}

// src/UI/WidgetKind.php
enum WidgetKind: int
{
    // ... existing cases ...
    case Slider = 9;
    case List = 10;
    // ...
}

// In SwiftUIBackend.php — add case to generateWidget()
WidgetKind::Slider => $this->generateSlider($widget),

// Add the method
private function generateSlider(Slider $widget): string
{
    $min = $widget->min();
    $max = $widget->max();
    $step = $widget->step();
    $binding = $widget->getValue();
    $value = $binding ? $binding->name : '0.0';
    $mods = $this->generateModifiers($widget->getStyle());
    return "Slider(value: \${$value}, in: {$min}...{$max}, step: {$step}){$mods}";
}

// In HtmlBackend.php — add case to generateWidget()
WidgetKind::Slider => $this->generateSlider($widget),

private function generateSlider(Slider $widget): string
{
    $min = $widget->min();
    $max = $widget->max();
    $step = $widget->step();
    $id = $widget->handle();
    $style = $this->generateStyle($widget->getStyle());
    return "<input type=\"range\" id=\"{$id}\" min=\"{$min}\" max=\"{$max}\" step=\"{$step}\"{$style}>";
}



declare(strict_types=1);

namespace Perry\Codegen;

use Perry\Build\Target;
use Perry\UI\Widget;
use Perry\UI\Widget\AppContainer;
use Perry\UI\WidgetKind;

final class FlutterBackend extends CodegenBackend
{
    public function name(): string
    {
        return 'flutter';
    }

    public function supports(Target $target): bool
    {
        return $target === Target::Android;  // or a custom Flutter target
    }

    public function generate(Widget $root): string
    {
        if ($root instanceof AppContainer) {
            return $this->generateApp($root);
        }
        return $this->generateWidget($root);
    }

    private function generateWidget(Widget $widget): string
    {
        return match ($widget->kind()) {
            WidgetKind::Text => $this->generateText($widget),
            WidgetKind::Button => $this->generateButton($widget),
            WidgetKind::VStack => $this->generateVStack($widget),
            // ... handle all widget kinds
            default => 'SizedBox()',
        };
    }

    private function generateText(\Perry\UI\Widget\Text $widget): string
    {
        $binding = $widget->getBinding();
        $content = $binding ? "\${{$binding->name}}" : "'{$widget->content()}'";
        return "Text({$content})";
    }

    private function generateButton(\Perry\UI\Widget\Button $widget): string
    {
        $label = $widget->label();
        $action = $this->generateAction($widget->getAction());
        return "ElevatedButton(onPressed: () {{ {$action} }}, child: Text('{$label}'))";
    }

    private function generateVStack(\Perry\UI\Widget\VStack $widget): string
    {
        $children = array_map(
            fn($c) => $this->generateWidget($c),
            $widget->children()
        );
        $body = implode(",\n        ", $children);
        return "Column(\n    children: [\n        {$body}\n    ]\n)";
    }

    private function generateAction(?\Perry\UI\Action $action): string
    {
        if ($action === null) return '';
        if ($action->type === \Perry\UI\ActionType::Closure) {
            $gen = new \Perry\Generator\DartGenerator();
            return $action->generate($gen);
        }
        return '';
    }
}

// src/Codegen/CodegenFactory.php
public function __construct()
{
    $this->register(new SwiftUIBackend());
    $this->register(new HtmlBackend());
    $this->register(new ComposeBackend());
    $this->register(new AndroidXmlBackend());
    $this->register(new WinUIBackend());
    $this->register(new Gtk4Backend());
    $this->register(new FlutterBackend());  // ← add here
}

$app = new App();
$app->setRoot($widgetTree);
echo $app->generateCode('flutter');



declare(strict_types=1);

namespace Perry\Generator;

use Perry\IR\Generator as GeneratorInterface;
use Perry\IR\*;

final class RustGenerator implements GeneratorInterface
{
    private array $stateVars;
    private array $declaredVars = [];

    public function __construct(array $stateVars = [])
    {
        $this->stateVars = array_flip($stateVars);
    }

    // Core
    public function generateProgram(Program $node): string
    {
        $lines = [];
        foreach ($node->statements as $stmt) {
            $lines[] = $stmt->accept($this);
        }
        return implode("\n", $lines);
    }

    public function generateAssignment(Assignment $node): string
    {
        $name = $node->variable;
        $value = $node->value->accept($this);

        if (isset($this->stateVars[$name])) {
            return "*{$name}.borrow_mut() = {$value}";
        }

        if (!in_array($name, $this->declaredVars)) {
            $this->declaredVars[] = $name;
            return "let mut {$name} = {$value}";
        }

        "{$name} = {$value}";
    }

    public function generateVariable(Variable $node): string
    {
        $name = $node->name;
        if (isset($this->stateVars[$name])) {
            return "{$name}.borrow()";
        }
        return $name;
    }

    public function generateLiteral(Literal $node): string
    {
        if (is_string($node->value)) {
            return "\"{$node->value}\"";
        }
        if (is_bool($node->value)) {
            return $node->value ? 'true' : 'false';
        }
        return (string) $node->value;
    }

    public function generateBinaryOp(BinaryOp $node): string
    {
        $left = $node->left->accept($this);
        $right = $node->right->accept($this);
        $op = match ($node->op) {
            '.' => '+',
            '===' => '==',
            '!==' => '!=',
            default => $node->op,
        };
        return "{$left} {$op} {$right}";
    }

    // ... implement all 50+ methods from the Generator interface
    // Copy the pattern from SwiftGenerator.php and adapt to Rust syntax

    public function generateIf(IfStatement $node): string { /* ... */ }
    public function generateWhile(WhileStatement $node): string { /* ... */ }
    public function generateFor(ForStatement $node): string { /* ... */ }
    // ... etc
}

use Perry\Generator\RustGenerator;

$gen = new RustGenerator(stateVars: ['display', 'count']);
$action = Action::fromClosure(function () use ($display) {
    $display = 'Hello';
});
echo $action->generate($gen);
// Output: *display.borrow_mut() = "Hello"

// In SwiftGenerator.php — add case in generateFunctionCall()

case 'array_map':
    // array_map(fn($x) => ..., $array) → array.map { x in ... }
    $callback = $args[0] ?? null;
    $array = $args[1]->accept($this) ?? '[]';
    if ($callback instanceof \Perry\IR\Closure) {
        $param = $callback->params[0] ?? 'item';
        $body = $callback->body->accept($this);
        return "{$array}.map {{ {$param} in {$body} }}";
    }
    return "{$array}.map {{ $0 }}";

case 'array_filter':
    // array_filter($array, fn($x) => ...) → array.filter { x in ... }
    $array = $args[0]->accept($this) ?? '[]';
    $callback = $args[1] ?? null;
    if ($callback instanceof \Perry\IR\Closure) {
        $param = $callback->params[0] ?? 'item';
        $body = $callback->body->accept($this);
        return "{$array}.filter {{ {$param} in {$body} }}";
    }
    return "{$array}.filter {{ $0 }}";

PHP closure
    ↓ nikic/php-parser
PHP AST
    ↓ Perry\IR\AstToIrVisitor
Perry IR (54 node types)
    ↓ Perry\Generator\{Swift,JavaScript,Kotlin,Dart,CSharp}Generator
Target language code
bash
php examples/calculator.php macos --build
# Output: build/Calculator.app

php examples/calculator.php web
# Output: build/calculator.html

php examples/pry.php windows --build
# Output: build/pry.exe
bash
php examples/pry.php windows --build
# Output: build/pry.exe + build/pry.html

# Also works on other platforms:
php examples/pry.php macos --build
php examples/pry.php web