Table of Contents


What is ECL?

ECL is a command language, that is, a language which is intended to be used to control something else. For example, ECL commands could be defined for controlling a mobile robot, and then an ECL program itself would do the actual driving by calling the defined commands. Another example would be to define commands which draw simple objects on the screen. An ECL program could then be written to draw more complicated objects from the object primitives defined. The power of ECL comes from two things: (1) its likeness to existing programming languages (most notably lisp), and (2) ECL is extensible, meaning that users can define new functions in C or C++ and have them availble in ECL programs. Included in the distribution is a sample program which simply invokes the ECL interpreter.

[Table of Contents]

Installing ECL

To install ECL, in the src directory type ./configure. This will take a few moments while features on your system are determined. After this has completed type make. This will build ECL together with a minimal sample program which simply interprets commands on the command line. ECL comes in two flavours: threaded and monolithic. The configure script determines whether your system has a pthread (POSIX Thread Implementation) installation and builds accordingly. After verifying that you have the latest distribution of ECL, any problems with installation should be e-mailed to the author with the subject "ecl install" and a detailed description of what you did, and the errors that occured. Output of the errors is essential, as well as the OS type and version (Linux 2.0.35, IRIX 6.2, etc.). Note that a basic familiarity with compiling and installing UNIX software is assumed. Don't send mail saying, "I couldn't compile it".

[Table of Contents]

Specification of ECL

Syntax

Every command in ECL is contained within parentheses, and must be followed by an argument list also enclosed in parentheses:
(func1 (arg1 arg2))
(func2 ())
As shown above, func1 takes two arguments, while func2 does not take any. Note that func2 must still be followed by empty parentheses to denote that it is a function. Any text not enclosed within balanced parenthetical expressions is considered a comment:
This is a comment in ECL
(print ("hello world"))
The above line printed hello world to stdout.
(print ("hello again"))
Commands are not sensitive to whitespace (except in the command interpreter which will be explained in the function reference):
(
  print (
    + (2 3)
    * (5 6)
  )
)
As shown above, return values from functions with higher precedence are propagated to become arguments of functions with lower precedence.

Any whitespace separates arguments. For example,

(print (hello there))
is a call to print with arg1=hello and arg2=there. To include whitespace in arguments, they must be protected using quotation marks:
(print ("hello there"))

[Table of Contents]

Types

ECL is a typeless language. For example, there is no need for format strings when using the print command:
(print (hello c 17 4.56))
Internally, every argument and return value is converted to one of four C types: long, double, char, string.

[Table of Contents]

Built-In Commands

The following are built in commands in ECL: print, +, -, *, /, %, set, and unset. Set and unset will be discussed in the following section.

Addition, and multiplication return the sum or product of their arguments:

(print (+ (2 3 4)))
(print (+ (2)))
(print (* (2 3 4)))
(print (* (3)))
outputs:
9
2
24
3

Subtraction, division, and modulus work as follows:

(print (- (2 3 4)))
(print (- (2)))
(print (/ (2 3 4)))
(print (/ (3)))
(print (% (9 5 3)))
outputs:
-5
 2
 0.166667
 3
 1

[Table of Contents]

Variables

Variables are declared and undeclared in ECL using the set and unset functions. In order to differentiate between variables and literals, variables must be preceeded by a $ if they are to be expanded. Note that this is not necessary in the definition of the variables. For example:
(set (var 4))
(set (words "hello world"))
(set (func print))
($func ($var $words))
(set (a +(2 3 4)))
(print ($a))
outputs:
4 hello world
7
Variables can be changed by using subsequent calls to set:
(set (a 4))
(print ($a))
(set (a 8))
(print ($a))
outputs:
4
8
Should a variable need to be undefined, this can be accomplished with unset:
(set (a 4))
(print ($a))
(unset (a))

[Table of Contents]

Using ECL

There are three different ways ECL can be used from a C(++) program: The first two are straightforward, the third will be discussed in the section below.

All programs which use ECL must declare #include "ecl.h" and must call ecl_init() before calling any other ECL functions. From that point, to enter the interactive command interpreter, call ecl_cmdint(). To execute commands in a file, call ecl_cmds_from_file().

[Table of Contents]

Error Handling

The error handling in ECL works as follows: there is a function ecl_error() which returns true if an error has been posted, and false otherwise. The error can be printed to a stream with ecl_p_error() and cleared with ecl_clear_error(). Note that printing an error does not clear the error. An error condition can be set by calling ecl_set_error() with a string. If there is an error which has not been cleared yet, ecl_set_error() does nothing. The interfaces for the error functions are described in the function reference section.

[Table of Contents]

Function Reference

void ecl_init(void)

Initializes all of ECL's internal data structures, etc. This function must be called before any calls to other ECL functions.

int ecl_cmd_int(void)

Begin interactive command interpreting on the stdin stream. This function returns when an end-of-file (EOF) delimiter has been read. In the interpreter, a new-line (carriage return) denotes the end of an expression unlike in cmds_from_file. The return value is the number of lines processed.

int ecl_cmds_from_file(FILE*)

Process all of the commands in the file defined by the FILE* passed in. Note that the file must have been opened successfully before calling this function. The return value is the number of commands processed.

ecl_boolean ecl_getline(FILE*)

Read one ECL expression from FILE into the expression buffer. This function must be called before a call to ecl_parse, and ecl_eval. These functions are only necessary if control must be returned to the calling program between the execution of ECL expressions. Return is true if an expression has been found, false otherwise.

ecl_node_ptr ecl_parse(void)

Build a parse tree for the current expression in the buffer (read in using ecl_getline). The return value is a pointer to the root of the parse tree, which can then be passed to ecl_eval to evaluate. As mentioned above, these functions are provided only in case control must go back to the calling program between the execution of ECL expressions.

void ecl_eval(ecl_node_ptr)

Evaluate the expression in the parse tree pointed to by ecl_node_ptr (returned by ecl_parse). An errors which occur during the evaluation will be available via ECL error functions.

void ecl_p_error(FILE*)

Print the current error description to the stream pointed to by FILE. This function does nothing if no error has been posted. The error condition is not cleared after printing - a call to ecl_clear_error will clear the error.

void ecl_set_error(char*)

Set the error condition to the string passed in.

ecl_boolean ecl_error(void)

Returns true if there is an error condition set, false otherwise.

void ecl_clear_error(void)

Clears the error condition. This is usually called after calling ecl_p_error.

[Table of Contents]

Writing Your Own Functions

The main reason to use ECL is is extensibility. This means that you can incorporate your own functions into the language. These functions are not limited in functionality, but must adhere to certain restrictions:

  1. All functions must return a value.
  2. All functions must have a defined behaviour for input of any type.
  3. All functions must be in the function table in table.c

Parameter to functions in ECL are of type ecl_node_ptr which is defined in ecl_types.h as follows:

typedef enum {ecl_float_t,ecl_int_t,ecl_char_t,ecl_string_t,ecl_null_t} ecl_type_t;

typedef struct {
  union {
    double f;
    long i;
    char c;
    char *s;
  } u;
  ecl_type_t type;
} var, *var_ptr;

typedef struct ecl_node {
  char *key;
  var value;
  struct ecl_node *parent;
  struct ecl_node *children;
  struct ecl_node *next;
} ecl_node, *ecl_node_ptr;

The type field in var determines the value contained by the union. The general way to write a function is to iterate over all of the parameters (by calling get_next_arg) and then set the parent node's value to your return value. This will be demonstrated with two examples from ECL's built-in functions: print and add.

Print

ecl_boolean print(ecl_node_ptr in){
  /* simply print out our arguments in tab-delimited form to stdout */

  ecl_node_ptr parent; /* we need this to set the return value */
  if (!in){ /* if in is NULL then we have no arguments */
    ecl_set_error("print - no arguments"); /* set the error condition */
    return true; /* return value of true indicates there was an error */
  }
  parent=in->parent; /* keep our parent */
  while (in){ /* iterate over all arguments */
    switch (in->value.type){ /* what type is the current arg */
    case ecl_string_t: /* is it a string? */
      printf("%s\t",in->value.u.s); /* print the string part of the union */
      break;
    case ecl_char_t: /* is it a char? */
      printf("%c\t",in->value.u.c); /* print the char part of the union */
      break;
    case ecl_int_t: /* is it an int? */
      printf("%ld\t",in->value.u.i); /* print the int part of the union */
      break;
    case ecl_float_t: /* is it a float? */
      printf("%f\t",in->value.u.f); /* print the float part of the union */
      break;
    case ecl_null_t: /* if it is null, don't print anything */
      break;
    }
    in=get_next_arg(in); /* get the next arg */
  }
  printf ("\n"); /* since we are printing tab-delimited, end the line */
  /* we don't return anything, so set our parent's value to null */
  parent->value.type=ecl_null_t;
  if (parent->value.type==ecl_string_t) /* does out parent have a string as a value? */
    free(parent->value.u.s); /* free the string to avoid leaks */
  return false; /* there were no errors */
}

Add

ecl_boolean add(ecl_node_ptr in){
  ecl_node_ptr parent;
  double sum=0; /* to keep the result */

  if (!in){
    ecl_set_error("add - no arguments"); /* were there any arguments? */
    return true; /* return error condition */
  }
  parent=in->parent; /* keep our parent for the return value */
  while (in){ /* iterate over all the arguments */
    switch(in->value.type){ /* what type is the current arg? */
    case ecl_string_t:
      /* don't know how to do string addition yet - skip it */
      break;
    case ecl_char_t:
      /* don't know how to do char addition either - skip it */
      break;
    case ecl_int_t:
      /* this we know how to do */
      sum+=in->value.u.i;
      break;
    case ecl_float_t:
      /* this too */
      sum+=in->value.u.f;
      break;
    case ecl_null_t:
      break;
    }
    in=get_next_arg(in); /* get the next argument */
  }
  /* free the parent's string if there was one */
  if (parent->value.type==ecl_string_t)
    free(parent->value.u.s);
  /* now figure out the type of our return value */
  if ((sum-(int)sum)==0){ /* we are an int */
    parent->value.type=ecl_int_t; /* set return type to int */
    parent->value.u.i=sum; /* set the sum */
  }
  else{
    parent->value.type=ecl_float_t; /* set return type to float */
    parent->value.u.f=sum; /* set the sum */
  }
  return false; /* there were no errors */
}

[Table of Contents]

Future Work

The following item are in the long term plans of ECL:

[Table of Contents]

Bugs

ECL is software under development, and as such may contain various bugs.

[Table of Contents]


Eric Bourque <ericb@computer.org>