Flow’s embedded expression language (Eel)

Flow provides different packages, one is the Eel package the embedded expression language. A developer can use Eel to generate a domain specific language, (=DSL). Neos, the CMS built upon Flow, uses Eel to parse parts of their TypoScript 2. Also I’ve heard of one using Eel to generate QR-Codes. Myself has used Eel like Vim auto command for filetype detection. There is a wide range of use cases where Eel can be used.

In this blog post you get a short introduction into the basics to understand how and when to use Eel.

A simple example

Given the following PHP Code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
protected $eelExpression = 'file.fileExtension == "xml" && String.substr(lines[1], 1, 10) == "checkstyle" && String.substr(lines[1], 21, 3) == "2.5"';
public function canHandle(Resource $file)
{
    $context = new Context([
        // Provide EelHelper to parsing.
        'String' => new EelHelper\StringHelper,
        // Provide "variables" to parsing.
        'file' => $file,
        'lines' => file($file->createTemporaryLocalCopy(), FILE_IGNORE_NEW_LINES),
    ]);

    return (bool)(new CompilingEvaluator)->evaluate($this->eelExpression, $context);
}

We have an expression inside $this->eelExpression and a method that will evaluate the expression. The result is a boolean value indicating whether the PHP class can handle the given file, based on the evaluated Eel expression.

The grammar

Eel already comes with a defined grammar, which is documented as plain text file inside the package.

It will define things like Types, e.g. true and false, and more complex object paths. Also expressions like mystr ~= /Test/ and myint + i < 42 are already defined.

The grammar is nothing more then grammar, there are no defined methods or variables out of the box, like used in the above example String.substr().

In the above example we already make use of the grammar by combining some conditions using && and comparing things using ==.

You need the context

To enable the evaluation of an Eel expression you need to provide a context to the evaluator. The expression will be evaluated inside this context.

The context allows you to define which expressions are available. As mentioned earlier, Eel ships with a grammar. But to allow the above expression to be evaluated, we also need to define what file and file.fileExtension is. Also we need to define String.substr().

That’s done with the following lines:

$context = new Context([
    // Provide EelHelper to parsing.
    'String' => new EelHelper\StringHelper,
    // Provide "variables" to parsing.
    'file' => $file,
    'lines' => file($file->createTemporaryLocalCopy(), FILE_IGNORE_NEW_LINES),
]);

Mainly the context is just a pure PHP array with key value pairs defining what’s available inside the expression during evaluation. In the above Example, we define file and lines. file is of type Resource and provides all public methods of the object. Also lines is just an array containing all lines of the given file.

String is “special” in that case that it’s a predefined EelHelper delivered as part of Eel. All helpers are auto-documented at Eel Helpers Reference.

Once you have defined the context, you are able to parse the expression, in that context.

You can parse the expression

Parsing is as easy as calling a method with two arguments:

return (bool)(new CompilingEvaluator)->evaluate($this->eelExpression, $context);

The result of the evaluation depends on the expression. It can be of any type. In case of a boolean expression like true != false the result is a boolean itself. In case of some string manipulation like String.substr("Hello World!", 0, 4) it’s a string.

The result depends on the expression and what you need. That’s why a cast to boolean is done in the above example, as the method has to return a boolean as defined by the interface in this example.

Further usages

Not always the result of the evaluate() method is what you need, but what you can do within the expression.

As you can provide own classes via context, you can do whatever makes sense, e.g. trigger methods of a PHP class to put something into a queue, generate an image or something else.

It’s up to the developer what to achieve with Eel. Eel is not, only, a template engine to parse a string and replace some parts. It’s a package to enable developers to create their own domain specific language, wherever they need it.

Of course you can use Eel standalone outside of Neos and Flow. Just make sure to use InterpretedEvaluator as evaluator. Also note, he’s not that good in performance, as mentioned in his PHP class doc.

When to use eel

As mentioned at the beginning, Eel enables you to define a DSL. So whenever you need a DSL you can use Eel. Some examples were already given, especially in the section Further usages.

So get fancy, start your inspiration and find use cases for Eel.

E.g. generate a new task runner like Grunt, rake, make, ... . Or a new template engine? Parse files, generate something like PDFs.

Note that you define the context, what is available to the user is up to you, as you define the domain specific language.

Complete example

Here comes one real world example where I’ve used Eel to ease work for developers.

The main idea is to parse a file that can be of any format like PHPUnit result, PHPLoc result, checkstyle output from phpcs, and so on. As each format is different, it should be possible to detect which parser should be used for the file, just by knowing the file.

The software provides and interface defining that each parser has to implement the canHandle(Resource $file) method. All that is provided is the file, nothing more. The parser itself should check whether he can parse the file.

Also a trait is provided by the software:

<?php
namespace CodeMonitoring\Framework\Parse;

/*
 * Copyright (C) 2016  Daniel Siepmann <coding@daniel-siepmann.de>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

use TYPO3\Flow\Resource\Resource;
use TYPO3\Eel\Context;
use TYPO3\Eel\CompilingEvaluator;
use TYPO3\Eel\Helper as EelHelper;

/**
 * Attach the trait to parser or importer, to provide configurable eel
 * expressions to determine whether they can parse a given file.
 *
 * TODO: Check whether we have to use ProtectedContext to prevent calls to
 * methods like local copy?
 */
trait EelParsingDetectionTrait
{
    /**
     * @var string
     */
    protected $eelExpression = 'file.fileExtension == "xml" && String.substr(lines[1], 1, 10) == "checkstyle" && String.substr(lines[1], 21, 3) == "2.5"';

    /**
     * Evaluate configured eel expression on file to detect whether it can be
     * processed.
     *
     * @param Resource $file
     *
     * @return bool
     */
    public function canHandle(Resource $file)
    {
        $context = new Context([
            // Provide EelHelper to parsing.
            'String' => new EelHelper\StringHelper,
            // Provide "variables" to parsing.
            'file' => $file,
            'lines' => file($file->createTemporaryLocalCopy(), FILE_IGNORE_NEW_LINES),
        ]);

        return (bool)(new CompilingEvaluator)->evaluate($this->eelExpression, $context);
    }
}

Each developer can now use this trait in his parser and inject the Eel expression using the Objects.yaml:

CodeMonitoring\Parser\Checkstyle\Parser\CheckstyleParser:
  properties:
    eelExpression:
      value: 'file.fileExtension == "xml" && String.substr(lines[1], 1, 10) == "checkstyle" && String.substr(lines[1], 21, 3) == "2.5"'

Instead of implementing the logic in his class, the developer now can configure the parsing detection, using the domain specific language.

Further reading

The package itself can be found at Github.

Documentation is available at Eel. Also the grammar is already documented. Same is true for the Eel Helpers Reference.