{"id":19994223,"url":"https://github.com/omranjamal/phraseengine","last_synced_at":"2025-05-04T13:31:03.677Z","repository":{"id":57323309,"uuid":"102288798","full_name":"omranjamal/PhraseEngine","owner":"omranjamal","description":"Language files on steroids for conversational UIs that aren't boring.","archived":false,"fork":false,"pushed_at":"2019-06-07T16:24:23.000Z","size":1459,"stargazers_count":25,"open_issues_count":5,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-09-30T23:12:40.636Z","etag":null,"topics":["conversational-ui","language-files","xml"],"latest_commit_sha":null,"homepage":"http://hedronium.github.io/PhraseEngine/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/omranjamal.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-09-03T19:25:50.000Z","updated_at":"2023-05-20T21:00:33.000Z","dependencies_parsed_at":"2022-09-21T01:03:54.157Z","dependency_job_id":null,"html_url":"https://github.com/omranjamal/PhraseEngine","commit_stats":null,"previous_names":["hedronium/phraseengine"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omranjamal%2FPhraseEngine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omranjamal%2FPhraseEngine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omranjamal%2FPhraseEngine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omranjamal%2FPhraseEngine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/omranjamal","download_url":"https://codeload.github.com/omranjamal/PhraseEngine/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224392276,"owners_count":17303660,"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":["conversational-ui","language-files","xml"],"created_at":"2024-11-13T04:54:24.245Z","updated_at":"2024-11-13T04:57:45.108Z","avatar_url":"https://github.com/omranjamal.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Phrase Engine Logo](https://hedronium.github.io/PhraseEngine/images/logo-%20only.png)\n\n\u003cbr\u003e\n\nUIs that say the same thing over and over again every time are just not fun and writing a few thousand sentences must suck. That leaves us with an AI that enslaves us all or **PhraseEngine**.\n\nWelcome to PhraseEngine's docs.\n\nYou can read the same Doc\nbut with pretty colors here: [PhraseEngine](http://hedronium.github.io/PhraseEngine/?gh)\n\nOr, you could try it out in your\nbrowser right now here: [Try PhraseEngine](http://hedronium.github.io/PhraseEngine/try.htm?gh)\n\n## Features\n\n- Familiar XML Syntax\n- Intuitive Tag Names\n- Smart Data Interpolation\n- Built in Randomization\n- Code Reuseability\n- Conditional Statements\n    - Basic Operators\n    - Selection\n    - Compound conditions\n    - Ability to act upon Internal State\n- Generators (for efficient sentence generation)\n- Helper Tags to workaround the limitations of XML\n- Chain Optimizer to Purge unused Nodes\n\n## Installation\n```BASH\nyarn add phrase-engine\n```\n\nOr if you're not using yarn (which you should be, it's awesome):\n```BASH\nnpm install phrase-engine --save\n```\nThat's it. You're ready to go. \n\n## Getting Started\nLet's compile a PhraseScript and generate exactly one sentence.\n\n```Javascript\nimport PhraseEngine from 'phrase-engine';\n\nconst engine = PhraseEngine.compile(`\n    \u003csentence\u003e\n        I like Trains.\n    \u003c/sentence\u003e\n`);\n\nconsole.log(engine.random()); // I like Trains.\n```\n\nOkay, now let's do two sentences.\n\n```XML\n\u003csentence\u003e\n    I \u003cmaybe\u003ereally\u003c/maybe\u003e like Trains.\n\u003c/sentence\u003e\n```\n\n```Javascript\nconsole.log(engine.random());\n```\nthe output will be either one of the following sentences.\n```\nI like trains.\nI really like trains.\n```\n\n## Nesting\nAll tags support nesting so you can mix and match as you please. To give an example\nlet's put `\u003cmaybe/\u003e` inside another `\u003cmaybe/\u003e` for kicks.\n\n```XML\n\u003csentence\u003e\n    I\n    \u003cmaybe\u003e\n    \treally \u003cmaybe\u003edo\u003c/maybe\u003e\n    \u003c/maybe\u003e\n    like Trains.\n\u003c/sentence\u003e\n```\nThis is totally valid. Use this often.\n\n## Generating\nthen let's get a list of all the sentences with `engine.iterate()`\n\n```JS\nconst engine = PhraseEngine.compile(xml_string);\n\n// .iterate() returns a generator.\n// console.log() on the generator won't be useful.\n\nfor (let sentence of engine.iterate()) { \n\tconsole.log(sentence);\n}\n\n```\nOutput:\n```\nI like Trains.\nI really like Trains.\nI really do like Trains.\n```\n\n`.iterate()` returns a generator in order to be efficient. In order to convert\nit into an array you could use ES6 syntax like...\n\n```JS\nlet array_of_sens = [...engine.iterate()];\n\n// or \nlet array_of_sens = Array(engine.iterate());\n```\n\n\n## Tags\n\n### \u0026lt;maybe/\u0026gt;\nYou've met this tag before.\n\n```XML\n\u003csentence\u003e\n    I \u003cmaybe\u003ereally\u003c/maybe\u003e like Trains.\n\u003c/sentence\u003e\n```\n\noutput...\n\n```Markdown\nI like Trains.\nI really like Trains.\n```\n\n### \u0026lt;either/\u0026gt;\n\nSo we know how to branch off into two paths, one with a few characters\nand another without. But what if we wanna select from a few different\nchoices at random?\n\n```XML\n\u003csentence\u003e\n    I\n    \u003cmaybe\u003ereally \u003cmaybe\u003edo\u003c/maybe\u003e\u003c/maybe\u003e\n    \u003ceither\u003e\n    \t\u003cthis\u003elike\u003c/this\u003e\n        \u003cor\u003elove\u003c/or\u003e\n    \u003c/either\u003e\n    Trains.\n\u003c/sentence\u003e\n```\n\nOutput:\n\n```\nI really do love Trains.\nI really do like Trains.\nI really love Trains.\nI really like Trains.\nI love Trains.\nI like Trains.\n```\n\n\u003e `\u003cthis/\u003e` and `\u003cor/\u003e` are actually aliases so\n\u003e you can use either one interchangeably. They exist\n\u003e to provide semantic value.\n\nNesting is possible here too!\n\n```XML\n\u003csentence\u003e\n    I\n    \u003ceither\u003e\n    \t\u003cthis\u003elike\u003c/this\u003e\n        \u003cor\u003e\n        \t\u003ceither\u003e\n            \t\u003cor\u003elove\u003c/or\u003e\n                \u003cor\u003e\n                    have a\n                    \u003ceither\u003e\n                    \t\u003cor\u003elove-hate\u003c/or\u003e\n                        \u003cor\u003elovely\u003c/or\u003e\n                        \u003cor\u003egood\u003c/or\u003e\n                    \u003c/either\u003e\n                    relationship with\n                \u003c/or\u003e\n            \u003c/either\u003e\n        \u003c/or\u003e\n    \u003c/either\u003e\n    Trains.\n\u003c/sentence\u003e\n```\n\noutput:\n\n```Markdown\nI have a good relationship with Trains.\nI have a lovely relationship with Trains.\nI have a love-hate relationship with Trains.\nI love Trains.\nI like Trains.\n```\n\n### \u0026lt;text/\u0026gt;\nIf you think naked text in XML is ugly. We can't blame you.\nThat is why a completely inert tag called `\u003ctext/\u003e`\nhas been included.\n\n```XML\n\u003csentence\u003e\n    \u003ctext\u003eI\u003c/text\u003e\n    \u003cmaybe\u003ereally \u003cmaybe\u003edo\u003c/maybe\u003e\u003c/maybe\u003e\n    \u003ceither\u003e\n    \t\u003cthis\u003elike\u003c/this\u003e\n        \u003cor\u003elove\u003c/or\u003e\n    \u003c/either\u003e\n    \u003ctext\u003eTrains.\u003c/text\u003e\n\u003c/sentence\u003e\n```\n\nIt has other uses when used with `\u003cref/\u003e` or `\u003cif/\u003e`\nbut we'll get to that later in this document.\n\n###  \u0026lt;spaceless/\u0026gt;\nDon't you hate it when adding a line break adds a space? `\u003cspaceless/\u003e` solves that.\n\n```XML\n\u003csentence\u003e\n    Me. Trians.\n    \u003ceither\u003e\n        \u003cthis\u003e\n            \u003ctext\u003eTo\u003c/text\u003e\n            \u003ctext\u003egether\u003c/text\u003e\n        \u003c/this\u003e\n        \u003cor\u003e\n            \u003cspaceless\u003e\n                \u003ctext\u003eTo\u003c/text\u003e\n                \u003ctext\u003egether\u003c/text\u003e\n            \u003c/spaceless\u003e\n        \u003c/or\u003e\n    \u003c/either\u003e\n    forever.\n\u003c/sentence\u003e\n```\n\n```\nMe. Trians. Together forever.\nMe. Trians. To gether forever.\n```\nTadda!\n\n###  \u0026lt;ref/\u0026gt;\nBut isn't writing the same tags over and over again a pain? especially\nif the blocks are large? Habe no fear, `\u003cref/\u003e` tags are here.\n\n```XML\n\u003csentence\u003e\n    I\n    \u003ceither id=\"affection\"\u003e\n        \u003cthis\u003elove\u003c/this\u003e\n        \u003cor\u003elike\u003c/or\u003e\n    \u003c/either\u003e\n    Trains. My cousin\n    \u003cref id=\"affection\" /\u003es\n    then too.\n\u003c/sentence\u003e\n```\nOutput:\n```Markdown\nI like Trains. My cousin likes then too.\nI like Trains. My cousin loves then too.\nI love Trains. My cousin likes then too.\nI love Trains. My cousin loves then too.\n```\nThis tag is PhraseEngine's solution to the DRY approach. It enables code reuse.\n\n\nAll you have to do is put an `id` attribute on a tag you want to reuse. Then create a `\u003cref/\u003e` tag with the same attribute where you wanna reuse it. \n\n\u003e It supports every tag except `\u003cor\u003e`, `\u003cthis\u003e`, `\u003cthen\u003e`, `\u003celse\u003e`\n\nAnother example:\n```XML\n\u003csentence\u003e\n    Omran\n    \u003ctext id=\"likes\"\u003e\n        \u003cmaybe\u003ereally \u003c/maybe\u003e likes trains\n    \u003c/text\u003e.\n    Narmo \u003cref id=\"likes\"/\u003e too.\n\u003c/sentence\u003e\n```\n\n```Markdown\nOmran really likes trains. Narmo really likes trains too.\nOmran really likes trains. Narmo likes trains too.\nOmran likes trains. Narmo really likes trains too.\nOmran likes trains. Narmo likes trains too.\n```\n\n### \u0026lt;data/\u0026gt;\n\nLet's mix info from the outside world into the sentences!\n\n```XML\n\u003csentence\u003e\n    \u003cdata key=\"first_name\"/\u003e\n    \u003cmaybe\u003ereally\u003c/maybe\u003e\n    likes trains.\n\u003c/sentence\u003e\n```\n\n```JS\nconst engine = PhraseEngine.compile(xml_string);\nconst sentences = engine.iterate({\n\tfirst_name: \"Omran\"\n});\n\nfor (let sentence of sentences) { \n\tconsole.log(sentence);\n}\n```\nYeah just send the data in as a Javascript object into the `.iterate()` method\nor `.random()` method.\n\nOutput:\n```\nOmran really likes trains.\nOmran likes trains.\n```\n\n\n#### with fallbacks\nImagine this scenario: You want to write a language file that says `\"Mr [x] likes trains.\"` you want to refer to them by their last name and only default to the first name if for some reason the last name isn't known.\n\n```XML\n\u003csentence\u003e\n    Mr.\n    \u003cdata key=\"last_name|first_name\"/\u003e\n    \u003cmaybe\u003ereally\u003c/maybe\u003e\n    likes trains.\n\u003c/sentence\u003e\n```\n\nif data is:\n```JS\n{\n\tfirst_name: \"Omran\",\n    last_name: \"Jamal\"\n}\n```\n\noutput will be:\n```Markdown\nMr. Jamal really likes trains.\nMr. Jamal likes trains.\n```\n\nif only first name is available:\n```JS\n{\n\tfirst_name: \"Omran\"\n}\n```\n\n```Markdown\nMr. Omran really likes trains.\nMr. Omran likes trains.\n```\n\n### \u0026lt;if/\u0026gt;\nIt's time to make the tough decisions in life. How to make sure your language files address Sir Lancelot as 'Sir' and not 'Mr'.\n\nLet's set the flag in our data.\n```JS\n{\n\tsir: true,\n    name: \"Lancelot\"\n}\n```\n```XML\n\u003csentence\u003e\n    \u003cif condition=\"sir\"\u003e\n        \u003cthen\u003eSir\u003c/then\u003e\n        \u003celse\u003eMr.\u003c/else\u003e\n    \u003c/if\u003e\n    \u003cdata key=\"name\"/\u003e\n    \u003cmaybe\u003ereally\u003c/maybe\u003e likes Trains.\n\u003c/sentence\u003e\n```\n\noutput:\n```Markdown\nSir Lancelot likes Trains.\nSir Lancelot really likes Trains.\n```\n\nlet's try that with `sir` as `false`\n```JS\n{\n\tsir: false,\n    name: \"Omran\"\n}\n```\n```Markdown\nMr. Omran likes Trains.\nMr. Omran really likes Trains.\n```\n\nIf statements only support booleans, and no support for comparison operators for now, this may change in the future but we don't see much of a use for\ncomparison operators at the moment. We don't intend to replace Javascript just supplement it.\n\nIf you think we're stupid, we probably are! Send us a pull request! Show us how it's done!\n\n#### Strings?\nOkay, booleans are easily resolved, what about strings?\nAll strings, even empty ones are truthy.\n```JS\n{\n    name: \"Omran\"\n}\n```\n```XML\n\u003csentence\u003e\n    \u003cif condition=\"name\"\u003e\n        \u003cthen\u003e\n            \u003cdata key=\"name\"/\u003e\n        \u003c/then\u003e\n        \u003celse\u003eHe\u003c/else\u003e\n    \u003c/if\u003e\n    likes Trains.\n\u003c/sentence\u003e\n```\n```Markdown\nOmran likes Trains.\n```\n\n\n#### What is `false`?\n\n`\u003cif/\u003e` actually fails silently by considering keys that don't exist as\n`false`. For example if we don't include `sir` at all...\n\n```JS\n{\n    name: \"Omran\"\n}\n```\n```Markdown\nMr. Omran likes Trains.\nMr. Omran really likes Trains.\n```\n\n#### Implicit \u0026lt;if/\u0026gt;\nWriting a `\u003cthen\u003e` or an `\u003celse\u003e` or both, is a tedious task, that is why\nimplicit `\u003cif/\u003e`s are a thing.\n\n```JS\n{\n\tsir: true,\n\tname: \"Lacelot\"\n}\n```\n```XML\n\u003csentence\u003e\n    \u003cif condition=\"sir\"\u003eSir\u003c/if\u003e\n    \u003cdata key=\"name\"/\u003e\n    likes Trains.\n\u003c/sentence\u003e\n```\n```Markdown\nSir Lancelot likes Trains.\n```\n\n#### Negating \u0026lt;if/\u0026gt;\nOkay there are three ways to negate an `\u003cif/\u003e`\n\n##### NOT Operator\n```JS\n{\n\tsir: false,\n\tname: \"Omran\"\n}\n```\n```XML\n\u003csentence\u003e\n    \u003cif condition=\"!sir\"\u003eMr.\u003c/if\u003e\n    \u003cdata key=\"name\"/\u003e\n    likes Trains.\n\u003c/sentence\u003e\n```\n```Markdown\nMr. Omran likes Trains.\n```\nHow the not operator works and how much you can do with it, will be discussed later in this document.\n\n##### \u0026lt;else/\u0026gt; only\n```JS\n{\n\tsir: false,\n\tname: \"Omran\"\n}\n```\n```XML\n\u003csentence\u003e\n    \u003cif condition=\"sir\"\u003e\n    \t\u003celse\u003eMr.\u003c/else\u003e\n    \u003c/if\u003e\n    \u003cdata key=\"name\"/\u003e\n    likes Trains.\n\u003c/sentence\u003e\n```\n```\nMr. Omran likes Trains.\n```\n\n##### \u0026lt;unless/\u0026gt; tag\n```JS\n{\n\tsir: false,\n\tname: \"Omran\"\n}\n```\n```XML\n\u003csentence\u003e\n    \u003cunless condition=\"sir\"\u003eMr.\u003c/unless\u003e\n    \u003cdata key=\"name\"/\u003e\n    likes Trains.\n\u003c/sentence\u003e\n```\n```\nMr. Omran likes Trains.\n```\nThe `\u003cunless/\u003e` tag is actually an alias (with negative logic) for `\u003cif/\u003e` that means unless supports `\u003cthen/\u003e`s and `\u003celse/\u003e`s',\n\n#### Compound Conditions\nSo in the previous section you got familiar with the `NOT` operator `!`.\nLet's see a list of what other operators `\u003cif/\u003e` has.\n\n| Name     | Operator  | Example Use                              |\n|----------|-----------|------------------------------------------|\n| NOT      | !         | condition=\"!sir\"                        |\n| AND      | \u0026         | condition=\"female \u0026 !married\"            |\n| OR       | \u0026#124;    | condition=\"engineer \u0026#124; scientist\"    |\n| BRACKETs | ( )       | condition=\"(a \u0026 !b) \u0026#124; (!a \u0026 b)\"     |\n\nLet's see them in action.\n\n```XML\n\u003csentence\u003e\n    \u003ctext\u003e\n        \u003cif condition=\"female \u0026 married\"\u003eMrs.\u003c/if\u003e\n        \u003cif condition=\"female \u0026 !married\"\u003eMs.\u003c/if\u003e\n        \u003cif condition=\"!female\"\u003eMr.\u003c/if\u003e\n    \u003c/text\u003e\n    \u003cdata key=\"last_name\"/\u003e\n    likes Trains.\n\u003c/sentence\u003e\n```\n```JS\n{\n    \"female\": true,\n    \"married\": true,\n\t\"last_name\": \"Jamal\"\n}\n```\n```Markdown\nMrs. Jamal likes Trains.\n```\n\n#### Internal State Conditions\nWouldn't it be great if we could act upon the state of another part of a sentence?\n\nConsider these two sentences:\n\n`They love trains.`  \n`She loves trains.`\n\nYou see the `s` in `loves` after `They` but not `She`? That can be achieved completely in PhraseEngine.\n\n##### IDs\nIt can act on IDs if a certain Id has been rendered. It evaluated to true. For example if the ID is `tomato` then `#tomato` in `\u003cif/\u003e` condition translates to `true`\n\n```XML\n\u003csentence\u003e\n    \u003ceither\u003e\n        \u003cthis id=\"they\"\u003eThey\u003c/this\u003e\n        \u003cthis id=\"she\"\u003eShe\u003c/this\u003e\n        \u003cthis id=\"he\"\u003eHe\u003c/this\u003e\n        \u003cthis id=\"self\"\u003eI\u003c/this\u003e\n    \u003c/either\u003e\n    \u003cspaceless\u003e\n        \u003ctext\u003elove\u003c/text\u003e\n        \u003cunless condition=\"#they | #self\"\u003es\u003c/unless\u003e\n    \u003c/spaceless\u003e\n    Trains.\n\u003c/sentence\u003e\n```\n```Markdown\nI love Trains.\nHe loves Trains.\nShe loves Trains.\nThey love Trains.\n```\nMAGIC.\n\n##### Classes\nClasses work the same way but the condition variable has to be prefixed with `.` like `.fruit`. For example if the class is `tomato` then `.tomato` in `\u003cif/\u003e` condition translates to `true`\n\n```XML\n\u003csentence\u003e\n    \u003ceither\u003e\n        \u003cthis class=\"singular\"\u003eThey\u003c/this\u003e\n        \u003cthis\u003eShe\u003c/this\u003e\n        \u003cthis\u003eHe\u003c/this\u003e\n        \u003cthis class=\"singular\"\u003eI\u003c/this\u003e\n    \u003c/either\u003e\n    \u003cspaceless\u003e\n        \u003ctext\u003elove\u003c/text\u003e\n        \u003cunless condition=\".singular\"\u003es\u003c/unless\u003e\n    \u003c/spaceless\u003e\n    Trains.\n\u003c/sentence\u003e\n```\n```Markdown\nI love Trains.\nHe loves Trains.\nShe loves Trains.\nThey love Trains.\n```\nMAGIC AGAIN.\n\n\u003e A tag can hace multiple classes. Just like HTML\n\u003e it works by space separating the classes like\n\u003e `\"food fruit tomato\"`\n\n### \u0026lt;select/\u0026gt;\nOkay writing a large block of if conditions suck. How about something like a `switch/case` statement?\n\n```XML\n\u003csentence\u003e\n    My\n    \u003cselect key=\"pet_type\"\u003e\n        \u003cfor value=\"canine\"\u003edog\u003c/for\u003e\n        \u003cfor value=\"feline\"\u003ecat\u003c/for\u003e\n        \u003cdefault\u003epet\u003c/default\u003e\n    \u003c/select\u003e\n    \u003cmaybe\u003ereally\u003c/maybe\u003e\n    likes Trains.\n\u003c/sentence\u003e\n```\n```JS\n{\n\tpet_type: \"feline\"\n}\n```\n```\nMy cat really likes Trains.\nMy cat likes Trains.\n```\n\ndefault example:\n\n```JS\n{\n\tpet_type: \"bovine\"\n}\n```\n```\nMy pet really likes Trains.\nMy pet likes Trains.\n```\n\nThe select statement does **NOT** fail silently. It will throw an error \nwhen the key it switches is not foundind data.\n\n### \u0026lt;br/\u0026gt;\nEvery needs line breaks.\n\n```XML\n\u003csentence\u003e\n    My Trains \u003cbr\u003e Hurt.\n\u003c/sentence\u003e\n```\noutput:\n```\nMy Trains\nHurt\n```\n\n## Misc\n### List Variables\nIt is sometimes useful to be able to get a list\nof all the variables that the language files use.\n\nFor that the `.vars()` method is included on a compiled\nengine.\n\n```JS\nconst engine = PhraseEngine.compile(`\n    \u003csentence\u003e\n        \u003cdata key=\"name\"/\u003e's\n        \u003cselect key=\"pet_type\"\u003e\n            \u003cfor value=\"feline\"\u003ecat\u003c/for\u003e\n            \u003cfor value=\"canine\"\u003edog\u003c/for\u003e\n        \u003c/select\u003e\n        likes Trains.\n    \u003c/sentence\u003e\n`);\n\nconsole.log(\n    engine.vars()\n);\n```\n\nthe output should be something like...\n\n```JS\n{\n    vars: {\n        name: [\n            {\n                type: \"string\",\n                last: true\n            }\n        ],\n        pet_type: [\n            {\n                type: \"enum\",\n                values: [\n                    \"feline\",\n                    \"canine\"\n                ]\n            }\n        ]\n    }\n}\n```\n\nYes, when it encounters a key on a `\u003cselect/\u003e` tag,\nit grabs all the values in the `\u003cfor/\u003e` tags under it\nand labels the variable to be a `enum`.\n\nIn the case of `\u003cdata/\u003e` tags the keys are labeled as `string` types\nand the last fallback has the extra `last: true` property to signify\n\"if this is not defined there's a chance an Exception will be thrown\"\n\nTo formally define the return type we should show you \nthe Typescript interfaces we used.\n\n```Typescript\ninterface StringVar {\n    type: 'string';\n    last: boolean;\n}\n\ninterface EnumVar {\n    type: 'enum';\n    values: string[];\n}\n\ninterface BooleanVar {\n    type: 'boolean';\n}\n\ntype VarType = StringVar | EnumVar | BooleanVar;\n\ninterface VarsPacket {\n    vars: {\n        [key: string]: Array\u003cVarType\u003e;\n    };\n}\n```\n\nIt happens to be an array because it is entirely possible for a key\nto be used for `\u003cdata/\u003e`, `\u003cselect/\u003e`, and even `\u003cif/\u003e` all in the same\nPhraseScript file.\n\n\n### Count Paths\nIt is also useful to know the number of paths sometimes.\n`.count(data)` helps with that.\n\n```JS\nconst engine = PhraseEngine.compile(`\n    \u003csentence\u003e\n        I \u003cmaybe\u003ereally\u003c/maybe\u003e likes Trains.\n    \u003c/sentence\u003e\n`);\n\nengine.count() // 3\n```\n\n\u003cp class=\"warn\"\u003e\u003c/p\u003e\n\u003e Using count is actually a very bad idea because\n\u003e it calculates that count by traveling every path in\n\u003e the PhraseScript, which happens to be a very expensive\n\u003e operation. We highly advice against using it.   \n\u003e    \n\u003e **Reason**: We're actually trying to solve this issue\n\u003e and making this operation more efficient\n\u003e but our attempts with caching and a few other methods\n\u003e fail thanks to PhraseEngine's ability to act upon\n\u003e Internal State. \n\nYou are much better off, counting and maybe caching the output\nfrom the generator.\n\n\n## Why?\nWe actually develop and maintain a chat bot called\n[Blood Bot](https://bloodbot.org). So, a few months ago\nwe decided we should vary our dumb bot's responses, but we were\nfaced with a dillema.\n\n**A:** We were too stupid to build full fledged AIs.  \n**B:** We didn't want to write a thousand sentences, 'cause we're also lazy.\n\nSo we did what every noob programmer does. Apply tons of Javascript.\nIt was all fun and sunshines until our language files started to grow.\nEvery branch of a sentence doubled our Javascript code. Every time we needed\nto conditionally render based on internal state, out code almost tripled.\nEven with the really nice helper functions and classes we built\nfor ourselves, it was a nightmare to maintain. Our language files had BUGs.\nGo figure.\n\nAs our Bot mostly operates in Bangladesh we needed to translate our language files to Bengali. Where do we even begin to talk about translators.\nEven the techsavy-est of translators failed to translate our\nEnglish language files.\n\nSo we thought, \"eyy, what would happen if we hooked up an XML parser to\nsome magical JS code and made a programming-ish language that was easy\nand familiar to programmers and translators alike.\"\n\nThree Months Later: PhraseEngine is Born.\n\n## License\n**MIT**. Go crazy.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fomranjamal%2Fphraseengine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fomranjamal%2Fphraseengine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fomranjamal%2Fphraseengine/lists"}