Embedthis Embedthis
Embedthis
Embedthis

Ejscript™ Language Tour

This will be a quick tour of Ejscript to give you a feel for the language, it's constructs and capabilities.

First make sure you have Ejscript installed on your system so you can type along as we go. This tour will work with the ejs command shell to run small scripts.

In this documentation command sessions are presented in blue boxes. Output from the system is highlighted in yellow. Bold yellow is used to highlight some output of specific interest. You can type these examples interactively into the shell or you can edit a file and run the file using "ejs filename".

Running the shell

Type ejs to run the Ejscript command shell. Once inside the shell, you can type Ejscript programs for immediate execution.

home> ejs
ejs-0>
				

Hello World

Every tour beings with the obligatory "Hello World" program. To print output, we use the print function.

ejs-0> print("Hello World")
Hello World

				

The ejs shell will also echo the last result to the console, so when inside the shell, the minimal hello world program is just the string "Hello World" itself.

ejs-0> "Hello World"
Hello World
				

Names

In Ejscript, variable and function names must begin with an alphabetic underscore "_" or dollar "$" character. The rest of the name may contain any number of alphabetic, underscore, dollar or numeric characters. These are all valid names with declarations and assignments.

ejs-0> var x
ejs-0> var $y
ejs-0> var __myColor
ejs-0> function print43() {}
				

Numbers and Expressions

You can use the ejs shell to calculate simple arithmetic:

ejs-0> 1 + 2
3
ejs-0> 1.5 * 2.5
3.75
				

Functions

Typing code into an interpreter is fun, but frequently you want to reuse some code again and again. For that we create functions which are reusable procedures.

ejs-0> function add(x, y) {
ejs-2>   return x + y
ejs-2> }
                    
				

To call a function, you

ejs-0> add(1,2)
3
ejs-0> add(5,5)
10
				

What is expecially useful, is that functions are objects and you can assign them to variables and pass them as arguments to other functions. More on this later.

ejs-0> var math = function (x, y) { return x + y; }
ejs-0> math(100, 200)
300
				

Classes

Classes are a new addition to Javascript coming in the ECMAscript Edition 4 specification. Fortunately they follow a familiar paradigm.

Classes enable us to group variables and a set of functions to operate on those variables. More importantly, we can create many object instances of a class.

ejs-0> class Shape {
ejs-2>   var height
ejs-2>   var width
ejs-2>   function Shape(length, depth) {
ejs-2>     height = length;
ejs-2>     width = depth;
ejs-2>   }
ejs-2> }
				

This code defines a new class call Shape. It has two property variables called height and width. These are created per object instance. We also have a special function called Shape(). This is a constructor and is called when we createa a new shape. We can now create as many shapes as we like. We use the new keyword to create a new shape. Each time, the constructor is called to initialize the shape.

ejs-2> s = new Shape(5, 10)
[object Shape]
ejs-2> s2 = new Shape(5, 5)
[object Shape]
				
We can also create new derrived shape classes using Shape as a base.
ejs-0> class Circle extends Shape {
ejs-2>   var radius
ejs-2>   function Circle(radius) {
ejs-2>     super(radius, radius)
ejs-2>   }
ejs-2> }
ejs-0> circle = new Circle(25)
[object Circle]
				

Once we've created an instance of an object, we can access the properties of that object using dot notation.

ejs-0> s = new Shape(100, 200)
ejs-0> print(s.height)
100
				

Objects

You can also create objects without having to create a class. Object literals provide a convenient way to create and initialize an object in one step:

ejs-0> s = { height: 500, width: 400 }
ejs-0> print(s.height)
500
ejs-0> serialize(s)
{
  height: 500,
  width: 400,
}
				

In this example, we also use the serialize function to convert the object "s" into a string exposing all the properties and their values. Object literals can also be nested.

Hashes

In Ejscript, objects are frequently used as hashes to store key / value pairs.

ejs-0> hash = new Object
ejs-0> hash["red"] = "Favorite color"
ejs-0> hash["blue"] = "Next favorite"
ejs-0> hash["green"] = "Least likely"
ejs-0> print(hash["red"])
Favorite Color
				

To access hash elements, use "[]" to lookup by a key string.

Classes and Interaces are also objects in their own right. You can store them and assign them. This is very useful in creating type factories. Continuing the previous example, we can store the Shape and Circle class types into a factory hash.


ejs-2> factory = {}
ejs-0> factory["shape"] = Shape
ejs-0> factory["circle"] = Circle
ejs-0> kind = "shape"
ejs-0> thing = new factory[kind]
				

In this example we used "factory = {}". This is a fequently used shorthand for saying factory = new Object.

Arrays

Arrays are created using new. There are several forms of constructor usage for Arrays, the most common is to specify the size of the array.

ejs-0> a = new Array(10)
ejs-0> print(a.length)
10
				
You can also create an Array using an Array literal syntax by enclosing the desired array elements inside square brackets.
ejs-0> b = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
ejs-0> print(b.length)
10
ejs-0> print(b)
0,1,2,3,4,5,6,7,8,9
				

You can print the length of the array using the ".length" getter property. You can also convert the array to a string and print it out.

Type Annotations

So far we've just declared variables and not specified their type. At run time, the Ejscript virtual machine manages the type of the variables. Sometimes known as Duck-typing, this is certainly easier than having to pre-declare the types of all variables. But there are cases where it is highly desirable to specify the type a variable can hold. Instead of typing "String name;" as you would in Java, in Ejscript you use:

var name: String
				

This defines a new variable called "name" and specifies that the variable will always hold values of type string. In Ejscript type annotations come after the variable and are optional. You can choose which variables, functions and parameters you wish to type.

Function arguments and function return values can also be typed:

function add(x: Number, y: Number): Number {
  return x + y
}
				

So when is it desirable to annotate your variable or function declarations with types?

  • When you are creating a class library and you want to specify exactly what types are acceptable as arguments to functions
  • You want your code to run as fast as possible. By adding type annotations, the compiler can perform optimizations to bind property accesses into actual storage references.
  • You want your code to be robust and you want the compiler and Virtual Machine to catch type mismatches for you as early as possible.

Another general rule is that when you are creating code that will be used by others and they need to have it clearly documented, it is often best to use type annotations.

Statements

Ejscript statements are C-like and include: break, case, cast, class, continue, catch, class, default, delete, do, finally, for, for/in, for each, function, if/else, include, interface, let, new, return, super, switch, try, use and var.

Let's use a few (we'll omit the shell prompts in this example):

/*
* Can use either C or C++ style comments
*/ class MyDemo { function test(low, high) { let i, j; // Declare locals with let let objects = new Array(10) // Create an array of ten elements for (i in 10) { // Classic for loop objects[i] = new Object // Store a new object } for each (o in objects) { // Iterate over all the objects try { // Can do something here to each object } catch (error) { print("Got an error: " + error) } } return low < high ? low : high } } var demo = new MyDemo demo.test(0, 10)

There were a couple of unusual constructs in that snippet to discuss:

Let Declarations and Block Scope

Variables may be declared with either var or let. Let will always define variables at the block level in which they are declared. ie. always block scoped. In ECMAScript, var declarations always hoist to nearest function, class or global space. This can cause problems and confusion, but ECMAScript is stuck due to compatibility considerations for legacy browser applications. Ejscript always (unless running in ECMA compliance mode) treats var declarations the same as let declarations. ie. always block scope. This uniformity greatly simplifies development.

For in

The "for (i in 10)" statement seems a little unusual. Isn't 10 a primitive type? The answer is yes, but Ejscript treats all numbers as objects. You can invoke methods on numbers and you can iterate through their values. This for/in statement will iterate through all the numbers from 0 up to, but not including 10. ie. 0-9. Much easier than typing "for (i = 0; i < 10; i++)" eh?

Iteration

Ejscript has powerful iteration capabilities and most types have built-in iterators for common tasks. Here are a few examples:

To loop through the characters in a string:

ejs-0> for each (s in "Hello")
ejs-0>   print(s)

H
e
l
l
o
				

To count to 100

ejs-0> for each (i in 100)
0
1
...
99
				

Scope

Ejscript is a lexically scoped language. What this means is that variables are visible by default in the block in which they are declared and in inner blocks.

In this example, the variable factor is visible inside the function increment, but the variable temp is local to the function and not visible outside.

ejs-0> var factor = 10
ejs-0> function increment(x) {
ejs-0>   var temp = x * x
ejs-0>   return temp + factor       // Variable factor is visible
ejs-0> }
ejs-0> // Variable temp is not visible here
				

But what if we declare another variable called "factor" inside the function. Consider this:

ejs-0> var factor = 10
ejs-0> function increment(x) {
ejs-0>   var factor = 1
ejs-0>   var temp = x * x
ejs-0>   return temp + factor       // factor == 1
ejs-0> }
ejs-0> // Variable temp is not visible here
				

This 2nd declaration of factor inside the function shadows the first declaration only while inside the function. Once outside, the first declaration is visible again.

These scoping rules apply to functions, classes, and indeed any blocks of code.

Namespaces

To help control the visibility of names, they can be given namespace qualifiers. Think of this as a two-dimensional variable name space. Instead of just the variable name, the fully qualified variable name consists of the name plus the namespace name.

Namespaces are useful to prevent names from different contexts clashing. In other languages such as Java, packages and imports are used to group and seggregate declarations. In Ejscript, namespaces are also used for this purpose.

With namespaces you can now have two variables named x in the same block.

ejs-0> var x = "first"
ejs-0> var "com.embedthis"::x = "second"
ejs-0> print(x)
first
ejs-0> print("com.embedthis"::x)
second
				

The second variable declaration is qualified by "com.embedthis" and is a separate declaration to the first. The namespace qualifier can either be a literal string or can be a variable of the type Namespace. The qualifier is separated from the name by the "::" delimiter.

While that looks okay, I hear you ask "surely there is a more convenient way to use namespaces?". Yes, there is:

ejs-0> var x = "first"
ejs-0> print(x)
first
ejs-0> use default namespace "com.embedthis"
ejs-0> var x = "second"
ejs-0> print(x)
second
				

The second declaration of x is defined with the "com.embedthis" qualifier. By placing a "use default namespace" at the top of a file, then all subsequent new declarations use that namespace qualifier.

Closures

To complete the discussion about Scope, Ejscript has an important and very powerful construct called a closure. This is where a function can be captured in the context in which it is defined. The capture includes its full lexical scope and if it is defined in a class, then the instance this reference is also captured.

Closures and method capture are most useful when you wish to pass a function with its execution context as a parameter to another function.

ejs-0> class Rectangle {
ejs-0>   var color = "red"
ejs-0>   function render() {
ejs-0>     print("In render: color " + color)
ejs-0>   }
ejs-0> }
ejs-0> box = new Rectangle
ejs-0> new Timer(5000, box.render)
				

This example creates a class called Rectangle and creates an instance called box. It then creates a timer which will invoke the render method of the rectangle in 5 seconds. When box.render is passed as a parameter, Ejscript captures the value of the this pointer and remembers it. When the timer later invokes the render method, it invokes it with the right instance of Rectangle.

Exceptions

Frequently things go wrong in a program. Exceptions allow you to structure your error handling code without obsecuring the main logic flow.

ejs-0> try {
ejs-0>   x = Object.missing
ejs-0> }
ejs-0> catch (error) {
ejs-0>   print("Caught the error")
ejs-0> }
Caught the error
				

Ejscript exceptions allow us to "try" something and if an error occurred, then we catch the error in a dedicated block of code.

Script Library

Ejscript comes with an extensive class library. It includes class for Events, Timers, File I/O, HTTP, Sockets, XML and much more. To give you a feel for the library, here is how to read lines from a file:

ejs-0> for each (line in File.getAsLines("filename")) {
ejs-0>   print(line)
ejs-0> }

 

© Embedthis Software LLC, 2003-2008. All rights reserved. Privacy Policy and Terms of Use. Updated June 19, 2008.