Lugaru's Epsilon
Programmer's
Editor 14.04

Context:
Epsilon User's Manual and Reference
   . . .
   Changing Epsilon
   Introduction to EEL
      Epsilon Extension Language Features
      EEL Tutorial
   Epsilon Extension Language
      EEL Command Line Flags
      The EEL Preprocessor
      Lexical Rules
      . . .
      Syntax Summary
   . . .

Previous   Up    Next
Epsilon Extension Language Features  Introduction to EEL   Epsilon Extension Language


Epsilon User's Manual and Reference > Introduction to EEL >

EEL Tutorial

This section will take you step by step through the process of creating a new command using EEL. You will learn how to use the EEL compiler, a few control structures and data types, and a few primitive operations. Most important, this section will teach you the mechanics of writing extensions in EEL.

As our example, we will write a simplified version of the insert-file command called simple-insert-file. It will ask for the name of a file, and insert the contents of the file before point in the current buffer. We will write it a few lines at a time, each time having the command do more until the whole command works. When you write EEL routines, you may find this the way to go. This method allows you to debug small sections of code.

Start Epsilon in a directory where you want to create the files for this tutorial. Using the find-file command (Ctrl-x Ctrl-f), create a file with the name "learn.e".

To write an extension, you: write the source code, compile the source code, load the compiled code, then run the command.

First, we write the source code. Type the following into the buffer and save it:

#include "eel.h"        /* standard definitions */

command simple_insert_file()
{
    char inserted_file[FNAMELEN];

    get_file(inserted_file, "Insert file", "");
    say("You typed file name %s", inserted_file);
}

Let's look at what the source code says. The first line includes the text of the file "eel.h" into this program, as though you had typed it yourself at that point.

Comments go between /* and */.

The file "eel.h" defines some system-wide constants, and a few global variables. Always include it at the beginning of your extension files.

The line

command simple_insert_file()

says to define a command with the name simple_insert_file. The empty parentheses mean that this function takes no parameters. The left brace on the next line and the right brace at the end of the file delimit the text of the command.

Each command or subroutine begins with a sequence of local variable declarations. Our command has one, the line

char inserted_file[FNAMELEN];

which declares an array of characters called inserted_file. The array has a length of FNAMELEN. The constant FNAMELEN (defined in eel.h) may vary from one version to another. It specifies the maximum file name length, including the directory name. The semicolon at the end of the line terminates the declaration.

The next statement

get_file(inserted_file, "Insert file", "");

calls the built-in subroutine get_file( ). This primitive takes three parameters: a character array to store the user's typed-in file name, a string with which to prompt the user, and a value to offer as a default. In this case, the Epsilon will prompt the user with the text between the double quotes (with a colon stuck on the end). We call a sequence of characters between double quotes a string constant.

When the user invokes this command, the prompt string appears in the echo area. Epsilon then waits for the user to enter a string, which it copies to the character array. While typing in the file name, the user may use Epsilon's file name completion and querying facility. This routine returns when the user hits the <Enter> key.

The next statement,

say("You typed file name %s", inserted_file);

prints in the echo area what file name the user typed in. The primitive say( ) takes one or more arguments. The first argument acts as a template, specifying what to print out. The "%s" in the above format string says to interpret the next argument as a character array (or a string), and to print that instead of the "%s". In this case, for the second argument we provided inserted_file, which holds the name of the file obtained in the previous statement.

For example, say the user types the file name "foo.bar", followed by <Enter>. The character array inserted_file would have the characters "foo.bar" in it when the get_file( ) primitive returns. Then the second statement would print out

You typed file name foo.bar

in the echo area.

One way to get this command into Epsilon is to run the EEL compiler to compile the source code into a form Epsilon can interpret, called a bytecode file. EEL source files end in ".e", and the compiler generates a file of compiled binary object code that ends in ".b". After you do that, you can load the .b file using the load-bytes command.

But an easier way that combines these steps is to use Epsilon's compile-buffer command on Alt-F3. This command invokes the EEL compiler, as if you typed

eel filename

where filename is the name of the file you want to compile, and then (if there are no errors) loads the resulting bytecode file. You should get the message "learn.b compiled and loaded." in the echo area.

Now that you've compiled and loaded learn.b, Epsilon knows about a command named simple-insert-file. Epsilon translates the underscores of command names to hyphens, so as to avoid conflicts with the arithmetic minus sign in the source text. So the name simple_insert_file in the eel source code defines simple-insert-file at command level.

Go ahead and invoke the command simple-insert-file. The prompt

Insert file: 

appears in the echo area. Type in a file name now. You can use all Epsilon's completion and querying facilities. If you press "?", you will get a list of all the files. If you type "foo?", you will get a list of all the files that start with "foo". <Esc> and <Space> completion work. You can abort the command with Ctrl-g.

After you type a file name, this version of the command simply displays what you typed in the echo area.

Let's continue with the simple-insert-file command. We will create an empty temporary buffer, read the file into that buffer, transfer the characters to our buffer, then delete the temporary buffer. Also, let's get rid of the line that displays what you just typed. Make the file learn.e look like this:

#include "eel.h"        /* standard definitions */

command simple_insert_file()
{
        char inserted_file[FNAMELEN];
        char *original_buffer = bufname;

        get_file(inserted_file, "Insert file", "");
        zap("tempbuf");      /* make an empty buffer */
        bufname = "tempbuf"; /* use that buffer */
        if (file_read(inserted_file, 1) != 0)
                error("Read error: %s", inserted_file);
                        /* copy the characters */
        xfer(original_buffer, 0, size());
                        /* move back to buffer */
        bufname = original_buffer;
        delete_buffer("tempbuf");
}

This version has one more declaration at the beginning of the command, namely

char *original_buffer = bufname;

This declares original_buffer to point to a character array, and initializes it to point to the array named bufname.

The value of the variable bufname changes each time the current buffer changes. For this reason, we refer to such variables as buffer-specific variables. At any given time, bufname contains the name of the current buffer. So this initialization in effect stores the name of the current buffer in the local variable original_buffer.

After the get_file( ) call, we create a new empty buffer named "tempbuf" with the statement "zap("tempbuf");". We then make "tempbuf" the current buffer by setting the bufname variable with the following.

bufname = "tempbuf";

Now we can read the file in:

if (file_read(inserted_file, 1) > 0)
        error("Read error: %s", inserted_file);

This does several things. First, it calls the file_read( ) primitive, which reads a file into the current buffer. It returns 0 if everything goes ok. If the file doesn't exist, or some other error occurs, it returns a nonzero error code. The actual return value in that case indicates the specific problem. This statement, then, executes the line

error("Read error: %s", inserted_file);

if an error occurred while reading the file. Otherwise, we move on to the next statement. The primitive error( ) takes the same arguments that say( ) takes. It prints out the message in the echo area, aborts the command, and drops any characters you may have typed ahead.

Now we have the text of the file we want to insert in a buffer named tempbuf. The next statement,

xfer(original_buffer, 0, size());

calls the primitive xfer( ), which transfers characters from one buffer to another. The first argument specifies the name of the buffer to transfer characters to. The second and third arguments give the region of the current buffer to transfer. In this case, we want to transfer characters to original_buffer, which holds the name of the buffer from which we invoked this command. We want to transfer the whole thing, so we give it the parameters 0 and size( ). The primitive size( ) returns the number of characters in the current buffer.

The last two statements return us to our original buffer and delete the temporary buffer.

#include "eel.h"        /* standard definitions */

char region_file[FNAMELEN];

command simple_insert_file() on cx_tab['i']
{
    char inserted_file[FNAMELEN], *buf;
    char *original_buffer = bufname;
    int err;

    iter = 0;
    get_file(inserted_file, "Insert file", region_file);
    mark = point;
    bufname = buf = temp_buf();
    err = file_read(inserted_file, 1);
    if (!err)
        xfer(original_buffer, 0, size());
    bufname = original_buffer;
    delete_buffer(buf);
    if (err)
        file_error(err, inserted_file, "read error");
    else
        strcpy(region_file, inserted_file);
}

The final version of this command adds several more details.

On the first line, we've added on cx_tab['i']. This tells Epsilon to bind the command to Ctrl-x i. We've added a new character pointer variable named buf, because we will use Epsilon's temp_buf( ) subroutine for our temporary buffer rather than the wired-in name of "tempbuf". This subroutine makes up an unused buffer name and creates it for us. It returns the name of the buffer.

The line

mark = point;

causes Epsilon to leave the region set around the inserted text. The xfer( ) will insert its text between mark and point. We've added the line iter = 0; to make the command ignore any numeric argument. Without this line, it would ask you for a file to insert over and over, if you accidentally gave it a numeric argument.

We now save the error code that file_read( ) returns so we can delete the temporary buffer in the event of an error. We also use the file_error( ) primitive rather than error( ) because the former will translate system error codes to text.

Finally, we added the line

char region_file[FNAMELEN];

to provide a default if you should execute the command more than once. Because this definition occurs outside of a function definition, the variable persists even after the command finishes. Variables defined within a function definition (local variables) go away when the function finishes. We copy the file name to region_file each time you use the command, and pass it to get_file( ) to provide a default value.



Previous   Up    Next
Epsilon Extension Language Features  Introduction to EEL   Epsilon Extension Language


Lugaru Epsilon Programmer's Editor 14.04 manual. Copyright (C) 1984, 2021 by Lugaru Software Ltd. All rights reserved.