CHAPTER 1

Get Started

Welcome to Julian scripting language! Julian is designed with a main goal from its very inception: help programmers to quickly externalize the business code. "Hold on," you ask, "What does it even mean?" Consider the scenario where you work as a Java EE programmer with an exclusive focus on the web backend. You deal with server side code all day alone and one day you decide to move some of them into a scripting engine, so that all you have to do in case of upgrading a piece of logic is to throw the updated script file to a certain folder and let the server code pick that up. This fantastic plan is called externalization.

But here is one more decision to make - what language should you choose? Looking at the most popular JVM languages available out there, we have JavaScript, Ruby (as JRuby), Python (as Jython), Scala, Groovy, Clojure, etc... They are all really nice languages, except for one crucial problem - you don't know any of them. As our scenario premise implies, since your job is to focus on backend development in a managed environement, the chance is you are more familiar and most comfortable with one language - Java. So what you really want to have is that while moving the business logic outside Java, you still retain the option of writing those code in a much Java-like fashion.

One may argue, in this world today who doesn't know a little bit of other languages? That's probably true, in particular for the part of "a little bit". What is not true, though, is that one person masters two languages of different archetype in the same time. None of the JVM languages I listed above can be categorically grouped under the same genre of Java, which has an unmistakable emphasis on OO programming. Most other languages, except JavaScript, are essentially function-OO hybrid, and their grammars prominently differ from that of Java. JavaScript, on the other hand, is a completely different archetype in disguise of Java imitation. To learn these languages and their grammar would require days if not weeks; to master them to the extent that you can confidently implement the complex business model would be an even more intimidating task to achieve.

Fear no more. This is where Julian comes to rescue. Its grammar and semantics are almost identical to Java/C#, just with some minor exceptions. So just design your classes and interfaces like you would do with Java. As a plus, it also provides an easy way to interact with your Java code. Eventually you will feel that you can choose to implement a piece of code either inside or outside Java, but with same mindset and thinking process. This is what I call a fast externalization of business code.

  class MyClass : ParentClass, InterfaceA, InterfaceB { // Declare class with its single parent class and multiple interfaces
    private int v = 5;
    MyClass(v, s) : super(s){ // Calling ParentClass's constructor
        this.v = v;
    }
    int getValue(){
        return value + MyClass.BASE;
    }
    static const int BASE = 10; // static members
    void funa(){} // Implementing InterfaceA
    void funa(int v){} // Method overloading
    InterfaceB funb() { // Implementing InterfaceB
        return this; // Refer to current instance
    }
  }

Does the code snippet above look anything familiar? Absolutely - it will almost have no issue getting compiled in a C# compiler. Julian chooses a lot of C#'s syntax over Java's for its relatively lower burden on the programmer. But I am pretty sure a Java programmer can understand this in one or two seconds even if he has no previous experience on C#.

At this point, the language should appeal to people who have so far buried their heads into managed programming all day along. But I am also expecting some of them start lamenting. "I don't want to write these boilerplate code anymore." Right, a lot of code in here is unnecessary boilerplate and should not continue haunting your life outside Java world, especially because without a compiler-driven validation they make even less sense than ever. So here comes the second goal of Julian - help people to transit from compiled language to interpreted language. This means in long haul you would start dropping these Java-based grammars and start embracing syntax and utilities which is leaner, more smooth and more agile. This tutorial will turn to these changes in later chapters. But now, let's start from what you would normally expect from the perspective of a Java/C# programmer.

Obtain Julian

To download the latest version of Julian, you can visit our website and the first thing you see should be a downloading button. Get the zip file and uncompress it. Use your favorite text editor to create a file named 'hello.jul', into which you add

  Console.println("Hello Julian!");

Save the file, then go to command line and input,

~> cd JSE/bin
bin> .\jse.sh -f hello.jul

You will then be greeted from a Julian script.

Use Julian from command line

The Julian command line tool, as demonstrated above, is a great utility for you to get things started. As ordinary development routine, your "inner loop" of Julian development would look like something as this:

  1. write some Julian code
  2. call jse against an entrance file
  3. fix bugs, add features, go to 1

Now let's see what else we can do with it. First, instead of using a script file, you can just inline the script in an -s option.

bin> julian -s "Console.println(\"hello\");"
hello
bin> _

The most important feature this tool provides is the REPL experiences, i.e. Read-Evaluate-Print loop. Let's start the app with -i option, 'i' for interactive.

bin> julian -i

This will override -s or -f options, and show a prompt waiting for further inputs.

>>> _

Now the user can type in statements and expressions, ending with a line break to trigger immediate evaluation. The exception here is if the user starts defining a new type, a line break will not necessarily trigger evaluation. For now, let's only deal with some simple expressions. A chapter towards the end of this tutorial will dedicate to the usage of this console.

>>> 3
3
>>> int i = 2 + 3
>>> i
5
>>> fun()
something returned by fun()
>>> _

Use Julian with a Java scripting engine (JSR-223)

Once you are done with the development of your Julian code, you will probably want to integrate it into your Java codebase. There is a standard for this purpose, which is governed by JSR-223, Scripting for the Java Platform. For more details on the standard, see here, but what you really need to know for now is only a couple of lines of Java code.

  // (Java)
  import javax.script.*;
  
  ////////////////////////////////////////////////////

  ScriptEngineManager scm = new ScriptEngineManager();
  ScriptEngine se = scm.getEngineByName("julian");
      
  try {
    FileReader reader = new FileReader("path/to/your/Julian/script.jul");
    se.eval(reader);
  } catch (FileNotFoundException e) {
    throw new JSEException("Script file not found", e);
  }

To make this code work, you must place JSE jar into the ext directory of your Java runtime. For example, on a Window installation, this folder's path could be C:\Program Files\Java\jre1.8.0_xxx\lib\ext.

The knowledge you have obtained so far should have enabled you to try out various Julian features through chapter 8. When we start talking about modules, we will come back to these tools and Java routines to expand our knowledge on their usage.

Loose scripting v.s. structured programming

There are mainly two flavors of writing a Julian program. The first is called loose scripting, which involves a single file that contains business logic without an enclosing type. This file may also contain some type definitions, which must appear at the top of the file though.

  //////// script.jul ////////
  
  // define some types
  class MyClass {
    MyClass(int value) { }
  }
  class AnotherClass {
    int value;
  }
  
  // scripting starts here
  
  // define some functions
  MyClass convert(AnotherClass another){
    return new MyClass(another.value);
  }
  
  // some logic
  int total = 10;
  MyClass[] arr = new MyClass[total];
  for (i = 0; i < total; i ++){
    AnotherClass ac = new AnotherClass();
    ac.value = i;
    MyClass mc = convert(ac);
    arr[i] = mc;
  }
  
  // define another function
  void processAll(MyClass[] arr){
  
  }
  
  // continue scripting
  processAll(arr);
  Console.println("All done!");

Loose scripting is great for writing really small pieces of code and can prove very useful in debugging and developing process. However, if you find yourself defining a lot of types in a single file, it's more likely you would prefer the other approach, which we call structured programming.

In structured programming, types are defined in their own files. Each file may contain any number of type definitions, but nothing else beyond them. They absolutely do not contain any loose logic. Then we use a loose script to serve as the entrance, a.k.a. the main() function, to call into those types, but that will be all this script does.

A type file:

  //// MyClass.jul ////
  module MyModule;
  
  class MyClass {
    MyClass(int value) { }
  }

Another type file:

  //// AnotherClass.jul ////
  module MyModule;
  
  class AnotherClass {
    int value;
  }

A third type file:

  //// Process.jul ////
  module MyModule;
  
  class Process {
    static MyClass convert(AnotherClass another){
      return new MyClass(another.value);
    }
    
    static void processAll(MyClass[] arr){
    
    }
    
    static void main(){
      int total = 10;
      MyClass[] arr = new MyClass[total];
      for (i = 0; i < total; i ++){
        AnotherClass ac = new AnotherClass();
        ac.value = i;
        MyClass mc = convert(ac);
        arr[i] = mc;
      }
      processAll(arr);
    }
  }

The entrance file:

  //// main.jul ////
  import MyModule;
  
  Process.main();
  
  Console.println("All done!");

Then the user calls JSE against main.jul, which would load other type files in the process. If a type file contains any code outside type definitions, those extra lines will be ignored during loading. The loose script file, or the entrance file must not contain module declaration. The entrance function is named main() by convention, but this is not required.