brew tap gravatalonga/ninja-lang
brew install ninja-lang
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
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
Download from github
git clone https://github.com/gravataLonga/ninja
cd ninja
go build -o ninja-lang
For more detail about language, you can check here (Still working in progress).
Resolved katas from this website
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);
/**
* 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; };
// <...>
or /* <...> */
Comments can start with double slash //
ou multiple lines with \* *\
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);
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];
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() {};
<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
var <identifier> = [<expressions>...]
var a = [1 + 1, 2, 4, function() {}, ["a", "b"]];
delete a[0];
It will keep the order
a[5] = "hello";
push(a, "anotherKey");
// push by empty braces
a[] = 6;
var <identifier> = {<expression>:<expression>,....}
var a = {"key":"hello","key" + "key":"hello2", "other":["nice", "other"], 2: true};
delete a["key"];
a["testing"] = "hello";
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);
if (<condition>) { <consequence> } elseif (<condition1>) { <consequence1> } else { <alternative> }
if (true) {
puts("Hello");
}
if (true) {
puts("Hello");
} else {
puts("Nope");
}
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");
}
<condition> ? <consequence> : <alternative>
true ? 10 : 20
<expression> ?: <expression>
var a = "hello";
var b = a ?: "world";
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;
}
We support object call in any of data type.
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
1.type(); // "INTEGER"
1.string(); // "1"
1.float(); // 1.0
var a = -1; a.abs(); // 1.0
1.0.type(); // "FLOAT"
1.0.string(); // "1.0"
var a = -1.0; a.abs(); // 1.0
1.5.round(); // 2.0
true.type(); // "BOOLEAN"
[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]
{"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.
var true false function return if
else for import delete break enum case
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!"
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?
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.
- Check tests
- Check resolved katas
go test -v -race ./...
Create svg graph:
interpreter result
go test -bench=. -cpuprofile cpu.prof
go tool pprof -svg cpu.prof > cpu.svg
go test -bench=. -trace trace.out
go tool trace trace.out
go test -race
- 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.