An open API service indexing awesome lists of open source software.

https://github.com/gravatalonga/ninja

Ninja Programming Language - General Scripting Language inspired in PHP and Javascript.
https://github.com/gravatalonga/ninja

golang pratt-parser programming-language

Last synced: 6 months ago
JSON representation

Ninja Programming Language - General Scripting Language inspired in PHP and Javascript.

Awesome Lists containing this project

README

          

![ninja programming language](./ninja.svg)

[![Testing](https://github.com/gravataLonga/ninja/actions/workflows/main.yml/badge.svg)](https://github.com/gravataLonga/ninja/actions/workflows/main.yml)

# Install

## Homebrew

```sh
brew tap gravatalonga/ninja-lang
brew install ninja-lang
```

## YUM / RPM

To enable, add the following file `/etc/yum.repos.d/ninja.repo`:

```sh
[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](https://github.com/gravataLonga/ninja/releases)

## Manual Installation

```sh
git clone https://github.com/gravataLonga/ninja
cd ninja
go build -o ninja-lang
```

# Documentation

For more detail about language, you can check [here](https://ninja.jonathan.pt) (Still working in progress).

# Demo

Resolved [katas](https://github.com/gravataLonga/ninja-lang-katas) from this [website](https://adventofcode.com/2015)

# Syntax

## Variable

`var = ;`

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 = function (?) { }`
`function (?) { }`

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:

1. **puts** - print at console
2. **len** - get length of object
3. **first** - get first item of array
4. **last** - get last item of array
5. **rest** - get items after first one
6. **push** - add item to array
7. **exit** - exit program
8. **args** - get arguments passed to ninja programs
9. **rand** - get random number from 0 to 1 float point
10. **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

` `

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.

`? `
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 = [...]`

```
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 = {:,....}`

```
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 () { } elseif () { } else { }`

#### 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

` ? : `

```
true ? 10 : 20
```

### Elvis Operator

` ?: `

```
var a = "hello";
var b = a ?: "world";
```

## Loop

`for (?;?;?) { }`

> **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

[Inspiration](https://craftinginterpreters.com/resolving-and-binding.html)

> 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

1. Check tests
2. Check resolved [katas](https://github.com/gravataLonga/ninja-lang-katas)

## Tests

```
go test -v -race ./...
```

## Profiling && Performance

### Where CPU resources spend more time
Create svg graph:
[interpreter result](https://github.com/google/pprof/blob/main/doc/README.md#interpreting-the-callgraph)
```
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.