/plainscript

A very plain scripting language

Primary LanguageTypeScriptMIT LicenseMIT

PlainScript

PlainScript is a very plain scripting language. Its only purpose is to serve as a starting point for student language design and implementation projects.

def sum_of_digits(n):
    if n < 0:
        return sum_of_digits(-n)
    elif n < 10:
        return n
    else:
        return sum_of_digits(n / 10) + (n % 10)

print(sum_of_digits(n: 8835299))

The Language

The language is described in some detail at the language home page.

Using PlainScript

The PlainScript compiler is now written in TypeScript! This means running PlainScript requires building the source code before being able to run the compiler. Clone the repository, and in its root directory run one of the following commands depending on your preferred package manager. If yarn:

yarn && tsc

If npm:

npm i && tsc

The compiled JavaScript is output to the built folder. You can now run node built/PlainScript.js to run the compiler!

A PlainScript Compiler

This project hosts a simple compiler that reads a PlainScript program from a file, translates it to JavaScript, and outputs the JavaScript code to standard output. It supports three options, -a (to display the abstract syntax tree then stop), -i (to display the analyzed semantic graph then stop), and -o (to turn optimizations on). Given the PlainScript program:

let x = 5 + 8
if true:
  print(-x)

invoking the compiler with the -a option outputs the abstract syntax tree for the program to standard output:

$ node built/plainscript.js -a example.pls

produces:

Program {
  statements:
   [ VariableDeclaration {
       ids: [ 'x' ],
       initializers:
        [ BinaryExpression {
            op: '+',
            left: NumericLiteral { value: 5 },
            right: NumericLiteral { value: 8 } } ] },
     IfStatement {
       cases:
        [ Case {
            test: BooleanLiteral { value: true },
            body:
             [ CallStatement {
                 call:
                  Call {
                    callee: IdentifierExpression { id: 'print' },
                    args:
                     [ Argument {
                         id: null,
                         expression: UnaryExpression { op: '-', operand: IdentifierExpression { id: 'x' } } } ] } } ] } ],
       alternate: null } ] }

The -i flag does semantic analysis, and writes out the decorated abstract syntax tree. So:

$ node built/plainscript.js example.pls -i

produces:

Program {
  statements:
   [ VariableDeclaration {
       ids: [ 'x' ],
       initializers:
        [ BinaryExpression {
            op: '+',
            left: NumericLiteral { value: 5 },
            right: NumericLiteral { value: 8 } } ],
       variables: [ Variable { id: 'x' } ] },
     IfStatement {
       cases:
        [ Case {
            test: BooleanLiteral { value: true },
            body:
             [ CallStatement {
                 call:
                  Call {
                    callee:
                     IdentifierExpression {
                       id: 'print',
                       referent:
                        FunctionObject {
                          id: 'print',
                          params: [ Parameter { id: '_', defaultExpression: null } ],
                          body: null,
                          requiredParameterNames: Set { '_' },
                          allParameterNames: Set { '_' } } },
                    args:
                     [ Argument {
                         id: null,
                         expression:
                          UnaryExpression {
                            op: '-',
                            operand: IdentifierExpression { id: 'x', referent: Variable { id: 'x' } } } } ] } } ] } ],
       alternate: null } ] }

Finally, here’s an example performing a full translation to JavaScript:

$ node built/plainscript.js example.pls
function print_1(_) {console.log(_);}
function sqrt_2(_) {return Math.sqrt(_);}
let x_3 = (5 + 8);
if (true) {
  print_1((- x_3));
}

You can use the optimization flag -o with our without the -i flag. In our little example, the optimizer will detect a constant folding opportunity and a conditional case that is always executed. The resulting JavaScript will be:

$ node built/plainscript.js example.pls
function print_1(_) {console.log(_);}
function sqrt_2(_) {return Math.sqrt(_);}
let x_3 = 13;
print_1((- x_3));

The compiler does not (yet) perform copy propagation and dead code elimination. That would be nice.