Natrix Language Documentation

The Natrix language (nx) is a simple language that as a set of functionalities such as loops, conditions, and functions.

Differences Between the Compiler and the Interpreter:

  • The Interpreter supports all of the functionalities described at this documentation;
  • The Compiler does not support arrays and recursivity.


Indice

  1. Input and Output
  2. Variable Declarations
  3. Data Types
  4. Comments
  5. Boolean Expressions
    1. Relational Operators
    2. If Statement
    3. Else And Else If Statements
    4. Logic Operators
    5. Ternary Operator
  6. Loops
    1. While
    2. For
    3. Foreach Loop
    4. Break and Continue
    5. Do While Loop
  7. Functions
    1. Recursivity
  8. Types Definition
    1. Set Definition
    2. Arrays Definition
    3. Function Size
  9. Bitwise Operators
  10. A Complete Natrix Program
  11. References



1 - Input and Output

The Natrix language supports three functions for writting and reading data. The functions print(),printn() and scanf(). The function print has one argument, a numeric value, and outputs it to the console, without the new line character. The function printn (print with new line) has one argument, a numeric value, and outputs it to the console with a new line character. The function scanf has one arguement, the identifier of a numeric variable, and saves the readed value from the keyboard. In the case of the input of an invalid value the scanf will assign the value 0 to the passed argument.

print(4);

printn(-3);

scanf(n);



2 - Variable Declarations

The Natrix language supports integer variables of 64 bits, the long type of C.

The declaration of an integer variable as the following syntax:

val «identifier» : int = «value»;

The assignment of a new value as a similar syntax:

«identifier» := «value»;

A pratic example:

val x : int = -3;

x := 5;

An important note: The assignment is mandatory in the declaration of a variable in Natrix. In other words, the following program is illegal:

val x : int;


Arithmetic Operations with Integers

As is normal in any language, we can perform numeric operations with integers.

The Natrix language supports a wide range of arithmetic operators, which are displayed in the following table.

Description Operator
Addition +
Subtraction -
Divisiton /
Módulo %
Multiplication *

It’s also possible to combine these operators with the assignment operator.

Example:

val x : int = 2;

x += 2;

This works with every binary operators.


Special Constants of the Integer Data Type

The data type int has two constants, maxint and minint, which are respectively the largest and smallest possible value that an integer variable can take.

That is, maxint has the value 9 223 372 036 854 775 807 and minint the value -9 223 372 036 854 775 807.



3 - Data Types

The complete list of the supported data types can be viewed below:

  • 64 bits integers;
  • Positive and negative integer sets;
  • One dimensional arrays.



4 - Comments

The Natrix language supports two types of comments, single line and multi line comments.
Comments are usefull because they are ignored by the compiler/interpreter and give usefull information to the programmer.

Example:

// This is a single line comment

(* This
is
a
multiline
comment *)



5 - Boolean Expressions

The Natrix language supports boolean expressions and disposes a set of relational and logic operators. It’s important to note that the Natrix language uses the same approach of the C language, the integer zero represents false and any other number is true.


5.1 - Relational Operators

Descrition Operator
Equality ==
Inequality !=
Less than <
Less or equal <=
Bigger than >
Bigger or equal >=


5.2 - If Statement

Using the operators above we can build the conditions that we would put in the if statement, it’s important to remember that the else branch is optional.

if( «condition» )
{ 
    // Branch that will be executed
    // If the condition is true
}
else
{
    // Branch that will be executed
    // If the condition is false
}

Example:

if(1 > 7)
{
    printn(1);
}
else
{
    printn(2); // This will be executed 
}

5.3 - Else And Else If Statements

When we have a sequence of if statements in which only one of them will be executed, we can take advantage of the chain if - else if else. It is recommended to use a sequence of else if statements instead of isolated ifs statements, as it is more optimized and has a more pleasant appearance.

Structure:

val x : int = 3;

if(x == 0)
{
    printn(0);
}
else if (x == 1)
{
    printn(1);
}
else if (x == 2)
{
    printn(2);
}
else
{
    printn(3)
}

The reason for being more optimized:

If we rewrite the previous program in a sequence of if statements, the computer would be obliged to test all of the conditions even if we knew that only one would be true. With if - else if - else we only need to test the necessary conditions if.


5.4 - Logic Operators

If we want to build more complex conditions (where we test more than one condition), we can use the logical operators and, or and not.

The complete list of the logical operators can be seen in the table bellow:

Descrition Operator
And &&
Or ||
Not !

Example:

if(1 > 7 && 2 == 3)
{
    y := y + 1;
}


5.5 - Ternary Operator

The ternary operator ?: allows the junction of an if-else statement that results in an assignment of the same variable.

Structure:

«condition» ? «return if true» : «return if false» 

The next code block explores the proximity of the ternary operator and the if statement.

val x : int = 5 > 3 ? 2 : 5; 

if(5 > 3)
{
    x := 2;
}
else
{
    x := 5;
}



6 - Loops

The Natrix langauge supports a vast set of loops, from the simple whileto the specific foreach. These loops will be discussed in the following sections.

In conjuction with these loops, the Natrix supports the break and continue statements. That allow a richest control of the program flux..

6.1 - While

The while loop is the most simple, and therefore the easiest to understand. Its purpose is to execute a set of statements while certain condition is true. The implementation of the while loop in the Natrix language is based on the C one.

while(«condition»)
{
     «statement block»
}

6.2 - For

O ciclo for é um ciclo semelhante ao while mas que conjuga três instruções num só comando. Como está ilustrado na estrutura seguinte, o ciclo for junta:

  • uma declaração,
  • uma condição,
  • e uma expressão (que será utilizada para atualizar o valor da variável anteriormente declarada).

Estrutura de um for na lingua Natrix:

for(«declaração de uma var» ; «condicao» ; «atualizacao do valor de var»)
{
     «instruções a executar»
}

Exemplo:

for(val x : int = 0; x < 2 ; x + 1)
{
    printn(x);
}

// Output esperado
// 0
// 1

6.3 - Ciclo Foreach

O ciclo foreach é ligueiramente diferente ao while e ao for pois não contém uma condição. O ciclo foreach tira proveito de um conjunto, e vai iterar o mesmo. Isto significa que em cada iteração o foreach irá tomar um valor diferente do conjunto.

É constituído por:

  • Uma declaração,
  • Um conjunto.

Estrutura:

foreach «variável» in «intervalo»
{
     «instruções a executar»
}

Exemplo:

foreach i in [0 .. 4]
{
     printn(i);
}

// Output esperado
// 0
// 1
// 2
// 3
// 4



6.4 - Break e Continue

O Natrix também suporta as instruções break e continue, que permitem respetivamente, terminar um ciclo e continuar para a iteração seguinte.

O exemplo seguinte utiliza o ciclo while mas é de realçar que todos os ciclos suportam este mecanismo.

val x : int = 0;

while(x < 3)
{
    scanf(x);

    // Caso o valor introduzido seja 0 entao terminamos o ciclo
    if(x == 0)
    {
        break;
    }
    
    // Caso o valor introduzido seja par passamos para a próxima iteração
    // Ou seja, o printn(x); e o x:= x + 1; não serão executados.
    if(x % 2 == 0)
    {
        continue;
    }
    
    printn(x);
    
    x := x + 1;
}

6.5 - Ciclo Do While

O último ciclo que a linguagem Natrix suporta é o ciclo do while. A grande diferença entre este ciclo para outros é que neste o bloco de instruções é executado pelo menos uma vez, visto que a sua condição se encontra no fim. A estrutura do ciclo do while segue um semelhante à seguinte.

do
{
  // instruções
  
}while(«condição»);

É de realçar a existência do ; no do while.

7 - Funções

O Natrix suporta funções por passagem de valor, a declaração de uma função com n argumentos segue a seguinte estrutura:

function «identificador»(«arg1 : tipo1», ..., «argn : tipon») : «retorno»
{ 
    ...
}

As funções são obrigatóriamente globais e devem retornar sempre um valor.

Exemplo de uma função que soma dois valores:

function somar(x : int, y : int) : int 
{
    return x + y;
}

Quando a nossa função tem apenas a instrução return podemos simplificar a sua construção por:

function somar(x : int, y : int) : int 
    => x + y;

7.1 - Recursividade

O Natrix suporta a recursividade de funções.

function sum(n : int) : int 
{
    if(n <= 0)
    {
        return 0;
    }
    
    return 1 + sum(n - 1);
}



8 - Criação de Tipos

É possível criarmos os nossos próprios tipos de dados em Natrix, para isto utilizamos a palavra-chave type. Existem dois tipos de dados que podemos criar no Natrix, conjuntos e arrays cada um destes é discutido com mais afinco nos próximos pontos.


8.1 - Definição de Conjuntos

A definição de um tipo conjunto permite que limitemos os valores válidos que uma variável deste tipo pode receber. Para além disso, os conjuntos podem ser utilizados no foreach e na definição do tipo array.

Criação de um tipo conjunto:

type y = [«inicio» .. «fim»];

No exemplo anterior utilizámos o operador .. que pode ser lido como “até”.

Como era de esperar, podemos declarar variáveis do tipo intervalo:

type t = [10 .. 20];
type f = [-5 .. 5];

val x : t = 10;

A variável x sendo do tipo t pode apenas receber valores que estejam contidos no intervalo de t, ou seja, valores que validem a expressão: 10 <= n <= 20.

Utilização de um conjunto no foreach

type t = [-5 .. 0];

foreach i in t
{
    print(i);
}

// Output
// -5
// -4
// -3
// -2
// -1
//  0


8.2 - Definição de Arrays (Vetores)

A definição de tipos array segue uma estrutura semelhante à dos conjuntos e pode ser realizada de duas formas diferentes


Primeira forma:

type a : array 5 of [minint .. maxint];

O tipo a define o tipo de dados array que contém 5 elementos e que pode receberes valores contidos no conjunti [minint .. maxint].

Este tipo pode ser posteriormente utilizado na declaração de variáveis do tipo array.

val v : a filled by 3;

Podemos também aceder a elementos de uma variável do tipo array da seguinte forma:

v[3] := 3;


Segunda forma:

Esta segunda forma é mais complexa mas permite que utilizemos conjuntos para limitar o índice de uma array.

type t = [10 .. 20];

type a : array t of [minint..maxint];

val v : a filled by 3;

Definimos um tipo especial de arrays que tem o intervalo t e cujos elementos são do tipo inteiro. O indíce das arrays que são definidas com intervalos começa e termina no intervalo. Neste exemplo v começa no indíce 10 e termina no 20.


printn(v[10]); // Output: 3

printn(v[20]); // Output: 3

printn(v[21]); // Run-time error

printn(v[9]); // Run-time error


8.3 - Funções Especiais de Arrays e Conjuntos

Os intervalos e as arrays possuem uma função especial, o size(). Esta função recebe um úninco argumento e devolve o tamanho do intervalo ou da array.

val tamanho : int = size([30 .. 35]);

Nota: A variável tamanho terá o valor 5.



9 - Operadores Bitwise

É possível realizar operações bit-a-bit em Natrix, utilizando para isso os operadores bitwise.

Descrição Operador
E &
Ou |
Complementar ~
Shift à direita »
Shift à esquerda «

10 - Exemplo de um Programa Completo em Natrix

O código aqui descrito é a solução do Problema A de lógica computacional de 2019/2020.

val n : int = 0;
val k : int = 0;
val a : int = 0;
val b : int = 0;

function isValid(a : int, b : int) : int
{
    return (a >= 0 && a < n) && (b >= 0 && b < n);
}

function move(a : int, b : int, prof : int) : int
{
    if( (!isValid(a, b)) || prof > k) {
        return 0;
    }

    val x : int = prof == k;

    return x + move(a - 2, b + 1, prof + 1) 
      + move(a - 1, b + 2, prof + 1) 
      + move(a + 1, b + 2, prof + 1) 
      + move(a + 2, b + 1, prof + 1) 
      + move(a + 2, b - 1, prof + 1) 
      + move(a + 1, b - 2, prof + 1) 
      + move(a - 1, b - 2, prof + 1) 
      + move(a - 2, b - 1, prof + 1);
}

scanf(n);
scanf(k);
scanf(a);
scanf(b);

printn(move(a, b, 0));



11 - Referências

Esta documentação é uma transcrição e a nossa interpretação da proposta do Professor Doutor Simão Sousa cuja página pode ser consultada:

O autor da imagem do pode ser consultado: