Features

https://chocolatey.org/packages/nubasic
https://github.com/eantcal/nubasic/releases/
Download nuBASIC

You can explore a collection of nuBASIC examples by visiting the following link:

https://github.com/eantcal/nubasic/tree/master/examples

These examples provide practical demonstrations of nuBASIC's capabilities and usage. By studying these code samples, you can gain insights into various programming techniques and learn how to leverage nuBASIC effectively for your own projects. Feel free to browse the repository and explore the diverse range of examples available.

Build and run nuBASIC

The source code of nuBASIC has been crafted in C++11, ensuring compatibility across various operating systems such as Windows, Linux, and MacOS. To compile nuBASIC, you have multiple options at your disposal.

For Windows users, you can effortlessly build it a Visual Studio application. The necessary project files are readily available, enabling a straightforward compilation process.

Alternatively, for those who prefer using GCC (version 4.8 or above), both cmake scripts and Visual Studio project files have been thoughtfully provided. This allows for a seamless build process regardless of your preferred development environment.

In summary, the flexibility offered by nuBASIC empowers developers to compile and build the language effortlessly on different operating systems. Whether you choose Visual Studio, GCC, or MinGW, the necessary resources are available to facilitate a smooth and hassle-free compilation process.

Building nuBASIC by using cmake

Note: in order to build 'tiny' version of nuBASIC you need to replace the cmake command with the following one:

cmake .. -DWITH_X11=OFF -DWITH_IDE=OFF

Building nuBASIC by using maiken

To build nuBASIC you can also use maiken which is a C++14 Cross platform YAML based build tool for GCC/CLANG/ICC/MSVC/NVCC (please see also https://maiken.github.io).

Maiken uses a specific make-script file. You can download the nuBASIC's maiken script file at link https://github.com/eantcal/nubasic/blob/master/mkn.yaml (it will be included in the new versions of nubasic source code).

Once you have installed maiken and you have got the script file, just copy this file within the nuBASIC source code directory and execute the following commands from there:

mkn clean -p nubasic-release

mkn build -p nubasic-release

At the end of build stage you will find the binary executable file nubasic within <source-code-dir>/bin/nubasic-release.

Getting the latest released code

To access the most recent version of the software, you can easily obtain it from GitHub. The source code of the project is meticulously managed through the git version control system. To acquire your personal copy of the project sources, simply execute the following command:

git clone https://github.com/eantcal/nubasic.git

To build nubasic you need g++ compiler and x11 development libs.

In order to build nubasic, it is necessary to install the GNU GCC Compiler and Development Environment. If you are using a Debian/Ubuntu distribution, you can follow these steps in the Terminal as a root user:

sudo apt-get install build-essential

By following these instructions, you will successfully set up the GNU GCC Compiler and Development Environment, enabling you to proceed with the nubasic build process.
Unless you configure nuBASIC to generate the "tiny" version by running the command "./configure --enable-tinyver," you will need to install the following additional packages: libx11-dev, sdl2-dev, xmessage, and xterm. These packages are essential to ensure the proper functionality of nuBASIC.

To install required libraries and external programs script for development libs and required tools

Open the Terminal and execute the following command using apt-get:

sudo apt-get -y install libx11-dev libsdl2-dev xterm xmessage

See also

Ubuntu:

Others:

Build and run nuBASIC on iOS devices

If you want to run text version of nuBASIC on iOS devices you can use iSH (https://ish.app)

apk add g++

apk add make

apk add cmake

apk add git 

git clone https://github.com/eantcal/nubasic.git

cd nubasic

mkdir build

cd build

cmake .. && make

./nubasic

Making the interpreter

The experience of writing a BASIC interpreter using modern C++ was truly enjoyable. BASIC itself is a language that evokes nostalgic memories from the early era of personal computing. Back in those days, iconic computers like the Commodore 64 featured an embedded BASIC interpreter, which added to the charm and allure of the computing experience.


Main interpreter components


In developing the nuBASIC interpreter, I followed an approach that primarily involves parsing the BASIC source code and compiling it into an Abstract Syntax Tree (AST). This AST is then used to execute the code. As a result, I created the following key components, which may not always correspond directly to a single class or other C++ element:

These components collectively form the foundation of the nuBASIC interpreter, enabling efficient parsing, execution, and management of BASIC programs while providing a comprehensive user experience.


A line-oriented language interpreter

The nuBASIC interpreter is designed to cater to the line-oriented nature of the BASIC language. In BASIC, the positioning of hard line breaks in the source code holds significance, distinguishing it from other programming languages.

In the nuBASIC interpreter, each code line in a BASIC program is treated as a self-contained unit. To reflect this, the interpreter itself operates in a line-oriented manner. The program's source text is divided into lines, and these lines are owned by the Interpreter class. The lines are stored in a map structure, where each line is associated with a line number and its corresponding text.

During the parsing process, each code line is transformed into an independent execution unit. The interpreter constructs a static program context, serving as the connecting code between program lines and statements. This context allows the Statement Parser to recognize complex language constructs that may span multiple lines and generates metadata to reference them. It's worth noting that a single line containing a control structure can encompass more than one statement.

By adopting a line-oriented approach, the nuBASIC interpreter ensures that each line in the program is treated as a distinct entity, facilitating the parsing and execution of BASIC programs while preserving the unique characteristics of the language.


Handling the tokens

Before parsing each line, the Tokenizer performs a separate lexical analysis, which involves creating tokens from the source text. Each token is implemented as a class and contains various attributes, including the original source text, token position within the text, length, type (such as 'identifier', 'operator', 'literal string', 'integer', etc.), and additional data.

In designing the token representation, I opted not to use a pure object-oriented programming (OOP) approach, which typically involves building a class hierarchy for token types. Instead, I chose a flat representation where the token type is simply an attribute of the token object, implemented as an "enum-class." This decision was made to facilitate the collection and handling of homogeneous token objects.

The Parser leverages the token type attribute to recognize and validate the syntax of the language. It utilizes this information to construct statement objects and generate the necessary meta-data that the interpreter relies on during program execution.

By utilizing tokens with flat token types, the nuBASIC interpreter streamlines the process of recognizing and validating the language syntax while efficiently building the required statement objects and runtime meta-data.


Token list container

In order to simplify the parser and reduce complexity, a Token List container class has been implemented. This class wraps around a standard Deque (double-ended queue) and provides additional functionality to facilitate the handling of token lists.

The Token List class offers convenient features designed to simplify the manipulation of token lists and contribute to a more streamlined parser implementation. These features aim to enhance the ease of working with token lists and alleviate the burden of managing complex data structures within the parser.


Parsing the code


When it comes to parsing the code in nuBASIC, there are multiple parsers in place to handle different aspects:

One of the primary challenges in parsing nuBASIC arises from handling expressions. For instance, when parsing an expression like "2+4*17", determining the correct syntax tree relies on analyzing the entire expression.

The Expression Parser must account for operator precedence and restructure the previous expression into something like "(2 + (4 * 17))" to form a well-formed syntax tree.

This approach ensures that expressions are parsed correctly, considering operator precedence and producing syntax trees that accurately represent the intended calculations:

                        '+'

                        / \

                      '2' '*'

                          / \

                        '4' '17'


Expression parsing involves several distinct steps to ensure accurate evaluation:

By following these steps, the Expression Parser effectively analyzes expressions, ensuring correct evaluation by considering operator precedence and constructing a syntax tree that accurately represents the expression's structure.

Let's take the mathematical expression "2+4*17" as an example. After undergoing the parsing process, the expression is represented by the following objects tree:

                            binary_expression

                                 (+, sum)

                                   / \

                             literal  \ 

                            (integer)  \

                                2    binary_expression

                                      (*, multiplication)

                                            / \

                                           /   \

                                     literal   literal

                                     (integer) (integer)

                                          4     17

In this tree representation, the "+" operator is the root node, with "2" as its left child and the "*" operator as its right child. The "*" operator, in turn, has "4" as its left child and "17" as its right child.

This tree structure accurately captures the hierarchical relationship between the operators and operands in the original expression, allowing for correct evaluation and calculation.

The abstract class expr_any_t serves as the base class for various subclasses and defines the virtual method eval(). Each subclass implements this method according to its specific functionality.

The eval() method has the following prototype:

virtual variant_t eval(rt_prog_ctx_t & ctx) const = 0;

This method takes a reference to the run-time program (execution) context, represented by the rt_prog_ctx_t object. It then evaluates the expression based on the given context and returns a variant_t object.

The variant_t object represents a flexible variant type that can hold different data types. The details of the variant_t object and its usage will be discussed in the following section.

By implementing the eval() method in the derived subclasses, the expression evaluation process is tailored to the specific behavior and requirements of each expression type.


Variant

The Variant class is designed to handle various types of data in the nuBASIC language in a unified and simplified manner. It significantly reduces the complexity of the evaluator.

To achieve this, I opted not to use C++ union because it only supports plain old data types and is not suitable for handling non-trivial complex types. Instead, the Variant class provides a flexible solution that can handle a wide range of BASIC language types seamlessly.

By utilizing the Variant class, the evaluator can manipulate different data types in a uniform way, making the code more concise and easier to maintain. This approach streamlines the handling of data within the interpreter and contributes to a more efficient and organized evaluation process.


Tracing execution of a simple program

Let us consider the following simple BASIC program, containing just a unique line with a unique statement:

10 PRINT 2+4*17

Suppose you have already inserted the program so you have just type “RUN” to execute it.

First nuBASIC_console() function gets the command string “RUN” from standard input, then invokes the function exec_command() which is a helper function that invokes the related interpreter exec_command() method, catching any exceptions.

This method parses a command in order to recognize it and perform the action required.

In this case it calls the rebuild() interpreter method which clears static and run-time context objects (removing both dynamic data and meta-data), then for each source line calls the update_program() method. This method creates a Tokenizer object and calls the compile_line() method of the Statement Parser object, held as attribute of the Interpreter object.

compile_line() method uses the Tokenizer to break down the source line into a language token list like the following (for simplicity no all token fields are reported below):

{ (“PRINT”, identifier), (“ “, blank), (“2”, integer), (“+”, operator), (“4”, integer), (“17”, integer) }

Each line of code is treated as a “block” which is a container of statements. Thus the method parse_block() is first called. This method iterates while the token list is not empty calling for each iteration the method parse_stmt(), which is able to recognize the statement in order to select the specific parse_xxx() method.

In our example, it recognizes the token “PRINT” (which is the first token of the token list), and calls the specific parse_print() method and this builds a stmt_print_t object which holds an expression object instance. parse_print() method calls the template function parse_arg_list() which builds an expression list for the PRINT statement. Each item of the list is an expression object built via the Expression Parser, ready to be evaluate by its eval() method against a run-time program (execution) context.

Finally, parse_print() method returns to the calling parse_block(). Which in turn returns an handle to statement block object by means of a (smart standard) shared pointer to the class object.

The statement handle is stored in a map (prog_line_t) where the key element is just the processing line number.

After building the program, interpreter calls the run() method which creates a program_t object instance passing to it program line and program context objects coming from previous building phase.

The program object executes each code line (represented by block statement object as discussed before) by calling the related run() virtual method. In our example the unique program line (a block statement object) contains the print statement object. Calling its run() method the related print statement run() method is finally invoked.

stmt_print_t run() method evaluates each argument of its argument list. The argument list is just a collection of expression objects which export the eval() method. The eval() method returns a variant object that can be printed out to the standard output.

How to extend the built-in function set

In this step-by-step guide, we will demonstrate how to add a new built-in function to the nuBASIC native API. As an example, let's consider adding a 1D-convolution math function that calculates the convolution of two vectors. When the vectors represent polynomial coefficients, convolving them is equivalent to multiplying the two polynomials.

To illustrate this, let's assume we have two vectors: u=(1,0,1) and v=(2,7), representing the coefficients of the polynomials x^2+1 and 2x+7, respectively. We aim to create a nuBASIC program that utilizes a new function, Conv, to perform the convolution. Here's an example of how the program would look:

Dim u(3) as Double

Dim v(2) as Double

Dim w(4) as Double

u(0)=1

u(1)=0

u(2)=1

v(0)=2

v(1)=7

w = Conv(u,v)

For i=0 To 3

  Print w(i);" ";

Next i

After executing the program with the provided input, the expected output is as follows:

2 7 2 7

This output indicates that the resulting vector, denoted as "w," contains the polynomial coefficients for the polynomial 2x^3 + 7x^2 + 2x + 7.

By convolving the two input vectors, u=(1, 0, 1) and v=(2, 7), the nuBASIC program successfully performs the desired operation and generates the corresponding polynomial coefficients in the output vector w.


nuBASIC Conv prototype

The function Conv can be defined with the following prototype:

function conv( v1(n) as Double, v2(m) as Double) as Double(n+m-1)

Additionally, we can include two optional parameters that represent the size of the vectors. This allows for reusing the same array to represent different vectors. The modified prototype would look like this:

function conv( v1(arraySize1) as Double, v2(arraySize2) as Double, n as Integer, m as Integer) as Double(n+m-1)

Here, the parameters arraySize1 and arraySize2 indicate the size of the arrays representing the vectors, while n and m represent the actual sizes of the vectors. The condition arraySize1 >= n > 0 ensures that the array size is greater than or equal to the size of vector v1, and similarly, arraySize2 >= m > 0 ensures the array size is greater than or equal to the size of vector v2.


Implementation of conv function in C++

Here is a possible implementation of the conv function in C++:

template<typename T>

std::vector<T> conv(const std::vector<T> &v1, const std::vector<T> &v2) {

    const int n = int(v1.size());

    const int m = int(v2.size());

    const int k = n + m - 1;

    std::vector<T> w(k, T());

    for (auto i=0; i < k; ++i) {

        const int jmn = (i >= m - 1) ? i - (m - 1) : 0;

        const int jmx = (i < n - 1) ? i : n - 1;

        for (auto j=jmn; j <= jmx; ++j) {

            w[i] += (v1[j] * v2[i - j]);

        }

    }

    return w;

}

This implementation takes two vectors, v1 and v2, as input and returns the convolution result as a new vector. It first determines the sizes of the input vectors, n and m, and calculates the size of the resulting vector as k = n + m - 1. Then, it creates a new vector w with the appropriate size, initialized with zeros.

Next, the function performs the convolution operation using nested loops. It multiplies the corresponding elements of v1 and v2 and accumulates the results at the appropriate index in the w vector.

Finally, the resulting vector w is returned.


Extending the global function set


To add a new function to the existing built-in API set, you would need to make modifications to the lib/nu_global_function_tbl.cc file, specifically within the global_function_tbl_t class and its static function get_instance().

The get_instance() function is responsible for populating a global map that associates each nuBASIC function name with a C++ functor (callback function). The prototype of the functor is as follows:

variant_t functor_name( rt_prog_ctx_t& ctx, const std::string& name, const nu::func_args_t& args );

The functor takes three parameters:

To implement the functor, you would typically follow these steps:

By modifying the global_function_tbl_t class and implementing your specific functor for the new function, you can seamlessly integrate it into the nuBASIC interpreter's built-in API.


Defining new conv functor

To create a conv_functor for the conv function, you would need to make modifications to the lib/nu_global_function_tbl.cc file. The skeleton of the conv_functor could be as follows:

variant_t conv_functor(

    rt_prog_ctx_t& ctx,

    const std::string& name,

    const nu::func_args_t& args

{

    // Get number of arguments

    // TODO

       // Validate the number of arguments

    // TODO

       // Process and validate the arguments

    // TODO 

    

       // Convert the input parameters into C++ parameters

    // TODO

       // Compute the result

    // TODO

       // Convert the result into nu::variant_t object

    // TODO

    return result;

}


Get and validate the number of input arguments

To implement a function that accepts either 2 or 4 arguments in accordance with the two nuBASIC prototypes of Conv, you would need to inspect the size of the args parameter and perform validation. The args parameter is a vector of nuBASIC expressions. Here's an example of how you can achieve this:

variant_t conv_functor(

   rt_prog_ctx_t& ctx,

   const std::string& name,

   const nu::func_args_t& args) 

{

       // Get number of arguments

    const auto args_num = args.size();

       // Validate the number of arguments

    // TODO

       // Process and validate the arguments

    // TODO

       // Convert the input parameters into C++ parameters

    // TODO

       // Compute the result

    // TODO

       // Convert the result into nu::variant_t object

    // TODO

    return result;

}

To validate the number of arguments we have to check if it is 2 or 4, otherwise we have to generate a run-time error.

For generating an error we can call the method throw_if() of the rt_error_code_t singleton object which generates an error depending on a specific error code.

variant_t conv_functor(

   rt_prog_ctx_t& ctx,

   const std::string& name,

   const nu::func_args_t& args) 

{

       // Get number of arguments

    const auto args_num = args.size();

    // Validate the number of arguments

    rt_error_code_t::get_instance().throw_if(

        args_num != 4 && args_num != 2, 0, rt_error_code_t::E_INVALID_ARGS, "");

    // Process and validate the arguments

    // TODO

       // Convert the input parameters into C++ parameters

    // TODO

       // Compute the result

    // TODO

       // Convert the result into nu::variant_t object

    // TODO

    return result;

}

In the previous source code, the throw_if() function generates a runtime error by throwing an exception of type rt_error_code_t::E_INVALID_ARGS when the predicate args_num != 2 and args_num != 4 is true. This ensures that an exception is thrown when the number of arguments is not valid.

See the rt_error_code_t class implementation (lib/nu_error_codes.cc) for more information on run-time error codes and related messages.


Converting the input args into C++ equivalent parameters

Since we have already validated the number of nuBASIC function arguments to be at least 2, we can proceed to retrieve the first two arguments from the args vector. We can accomplish this by accessing the shared pointers to instances of nuBASIC expressions and calling the eval() method using the runtime context associated with executing the nuBASIC function (ctx). Here's the updated code snippet:

variant_t conv_functor(

   rt_prog_ctx_t& ctx,

   const std::string& name,

   const nu::func_args_t& args) 

{

    // Get the number of input arguments

    const auto args_num = args.size();

    // Validate the input arguments (args).

    rt_error_code_t::get_instance().throw_if(

       args_num != 4 && args_num != 2, 0, rt_error_code_t::E_INVALID_ARGS, "");

    // Process and validate the arguments

    auto variant_v1 = args[0]->eval(ctx);

    auto variant_v2 = args[1]->eval(ctx);

    // ...

    // Call the C++ function conv for processing the input data and doing the actual job.

    // TODO

    // Get the result and convert it into nu::variant_t which is how internally the result is represented.
    // ...

    return result;

}

In the updated code, we retrieve the first two arguments by accessing the elements at index 0 and 1 of the args vector. We then call the eval() method on each argument, passing in the runtime context ctx, and store the evaluated results in arg1 and arg2. These variables will now hold the evaluated values of the first two nuBASIC function arguments.

It is important to respect the original semantics of evaluating expression arguments from left to right, as well as the order in which the arguments are listed in the args vector. To ensure this, we need to evaluate the arguments in the correct order to avoid introducing unexpected behaviors. Each expression may execute functions with side effects on the global context, and the order of these side effects must be preserved.

Once we have evaluated the first two arguments and obtained the resulting variant objects, we can assume that they represent vectors of numbers. It doesn't matter if they are of type Integer, Float, Double, or any other numeric type, as they can be safely converted to Double. To verify if the evaluated objects actually represent vectors, we can use the vector_size() method of a variant object.


Processing and validation of all the input arguments

Now we can move on to processing and validating all the input arguments. We will check the actual size of the vectors. If we have only two parameters, we can assume that the vector size is the same as the array size.

If the number of arguments is four, we need to process the additional two arguments as integers. It is important to generate a runtime error if the first two arguments are not vectors or if any of the additional two arguments are invalid, meaning they are greater than the array size.

Here's an updated code snippet that incorporates these checks:

variant_t conv_functor(

    rt_prog_ctx_t& ctx,

    const std::string& name,

    const nu::func_args_t& args

{

    // Get number of arguments

    const auto args_num = args.size();


    // Validate the arguments

    rt_error_code_t::get_instance().throw_if(

        args_num != 4 && args_num != 2, 0, rt_error_code_t::E_INVALID_ARGS, "");


    auto variant_v1 = args[0]->eval(ctx);

    auto variant_v2 = args[1]->eval(ctx);

       const auto actual_v1_size = variant_v1.vector_size();

    const auto actual_v2_size = variant_v2.vector_size();

    const size_t size_v1 =

       args_num == 4 ? size_t(args[2]->eval(ctx).to_long64()) : actual_v1_size;

    const size_t size_v2 =

       args_num == 4 ? size_t(args[3]->eval(ctx).to_long64()) : actual_v2_size;

    rt_error_code_t::get_instance().throw_if(

       size_v1 > actual_v1_size || size_v1<1, 0, rt_error_code_t::E_INV_VECT_SIZE, args[0]->name());

    rt_error_code_t::get_instance().throw_if(

       size_v2 > actual_v2_size || size_v2<1, 0, rt_error_code_t::E_INV_VECT_SIZE, args[1]->name());

     

    // Convert the input parameters into C++ parameters

    // TODO

    // Compute the result

    // TODO

    // Convert the result into nu::variant_t object

    // TODO

    return result;

}


Calculate the result and convert it into nu::variant_t which is how internally the result is represented.


Now we can proceed to calculate the result and convert it into a nu::variant_t, which is the internal representation of the result.

Assuming we have successfully processed the input arguments and obtained the two variant objects (arg1 and arg2) representing the input vectors, as well as their respective sizes (size1 and size2), we can transform them into std::vector<double>. Here's an updated code snippet:

std::vector<double> v1(size_v1);

bool ok = variant_v1.copy_vector_content(v1);

rt_error_code_t::get_instance().throw_if(

   !ok, 0, rt_error_code_t::E_INV_VECT_SIZE, args[0]->name());

std::vector<double> v2(size_v2);

ok = variant_v2.copy_vector_content(v2);

rt_error_code_t::get_instance().throw_if(

   !ok, 0, rt_error_code_t::E_INV_VECT_SIZE, args[1]->name());

Assuming v1 and v2 are C++ vectors of type double initialized to 0, we can proceed with copying the content of variant_v1 into a vector of double precision floating-point numbers using the copy_vector_content() method. If the conversion is not possible, the method will fail and generate a runtime error. If no errors occur at this point, we can call the C++ function conv to perform the actual computation:

auto vr = conv(v1, v2);

After calling the conv function and obtaining the result in vr, we create a nu::variant_t object named result by moving the result vector vr into it. 

nu::variant_t result(std::move(vr));

Finally, we return the result variant as the output of the function.

return result;

Putting it all together, the modified code would look like this:

variant_t conv_functor(

    rt_prog_ctx_t& ctx,

    const std::string& name,

    const nu::func_args_t& args

{

    // Get number of arguments

    const auto args_num = args.size();

    // Validate the number of arguments

    rt_error_code_t::get_instance().throw_if(

       args_num != 4 && args_num != 2, 0, rt_error_code_t::E_INVALID_ARGS, "");

    // Process and validate the arguments

    auto variant_v1 = args[0]->eval(ctx);

    auto variant_v2 = args[1]->eval(ctx);

    const auto actual_v1_size = variant_v1.vector_size();

    const auto actual_v2_size = variant_v2.vector_size();

    const size_t size_v1 =

       args_num == 4 ? size_t(args[2]->eval(ctx).to_long64()) : actual_v1_size;

    const size_t size_v2 =

       args_num == 4 ? size_t(args[3]->eval(ctx).to_long64()) : actual_v2_size;

    rt_error_code_t::get_instance().throw_if(

       size_v1 > actual_v1_size || size_v1<1, 0, rt_error_code_t::E_INV_VECT_SIZE, args[0]->name());

    rt_error_code_t::get_instance().throw_if(

       size_v2 > actual_v2_size || size_v2<1, 0, rt_error_code_t::E_INV_VECT_SIZE, args[1]->name());

    // Convert the input parameters into C++ parameters

    std::vector<double> v1(size_v1);

    std::vector<double> v2(size_v2);

    bool ok = variant_v1.copy_vector_content(v1);

    rt_error_code_t::get_instance().throw_if(

       !ok, 0, rt_error_code_t::E_INV_VECT_SIZE, args[0]->name());

    ok = variant_v2.copy_vector_content(v2);

    rt_error_code_t::get_instance().throw_if(

       !ok, 0, rt_error_code_t::E_INV_VECT_SIZE, args[1]->name());

    // Compute the result

    auto vr = conv(v1, v2);

    // Convert the result into nu::variant_t object

    nu::variant_t result(std::move(vr));

    return result;

}

To map the conv function and insert the functor into the fmap defined inside the global_function_tbl_t::get_instance() function, you would modify the nu_builtin_help.cc file as follows:

fmap["conv"] = conv_functor;

This ensures that the conv function is mapped to the conv_functor callback function.

Next, to extend the inline help and include information about the conv function, you would add a specific entry to the _help_content collection in the nu_builtin_help.cc file:

static help_content_t _help_content[] = {


// ... 

{ lang_item_t::FUNCTION, "conv",

        "Returns a vector of Double as result of convolution 2 given vectors of numbers",

        "Conv( v1, v2 [, count1, count2 ] ))" },

// ...

};

This entry provides a description of the conv function, its usage syntax, and a brief explanation of what it does. This will allow users to access information about the conv function using commands like help and apropos from the nuBASIC console.

Finally, to add conv to the list of reserved words, you would modify the nu_reserved_keywords.cc file, specifically the reserved_keywords_t::list set:

const std::set<std::string> reserved_keywords_t::list = {

    // ...

   "conv", 

    //

};


By including "conv" in the list of reserved keywords, you ensure that it is recognized as a valid keyword in the nuBASIC language.

nuBASIC IDE

nuBASIC IDE (Integrated Development Environment, for Windows and Linux/GTK+) includes a syntax highlighting editor and debugger. It can be an alternative to the inline editor of CLI.

IDE provides comprehensive facilities to programmers for software development, like the syntax highlighting, which is the ability to recognize keywords and display them in different colors. 

Debugger lets you place breakpoints in your source code, add field watches, step through your code, run into procedures, take snapshots and monitor execution as it occurs.

This is particularly useful to write non-trivial programs.