Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/omranjamal/phraseengine

Language files on steroids for conversational UIs that aren't boring.
https://github.com/omranjamal/phraseengine

conversational-ui language-files xml

Last synced: 2 months ago
JSON representation

Language files on steroids for conversational UIs that aren't boring.

Awesome Lists containing this project

README

        

![Phrase Engine Logo](https://hedronium.github.io/PhraseEngine/images/logo-%20only.png)


UIs 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**.

Welcome to PhraseEngine's docs.

You can read the same Doc
but with pretty colors here: [PhraseEngine](http://hedronium.github.io/PhraseEngine/?gh)

Or, you could try it out in your
browser right now here: [Try PhraseEngine](http://hedronium.github.io/PhraseEngine/try.htm?gh)

## Features

- Familiar XML Syntax
- Intuitive Tag Names
- Smart Data Interpolation
- Built in Randomization
- Code Reuseability
- Conditional Statements
- Basic Operators
- Selection
- Compound conditions
- Ability to act upon Internal State
- Generators (for efficient sentence generation)
- Helper Tags to workaround the limitations of XML
- Chain Optimizer to Purge unused Nodes

## Installation
```BASH
yarn add phrase-engine
```

Or if you're not using yarn (which you should be, it's awesome):
```BASH
npm install phrase-engine --save
```
That's it. You're ready to go.

## Getting Started
Let's compile a PhraseScript and generate exactly one sentence.

```Javascript
import PhraseEngine from 'phrase-engine';

const engine = PhraseEngine.compile(`

I like Trains.

`);

console.log(engine.random()); // I like Trains.
```

Okay, now let's do two sentences.

```XML

I really like Trains.

```

```Javascript
console.log(engine.random());
```
the output will be either one of the following sentences.
```
I like trains.
I really like trains.
```

## Nesting
All tags support nesting so you can mix and match as you please. To give an example
let's put `` inside another `` for kicks.

```XML

I

really do

like Trains.

```
This is totally valid. Use this often.

## Generating
then let's get a list of all the sentences with `engine.iterate()`

```JS
const engine = PhraseEngine.compile(xml_string);

// .iterate() returns a generator.
// console.log() on the generator won't be useful.

for (let sentence of engine.iterate()) {
console.log(sentence);
}

```
Output:
```
I like Trains.
I really like Trains.
I really do like Trains.
```

`.iterate()` returns a generator in order to be efficient. In order to convert
it into an array you could use ES6 syntax like...

```JS
let array_of_sens = [...engine.iterate()];

// or
let array_of_sens = Array(engine.iterate());
```

## Tags

### <maybe/>
You've met this tag before.

```XML

I really like Trains.

```

output...

```Markdown
I like Trains.
I really like Trains.
```

### <either/>

So we know how to branch off into two paths, one with a few characters
and another without. But what if we wanna select from a few different
choices at random?

```XML

I
really do

like
love

Trains.

```

Output:

```
I really do love Trains.
I really do like Trains.
I really love Trains.
I really like Trains.
I love Trains.
I like Trains.
```

> `` and `` are actually aliases so
> you can use either one interchangeably. They exist
> to provide semantic value.

Nesting is possible here too!

```XML

I

like


love

have a

love-hate
lovely
good

relationship with




Trains.

```

output:

```Markdown
I have a good relationship with Trains.
I have a lovely relationship with Trains.
I have a love-hate relationship with Trains.
I love Trains.
I like Trains.
```

### <text/>
If you think naked text in XML is ugly. We can't blame you.
That is why a completely inert tag called ``
has been included.

```XML

I
really do

like
love

Trains.

```

It has other uses when used with `` or ``
but we'll get to that later in this document.

### <spaceless/>
Don't you hate it when adding a line break adds a space? `` solves that.

```XML

Me. Trians.


To
gether



To
gether



forever.

```

```
Me. Trians. Together forever.
Me. Trians. To gether forever.
```
Tadda!

### <ref/>
But isn't writing the same tags over and over again a pain? especially
if the blocks are large? Habe no fear, `` tags are here.

```XML

I

love
like

Trains. My cousin
s
then too.

```
Output:
```Markdown
I like Trains. My cousin likes then too.
I like Trains. My cousin loves then too.
I love Trains. My cousin likes then too.
I love Trains. My cousin loves then too.
```
This tag is PhraseEngine's solution to the DRY approach. It enables code reuse.

All you have to do is put an `id` attribute on a tag you want to reuse. Then create a `` tag with the same attribute where you wanna reuse it.

> It supports every tag except ``, ``, ``, ``

Another example:
```XML

Omran

really likes trains
.
Narmo too.

```

```Markdown
Omran really likes trains. Narmo really likes trains too.
Omran really likes trains. Narmo likes trains too.
Omran likes trains. Narmo really likes trains too.
Omran likes trains. Narmo likes trains too.
```

### <data/>

Let's mix info from the outside world into the sentences!

```XML


really
likes trains.

```

```JS
const engine = PhraseEngine.compile(xml_string);
const sentences = engine.iterate({
first_name: "Omran"
});

for (let sentence of sentences) {
console.log(sentence);
}
```
Yeah just send the data in as a Javascript object into the `.iterate()` method
or `.random()` method.

Output:
```
Omran really likes trains.
Omran likes trains.
```

#### with fallbacks
Imagine 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.

```XML

Mr.

really
likes trains.

```

if data is:
```JS
{
first_name: "Omran",
last_name: "Jamal"
}
```

output will be:
```Markdown
Mr. Jamal really likes trains.
Mr. Jamal likes trains.
```

if only first name is available:
```JS
{
first_name: "Omran"
}
```

```Markdown
Mr. Omran really likes trains.
Mr. Omran likes trains.
```

### <if/>
It's time to make the tough decisions in life. How to make sure your language files address Sir Lancelot as 'Sir' and not 'Mr'.

Let's set the flag in our data.
```JS
{
sir: true,
name: "Lancelot"
}
```
```XML


Sir
Mr.


really likes Trains.

```

output:
```Markdown
Sir Lancelot likes Trains.
Sir Lancelot really likes Trains.
```

let's try that with `sir` as `false`
```JS
{
sir: false,
name: "Omran"
}
```
```Markdown
Mr. Omran likes Trains.
Mr. Omran really likes Trains.
```

If 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
comparison operators at the moment. We don't intend to replace Javascript just supplement it.

If you think we're stupid, we probably are! Send us a pull request! Show us how it's done!

#### Strings?
Okay, booleans are easily resolved, what about strings?
All strings, even empty ones are truthy.
```JS
{
name: "Omran"
}
```
```XML





He

likes Trains.

```
```Markdown
Omran likes Trains.
```

#### What is `false`?

`` actually fails silently by considering keys that don't exist as
`false`. For example if we don't include `sir` at all...

```JS
{
name: "Omran"
}
```
```Markdown
Mr. Omran likes Trains.
Mr. Omran really likes Trains.
```

#### Implicit <if/>
Writing a `` or an `` or both, is a tedious task, that is why
implicit ``s are a thing.

```JS
{
sir: true,
name: "Lacelot"
}
```
```XML

Sir

likes Trains.

```
```Markdown
Sir Lancelot likes Trains.
```

#### Negating <if/>
Okay there are three ways to negate an ``

##### NOT Operator
```JS
{
sir: false,
name: "Omran"
}
```
```XML

Mr.

likes Trains.

```
```Markdown
Mr. Omran likes Trains.
```
How the not operator works and how much you can do with it, will be discussed later in this document.

##### <else/> only
```JS
{
sir: false,
name: "Omran"
}
```
```XML


Mr.


likes Trains.

```
```
Mr. Omran likes Trains.
```

##### <unless/> tag
```JS
{
sir: false,
name: "Omran"
}
```
```XML

Mr.

likes Trains.

```
```
Mr. Omran likes Trains.
```
The `` tag is actually an alias (with negative logic) for `` that means unless supports ``s and ``s',

#### Compound Conditions
So in the previous section you got familiar with the `NOT` operator `!`.
Let's see a list of what other operators `` has.

| Name | Operator | Example Use |
|----------|-----------|------------------------------------------|
| NOT | ! | condition="!sir" |
| AND | & | condition="female & !married" |
| OR | | | condition="engineer | scientist" |
| BRACKETs | ( ) | condition="(a & !b) | (!a & b)" |

Let's see them in action.

```XML


Mrs.
Ms.
Mr.


likes Trains.

```
```JS
{
"female": true,
"married": true,
"last_name": "Jamal"
}
```
```Markdown
Mrs. Jamal likes Trains.
```

#### Internal State Conditions
Wouldn't it be great if we could act upon the state of another part of a sentence?

Consider these two sentences:

`They love trains.`
`She loves trains.`

You see the `s` in `loves` after `They` but not `She`? That can be achieved completely in PhraseEngine.

##### IDs
It 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 `` condition translates to `true`

```XML


They
She
He
I


love
s

Trains.

```
```Markdown
I love Trains.
He loves Trains.
She loves Trains.
They love Trains.
```
MAGIC.

##### Classes
Classes 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 `` condition translates to `true`

```XML


They
She
He
I


love
s

Trains.

```
```Markdown
I love Trains.
He loves Trains.
She loves Trains.
They love Trains.
```
MAGIC AGAIN.

> A tag can hace multiple classes. Just like HTML
> it works by space separating the classes like
> `"food fruit tomato"`

### <select/>
Okay writing a large block of if conditions suck. How about something like a `switch/case` statement?

```XML

My

dog
cat
pet

really
likes Trains.

```
```JS
{
pet_type: "feline"
}
```
```
My cat really likes Trains.
My cat likes Trains.
```

default example:

```JS
{
pet_type: "bovine"
}
```
```
My pet really likes Trains.
My pet likes Trains.
```

The select statement does **NOT** fail silently. It will throw an error
when the key it switches is not foundind data.

### <br/>
Every needs line breaks.

```XML

My Trains
Hurt.

```
output:
```
My Trains
Hurt
```

## Misc
### List Variables
It is sometimes useful to be able to get a list
of all the variables that the language files use.

For that the `.vars()` method is included on a compiled
engine.

```JS
const engine = PhraseEngine.compile(`

's

cat
dog

likes Trains.

`);

console.log(
engine.vars()
);
```

the output should be something like...

```JS
{
vars: {
name: [
{
type: "string",
last: true
}
],
pet_type: [
{
type: "enum",
values: [
"feline",
"canine"
]
}
]
}
}
```

Yes, when it encounters a key on a `` tag,
it grabs all the values in the `` tags under it
and labels the variable to be a `enum`.

In the case of `` tags the keys are labeled as `string` types
and the last fallback has the extra `last: true` property to signify
"if this is not defined there's a chance an Exception will be thrown"

To formally define the return type we should show you
the Typescript interfaces we used.

```Typescript
interface StringVar {
type: 'string';
last: boolean;
}

interface EnumVar {
type: 'enum';
values: string[];
}

interface BooleanVar {
type: 'boolean';
}

type VarType = StringVar | EnumVar | BooleanVar;

interface VarsPacket {
vars: {
[key: string]: Array;
};
}
```

It happens to be an array because it is entirely possible for a key
to be used for ``, ``, and even `` all in the same
PhraseScript file.

### Count Paths
It is also useful to know the number of paths sometimes.
`.count(data)` helps with that.

```JS
const engine = PhraseEngine.compile(`

I really likes Trains.

`);

engine.count() // 3
```


> Using count is actually a very bad idea because
> it calculates that count by traveling every path in
> the PhraseScript, which happens to be a very expensive
> operation. We highly advice against using it.
>
> **Reason**: We're actually trying to solve this issue
> and making this operation more efficient
> but our attempts with caching and a few other methods
> fail thanks to PhraseEngine's ability to act upon
> Internal State.

You are much better off, counting and maybe caching the output
from the generator.

## Why?
We actually develop and maintain a chat bot called
[Blood Bot](https://bloodbot.org). So, a few months ago
we decided we should vary our dumb bot's responses, but we were
faced with a dillema.

**A:** We were too stupid to build full fledged AIs.
**B:** We didn't want to write a thousand sentences, 'cause we're also lazy.

So we did what every noob programmer does. Apply tons of Javascript.
It was all fun and sunshines until our language files started to grow.
Every branch of a sentence doubled our Javascript code. Every time we needed
to conditionally render based on internal state, out code almost tripled.
Even with the really nice helper functions and classes we built
for ourselves, it was a nightmare to maintain. Our language files had BUGs.
Go figure.

As our Bot mostly operates in Bangladesh we needed to translate our language files to Bengali. Where do we even begin to talk about translators.
Even the techsavy-est of translators failed to translate our
English language files.

So we thought, "eyy, what would happen if we hooked up an XML parser to
some magical JS code and made a programming-ish language that was easy
and familiar to programmers and translators alike."

Three Months Later: PhraseEngine is Born.

## License
**MIT**. Go crazy.