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) 10You 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?
- When you are creating a class library and you want to specify exactly what types are acceptable as arguments to functions.
- When 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.
- When you want your code to be robust and you want the compiler and Virtual Machine to convert types or 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, 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> }