View on GitHub

NianioLang

Procedural programming language without pointers

Get all dependencies

Debian / Ubuntu

Assumptions

During this tutorial we will use ~/nl2/ directory for NL compiler files and ~/project/ directory for newly created project files.

Download and build NianioLang

cd ~/
git clone https://github.com/nianiolang/nl2.git
cd nl2
make

After executing those commands directory ~/nl2 should contain file mk_cache.exe ‒ main compiler executable.

Prepare NianioLang library files

NianioLang library needs to be initialized before running compiled scripts.

mkdir ~/project
cd ~/project
bash ~/nl2/nl_init_c.sh nl_lib

Expected directory tree after executing commands above:

~/project
└── nl_lib
    ├── array.nl
    ├── boolean.nl
    ├── boolean_t.nl
    ├── c_fe_lib.c
    ├── c_fe_lib.h
    ...

Create basic nianio function

def nianio::nianio(ref state, cmd) {
	var extcmds = [];
	match (cmd) case :inc(var number) {
		state->number += number;
	} case :print {
		extcmds []= :print_text(state->number);
	}
	return extcmds;
}
def nianio::initial_state() {
	return {
		number => 0,
	};
}

Executing this command will generate result C files for NL lib and nianio.nl in ~/project/nl_out. Expected project structure at this point:

~/project
├── nl_lib
│   ├── array.nl
│   ├── boolean.nl
│   ├── boolean_t.nl
│   ├── c_fe_lib.c
│   ├── c_fe_lib.h
│   ...
├── nl_out
│   ├── array.c
│   ├── array.h
│   ├── boolean.c
│   ├── boolean.h
│   ...
│   ├── nianio.c
│   ├── nianio.h
│   ...
└── nl_sources
    └── nianio.nl

Call nianio function from C code

#include <stdio.h>
#include "nl_out/nianio.h"
#include "nl_lib/c_rt_lib.h"

int main() {
    c_rt_lib0init();

    ImmT state = NULL;
    ImmT cmd = NULL;
    ImmT extcmds = NULL;
    ImmT print_text = NULL;

    c_rt_lib0move(&state, nianio0initial_state());

    c_rt_lib0move(&cmd, c_rt_lib0ov_mk_arg(c_rt_lib0string_new("inc"), c_rt_lib0int_new(1)));
    c_rt_lib0move(&extcmds, nianio0nianio(&state, cmd));
    c_rt_lib0move(&extcmds, nianio0nianio(&state, cmd));
    c_rt_lib0move(&extcmds, nianio0nianio(&state, cmd));

    c_rt_lib0move(&cmd, c_rt_lib0ov_mk_none(c_rt_lib0string_new("print")));
    c_rt_lib0move(&extcmds, nianio0nianio(&state, cmd));

    print_text = c_rt_lib0string_new("print_text");
    for (int i = 0; i < c_rt_lib0array_len(extcmds); i++) {
        ImmT extcmd = NULL;
        c_rt_lib0move(&extcmd, c_rt_lib0array_get(extcmds, i));
        if (c_rt_lib0ov_is(extcmd, print_text)) {
            ImmT as_print_text = NULL;
            c_rt_lib0move(&as_print_text, c_rt_lib0ov_as(extcmd, print_text));
            printf("%lld\n", getIntFromImm(as_print_text));
            c_rt_lib0clear(&as_print_text);
        }
        c_rt_lib0clear(&extcmd);
    }   

    c_rt_lib0clear(&print_text);
    c_rt_lib0clear(&extcmds);
    c_rt_lib0clear(&state);
    c_rt_lib0clear(&cmd);

    return 0;
}

Details of C interface are described in section below.

Download complete project

C interface for NL

Nianio function development

Note: --ide flag does not detect changes in C files. After changing C files it is needed to save some NL file to trigger recompilation.

Type checking

After adding types to NL code, compiler will statically check types of all function calls issued from NianioLang code. Checking argument types for functions called from C is possible only in runtime. To do that, add ptd::ensure call at the beginning of each function called from C:

def nianio::nianio(ref state : @nianio::state, cmd : @nianio::cmd) : @nianio::ext_cmds {
	ptd::ensure(@nianio::state, state);
	ptd::ensure(@nianio::cmd, cmd);
	var ext_cmds = [];
	...
	return ext_cmds;
}

Own types

Own types are types parallel to ptd with usage restricted to passing by ref (no copying, returning or passing by value is allowed). Thanks to that compiler is able to generate efficient target code. Main use case of own types is nianio state. To make state own, first define state type and use it in nianio function declaration

use ptd;
use own;

def nianio::state() {
	return own::rec({
		number => ptd::int(),
	});
}

def nianio::nianio(ref state : @nianio::state, cmd) {
	...
}

Because own types cannot be returned from function, initializing function has to take state as a ref argument and fill it with initial values

def nianio::init_state(ref state : @nianio::state) {
    state = {
        number => 0,
    };
}

Finally, in C create state variable of type module0function0type and use init function to initialize C state variable. Then it can be passed to nianio function as before. To destroy it at the and of program use module0function0type0clear instead of c_rt_lib0clear.

int main() {
...
    nianio0state0type state = {};
    nianio0init_state(&state);
...
    nianio0state0type0clear(state);
}

Debugging

To enable debugging symbols, add flag -g to gcc compilation command.