{ mini } |
About:
Miniscript is a minimal dialect of Javascript. It is influenced by the simplicity of Python and Ruby, and
inherits some good parts of other popular languages. Redundant semicolons, parentheses and brackets are
removed, repeating patterns have been reduced and statements have been "mini"mized.
Install:
You can install the miniscript compiler using the following command:
$ npm install -g miniscript
Then you got mini as the main command from your console
$ mini //Launches interactive console
$ mini -r //Runs specified script
$ mini -c //Compiles given script
$ mini -d //Compiles a folder recursively
If file extensions are omitted, they are assumed to be ".mini" and ".js" where applicable.
On the last two commands you can specify the output using "-o" switch.
Documentation:
Same as javascript, with some minor differences. First one is that ".1" and "1." decimal literals have been removed. It is advised to use "0.1" and "1.0" instead for better readability. Object definitions don't need commas to separate properties, but if you plan to write them in one line a comma separator makes them much more readable.
person = {
name = {
first = 'John'
last = 'Smith'
}
height = 1.76, weight = 65.0
hair = 'brown'
}
var person;
person = {
name: {
first: 'John',
last: 'Smith'
},
height: 1.76,
weigth: 65.0,
hair: 'brown'
};
Functions in miniscript are anonymous and they are assigned to a variable. Lambdas follow the same principle but they don't have a body and return a value instead, just like any mathematical function.
foo = (bar, baz) {
print bar, baz
}
//Parenthesis for 0 or 1 arguments is optional
full = () => null
negate = x => -x
add = (a, b) => a + b
add23 = (a, b => a + b)(2, 3)
var foo, full, negate, add, add23;
foo = function (bar, baz) {
console.log(bar, baz);
};
full = function() {
return null;
};
negate = function(x) {
return -x;
};
add = function(a, b) {
return a + b;
};
add23 = (function(a, b) {
return a + b;
})(2, 3);
Sequences have an initial value, an end value and an optional step. The step can be either a positive number or a function literal. Be careful with these expressions, as a careless implementation may lead to infinite loops and memory exhaust. The sequence expression of the first example is flattened inside the array because its value is known in compile time. Maybe in future versions dynamic sequences will have the same effect.
countdown = [10 to 1, 'Go!']
even = 1 to 9 by 2
pow2 = 1 to 1024 by (a => 2 * a)
//next = callback(result[i-1], result[i])
fib = [0, 1] to 1e+9 by (a, b => a + b)
String::next =
=> String.fromCharCode(this.charCodeAt(0) + 1)
az = 'a' to 'z' by (a => a.next())
var _utils = require("miniscript/utils");
var countdown, even, pow2, fib, az;
countdown = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 'Go!'];
even = [1, 3, 5, 7, 9];
pow2 = _utils.recursion(1, 1024, (function(a) {
return 2 * a;
}), false);
fib = _utils.recursion([0, 1], 1e+9, (function(a, b) {
return a + b;
}), false);
String.prototype.next = function() {
return String.fromCharCode(this.charCodeAt(0) + 1)
};
az = _utils.recursion('a', 'z', (function(a) {
return a.next();
}), false);
Same as Javascript plus simple array destructuring. More will come in the next version.
x = y = 1
y += x += 3
x, y = y, x //same as x, y = [y, x]
arr = => 1 to 100
n1, n2, n3 = arr()[5..]
var _utils = require("miniscript/utils");
var x, y, _t, arr, n1, n2, n3;
x = y = 1;
y += x += 3;
_t = [y, x], x = _t[0], y = _t[1];
arr = function() {
return _utils.range(1, 100, 1);
};
_t = arr().slice(5), n1 = _t[0], n2 = _t[1], n3 = _t[2];
Parentheses and brackets are removed from the if statement. Switch cases are isolated and default is always the last case if needed.
//...
if req.url is '/favicon.ico'
img = fs.readFileSync('./favicon.ico')
res.writeHead(200,{"Content-Type"= "image/x-icon"})
res.end(img, 'binary')
elif fs.exists(res.url)
page = fs.readFileSync(res.url).toString()
res.writeHead(200, {"Content-Type"= "text/html"})
res.end(page)
else
res.writeHead(404, {"Content-Type"= "text/plain"})
res.end('Error 404: Page not found')
end
//...
x = Math.floor(Math.random() * 3) + 1
switch x
case 1, 2
print 'x is 1 or 2'
case 3
print 'x is 3'
default
print 'whatever'
end
var img, page, x;
if (req.url === '/favicon.ico') {
img = fs.readFileSync('./favicon.ico');
res.writeHead(200, {"Content-Type": "image/x-icon"});
res.end(img, 'binary');
} else if (fs.exists(res.url)) {
page = fs.readFileSync(res.url).toString();
res.writeHead(200, {"Content-Type": "text/html"});
res.end(page);
} else {
res.writeHead(404, {"Content-Type": "text/plain"});
res.end('Error 404: Page not found');
}
x = Math.floor(Math.random() * 3) + 1;
switch (x) {
case 1:
case 2:
console.log('x is 1 or 2');
break;
case 3:
console.log('x is 3');
break;
default:
console.log('whatever');
}
The default for statement has been deleted from the grammar and it is replaced by many abstractions. While and Do-While remain the same.
array = []
for 5 times
array.push(1 to 3)
end
sum = 0
for index i, j of array
sum += array[i,j]
end
print "Sum is %sum"
users = [
{ name='john', pass='1234' },
{ name='alex', pass='413x' }
]
for each name, pass in users
print "Name: %name\tPass: %pass"
end
//Default ES6 'for-of'
for num of array[0]
print num
end
write 'Odd numbers:'
for i of 9 to 1 by 2
write ' %i'
end
write \n
for idx, num of array[0]
print "Index: %idx\tValue: %num"
end
//Default 'for-in'
for key in users[0]
print key
end
for key, val in users[0]
print "Key: %key\tValue: %val"
end
while true
break
end
do
break
while true;
var array, _i, _len, sum, i, j, _len1, users, name, pass, _arr, num, idx, key;
array = [];
for (_i = 0, _len = 5; _i < _len; ++_i) {
array.push([1, 2, 3]);
}
sum = 0;
for (i = 0, _len = array.length; i < _len; ++i) {
for (j = 0, _len1 = array[i].length; j < _len1; ++j) {
sum += array[i][j];
}
}
console.log("Sum is " + sum);
users = [{name: 'john', pass: '123'}, {name: 'alex', pass: '413x'}];
for (_i = 0, _len = users.length; _i < _len; ++_i) {
name = users[_i].name;
pass = users[_i].pass;
console.log("Name: " + name + "\tPass: " + pass);
}
for (_i = 0, _arr = array[0], _len = _arr.length; _i < _len; ++_i) {
num = _arr[_i];
console.log(num);
}
process.stdout.write('Odd numbers:');
for (i = 9; i >= 1; i -= 2) {
process.stdout.write(' ' + i);
}
process.stdout.write("\n");
for (idx = 0, _arr = array[0], _len = _arr.length; idx < _len; ++idx) {
num = _arr[idx];
console.log("Index: " + idx + "\tValue: " + num);
}
_arr = users[0];
for (key in _arr) {
console.log(key);
}
_arr = users[0];
for (key in _arr) {
val = _arr[key];
console.log("Key: " + key + "\tValue: " + val);
}
while (true) {
break;
}
do {
break;
} while (true);
A bit of Ruby with C++ extension style creates miniscript's classes.
The new
function is the constructor of the
class while the other variables define the prototype of the class. Types
are also classes with some optional members defined in braces. They are
often used for some simple class definitions or for specifying general classes.
import lodash as _
type Point {x, y}
class Polygon
toString = () =>
"%.constructor.name: %JSON.stringify(.points)"
new = (points) {
if _.every(points, p => p instanceof Point)
.points = points
else
throw new Error("Bad arguments")
end
}
end
points = [new Point(0,0), new Point(4,0), {x=0,y=3}]
try
triangle = new Polygon(points)
catch e
print e
end
var _, points, triangle;
_ = require('lodash');
function Point(x, y) {
this.x = x;
this.y = y;
}
function Polygon(points) {
if (_.every(points, function(p) {
return p instanceof Point;
})) {
this.points = points;
} else {
throw new Error("Bad arguments");
}
}
Polygon.prototype.toString = function() {
return this.constructor.name + ": " +
JSON.stringify(this.points);
};
points = [new Point(0, 0), new Point(4, 0), {x: 0, y: 3}];
try {
triangle = new Polygon(points);
} catch (e) {
console.log(e);
}
Below are two examples of how you extend objects. Super must be called when
a class is extended and a custom cunstructor with arguments is defined.
Super must be at the first line of the constructor else a syntax error is thrown.
In the constructor's argument list you can use
or just this
.arg.arg
for assigning class members same as the constructor's arguments.
type Animal { group, colors, legs, sound }
class Dog : Animal
new = (.name, colors) {
super('mammal', colors, 4, 'woof woof')
//just for showcase, if keyword this
//is used as the first argument in a
//function or lambda definition then
//it binds the current value of this
.breed = this => .constructor.name
}
pet = () {
print '%.name: %.sound!'
}
end
type Corgi : Dog
mydog = new Corgi('frisky', ['black', 'brown', 'white'])
mydog.pet()
print mydog.breed()
var _utils = require("miniscript/utils");
var mydog;
function Animal(group, colors, legs, sound) {
this.group = group;
this.colors = colors;
this.legs = legs;
this.sound = sound;
}
function Dog(name, colors) {
Animal.call(this, 'mammal', colors, 4, 'woof woof');
this.name = name;
this.breed = (function(_this) {
return function() { return _this.constructor.name }
})(this);
} _utils.extend(Dog, Animal);
Dog.prototype.pet = function () {
console.log(this.name + ': ' + this.sound + '!');
};
function Corgi() {
return Dog.apply(this, arguments);
} _utils.extend(Corgi, Dog);
mydog = new Corgi('frisky', ['black', 'brown', 'white']);
mydog.pet();
console.log(mydog.breed());
type Point {x, y}
type Point3D : Point {z}
//z=0 x=1 y=2
print new Point3D(0, 1, 2)
var _utils = require("miniscript/utils");
function Point(x, y) {
this.x = x;
this.y = y;
}
function Point3D(z) {
Point.apply(this, [].slice.call(arguments, 1));
this.z = z;
} _utils.extend(Point3D, Point);
console.log(new Point3D(0, 1, 2));
Import replaces the most common require
cases. The name chosen
for the files is the last identifier of the module expression.
Inside a string the name chosen is the filename/packagename with
a valid variable name; spaces, dots and dashes are allowed and are
turned into underscores. Import doesn't accept variables so require
should be used. Export keyword is used to create a module.exports
object.
import http, fs
import fs.readFileSync as read
import '../run test.js'
run_test.example(1)
//file: run test.js
export example = (n) {
try
require('./example' + n + '.js')
catch err
print 'example not found'
end
}
//endfile
import '../class.js'.TreeNode
print new TreeNode(1, null, new TreeNode(2, null, null))
//file: class.js
export type TreeNode { val, left, right }
var http, fs, read, run_test, example, TreeNode;
http = require('http'), fs = require('fs');
read = require('fs').readFileSync;
run_test = require('../run test.js');
run_test.example(1);
example = function (n) {
try {
require('./example' + n + '.js');
} catch(err) {
console.log('example not found');
}
}
TreeNode = require('../class.js').TreeNode;
console.log(new TreeNode(1, null, new TreeNode(2, null, null)));
function TreeNode(val, left, right) {
this.val = val;
this.left = left;
this.right = right;
}
//each file will have one value exported
module.exports = {
example: example,
TreeNode: TreeNode
};
In future versions there may be a multiple catch statement
with syntax like: catch
SyntaxError
err
//extending Error won't work properly because "javascript"
class error
new = (.message) {
.name = .constructor.name
.stack = (new Error).stack
}
end
type SuperError : error
try
throw SuperError("Nothing special")
catch err
if err instanceof SuperError
print "super: " + err.message
end
finally
//Optional as usual
end
var _utils = require("miniscript/utils");
function error(message) {
this.message = message;
this.name = this.constructor.name;
this.stack = (new Error).stack;
}
function SuperError() {
return error.apply(this, arguments);
} _utils.extend(SuperError, error);
try {
throw SuperError("Nothing special");
} catch(err) {
if (err instanceof SuperError) {
console.log("super: " + err.message);
}
} finally {
}
For now error stack will display the lines and collumns of the generated file
instead of the actual ".mini" file. The .member
that is automatically
turned into
has a problem being
applied inside strings (non-interpolated). String interpolation is detected with a
simple regural expression for now so it can't parse parentheses inside parentheses
and other recursive patterns. Same goes with regular expressions, if you happen to
have 2 divisions in one line then the compiler will try to parse it as a regex, in
the next version this will be fixed. Below are the workarounds for the this
.member.member
problem.
import d3
//problematic
svg = d3.select("body").append("svg")
.attr("width", 300)
.attr("height", 300)
print " .smth"
var d3, svg;
d3 = require('d3');
svg = d3.select("body").append("svg");
this.attr("width", 300);
this.attr("height", 300);
console.log(" this.smth");
import d3
//python style
svg = d3.select("body").append("svg") \
.attr("width", 300) \
.attr("height", 300)
print " \.smth"
var d3, svg;
d3 = require('d3');
svg = d3.select("body").append("svg")
.attr("width", 300)
.attr("height", 300);
console.log(" .smth");
import d3
//standard style
svg = d3.select("body").append("svg").
attr("width", 300).
attr("height", 300)
print ".smth"
var d3, svg;
d3 = require('d3');
svg = d3.select("body").append("svg").
attr("width", 300).
attr("height", 300);
console.log(".smth");