You are a Nougaro developer, and you want to write an amazing library to implement python amazing things in Nougaro? You're at the right place!!
You have to options: either writting it in Nougaro (with limited resources), either writting it in Python (with unlimited resources).
Write libs in Nougaro
Requirements
You need to know how to code in Nougaro.
Start coding
The file should be named nameofthelib.noug
and be located in the lib_
folder in the Nougaro repository.
Simply write all your code. If you want something to be accessible when imported, use one of those syntaxes:
export identifier (as identifier)
export (any expression) as identifier
You can find examples of built-in modules written in Nougaro in the lib_
folder.
Write libs in Python
Warning
Everything changed in 0.16.0-beta and in 0.19.0-beta
Requirements
You need to know how to code in Python. Don't worry, that's easy ;)
Start coding
The file should be named nameofthelib_.py
(the “_” is VERY important!!)
The file will be placed in the lib_
folder in the Nougaro repository.
Paste this generic code in your file:
# -*- coding:utf-8 -*-
""" YourModuleName module
YourModuleName is a module that do [...] and [...] and also [...].
"""
# IMPORTS
# nougaro modules imports
from lib_.lib_to_make_libs import * # useful stuff to make libs.
# built-in python imports
# here put the useful python imports your module will need ;)
__LIB_VERSION__ = 4
Note
Lib versions depending on Nougaro versions:
- prior to 0.20.0:
1
- 0.20.0:
2
- 0.21.0:
3
- 0.22.0-0.23.0:
4
Lib with constants
If your lib contains constants, you can add them to the code, outside any class.
Use py2noug
function imported from lib_to_make_libs
to convert Python values to Nougaro values.
Python types that can be converted into Nougaro values are: str
, int
, float
, bool
(which is converted to 0 or 1), list
, tuple
(converted into list), dict
(converted into a list of lists, each sub-list is [key, value]
), None
. Other types will return generic value Value
, which you can't make anything with...
Example:
AMAZING_CONSTANT = py2noug(3, *default_pos()) # will be Number(3), type 'int'
ANOTHER_AMAZING_CONSTANT = py2noug("Hello world :)", *default_pos()) # will be String("Hello world :)"), type 'str'
(py2noug
and default_pos
are imported from lib_to_make_libs
)
Lib with functions
If you want functions in your lib, paste this code in your file (replace the generic names/texts with appropriate things):
class YourModuleName(ModuleFunction):
""" YourModuleName Module """
functions: dict[str, builtin_function_dict] = {}
def __init__(self, function_name: str):
super().__init__("your_module_name", function_name, "https://link_to_report_bugs.com (not required)", functions=self.functions)
def copy(self):
"""Return a copy of self"""
copy = YourModuleName(self.name)
return self.set_context_and_pos_to_a_copy(copy)
def is_eq(self, other: Value):
return isinstance(other, YourModuleName) and self.name == other.name
Changed in 0.20.0-beta
Prior to 0.20.0, the method is_eq
was named get_comparison_eq
and had this syntax:
def get_comparison_eq(self, other: Value):
if isinstance(other, YourModuleName):
return Number(self.name == other.name, self.pos_start, other.pos_end).set_context(self.context), None
return Number(False, self.pos_start, other.pos_end).set_context(self.context), None
Then, to add a function, add a method in this class following this syntax:
def execute_function_name(self, context: Context):
""" Docstring (optional) """
assert context.symbol_table is not None
...
functions["function_name"] = {
"function": execute_function_name,
"param_names": ["parameter_1", "parameter_2"],
"optional_params": ["optional_parameter_1"],
"should_respect_args_number": True,
# The two following values are used internally.
# They are not documented, don’t set them to True except if you know what you’re doing.
"run_noug_dir": False,
"noug_dir": False
}
Changed in 0.22.0-beta
Prior to 0.22.0-beta, the key "run_noug_dir"
was called "run_noug_dir_work_dir"
.
- keys
param_names
andoptional_params
should be of typelist[str]
. should_respect_args_number
is a bool. It isTrue
when the number of given arguments should match with the number of parameters, andFalse
if not.
You can then add code in your function.
Get arguments
To get the arguments given by the user in your functions, use
context.symbol_table.getf("identifier")
Replace the identifier by the one you given to the parameter.
If your parameter is optional and that the user didn’t specified it, the default value is a Python None
(not a Nougaro NoneValue
).
Danger
Please don’t use .get
symbol table method, it does NOT work in modules (even if it looks like) and should NOT be used. (The short explanation why is that this method looks for the name also in parent symbol table, so if an optional param is not given but the user defined a variable with that name, it will be taken as the value for that param, which is not what we want. We don’t care about parent symbol table, so we just look in the current symbol table which only contains the parameters. The corresponding issue is #10.)
Values
Important
You often have to switch between Nougaro and Python values! To convert python to Nougaro values, use py2noug
function. To convert Nougaro to python values, use noug2py
. Both functions are imported from lib_to_make_libs
. You reed a position with py2noug
: you can simply put self.pos_start
and self.pos_end
or the position of any value you want.
What to return? Values and errors.
If you don't need to return anything, simply use return RTResult().success(NoneValue(*default_pos(), False))
.
Return errors
Oops! It crashed! How to say it to the user under a good shape? Simply use this syntax:
return RTResult().failure(NameOfTheError(
self.pos_start, self.pos_end,
"error message.",
context
))
If the user gave you a value with an incorrect type, use:
return RTResult().failure(RTTypeErrorF(
bad_argument.pos_start, bad_argument.pos_end,
"first/second/third…", "name of the module.name of the function", "type that should be found", bad_argument,
context
))
Replace "first/second/third…"
by the actual index of the argument. If it is the first argument, put "first"
, et cætera. The error will print, for instance, second argument of builtin function 'name of the function' should be 'type that should be found', not 'type of (bad_argument)'.
You can replace self
by any Nougaro value, such as your arguments.
For example, in the lib math
:
if value.value < 0: # we check if the value is greater than (or equal to) 0
return RTResult().failure(RTArithmeticError(
value.pos_start, value.pos_end,
"first argument of the built-in function 'math.sqrt' must be greater than (or equal to) 0.",
exec_context, "lib_.math_.Math.execute_math_sqrt"
))
All the errors available in a Module:
RunTimeError
: basic run-time errorRTIndexError
: IndexErrorRTArithmeticError
: ArithmeticErrorRTNotDefinedError
: NotDefinedErrorRTTypeErrorF
: TypeError (you give a Number but you need a String)RTFileNotFoundError
: FileNotFoundErrorRTAssertionError
: AssertionError (not useful in libs...)RTAttributeError
: AttributeErrorRTOverflowError
: OverflowErrorRTRecursionError
: RecursionError (new in 0.20.0)PythonError
: PythonError: an error due to Python. Don’t put a str error message: pass directly the Python Exception.
Return values
To return a Nougaro value, use
return RTResult().success(value)
To return a Python value, use
return RTResult().success(py2noug(value, self.pos_start, self.pos_end))
WHAT_TO_IMPORT
: DO NOT forget that one!!
When you have finished coding your amazing lib, create the WHAT_TO_IMPORT
dict. It tells to the Nougaro Interpreter what should be imported and with which name. Follow this syntax:
WHAT_TO_IMPORT = {
"name_of_a_constant": IDENTIFIER,
"name_of_a_function": YourModuleName('function_name'),
}
The function name is what you specified as the key of the functions
dictionary.
Custom errors
To use a custom error in your code, simply copy-paste this code:
class YourErrorName(RunTimeError):
"""YourErrorName is an error that can be triggered ONLY via functions in this module."""
def __init__(self, pos_start: Position, pos_end: Position, details: str, context: Context, origin_file: str = "lib_.(nameofthelib)_"):
super().__init__(pos_start, pos_end, details, context, rt_error=False, error_name="YourErrorName", origin_file=origin_file)
Then, call it like any other errors (like this)
Note: origin_file
is used for debugging. To trigger it, you can run:
nougaro> import debug;debug.enable()
DEBUG mode is now ENABLED.
[<module debug>, None]
nougaro> (anything that throws an error)
Example:
nougaro> import debug;debug.enable()
DEBUG mode is now ENABLED.
[<module debug>, None]
nougaro> thisisnotdefined
[identifier:"thisisnotdefined", end of file]
list:[(var_access:[identifier:"thisisnotdefined"], False)]
(from src.runtime.interpreter.Interpreter.visit_VarAccessNode (is NOT lib and close match in symbol table))
Traceback (most recent call last):
In file <stdin>, line 1, in <program>:
thisisnotdefined
^^^^^^^^^^^^^^^^
NotDefinedError: name 'thisisnotdefined' is not defined. Did you mean 'is_none'?
nougaro> this is a syntax error
[identifier:"this", identifier:"is", identifier:"a", identifier:"syntax", identifier:"error", end of file]
(from src.parser.parser.Parser.statements)
In file <stdin>, line 1:
this is a syntax error
^^
InvalidSyntaxError: unexpected token: identifier:"is".
nougaro>
Here, origin_file
is src.runtime.interpreter.Interpreter.visit_VarAccessNode
and src.parser.parser.Parser.parse
.
Example: RTStatisticsError
in built-in lib statistics
class RTStatisticsError(RunTimeError):
"""StatisticsError is an error that can be triggered ONLY via functions in this module."""
def __init__(self, pos_start: Position, pos_end: Position, details: str, context: Context,
origin_file: str = "lib_.statistics_"):
super().__init__(pos_start, pos_end, details, context, rt_error=False, error_name="StatisticsError",
origin_file=origin_file)
Examples
You can read builtin files stored in lib_
folder to make you a good idea of what a nice-coded lib is ;)
Here you go!
If you think that your lib is absolutely awesome, you can open a PR to submit your idea. The best ideas will be implemented into vanilla Nougaro! If you think that this document is incomplete, that it didn’t help you, or just to find some help, open an issue.