{"id":17272837,"url":"https://github.com/marcioalmada/yay","last_synced_at":"2025-05-16T03:05:17.029Z","repository":{"id":36761920,"uuid":"41068597","full_name":"marcioAlmada/yay","owner":"marcioAlmada","description":"Yay is a high level PHP preprocessor","archived":false,"fork":false,"pushed_at":"2024-01-31T15:54:40.000Z","size":507,"stargazers_count":573,"open_issues_count":13,"forks_count":35,"subscribers_count":21,"default_branch":"master","last_synced_at":"2025-05-16T03:05:10.948Z","etag":null,"topics":["macro-dsl","macros","parser-combinators","preprocessor"],"latest_commit_sha":null,"homepage":"https://github.com/marcioAlmada/yay","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/marcioAlmada.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-08-20T01:30:54.000Z","updated_at":"2025-02-23T20:31:05.000Z","dependencies_parsed_at":"2024-06-18T21:43:14.473Z","dependency_job_id":null,"html_url":"https://github.com/marcioAlmada/yay","commit_stats":{"total_commits":337,"total_committers":9,"mean_commits":37.44444444444444,"dds":0.05044510385756673,"last_synced_commit":"a647a0ff7932599e1cea144f056c8eb8f18f75fd"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcioAlmada%2Fyay","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcioAlmada%2Fyay/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcioAlmada%2Fyay/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcioAlmada%2Fyay/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marcioAlmada","download_url":"https://codeload.github.com/marcioAlmada/yay/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254459088,"owners_count":22074605,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["macro-dsl","macros","parser-combinators","preprocessor"],"created_at":"2024-10-15T08:49:36.719Z","updated_at":"2025-05-16T03:05:12.013Z","avatar_url":"https://github.com/marcioAlmada.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# YAY!\n\n[![Build Status](https://travis-ci.org/marcioAlmada/yay.svg?branch=master)](https://travis-ci.org/marcioAlmada/yay)\n[![Coverage Status](https://coveralls.io/repos/github/marcioAlmada/yay/badge.svg?branch=travis)](https://coveralls.io/github/marcioAlmada/yay?branch=travis)\n[![Latest Stable Version](https://poser.pugx.org/yay/yay/v/stable.png)](https://packagist.org/packages/yay/yay)\n[![Join the chat at https://gitter.im/marcioAlmada/yay](https://badges.gitter.im/marcioAlmada/yay.svg)](https://gitter.im/marcioAlmada/yay?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n[![License](https://poser.pugx.org/yay/yay/license.png)](https://packagist.org/packages/yay/yay)\n\n**YAY!** is a high level parser combinator based PHP preprocessor that allows anyone to augment PHP with PHP :boom:\n\nThis means that language features could be distributed as composer packages (as long as the macro based implementations\ncan be expressed in pure PHP code, and the implementation is fast enough).\n\n[Roadmap](https://github.com/marcioAlmada/yay/issues/3).\n\n## Set Up\n\n```bash\ncomposer require yay/yay:dev-master\n```\n\n## Usage\n\n### Command Line\n\n```\nyay some/file/with/macros.php \u003e\u003e target/file.php\n```\n\n### Runtime Mode\n\nThe \"runtime\" mode is W.I.P and will use stream wrappers along with composer integration in order\nto preprocess every file that gets included. It may have some opcache/cache support, so files will be\nonly preprocessed/expanded once and when needed.\n\nSee feature progress at issue [#11](https://github.com/marcioAlmada/yay/issues/11).\n\n## How it works\n\n### Very Simple Example\n\nEvery macro consist of a matcher and an expander that when executed allows you to augment PHP.\nConsider the simplest example possible:\n\n```php\n$(macro :unsafe) { $ } \u003e\u003e { $this } // this shorthand\n```\n\nThe macro is basically expanding a literal `$` token to `$this`. The following code would expand to:\n\n```php\n// source                                |   // expansion\nclass Foo {                              |   class Foo {\n    protected $a = 1, $b = 2, $c = 3;    |       protected $a = 1, $b = 2, $c = 3;\n                                         |        \n    function getProduct(): int {         |       function getProduct(): int {\n        return $-\u003ea * $-\u003eb * $-\u003ec;       |           return $this-\u003ea * $this-\u003eb *$this-\u003ec;\n    }                                    |       }\n}                                        |   }\n```\n\n\u003e Notice that the `:unsafe` tag is necessary to avoid macro hygiene on `$this` expansion.\n\nThis macro is actually very naive, a more producion ready version would be:\n\n\n```php\n$(macro :unsafe){\n    $ // litterally matches '$'\n    // but not followed by:\n    $(not(token(T_VARIABLE))) // avoids var var false positives such as '$$foo'\n    $(not(token('{'))) // avoids false positives such as '${foo}'\n} \u003e\u003e {\n    $this\n}\n```\n\n### Simple Example\n\nApart from literal characher sequences, it's also possible to match specific token types using the token matcher in\nthe form of `$(TOKEN_TYPE as label)`.\n\nThe following macro matches token sequences like `__swap($x, $y)` or `__swap($foo, $bar)`:\n\n```php\n$(macro) {\n    __swap ( $(T_VARIABLE as A) , $(T_VARIABLE as B) )\n} \u003e\u003e {\n    (list($(A), $(B)) = [$(B), $(A)])\n}\n```\n\nThe expansion should be pretty obvious:\n```php\n// source              |    // expansion\n__swap($foo, $bar);    |    (list($foo, $bar) = [$bar, $foo]); \n```\n\n### Another Simple Example\n\nTo implement `unless` we need to match the literal `unless` keyword followed by a layer of tokens between parentheses\n`(...)` and a block of code `{...}`. Fortunately, the macro DSL has a very straightforward layer matching construct:\n\n```php\n$(macro) {\n    unless ($(layer() as expression)) { $(layer() as body) }\n} \u003e\u003e {\n    if (! ($(expression))) {\n        $(body)\n    }\n}\n```\n\nThe macro in action:\n\n```php\n// source                   |   // expansion\nunless ($x === 1) {         |   if (! ($x === 1)) {\n    echo \"\\$x is not 1\";    |       echo \"\\$x is not 1\";\n}                           |   }\n```\n\n\u003e PS: Please don't implement \"unless\". This is here just for didactic reasons.\n\n### Advanced Example\n\nA more complex example could be porting enums from the future to PHP with a syntax like:\n\n```php\nenum Fruits {\n    Apple,\n    Orange\n}\n\nvar_dump(\\Fruits::Orange \u003c=\u003e \\Fruits::Apple);\n```\nSo, syntactically, enums are declared with the literal `enum` word followed by a `T_STRING` and a comma\nseparated list of identifiers withing braces such as `{A, B, C}`.\n\nYAY uses parser combinators internally for everything and these more high level parsers are fully\nexposed on macro declarations. Our enum macro will need high level matchers like `ls()` and `label()`\ncombined to match the desired syntax, like so:\n\n```php\n$(macro) {\n    enum $(T_STRING as name) {\n        $(\n            // ls() matches a delimited list\n            // in this case a list of label() delimited by ',' such as `foo, bar, baz`\n            ls\n            (\n                label() as field\n                ,\n                token(',')\n            )\n            as fields\n        )\n    }\n} \u003e\u003e {\n    \"it works\";\n}\n```\n\nThe macro is already capable to match the enum syntax:\n\n```php\n// source                      // expansion\nenum Order {ASC, DESC};    |   \"it works\";\n```\n\nI won't explain how enums are implemented, you can read the [RFC](https://wiki.php.net/rfc/enum) if you wish\nand then see how the expansion below works:\n\n```php\n// things here would normally be under a namespace, but since we want a concise example...\n\ninterface Enum\n{\n}\n\nfunction enum_field_or_class_constant(string $class, string $field)\n{\n    return (\\in_array(\\Enum::class, \\class_implements($class)) ? $class::$field() : \\constant(\"{$class}::{$field}\"));\n}\n\n$(macro :unsafe) {\n    // the enum declaration\n    enum $(T_STRING as name) {\n        $(\n            ls\n            (\n                label() as field\n                ,\n                token(',')\n            )\n            as fields\n        )\n    }\n} \u003e\u003e {\n    class $(name) implements Enum {\n        private static $registry;\n\n        private function __construct() {}\n\n        static function __callStatic(string $type, array $args) : self {\n            if(! self::$registry) {\n                self::$registry = new \\stdclass;\n                $(fields ... {\n                    self::$registry-\u003e$(field) = new class extends $(name) {};\n                })\n            }\n\n            if (isset(self::$registry-\u003e$type)) return self::$registry-\u003e$type;\n\n            throw new \\Exception(sprintf('Undefined enum type %s-\u003e%s', __CLASS__, $type));\n        }\n    }\n}\n\n$(macro) {\n    $(\n        // sequence that matches the enum field access syntax:\n        chain(\n            ns() as class, // matches a namespace\n            token(T_DOUBLE_COLON), // matches T_DOUBLE_COLON used for static access\n            not(class), // avoids matching `Foo::class`, class resolution syntax\n            label() as field, // matches the enum field name\n            not(token('(')) // avoids matching static method calls such as `Foo::bar()`\n        )\n    )\n} \u003e\u003e {\n    \\enum_field_or_class_constant($(class)::class, $$(stringify($(field))))\n}\n```\n\n\u003e More examples within the phpt tests folder https://github.com/marcioAlmada/yay/tree/master/tests/phpt\n\n# FAQ\n\n\u003e Why \"YAY!\"?\n\n\\- PHP with feature \"x\": yay or nay? :wink:\n\n\u003e Where is the documentation?\n\nA cookbook is on the making\n\n\u003e Why are you working on this?\n\nBecause it's being fun. It may become useful. [Because we can™](https://github.com/haskellcamargo/because-we-can).\n\n# Conclusion\n\nFor now this is an experiment about how to build a high level preprocessor DSL using parser combinators\non a languages like PHP. Why?\n\nPHP is very far from being [homoiconic](https://en.wikipedia.org/wiki/Homoiconicity) and therefore requires\ncomplex deterministic parsing and a big AST implementation with a node visitor API to modify source code - and\nin the end, you're not even able to easily process unknown syntax `¯\\_(⊙_ʖ⊙)_/¯`.\n\nThat's why this project was born. It was also part of the challenge:\n\n0. Create a minimalistic architecture that exposes a subset of the internal components, that power the preprocessor itself, to the user DSL.\n0. Create parser combinators with decent error reporting and grammar invalidation, because of 1\n\n## Copyright\n\nCopyright (c) 2015-* Márcio Almada. Distributed under the terms of an MIT-style license.\nSee LICENSE for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcioalmada%2Fyay","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarcioalmada%2Fyay","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcioalmada%2Fyay/lists"}