Hecl Programming Language

David Welton

DedaSys

Table of Contents

Introduction
Installation
Hecl Tutorial
Hecl Commands
Interfacing Hecl and Java
Calling Hecl code from Java
Creating new Hecl commands
JavaDocs
Hecl and J2ME

Introduction

The Hecl Programming Language is a high-level scripting language implemented in Java. It is intended to be small, extensible, extremely flexible, and easy to learn and use.

Why Hecl? Hecl is intended as a complement to the Java programming language, not a replacement. It tries to do well what Java doesn't, and leaves those tasks to Java for which it is best suited, by providing an API that makes it easy to tie the two together. Hecl aims to be a very immediate language - you can pick it up and start doing useful things with it quickly - even people without formal training. Hecl is easy to learn. Where Java is verbose and rigid, Hecl is forgiving and quick to write. For instance, System.out.println("Hello World"); vs puts "Hello World" - 41 keystrokes (shifted letters count double) versus 22. Hecl is built to "scale down" - especially in terms of its users, meaning that it is very quick to learn, and can be quickly put to productive use even by those who are not programmers by trade.

This makes Hecl ideal for large applications written in Java that would like to provide a user friendly scripting interface, rather than, say, a clunky XML based system. Examples include: scripted web pages, command/control logic in long running applications, and, I'm sure, many environments I've never considered. Instead of a simple, static configuration file, you can give your users the power to program portions of the system to do things that you hadn't thought of when you wrote the software originally.

Hecl is a small language with a minimal core. The idea is to provide only what's necessary in the language itself, and as needed, add in extensions for specific tasks. Core Hecl is small enough to run on my Nokia 3100 cell phone as a J2ME application, presenting the interesting possibility of writing scripts, or at some point, maybe even scripting entire applications, for devices running embedded Java.

Contributions in the form of code, ideas, suggestions, or even donations are welcome. Hecl is still growing, so your thoughts are important, and you can help shape the language's future. You can download the code via CVS from the SourceForge project page: .

Hecl is available under the liberal Apache 2.0 open source license. Which says, more or less, that you may use Hecl in your own applications, even if they are not open source. You have to give me credit, though. Read the license itself to clear up any doubts. Oh, and incidentally, I don't see the license as being incompatible with the GPL, so feel free to utilize Hecl in your GPL product (I have added a note to this effect in the NOTICE file that must accompany products using the Hecl code).

I owe thanks to a lot of people for Hecl. First and foremost the creator of the Tcl programming language, Dr. John Ousterhout. While I have attempted to improve some things that I did not care for in Tcl, it is obvious that the simple, extremely flexible command-based approach that Hecl takes is derived from Tcl. I also borrowed some ideas from the (mostly defunct) Jacl implementation of Tcl in Java. Many thanks are also due my friend Salvatore Sanfilippo, with whom I have spent many hours discussing Hecl, Tcl, and the philosophy of programming languages in general. And of course, I owe a huge debt of gratitude to my fianceè, Ilenia, who puts up with all the hours I spend in front of "that damn computer".

Installation

Hecl is easy to compile and install as a standard J2SE application.

  1. Hecl uses the Apache Ant build system, so you need to install that to compile Hecl.
  2. To compile the standard, J2SE version of Hecl, do this:
    ant packageCommandline
    
  3. You should now have a Hecl.jar file. To run it, do this:
    java -jar Hecl.jar Hecl
    hecl> puts "hello world"
    hello world
    
  4. If you wish, you can compile Hecl to native code with GCJ:
    gcj -o hecl --main=Hecl Hecl.jar
    davidw@medford:~/workshop/hecl$ ./hecl
    hecl> puts "hello world"
    hello world
    	
  5. If you want to check your installation of Hecl, you can run the test suite to make sure everything checks out:
    java -classpath Hecl.jar Hecl tests/suite.hcl
    	
    A performance test is also supplied so that you can compare numbers if you're curious, or want to hack on Hecl to improve its speed:
    java -classpath Hecl.jar Hecl tests/performance.hcl
    	

Hecl Tutorial

Like many people, I enjoy taking something and experimenting with it before going and reading the instructions! With this in mind, I have written a brief tutorial that you can use to start exploring Hecl on your own.

Of course, we would be out of place not to begin with the famous "Hello, World". Behold:

puts "Hello, World"

Hecl is based on the notion of commands, which take any number of arguments. The puts command takes one argument, a string, and prints it out.

Like all programming languages, Hecl provides variables that may be used to store information. Here, we set a variable, rating, and then print it out in the midst of a string. This is called "interpolation", and is a convenient way of creating new strings.

set rating 10
puts "Hecl, from 1 to 10: $rating"

Something else we notice in the above examples is that we use double quotes "" to group a series of things. In Hecl, commands and their arguments are separated by spaces. Since puts only takes one argument, a string, we use the quotes to group several words together in order to pass them as one string to the command. Many languages require quotes to delineate a string, but in Hecl that is not necessary if the string has no spaces in it. For instance, puts helloworld is legitimate.

Another way of grouping multiple words in Hecl is with braces: {}. Hecl does not automatically perform any substitution on the variables or commands grouped within braces, as it does with quotes.

puts {The $dollar $signs $are printed	    literally$$ - no substitution}

Aside from the dollar sign, which returns a copy of the value of a variable, it is also possible to utilize the results of one command as the input of a second command. For example:

set rating 10
puts "Rating:"
puts [set rating]

In this case, we pass the results of the set command to the puts command. In reality, set rating is just a long way of writing $rating but it's a good example.

Like everything else in Hecl, we perform math operations as commands:

puts "2 + 2 = [+ 2 2]"

In the example, the + takes two arguments, adds them together and return the result, which is then printed out by the puts command.

In order to choose between one or more

set temp 10
if { < $temp 0 } {
    puts "It's freezing"
} else {
    puts "Not freezing"
}

References:

set a 1
set b &a
puts $b
# Returns '1'
set a 2
puts $b
# Returns '2'

"while" loop command:

set i 0
while { < &i 10 } {
    puts "i is now $i"
    incr &i
}

Lists:

set foo [list a b c]
set bar {a b c}
lappend &foo d
lappend &bar d
set foo
# Returns 'a b c d'
set bar
# Returns 'a b c d'

Hash tables:

set foo [hash {a b c d}]
puts [hget &foo a]
# prints 'b'
puts [hget &foo c]
# prints 'd'
hset &foo c 2
puts [hget &foo c]
# prints '2'
puts $foo
# prints 'a b c 2' (although not necessarily in that order)

"foreach" loop command:

set lst {a b c d e f}
foreach {m n} $lst {
    puts  "It is possible to grab two variables at a time: $m $n"
}

foreach {x} $lst {
    puts  "Or one at a time: $x"
}

Create new commands with the "proc" command. In this example we create a command that prints out a numbered list.

set list {red blue green}
proc printvals {vals} {
    set num 1
    foreach v $vals {
	puts "$num - $v"
	incr &num
    }
}

printvals &list

Hecl is very flexible - in this example, we create a "do...while" loop command that works as if it were a native loop construct.

proc do {code while condition} {
    upeval $code
    while { upeval &condition } {
	upeval $code
    }
}

set x 100
set foo ""
do {
    append &foo $x
    incr &x
} while { < &x 10 }
set foo
# Returns 100 - because the loop is run once and only once.

Hecl Commands

+ - * / - Basic math commands.
append - Append text to a variable.
break - Break out of a loop.
catch - Evaluates a script, catching any errors.
continue - Skip to next cycle of a loop
eq - Tests string equivalence.
eval - Evaluate Hecl code.
filter - Filter a list.
for - For loop.
foreach - Iterate over elements in a list.
global - Use global variable from within a proc.
hash - Create and manipulate hash tables.
if - Conditionally execute code.
incr - Increment a variable.
intro - Introspection command.
join - Join elements of a list to create a string.
lappend - Append an element to a list.
lindex - Return the Nth element of a list
list - Create a list
llen - List length.
lset - Set list elements.
proc - Create a new procedure.
puts - Print text.
return - Returns a value from a procedure.
search - Find the first instance of something in a list.
set - Set a variable.
sindex - Return the index'th character of string.
slen - String length.
sort - Sorts list alphabetically.
source - Evaluate Hecl script in a file.
split - Split a string into a list.
time - Time the execution of a script.
true - Returns true.
upeval - Evaluate script in next stack frame up.
while - While loop.

Name

+ - * / — Basic math commands.

Synopsis

+ number number
- number number
* number number
/ number number

Description

The basic math commands take two arguments and carry out a numerical operation on them. In subtraction, the second argument is taken from the first. In division, the first argument is divided by the second.

Example

puts [+ 2 2]
puts [- 10 1]
puts [* 6 7]
puts [/ 100 5]
	  

Produces:

2
9
42
20

Name

append — Append text to a variable.

Synopsis

append varreference string

Description

The append command takes two arguments, a variable reference, and a string to append to the variable.

Example

set foo "bar"
append &foo "beebop"
# The foo variable now contains the string barbeebop
	  

Produces:

barbeebop

Name

break — Break out of a loop.

Synopsis

break

Description

The break command breaks out of a loop. If this command is not run from within a loop - the while or foreach commands for instance, it generates an error.

Example

set i 0
while { true } {
    if { > $i 100 } {
        break
    }
    incr &i
}
	  

In what would otherwise be an endless loop, the break command is used to exit.


Name

catch — Evaluates a script, catching any errors.

Synopsis

catch script ?varname?

Description

The catch command evaluates a script, and returns 0 if the script evaluated successfully. If there were errors, catch returns 1. Optionally, a variable name may be passed to the command, where the results of the script evaluation will be placed. In the case of errors, the stack trace will be placed in the variable argument. If the script executes without problems, the variable will contain the result of the script execution.

Example

catch nosuchcommand foo
set foo
	  

Produces:

	  {ERROR {Command nosuchcommand does not exist}}
	

Name

continue — Skip to next cycle of a loop

Synopsis

continue

Description

The continue command is used within the bodies of looping commands such as if and while. When continue is called, execution of the loop body stops and and execution moves on to the next iteration of the loop.

Example

set i 0
set res {}
foreach x {a b c d e} {
    incr &i
    continue
    append &res $x
}
puts $i
puts $res
	

Produces:

5

The res variable is never appended to, so printing it out produces an empty string.


Name

eq — Tests string equivalence.

Synopsis

eq string1 string2

Description

The eq commands compares two strings, returning 1 if they are equal, 0 if they are not.

Example

if {eq 1 1.0} {
    puts "True"
} else {
    puts "False"
}
	  

Produces:

False

Despite being numerically equivalent, the strings "1" and "1.0" are different.


Name

eval — Evaluate Hecl code.

Synopsis

eval code

Description

The eval command takes a string containing Hecl commands, evaluates them, and returns the result.

Example

set i 0
set str {incr}
lappend &str "i"
eval $str
puts $i
	  

Produces:

1

Name

filter — Filter a list.

Synopsis

filter list varname script

Description

The filter command takes a list and filters it according to the code provided in code. The current element of the list being considered is stored in the varname provided. A list of 'matches' is returned.

Example

set lst {1 2 3 4 5 4 3 2 1}
puts [filter $lst x {= $x 4}]
	  

Produces:

4 4

Name

for — For loop.

Synopsis

for initialization test step body

Description

The for command is like in many other languages like C and Java. As arguments, it takes an initialization option, which is often used to set a variable to some initial value, a test to determine whether to continue running, a step script option which is run at each iteration of the body (to increment a variable, for example), and the body itself.

Example

set out {}
for {set i 0} {< $i 10} {incr &i} {
    append &out $i
}
puts $out
	  

Produces:

0123456789

Name

foreach — Iterate over elements in a list.

Synopsis

foreach varname list body
foreach varlist list body

Description

The foreach command iterates over a list. For each element of the list, varname is set to a new element of the list, and then body is run.

Example

set lst {a b c d e}
set res {}
foreach el $lst {
    append &res $el
}
puts $res
	  

Produces:

abcde

Name

global — Use global variable from within a proc.

Synopsis

global varname

Description

By default, Hecl variables are always local. Global variables are not visible from within procedures. The global command makes global variable varname visible within a procedure.

Example

set foo 1
proc incfoo {} {
    global foo
    incr &foo
}
incfoo
puts $foo
	  

Produces:

2

Name

hash — Create and manipulate hash tables.

Synopsis

hash list
hget hash key
hset hash key value

Description

The hash command takes an even-numbered list and creates a hash table from it, using the even elements as keys, and odd elements as values. A new hash table is returned. The hget and hset commands operate on hash tables. Both take a hash table as their first argument. hget also takes a key, and returns the corresponding value, or an error if no key by that name exists.

Example

set foo [hash {a b c d}]
hset &foo a 42
puts [hget &foo a]
	  

Produces:

42

Name

if — Conditionally execute code.

Synopsis

if test code ?elseif test code...? ?else code?

Description

The if command executes Hecl code conditionally. In its most basic form, it executes a test. If the results are not 0, then it executes code. If not, no further actions take place. if may take any number of elseif clauses, which have their own test and code. Finally, if none of the conditions has matched, it is also possible to supply an else clause that will be executed if the results of the if and elseif tests were all false.

Example

if { true } {
    puts "true"
} else {
    puts "false"
}
	  

Produces:

true
if { > 0 1 } {
    puts "true"
} else {
    puts "false"
}
	  

Produces:

false

Name

incr — Increment a variable.

Synopsis

incr varreference integer

Description

The incr command takes a variable reference, and adds integer to it.

Example

set foo 1
incr &foo
puts "foo is $foo"
incr &foo 10
puts "foo is now $foo"
	  

Produces:

2
12

Name

intro — Introspection command.

Synopsis

intro ?commands?

Description

The intro command is used for Hecl introspection. It takes a subcommand which causes it to perform the desired function.

Example

 puts [sort [intro commands]]
	  

Produces:

* + - / < = > append break catch continue copy eq eval filter for foreach global hash hget hset if incr intro join lappend lindex list llen lset proc puts ref return search set sindex slen sort source sourcehere split time true upeval while

Name

join — Join elements of a list to create a string.

Synopsis

join list ?string?

Description

The join command takes a list argument, and optionally, a string argument. It joins all elements of the list together with the string, or, if a string is not provided, with a space.

Example

puts [join {a b c} "|"]
	  

Produces:

a|b|c

Name

lappend — Append an element to a list.

Synopsis

lappend listreference element

Description

The lappend takes a reference to a list, and an element to add to that list.

Example

set foo a
lappend &foo "b"
puts $foo
lappend &foo "c d"
puts $foo
	  

Produces:

a b
a b {c d}

Name

lindex — Return the Nth element of a list

Synopsis

lindex list index

Description

The lindex command takes a list and an index number as arguments, and return's the index'th element of the list.

Example

puts [lindex {a b c} 2]
	

Produces:

c

Name

list — Create a list

Synopsis

list element ?element...?

Description

The list command takes any number of arguments and returns a list.

Example

puts [list a b c [list 1 2 3]]
	

Produces:

a b c {1 2 3}

Name

llen — List length.

Synopsis

llen list

Description

The llen returns the length of its list argument.

Example

puts [llen {1 2 3 {a b c}}]
	  

Produces:

4

Name

lset — Set list elements.

Synopsis

lset listref index ?replacement?

Description

The lset command sets the index'th element of the list to replacement. If replacement is not present, then the element is deleted.

Example

    set lst {a b c}
    lset &lst 1 x
    puts $lst

    lset &lst 1
    puts $lst
	  

Produces:

a x c
a c

Name

proc — Create a new procedure.

Synopsis

proc name arglist body

Description

The proc command creates new procedures, which are virtually indistinguishable from built-in Hecl commands. name is the name of the new command, arglist is a list of arguments that the new command will take and make available as local variables within the body, which is the code executed every time the command is called.

Example

proc addlist {lst} {
    set res 0
    foreach e &lst {
	incr &res $e
    }
    return &res
}

puts [addlist {1 2 3 4 5}]
	  

Produces:

15

Name

puts — Print text.

Synopsis

puts text

Description

The puts command prints text to stdout.

Example

puts "Hello, world"
	  

Produces:

Hello, world

Name

return — Returns a value from a procedure.

Synopsis

return value

Description

The return command returns a value from a proc command.

Example

proc someproc {} {
    set res 1
    return &res
    set res 2
    return &res
}
puts [someproc]
	  

Produces:

1

Name

search — Find the first instance of something in a list.

Synopsis

search list varname script

Description

The search command is similar to filter in functionality, except that it stops searching on the first match.

Example

set lst {1 2 3 4 5 4 3 2 1}
puts [search $lst x {= $x 4}]
	  

Produces:

4

Name

set — Set a variable.

Synopsis

set varname ?value?

Description

The set sets the value of a variable varname to value value. If value is not provided, returns the value of varname.

Example

set foo "bar"
set bee bop
puts "foo is $foo and bee is $bee"
	  

Produces:

1
foo is bar and bee is bop

Name

sindex — Return the index'th character of string.

Synopsis

sindex string index

Description

The sindex command returns the index'th character of string.

Example

puts [sindex "Hello, world" 0]
puts [sindex "Hello, world" 11]
	  

Produces:

H
d

Name

slen — String length.

Synopsis

slen string

Description

The slen returns the length of string.

Example

puts [slen "abcdefghijklmnopqrstuvwxyz"]
	  

Produces:

26

Name

sort — Sorts list alphabetically.

Synopsis

sort list

Description

The sort command returns an alphabetically sorted list of the contents of list.

Example

puts [sort {padova rovigo verona vicenza venezia treviso belluno}]
	  

Produces:

belluno padova rovigo treviso venezia verona vicenza

Name

source — Evaluate Hecl script in a file.

Synopsis

source filename

Description

The source command evaluates the Hecl script located in file filename.

Example

# Variable foo is defined as "Hello world" in foo.hcl
source foo.hcl
puts $foo
	  

Produces:

Hello world

Name

split — Split a string into a list.

Synopsis

split string ?splitstring?

Description

The split command takes a string and splits it into a list, divided by splitstring, which defaults to " " if not present.

Example

puts [split "aaa;bbb;ccc" ";"]
puts [split "aaa bbb ccc"]
puts [split "aaaxbbbycccxyddd" "xy"]
	  

Produces:

aaa bbb ccc
aaa bbb ccc
aaaxbbbyccc ddd

Name

time — Time the execution of a script.

Synopsis

time script ?repetitions?

Description

The time command executes script repetitions times, if repetitions is present, or once if not. It measures the amount of time taken by this execution in milliseconds, and returns it.

Example

set time [time {
    set i 0
    while { < &i 100 } {
	incr &i
    }
} 10]
puts "Time is $time"
	  

Produces:

Time is 6

Name

true — Returns true.

Synopsis

true

Description

The true command returns 1, or true.

Example

if { true } {
    puts "true is true"
}
	  

Produces:

true is true

Name

upeval — Evaluate script in next stack frame up.

Synopsis

upeval script

Description

The upeval command evaluates script one stack frame up from the current stack frame.

Example

proc stackframe {} {
    upeval { incr &foo }
}
set foo 1
stackframe
puts $foo
	  

Produces:

2

Name

while — While loop.

Synopsis

while condition body

Description

The while command continues to evaluate body while condition is true.

Example

set i 0
while { < &i 6 } {
    puts "i is $i"
    incr &i
}
	  

Produces:

i is 0
i is 1
i is 2
i is 3
i is 4
i is 5

Interfacing Hecl and Java

Calling Hecl code from Java

Hecl is not a replacement for Java, and is indeed meant to work hand in hand with Java. We attempt to make it as easy as possible to call Java from Hecl, via the creation of new Hecl commands that can call Java code, in addition to calling Hecl from Java, which is a matter of a few lines of code. For example, to evaluate a Hecl file from Java:

	try {
	    /* First, create a new interpreter, and pass it a
	     * mechanism to load resources with - in this case,
	     * files. */
	    Interp interp = new Interp(new LoadFile());
	    /* Initialize the standard Hecl environment. */
	    com.dedasys.hecl.Standard.init(interp);
	    /* Evaluate the file at args[0] */
	    Eval.eval(interp, interp.getscript(args[0]));
	} catch (Exception e) {
	    System.err.println(e);
	}
      

The above code creates a new interpreter. Currently, interpreters must know about how they are to load files, so the interpreter is instantiated with a new LoadFile, which, as the name suggests, contains code to load files from the file system. Since Hecl is also meant for embedded environments, it is also possible to create loader classes that load code from a compiled-in string, from the network, or whatever other form of storage you may have in mind.

After creating the interpreter, we call Standard's init method. Standard is a class that adds several commands that we want to be present in standard J2SE Hecl, but not in J2ME Hecl.

The real work is done by Eval.init, which takes an interpreter and a Hecl Thing as arguments. In this case, the Thing is the Hecl code read from the disk by LoadFile.

Creating new Hecl commands

Creating new Hecl commands is relatively simple. The first step is to create a new class for your command in a file, say HelloCmd.java. The code would look something like this:

class HelloCmd implements Command {

    public void cmdCode(Interp interp, Thing[] argv)
	throws HeclException {

	System.out.println("Hello world");
    }
}
      

The command takes an interpreter and an array of Things as arguments, where the first Thing is the name of the command itself, and the others are the arguments to it.

The "glue" that connects the name of your Hecl command with the Java code is also relatively simple:

	interp.commands.put("hello", new HelloCmd());
      

Easy, no? There are a few other useful methods that you should be aware of, to share variables between Hecl and Java, and to return results from your Hecl commands:

  • interp.setVar(Thing varname,
                  Thing value);
    This sets the value of varname to some value.
  • Thing interp.getVar(Thing varname);
    Thing interp.getVar(String varname); These methods take a variable name, either in string form or as a Thing, and return the Thing associated with that variable.
  • interp.result is used to set the result of a command. This oft-used variable is accessed directly for simplicity, speed and smaller code size.

  • int IntThing.get(Thing thing); Get an int from a Thing.
  • String StringThing.get(Thing thing); Get a String from a Thing.
  • Thing IntThing.create(int i); Creates a new thing from an int.

JavaDocs

For the complete Hecl javadoc documentation, see the Hecl Javadocs. And, of course, look at the Hecl source code to see how it's done!

Hecl and J2ME

Hecl is designed to be small enough to run on mobile devices such as cell phones. This means that it has been necessary to limit ourselves to Java API's that work with J2ME.

FIXME - more later.