/flatscript

A Javascript-targeting programming language that makes it easy to write asynchronous code in a synchronous way

Primary LanguageVim ScriptMIT LicenseMIT

Flatscript

Flatscript is a language and compiler that generates Javascript from a Python like language.

Installation and Build

Flatscript is implemented by Flatscript itself so it is not possible to build it from scratch.

You need to install it from npm before compiling it.

npm install -g flatscript

You could check the installation by the following command

flsc -h

Key Features

Javascript is famous for its callback-hell coding style. In Flatscript it allows developers to write code in synchronous styles and the compiler will translate it to asynchronous Javascript code.

For example, Flatscript code like:

for i range 10
    if i != 0
        setTimeout(%, 1000)
    console.log(i)

will print 0 up to 9 and "sleep" for 1 second between each print.

And for Flatscript code like:

fs: require('fs')
try
    console.log(fs.readFile('a.txt', %%) + fs.readFile('b.txt', %%))
catch e
    console.error(e)
console.log('end')

will work in this order

  • read "a.txt"
  • read "b.txt"
  • concatenate their content
  • output to console

If any error, like file not found, occurs, the work flow will be interrupted and the error will be caught and sent to stderr. A message "end" would get outputted in the end.

Those features are an alternative of ES7 await. The lexical token % and %% indicates the argument should be a callback whose body will be generated by the compiler from the latter part of the syntax tree.

To read a list of files and store their content, Flatscript code is like

fs: require('fs')
files: ['a.txt', 'b.txt', 'c.txt']
content: []
try
    for i range files.length
        content.push(fs.readFile(files[i], %%).toString())
    console.log('content', content)
catch e
    console.error(e)
console.log('end')

Or more simply, to use Flatscript pipeline syntax like (using pipeline mapping operator |:)

fs: require('fs')
files: ['a.txt', 'b.txt', 'c.txt']
try
    console.log('content', files |: fs.readFile($, %%).toString())
catch e
    console.error(e)
console.log('end')

To encapsule this into a regular asynchronous function (like an async function in ES7)

fs: require('fs')

func readFiles(fileList, %%)
    return fileList |: fs.readFile($, %%).toString()

try
    console.log('content', readFiles(['a.txt', 'b.txt', 'c.txt'], %%))
catch e
    console.error(e)
console.log('end')

Other Features

Indentation-indicated syntax

Code samples

# define a function that calculates fibonacci number
func fib(n)
    if n <= 1
        return 1
    return fib(n - 1) + fib(n - 2)

Ouput

function $Rfib($Rn) {
    if (($Rn <= 1)) {
        return 1;
    }
    return ($Rfib(($Rn - 1)) + $Rfib(($Rn - 2)));
}

Flatscript will also do some name mangling.

It is easy to break a long line into shorter ones, by hitting return after proper tokens. Code samples

['this', 'is', 'a',
    'long', 'list']

callFunction('with', 'several'
        , 'arguments')

x: a +
    b

Anonymous function in an easy way

Anonymous functions are written in this way without any keywords

(parameters):
    function-body

Code samples

fs.read('some-file', (error, content):
        console.log(content.toString())
    )

Output

fs.read("some-file", (function ($Rerror, $Rcontent) {
    console.log($Rcontent.toString());
}));

Convert synchronous code into asynchronous

In a call to a function which takes a callback with parameters (error, result), the callback argument could be represented as %%, and latter expressions and statements will become the body of the callback. The former and latter relationship is determined by the syntax tree, for instance, in the binary operation a + b, b is the latter of a. More detailed example:

func read(fileA, fileB, %%)
    return fs.read(fileA, %%) + fs.read(fileB, %%)

JS code generated as (demangled)

function read(fileA, fileB, $racb) {
    fs.read(fileA, (function (err, $ar_0) {
        if (err) return $racb(err);
        fs.read(fileB, (function (err, $ar_1) {
            if (err) return $racb(err);
            return $racb(null, $ar_0 + $ar_1);
        }));
    }));
}

Similarly, in a call to a function which takes a callback with no parameters, the callback could be represented as %. For example

console.log(0)
setTimeout(%, 1000)
console.log(1)
setTimeout(%, 1000)
console.log(2)

JS code generated as

console.log(0);
setTimeout((function() {
    console.log(1);
    setTimeout((function() {
        console.log(2);
    }), 1000);
}), 1000);

Pipe a list into a result

It uses a pipeline to iterate over a list. Pipeline operators are |: and |?. The former represents a mapping operation while the latter represents a filtering. Within a pipeline, use $ to reference the element, and $i for the index.

Code sample

x: [1, 1, 2, 3, 5, 8, 13]
console.log(x |: $ * $)
console.log(x |? $ % 3 = 1)
console.log(x |: $i % 2 = 0)
console.log(x |? $i % 2 = 0)

Results

[1, 1, 4, 9, 25, 64, 169]
[1, 1, 13]
[true, false, true, false, true, false, true]
[1, 2, 5, 13]

Pipeline could be used along with regular asynchronous calls.

Code sample

func readFiles(fileList, %%)
    fileContent: fileList |: fs.read($, %%)
    return fileContent.join('')

Output

function $RreadFiles($RfileList, $racb) {
    var $RfileContent;
    var $ar_0 = (function ($list) {
        function $next($index, $result) {
            var $key = null;
            if ($index === $list.length) {
                $RfileContent = $result;
                return $racb(null, $RfileContent.join(""));
            } else {
                var $element = $list[$index];
                fs.read($element, (function ($cb_err, $ar_1) {
                    if ($cb_err) return $racb($cb_err);
                    $result.push($ar_1);
                    return $next($index + 1, $result);
                }));
            }
        }
        $next(0, []);
    })($RfileList);
}

Use the Compiler

Run

Flatscript will read source code from stdin or a file (with -i option), and print Javascript via stdout, or to a file (with -o option). The ordinary ways to compile

flsc < source.fls > output.js
flsc -i source.fls -o output.js

Or pipe the program to node

flsc < source.fls | node
flsc -i source.fls | node

FAQ

Why the compiler complains names like 'require'/'document'/'window' not defined?

Flatscript checks name definition at compile time, and it is not possible to use any name that is not defined or not marked as external.

You could declare external names via -e option, like

flsc -e document -e window -i client/source.fls > client/output.js
flsc -e document:window -i client/source.fls > client/output.js
flsc -e require -i server/source.fls -o server/output.js

Or using extern statement in the source file:

extern require
fs: require('fs')
console.log(fs.readFile('a.txt', %%))

How could I use jQuery in Flatscript?

Use jQuery the identifier instead of $ because $ means the current list element in pipeline context, like

buttons: jQuery('.btn')

For More Information

Please read the wiki pages.