The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

English::Script - Parse English subset and convert to data or code

VERSION

version 1.06

SYNOPSIS

    use English::Script;

    my $js = English::Script->new->parse('Set the answer to 42.')->render;

    my $es = English::Script->new(
        grammar     => '# grammar',
        renderer    => 'JavaScript',
        render_args => {},
    );

    my $grammar  = $es->grammar('# set grammar');
    $es          = $es->append_grammar('# append grammar');
    my $renderer = $es->renderer('JavaScript');
    $renderer    = $es->renderer( 'JavaScript', {} );

    $es = $es->parse('Set the answer to 42.');

    my $data = $es->data;
    my $yaml = $es->yaml;

    $js = $es->render;
    $js = $es->render('JavaScript');
    $js = $es->render( 'JavaScript', {} );

DESCRIPTION

The module will parse a limited subset of English (using Parse::RecDescent grammar) and convert it to either a Perl data structure or YAML. It can then render this to code. The default renderer is JavaScript.

Why? Well, the goal is to provide a means by which some basic logic can be written in English and (at least in theory) be read, maintained, and extended by normal humans (which is to say, non-programmers).

METHODS

The following are the methods of the module.

new

Returns an instantiated object of the class.

    my $js = English::Script->new;

Optionally, you can provide certain settings.

    $es = English::Script->new(
        grammar     => '# grammar',   # replaces default grammer
        renderer    => 'JavaScript',  # set the renderer; default: JavaScript
        render_args => {},            # arguments for the renderer
    );

Renderers are subclasses of English::Script. The default is English::Script::JavaScript, which ships with English::Script. The name provided via the renderer property is appended to "English::Script::" to locate the renderer class.

parse

Parse a string input based on the grammar.

    $es = $es->parse('Set the answer to 42.');

This method will return the object. If parsing fails, an error will be thrown. You can catch this error and then inspect data to get a list of all errors.

    use exact;
    use DDP;

    try {
        $es->parse('Set the answer to 42.');
    }
    catch {
        p $es->data->{errors};
    };

render

If no arguments are provided, this method will call whatever renderer is set via the renderer attribute to render code from the data parsed.

    $js = $es->render;

You can optionally explicitly set a renderer or a renderer and arguments for the renderer.

    $js = $es->render('JavaScript');
    $js = $es->render( 'JavaScript', {} );

The method will return the rendered code as a scalar string.

grammar

This is a getter/setter for the grammar, which is a string suitable for Parse::RecDescent.

    my $grammar = $es->grammar('# set grammar');

append_grammar

Append a string to whatever's currently set in the grammar attribute.

    $es = $es->append_grammar('# append grammar');

This method will return the object.

renderer

This is a getter/setter for the renderer. You can provide either the name of a renderer (which should be the suffix added to "English::Script::" to locate the renderer class) or the name of the renderer and a hashref of arguments for that renderer.

    my $renderer = $es->renderer('JavaScript');
    $renderer    = $es->renderer( 'JavaScript', {} );

data

Returns the Perl data structure of whatever was succesfully parsed.

    my $data = $es->data;

yaml

Returns YAML of whatever was succesfully parsed.

    my $yaml = $es->yaml;

DEFAULT GRAMMAR

The default grammar is a limited and simple set of basic English phrases. The intent of this "language" is not to be particularly expressive, but provide just enough to be useful in basic situations.

Parsable input needs to be composed of sentences. Line breaks and other spacing is ignored, but purely for the sake easy reading, the examples below generally follow a sentence being all on one line. This is not required.

Say

To "say" something means to output it in some way. For the JavaScript renderer, this means a call to console.log. Say commands require an expression. Expressions contain at least one object and possibly some operations. An object is a string, number, word, or call.

Here are some simple examples:

    Say 42.
    Say "Hello World".
    Say 42 plus 1138 times 13 divided by 12.

Set

The "set" command assigns a value derived from an expression to an object.

    Set prime to 3.
    Set the special prime value to 3.
    Set the answer to 123,456.78.

Numbers can be floating point and contain commas, which will be ignored. For example, "123,456.78" becomes 123456.78.

In the case of multiple words (or words and numbers) provided as an object, the assumption will be that these are sequences of objects in a tree. For example:

    Set the special prime value to 3.

The special prime" becomes special.prime in JavaScript.

Certain words are ignored completely, like "the" and "a" and all other articles. Also phrases like "value of" or "list of" or "there are" and "there is" are ignored. For objects, words like "list" or "value" or "text" or "number" are ignored. For example:

    Set the special prime list value string text number list array to 3.

The object above becomes special.prime in JavaScript (and is assigned the integer 3).

Words and numbers can form an object. For example:

    Set the sum of 27 to the value of 3 plus 5 times 10 divided by 2 minus 1.

The object above is sum.of.27.

In all cases, everything outside of strings, denoted by double-quotes, will be considered case-insensitive.

Comments

Comments are any text between parentheses. The text can contain line-breaks or any other spacing. However, comments must not be embedded inside sentences. They can be inside blocks, like in "if" or "for" blocks, but in parallel to sentences.

    (This is a single-line comment.)

    (This is a
    multi-line comment.)

    If prime is 3, then apply the following block.
        Set the answer to 42.
        (This is a comment.)
    This ends the block.

Note that the spacing and line breaks in the examples above is purely for easier reading. It's not required.

Lists

To create an array, assign a list to an object.

    Set the primes to 5, 6, and 7.

The "and" isn't necessarily required; however, the spaces and commas are required.

You can reference a specific item in a list by number (starting at 1):

    Set the favorite number to item 1 of favorite numbers.

Given a list stored in answer, you can then shift off a value:

    Set the prime value to a removed item from the primes list.

Length

You can get the length of a list (the number of items it contains) or the length of a string by seeking it's "length":

    Set string size to the length of strings example.
    Set primes size to the length of the primes list.

You can also find the length of a specific item of a list:

    Set the special size to the length of item 1 of favorite numbers.

Append

You can append to a string or to a list.

    Append "+" to the answer text.
    Append 9 to the primes list.

Math

Basic math functions are supported:

    Add 42 to the favorite number.
    Subtract 42 from the favorite number.
    Multiply the favorite number by 42.
    Divide the favorite number by 42.

If

Basic conditionals are supported.

    If prime is 3, then set add 3 to the sum of primes.

You can also setup blocks. Note that in the following example, the spacing and line breaks exist purely to aid in reading. They're not required.

    If prime is 3, then apply the following block.
        Set the answer to 42.
        Set THX to 1138.
    This ends the block.

You can name the blocks as well, if you want. So you could write "then apply the following set up some things block."

The booleans of "true" and "false" are supported.

    Set something to true.
    If something is true, then say "It's true!".

Otherwise

The "otherwise" command works as an else, but it must be in an immediately following sentence from an "if" sentence.

    If the prime is 3, then set result to true. Otherwise, set result to false.

You can create the equivalent of an else if via:

    If the prime is 3, then set result to true.
    Otherwise, if the answer is not 42, then set result to false.

Conditionals

A few basic conditionals are supported.

  • is

  • is not

  • is less than

  • is greater than

  • is less than or equal to

  • is greater than or equal to

You can check if a string is in a larger string, if an item is in a list, or if a string begins with another string or not using:

  • is in

  • is not in

  • begins with

  • does not begin with

Basic logical combinations of conditionals are possible with:

  • and

  • or

For

For loops that iterate through items in a list are supported.

    Set primes to 3, 5, and 7. For each prime in primes, apply the following
    block. Add prime to sum. This ends the block.

Variable Scope

All variables are scoped globally. Everywhere. Always. If you setup a for loop and name the iterator something, that something will available everywhere.

LANGUAGE RENDERER MODULES

Language renderer modules must support a new() method and a render() method. Beyond that, you can do just about whatever you want to make rendering work.

JavaScript

The default renderer "JavaScript" will render... wait for it... JavaScript.

    English::Script->new(
        renderer    => 'JavaScript',
        render_args => { compress => 'clean' },
    )->parse('Set answer to 42.')->render;

The optional render_args value if provided should be a hashref of settings that are passed directly to JavaScript::Packer as options.

SEE ALSO

Parse::RecDescent, JavaScript::Packer.

You can also look for additional information at:

AUTHOR

Gryphon Shafer <gryphon@cpan.org>

COPYRIGHT AND LICENSE

This software is Copyright (c) 2020-2050 by Gryphon Shafer.

This is free software, licensed under:

  The Artistic License 2.0 (GPL Compatible)