
Any whitespace separates arguments. For example,
Addition, and multiplication return the sum or product of their
arguments:
Subtraction, division, and modulus work as follows:
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().
Parameter to functions in ECL are of type ecl_node_ptr which is
defined in ecl_types.h as follows:
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.
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".
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.
(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"))
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.
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.
(print (+ (2 3 4)))
(print (+ (2)))
(print (* (2 3 4)))
(print (* (3)))
outputs:
9
2
24
3
(print (- (2 3 4)))
(print (- (2)))
(print (/ (2 3 4)))
(print (/ (3)))
(print (% (9 5 3)))
outputs:
-5
2
0.166667
3
1
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))
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.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.
Function Reference
void ecl_init(void)
int ecl_cmd_int(void)
int ecl_cmds_from_file(FILE*)
ecl_boolean ecl_getline(FILE*)
ecl_node_ptr ecl_parse(void)
void ecl_eval(ecl_node_ptr)
void ecl_p_error(FILE*)
void ecl_set_error(char*)
ecl_boolean ecl_error(void)
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:
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 */
}
Future Work
The following item are in the long term plans of ECL:
Bugs
ECL is software under development, and as such may contain various
bugs.
Eric Bourque <ericb@computer.org>