Install
Homebrew
brew tap gravatalonga/ninja-lang
brew install ninja-lang
YUM / RPM
To enable, add the following file /etc/yum.repos.d/ninja.repo
:
[ninja]
name=Ninja Programming Language
baseurl=https://yum.fury.io/gravatalonga/
enabled=1
gpgcheck=0
Check if correctly created
yum --disablerepo=* --enablerepo=ninja list available
To install you only need run following command:
yum install ninja-lang
APT
To configure apt access, create a following file /etc/apt/sources.list.d/ninja.list
with content of :
deb [trusted=yes] https://apt.fury.io/gravatalonga/ /
Or use this one line command:
echo "deb [trusted=yes] https://apt.fury.io/gravatalonga/ /" > /etc/apt/sources.list.d/ninja.list
and them you can install
sudo apt install ninja-lang
Manual Download
Download from github
Manual Installation
git clone https://github.com/gravataLonga/ninja
cd ninja
go build -o ninja-lang
Documentation
For more detail about language, you can check here (Still working in progress).
Demo
Resolved katas from this website
Syntax
Variable
var <identifier> = <expression>;
Examples
var a = 1;
var a1 = "Name";
var b = 2.0;
var c = a + 1;
var d = a + b;
var e = ++a;
var f = function () {};
var g = [1, 2, 3, "hello", function() {}];
var h = {"me":"Jonathan Fontes","age":1,"likes":["php","golang","ninja"]}
var i = a < b;
var j = true;
var k = !j;
var l = if (a) { "yes" } else { "no" };
g[0] = 10;
g[4] = "new value"; // it will append to array.
h["other"] = true;
"ola"[0] // print o
It's possible to reassign variable for example:
var a = 1;
a = a + 1;
puts(a);
Data Types Availables
/**
* Booleans
*/
true;
false;
/**
* Integer
*/
1;
20000;
/**
* Floats
*/
100.20;
5.20;
/**
* Scientific Notation
*/
1e3;
2e-3;
/**
* Hexadecimal Number
*/
0x00F
0xf
0x10C
/**
* Strings
*/
"hello"
"\u006E\u0069\u006E\u006A\u0061" // ninja in unicode chars
"\n\r\t\b\f" // special caracters is also supported
/**
* Array
*/
[1, "a", true, function() {}]
/**
* Hash
*/
{"key":"value","arr":[],"other":{}}
/**
* Functions
*/
function () {}()
var a = function () {}; a();
function a() { }; a();
function (a) { return function() { return a; }}(10)();
function (a, b = 1) { return a + b; };
Comments
// <...>
or /* <...> */
Comments can start with double slash //
ou multiple lines with \* *\
Functions
var <identifier> = function (<identifierarguments>?) { <statements> }
function <identifier> (<identifierarguments>?) { <statements> }
Functions is where power of language reside, it's a first-citizen function, which mean it can accept function as arguments or returning function. We got two ways declaring functions, literal or block.
function say(name) {
puts("Hello: " + name);
}
Or
var say = function(name) {
puts("Hello: " + name);
}
They are exactly same, but this is illegal:
var say = function say(name) {
puts("Hello: " + name);
}
We also could declare default values for parameters
function add (a, b = 20) {
return a + b;
}
add(10);
add(10, 30);
Builtin Functions
There are several builtin functions that you can use:
- puts - print at console
- len - get length of object
- first - get first item of array
- last - get last item of array
- rest - get items after first one
- push - add item to array
- exit - exit program
- args - get arguments passed to ninja programs
- rand - get random number from 0 to 1 float point
- time - return Unix time, the number of seconds elapsed
var a = [1, 2, 3, 4];
puts(len(a)); // print 4
puts(len("Hello!")); // print 5
var a = [1, 2, 3, 4];
puts(first(a)); // print 1
puts("Hello World"); // print in screen
var a = [1, 2, 3, 4];
puts(last(a)); // print 4
var a = [1, 2, 3, 4];
puts(rest(a)); // print [2, 3, 4]; (all but not first)
var a = [1, 2, 3, 4];
puts(push(a, 5)); // print [1, 2, 3, 4, 5];
Import
You can import another ninja files, it will act like require file.php
in php language.
import "testing.ninja";
var lib = import "mylib.ninja"; // return function() {};
Operators && Logics Operators
<expression> <operator> <expression>
Logic's Operators
10 < 10; // FALSE
10 > 10; // FALSE
10 == 10; // TRUE
10 != 10; // FALSE
10 <= 10; // TRUE
10 >= 10; // TRUE
10 && 10; // TRUE
10 || 10; // TRUE
!10; // FALSE
!!10; // TRUE
Note: a value is considered truthy when a value is not nil or not false.
<expression>? <operator> <expression>
Arithmetics Operators
1 + 1; // SUM
1 - 1; // SUBTRACT
1 / 1; // DIVIDER
1 * 1; // MULTIPLE
4 % 2; // MOD
10 ** 0; // POW
10 & 2; // AND Bitwise operator
10 | 2; // OR Bitwise operator
10 ^ 2; // XOR Bitwise operator
10 << 2; // Shift left (multiply each step)
10 >> 2; // Shift right (divide each step)
++1; // First increment and then return incremented value
--1; // First decrement and then return decremented value
1++; // First return value and then increment value
1--; // First return value and then decrement value
Data Structures
Array
var <identifier> = [<expressions>...]
var a = [1 + 1, 2, 4, function() {}, ["a", "b"]];
Delete index
delete a[0];
It will keep the order
Add Key
a[5] = "hello";
push(a, "anotherKey");
// push by empty braces
a[] = 6;
Hash
var <identifier> = {<expression>:<expression>,....}
var a = {"key":"hello","key" + "key":"hello2", "other":["nice", "other"], 2: true};
Delete Key
delete a["key"];
Add Key
a["testing"] = "hello";
Enum
enum STATUS {
case OK: true;
case NOK: false;
}
enum RESPONSE {
case OK: 200;
case NOT_FOUND: 404;
case ERR_MSG: "There are some errors"
case TIME: 32.2 + 43.3;
case TEST: if (true) { 0 } else { 1 };
}
then you can use his values:
puts(STATUS::OK);
Conditions
If / ElseIf / Else
if (<condition>) { <consequence> } elseif (<condition1>) { <consequence1> } else { <alternative> }
Simple If Condition
if (true) {
puts("Hello");
}
If / Else
if (true) {
puts("Hello");
} else {
puts("Nope");
}
If / ElseIf / Else
if (true) {
puts("Hello");
} elseif (1 > 2) {
puts("1 is greater than 2");
} else {
puts("Nope");
}
We can omit else condition
if (true) {
puts("Hello");
} elseif (1 > 2) {
puts("1 is greater than 2");
}
if (true) {
puts("Hello");
}
Ternary
<condition> ? <consequence> : <alternative>
true ? 10 : 20
Elvis Operator
<expression> ?: <expression>
var a = "hello";
var b = a ?: "world";
Loop
for (<initial>?;<condition>?;<iteration>?) { <statements> }
Note initial, condition and iteration is optional, you can omit
var i = 0;
for(;i<=3;++i) {
puts(i);
}
var a = [1, 2, 3];
for(var i = 0; i <= len(a)-1; ++i) {
puts(a[i]);
}
for(var i = 0; i <= len(a)-1; i = i + 1) {
puts(a[i]);
}
for(;;) {
break;
}
Object Call
We support object call in any of data type.
String
Here a full of list of support object call for string:
"hello".type(); // "STRING"
"hello".length(); // 5
"a,b,c".split(","); // ["a", "b", "c"];
"hello world".replace("world", "ninja"); // "hello ninja"
"hello world".contain("hello"); // TRUE
"hello world".index("Hello"); // 0
"hello world".upper(); // "HELLO WORLD"
"HELLO WORLD".lower(); // "hello world"
" hello world ".trim(); // "hello world"
"1".int(); // 1
"1.1".float(); // 1.1
Integer
1.type(); // "INTEGER"
1.string(); // "1"
1.float(); // 1.0
var a = -1; a.abs(); // 1.0
Float
1.0.type(); // "FLOAT"
1.0.string(); // "1.0"
var a = -1.0; a.abs(); // 1.0
1.5.round(); // 2.0
Boolean
true.type(); // "BOOLEAN"
Array
[1, 2, 3].type(); // "ARRAY"
[1, 2, 3].length(); // 3
[1, 2, 3].joint(","); // "1,2,3"
[1, 2, 3].push(4); // return null, but underlie value of array was change to [1, 2, 3, 4]
[1, 2, 3].pop(); // return 3 and underlie value of array was change to [1, 2]
[1, 2, 3].shift(); // return 1 and underlie value of array was change to [2, 3]
[1, 2, 3].slice(1); // copy array with following elements [2, 3]
[1, 2, 3].slice(1, 1); // copy array with following elements [2]
Hash
{"a":1,"b":2}.type(); // "HASH"
{"a":1,"b":2}.keys(); // ["a", "b"];
{"a":1,"b":2}.values(); // [1, 2];
{"a":1,"b":2}.merge({"c":3}); // {"a":1,"b":2,"c":3}
{"a":1,"b":2}.has("a"); // true
Note: Order of keys isn't preserved.
Keywords
var true false function return if
else for import delete break enum case
Extending Ninja Programming Language
You can extending ninja by creating a custom plugins, create an file for example:
package main
import . "github.com/gravataLonga/ninja/object"
func Hello(args ...Object) Object {
return &String{Value: "Hello World!"}
}
func main() {
panic("this is a plugin")
}
Then, compile as plugin:
go build -buildmode=plugin -o hello.so hello.go
After this you can import and called it in your ninja programs, like so:
var plugin = plugin("hello"); // don't need ".so" extension
puts(plugin.hello()); // it will print "Hello World!"
Lexical Scooping
To “resolve” a variable usage, we only need to calculate how many “hops” away the declared variable will be in the environment chain. The interesting question is when to do this calculation—or, put differently, where in our interpreter’s implementation do we stuff the code for it?
A variable resolution pass
After the parser produces the syntax tree, but before the interpreter starts executing it, we’ll do a single walk over the tree to resolve all of the variables it contains. Additional passes between parsing and execution are common. If had static types, we could slide a type checker in there. Optimizations are often implemented in separate passes like this too. Basically, any work that doesn't rely on state that’s only available at runtime can be done in this way.
When binding a variable we need to know how depth we are in rabbit hole.
Examples
- Check tests
- Check resolved katas
Tests
go test -v -race ./...
Profiling && Performance
Where CPU resources spend more time
Create svg graph:
interpreter result
go test -bench=. -cpuprofile cpu.prof
go tool pprof -svg cpu.prof > cpu.svg
Cores
go test -bench=. -trace trace.out
go tool trace trace.out
Test Race Condition
go test -race
References
- Structure and Interpretation of Computer Programs, Second Edition
- Engineering a Compiler, Second Edi- tion. Morgan Kaufmann.
- Parsing Techniques. A Practical Guide.. Ellis Horwood Limited.
- Modern Compiler Design, Second Edition. Springer
- The Elements Of Computing Systems. MIT Press.