PHPackages                             yay/yay - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. yay/yay

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

yay/yay
=======

A high level PHP Pre-Processor

0.7.0(7y ago)5742.9k↓100%32[3 issues](https://github.com/marcioAlmada/yay/issues)[3 PRs](https://github.com/marcioAlmada/yay/pulls)3MITPHPPHP 7.\*CI failing

Since Feb 27Pushed 2y ago20 watchersCompare

[ Source](https://github.com/marcioAlmada/yay)[ Packagist](https://packagist.org/packages/yay/yay)[ RSS](/packages/yay-yay/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (7)Dependencies (3)Versions (15)Used By (3)

YAY!
====

[](#yay)

[![Build Status](https://camo.githubusercontent.com/ac26f63b47004cb804ac09a9f1bed11299763765036c04bcdbac953d243fb265/68747470733a2f2f7472617669732d63692e6f72672f6d617263696f416c6d6164612f7961792e7376673f6272616e63683d6d6173746572)](https://travis-ci.org/marcioAlmada/yay)[![Coverage Status](https://camo.githubusercontent.com/8d80ec354df5fef2132ecc48eddce38a07c501ed90af0b813f09a155fc65941a/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f6769746875622f6d617263696f416c6d6164612f7961792f62616467652e7376673f6272616e63683d747261766973)](https://coveralls.io/github/marcioAlmada/yay?branch=travis)[![Latest Stable Version](https://camo.githubusercontent.com/266f0d7bb0950bb5a608401acb02d79752325fac075e248c0f54674c5a81974d/68747470733a2f2f706f7365722e707567782e6f72672f7961792f7961792f762f737461626c652e706e67)](https://packagist.org/packages/yay/yay)[![Join the chat at https://gitter.im/marcioAlmada/yay](https://camo.githubusercontent.com/aabe1b48391ad6b7646b0dfb1c0c2deb34db9502811b661f0cbd453cc638387b/68747470733a2f2f6261646765732e6769747465722e696d2f6d617263696f416c6d6164612f7961792e737667)](https://gitter.im/marcioAlmada/yay?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)[![License](https://camo.githubusercontent.com/bd146efc0fe521a677c1649d2e9ec30bc63d8b2b07dee9d1cfb81c47a4d75d11/68747470733a2f2f706f7365722e707567782e6f72672f7961792f7961792f6c6963656e73652e706e67)](https://packagist.org/packages/yay/yay)

**YAY!** is a high level parser combinator based PHP preprocessor that allows anyone to augment PHP with PHP 💥

This means that language features could be distributed as composer packages (as long as the macro based implementations can be expressed in pure PHP code, and the implementation is fast enough).

[Roadmap](https://github.com/marcioAlmada/yay/issues/3).

Set Up
------

[](#set-up)

```
composer require yay/yay:dev-master
```

Usage
-----

[](#usage)

### Command Line

[](#command-line)

```
yay some/file/with/macros.php >> target/file.php

```

### Runtime Mode

[](#runtime-mode)

The "runtime" mode is W.I.P and will use stream wrappers along with composer integration in order to preprocess every file that gets included. It may have some opcache/cache support, so files will be only preprocessed/expanded once and when needed.

See feature progress at issue [\#11](https://github.com/marcioAlmada/yay/issues/11).

How it works
------------

[](#how-it-works)

### Very Simple Example

[](#very-simple-example)

Every macro consist of a matcher and an expander that when executed allows you to augment PHP. Consider the simplest example possible:

```
$(macro :unsafe) { $ } >> { $this } // this shorthand
```

The macro is basically expanding a literal `$` token to `$this`. The following code would expand to:

```
// source                                |   // expansion
class Foo {                              |   class Foo {
    protected $a = 1, $b = 2, $c = 3;    |       protected $a = 1, $b = 2, $c = 3;
                                         |
    function getProduct(): int {         |       function getProduct(): int {
        return $->a * $->b * $->c;       |           return $this->a * $this->b *$this->c;
    }                                    |       }
}                                        |   }
```

> Notice that the `:unsafe` tag is necessary to avoid macro hygiene on `$this` expansion.

This macro is actually very naive, a more producion ready version would be:

```
$(macro :unsafe){
    $ // litterally matches '$'
    // but not followed by:
    $(not(token(T_VARIABLE))) // avoids var var false positives such as '$$foo'
    $(not(token('{'))) // avoids false positives such as '${foo}'
} >> {
    $this
}
```

### Simple Example

[](#simple-example)

Apart from literal characher sequences, it's also possible to match specific token types using the token matcher in the form of `$(TOKEN_TYPE as label)`.

The following macro matches token sequences like `__swap($x, $y)` or `__swap($foo, $bar)`:

```
$(macro) {
    __swap ( $(T_VARIABLE as A) , $(T_VARIABLE as B) )
} >> {
    (list($(A), $(B)) = [$(B), $(A)])
}
```

The expansion should be pretty obvious:

```
// source              |    // expansion
__swap($foo, $bar);    |    (list($foo, $bar) = [$bar, $foo]);
```

### Another Simple Example

[](#another-simple-example)

To implement `unless` we need to match the literal `unless` keyword followed by a layer of tokens between parentheses `(...)` and a block of code `{...}`. Fortunately, the macro DSL has a very straightforward layer matching construct:

```
$(macro) {
    unless ($(layer() as expression)) { $(layer() as body) }
} >> {
    if (! ($(expression))) {
        $(body)
    }
}
```

The macro in action:

```
// source                   |   // expansion
unless ($x === 1) {         |   if (! ($x === 1)) {
    echo "\$x is not 1";    |       echo "\$x is not 1";
}                           |   }
```

> PS: Please don't implement "unless". This is here just for didactic reasons.

### Advanced Example

[](#advanced-example)

A more complex example could be porting enums from the future to PHP with a syntax like:

```
enum Fruits {
    Apple,
    Orange
}

var_dump(\Fruits::Orange  \Fruits::Apple);
```

So, syntactically, enums are declared with the literal `enum` word followed by a `T_STRING` and a comma separated list of identifiers withing braces such as `{A, B, C}`.

YAY uses parser combinators internally for everything and these more high level parsers are fully exposed on macro declarations. Our enum macro will need high level matchers like `ls()` and `label()`combined to match the desired syntax, like so:

```
$(macro) {
    enum $(T_STRING as name) {
        $(
            // ls() matches a delimited list
            // in this case a list of label() delimited by ',' such as `foo, bar, baz`
            ls
            (
                label() as field
                ,
                token(',')
            )
            as fields
        )
    }
} >> {
    "it works";
}
```

The macro is already capable to match the enum syntax:

```
// source                      // expansion
enum Order {ASC, DESC};    |   "it works";
```

I won't explain how enums are implemented, you can read the [RFC](https://wiki.php.net/rfc/enum) if you wish and then see how the expansion below works:

```
// things here would normally be under a namespace, but since we want a concise example...

interface Enum
{
}

function enum_field_or_class_constant(string $class, string $field)
{
    return (\in_array(\Enum::class, \class_implements($class)) ? $class::$field() : \constant("{$class}::{$field}"));
}

$(macro :unsafe) {
    // the enum declaration
    enum $(T_STRING as name) {
        $(
            ls
            (
                label() as field
                ,
                token(',')
            )
            as fields
        )
    }
} >> {
    class $(name) implements Enum {
        private static $registry;

        private function __construct() {}

        static function __callStatic(string $type, array $args) : self {
            if(! self::$registry) {
                self::$registry = new \stdclass;
                $(fields ... {
                    self::$registry->$(field) = new class extends $(name) {};
                })
            }

            if (isset(self::$registry->$type)) return self::$registry->$type;

            throw new \Exception(sprintf('Undefined enum type %s->%s', __CLASS__, $type));
        }
    }
}

$(macro) {
    $(
        // sequence that matches the enum field access syntax:
        chain(
            ns() as class, // matches a namespace
            token(T_DOUBLE_COLON), // matches T_DOUBLE_COLON used for static access
            not(class), // avoids matching `Foo::class`, class resolution syntax
            label() as field, // matches the enum field name
            not(token('(')) // avoids matching static method calls such as `Foo::bar()`
        )
    )
} >> {
    \enum_field_or_class_constant($(class)::class, $$(stringify($(field))))
}
```

> More examples within the phpt tests folder

FAQ
===

[](#faq)

> Why "YAY!"?

\- PHP with feature "x": yay or nay? 😉

> Where is the documentation?

A cookbook is on the making

> Why are you working on this?

Because it's being fun. It may become useful. [Because we can™](https://github.com/haskellcamargo/because-we-can).

Conclusion
==========

[](#conclusion)

For now this is an experiment about how to build a high level preprocessor DSL using parser combinators on a languages like PHP. Why?

PHP is very far from being [homoiconic](https://en.wikipedia.org/wiki/Homoiconicity) and therefore requires complex deterministic parsing and a big AST implementation with a node visitor API to modify source code - and in the end, you're not even able to easily process unknown syntax `¯\_(⊙_ʖ⊙)_/¯`.

That's why this project was born. It was also part of the challenge:

1. Create a minimalistic architecture that exposes a subset of the internal components, that power the preprocessor itself, to the user DSL.
2. Create parser combinators with decent error reporting and grammar invalidation, because of 1

Copyright
---------

[](#copyright)

Copyright (c) 2015-\* Márcio Almada. Distributed under the terms of an MIT-style license. See LICENSE for details.

###  Health Score

40

—

FairBetter than 88% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity41

Moderate usage in the ecosystem

Community28

Small or concentrated contributor base

Maturity61

Established project with proven stability

 Bus Factor1

Top contributor holds 94% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~69 days

Total

8

Last Release

2876d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/e6b4f6d6d37192719fb4d4d03bbdb9e866972a2b6556da9967b10b35843809f1?d=identicon)[marcioAlmada](/maintainers/marcioAlmada)

---

Top Contributors

[![marcioAlmada](https://avatars.githubusercontent.com/u/227395?v=4)](https://github.com/marcioAlmada "marcioAlmada (328 commits)")[![carusogabriel](https://avatars.githubusercontent.com/u/16328050?v=4)](https://github.com/carusogabriel "carusogabriel (6 commits)")[![assertchris](https://avatars.githubusercontent.com/u/200609?v=4)](https://github.com/assertchris "assertchris (5 commits)")[![BackEndTea](https://avatars.githubusercontent.com/u/14289961?v=4)](https://github.com/BackEndTea "BackEndTea (4 commits)")[![michelezamuner](https://avatars.githubusercontent.com/u/13472063?v=4)](https://github.com/michelezamuner "michelezamuner (2 commits)")[![nunomaduro](https://avatars.githubusercontent.com/u/5457236?v=4)](https://github.com/nunomaduro "nunomaduro (2 commits)")[![Hywan](https://avatars.githubusercontent.com/u/946104?v=4)](https://github.com/Hywan "Hywan (1 commits)")[![SerafimArts](https://avatars.githubusercontent.com/u/2461257?v=4)](https://github.com/SerafimArts "SerafimArts (1 commits)")

---

Tags

macro-dslmacrosparser-combinatorspreprocessorlanguagesyntaxpre-processor

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/yay-yay/health.svg)

```
[![Health](https://phpackages.com/badges/yay-yay/health.svg)](https://phpackages.com/packages/yay-yay)
```

###  Alternatives

[phpoption/phpoption

Option Type for PHP

2.7k541.2M159](/packages/phpoption-phpoption)[scrivo/highlight.php

Server side syntax highlighter that supports 185 languages. It's a PHP port of highlight.js

71040.3M82](/packages/scrivo-highlightphp)[patrickschur/language-detection

A language detection library for PHP. Detects the language from a given text string.

8513.2M18](/packages/patrickschur-language-detection)[wapmorgan/morphos

A morphological solution for Russian and English language written completely in PHP. Provides classes to inflect personal names, geographical names, decline and pluralize nouns, generate cardinal and ordinal numerals, spell out money amounts and time.

8351.3M7](/packages/wapmorgan-morphos)[coderello/laravel-nova-lang

Language support for Laravel Nova.

4251.2M2](/packages/coderello-laravel-nova-lang)[snipe/banbuilder

Library to easily handle profanity filtering in PHP projects.

3371.5M3](/packages/snipe-banbuilder)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
