Skip to content
Matt Bierner edited this page Nov 13, 2015 · 3 revisions

The builtin Apep combinators all automatically convert non-generator arguments into generators values. This allows for more expressive grammars.

// Same behavior as in the README
const tvHackerQuip = pep.declare(() =>
    pep.seq(exlaimation, ' ',
        pep.choice(
            ['The ', subject, " is ", somethingBad, "!"],
            [evilDoer, ' ', doingSomthingBad, " the ", target, "!"])));

Note how ' ' and other string literals are not generators. Also, Javascript arrays are used as shorthand for pep.seq.

What gets Wrapped?

When evaluating an argument, Apep uses the following mapping:

  • Generators are passed through unmodified. You can yield a generator itself with pep.lit(generator) or force one to be converted to a string with pep.str(generator).
  • Arrays are converted to pep.seq(...).
  • undefined triggers an exception. This is almost always a sign that something is wrong with your grammar. If you want to yield an undefined value, use pep.lit(undefined), or pep.str(undefined) if you want the string representation of undefined.
  • Any remaining values are passed to pep.str(...). This includes numbers, strings, objects, booleans, null, functions, and every other class of Javascript object except for undefined.

What wraps things?

All builtin Apep generators wrap all of their generator arguments. A few key points however:

  • Returned values are NOT wrapped. pep.chain for example does not attempt to wrap the result of the mapping function, and neither does pep.declare. You must always explicitly return a generator in these cases.

  • Method chaining does not work on unwrapped values. pep.seq(a, b) will always wrap both a and b, while a.seq(b) will fail if a is not a real generator.

When should I rely on auto wrapping vs using real generators

It is safe to use autowrapping if no one will be able to tell the difference. Temporary generators that exist only as arguments to other generators are one example of this.

const tvHackerQuip = pep.declare(() =>
    pep.seq(exlaimation, ' ',
        pep.choice(
            ['The ', subject, " is ", somethingBad, "!"],
            [evilDoer, ' ', doingSomthingBad, " the ", target, "!"])));

The argument values cannot be accessed after they are passed, so it is safe to use ' ' and the other string literals here, along with the arrays to express pep.seq. No external code can ever tell the difference.

ALWAYS use a true generator if external code can access the variable.

// Never do this.
const action = 'hop'
const person = pep.choice('pop');
const title = [action, 'on', person];

This only works if action and title are used in specific ways:

pep.chain(title, x => ...)   // ok
title.chain(x => ...)   // CRASH

pep.map(title, x => ...)   // ok
title.map(x => ...)   // Not what you expect, calls `Array.prototype.map` instead of `pep.map`

Array.from(pep.begin(title)))   // OK
Array.from(title)   // Not what you expect

For Combinators

When writing a combinator, follow these rules to make sure it works correctly with both generators and values that can be converted to generators:

  • ALWAYS use pep.FN(g1, ...) inside of the combinator instead of g1.FN(...). If g1 is an unwrapped value, g1.FN will not exist.
  • NEVER try to inspect the generator values passed into the combinator to tell if it is a generators or a value. You will get it wrong. Just assume they are good and forward them to Apep.