Ejscript Language Tour

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

First make sure you have completed the Quick Start and that you have Ejscript installed on your system so you can type along as we go. This tour will work with the Ejscript 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

or print as hex:

ejs-0> "%x" % 50000
c350 

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:

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

Especially 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 that follows a very 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 called 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 create a new shape object. We can now create as many shapes as we like via the new keyword. 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 derived shape classes using the Shape class 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 Interfaces 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 frequently used shorthand for saying factory = new Object.

Arrays

Arrays are created using the new keyword. 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 function. 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 a postfix type annotation. For example:

var name: String

This defines a new variable called name that will always hold strings. 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?

Another general rule is that when you are creating code that will be used by others and they need to have it clearly documented, 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. i.e. always block scoped. In ECMAScript, var declarations always hoist to the nearest enclosing function, class or global space.

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. i.e. 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. This means 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>   let 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 second 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 segregate 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. Namespaces are used most frequently in modules.

Modules

The most common need for Namespaces is to package related code so that it will not have name clashes with other code declarations. Java uses packages and import directives for this purpose, Ejscript uses modules — based on Namespaces. A module directive groups variables, functions and classes into a single namespace. A module can be imported via the "require" directive.

ejs-0> module Test {
ejs-0>     var x = "hello"
ejs-0> }
ejs-0> require Test
ejs-0> print(x)
hello

This code will create a module Test with a variable X that is actually named: "Test::x". i.e it is prefixed with the namespace "Test". A typical paradigm is to create modules patterned after a domain name such as "com.embedthis.Test".

If using the ec stand-alone compiler, the Test module will be compiled into a separate byte code file called "test.mod".

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 the function is a method in a class, then the this instance object will also be 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 box (this) reference 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 obscuring the main logic flow.

ejs-0> try {
ejs-0>   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 Path("filename").readLines()) {
ejs-0>   print(line)
ejs-0> }

© Embedthis Software. All rights reserved.