You've already forked gnatcoll-core
mirror of
https://github.com/AdaCore/gnatcoll-core.git
synced 2026-02-12 12:58:57 -08:00
Define So_Ext and use it to compute Library_Version. Use explicit sources subdir at installation to ensure that the same source directory is used for all library variants. TN: P909-033 no-precommit-check Change-Id: I717c40f0f55001e3fa63c519b35820eaf6dc9f2d
1100 lines
44 KiB
ReStructuredText
1100 lines
44 KiB
ReStructuredText
.. _Embedding_script_languages:
|
|
|
|
***************************************
|
|
**Scripts**: Embedding script languages
|
|
***************************************
|
|
|
|
In a lot of contexts, you want to give the possibility to users to extend
|
|
your application. This can be done in several ways: define an Ada API from
|
|
which they can build dynamically loadable modules, provide the whole source
|
|
code to your application and let users recompile it, interface with a simpler
|
|
scripting languages,...
|
|
|
|
Dynamically loadable modules can be loaded on demand, as their name indicate.
|
|
However, they generally require a relatively complex environment to build,
|
|
and are somewhat less portable. But when your users are familiar with Ada,
|
|
they provide a programming environment in which they are comfortable.
|
|
As usual, changing the module requires recompilation, re-installation,...
|
|
|
|
Providing the source code to your application is generally even more
|
|
complex for users. This requires an even more complex setup, your application
|
|
is generally too big for users to dive into, and modifications done by one
|
|
users are hard to provide to other users, or will be lost when you
|
|
distribute a new version of your application.
|
|
|
|
The third solution is to embed one or more scripting languages in your
|
|
application, and export some functions to it. This often requires your users
|
|
to learn a new language, but these languages are generally relatively simple,
|
|
and since they are interpreted they are easier to learn in an interactive
|
|
console. The resulting scripts can easily be redistributed to other users or
|
|
even distributed with future versions of your application.
|
|
|
|
The module in GNATColl helps you implement the third solution. It was
|
|
used extensively in the GPS programming environment for its python interface.
|
|
|
|
|Tip| Each of the scripting language is optional
|
|
|
|
This module can be compiled with any of these languages as an optional
|
|
dependency (except for the shell language, which is always built-in, but is
|
|
extremely minimal, and doesn't have to be loaded at run time anyway).
|
|
If the necessary libraries are found on the system, GNATColl will
|
|
be build with support for the corresponding language, but your application
|
|
can chose at run time whether or not to activate the support for a specific
|
|
language.
|
|
|
|
.. index:: test driver
|
|
.. index:: testing your application
|
|
|
|
|Tip| Use a scripting language to provide an automatic testing framework for
|
|
your application.
|
|
|
|
The GPS environment uses python command for its *automatic test suite*,
|
|
including graphical tests such as pressing on a button, selecting a
|
|
menu,...
|
|
|
|
.. _Supported_languages:
|
|
|
|
Supported languages
|
|
===================
|
|
|
|
The module provides built-in support for several scripting languages, and
|
|
other languages can "easily" be added. Your application does not change
|
|
when new languages are added, since the interface to export subprograms
|
|
and classes to the scripting languages is language-neutral, and will
|
|
automatically export to all known scripting languages.
|
|
|
|
The Core component provides support for the following language:
|
|
|
|
*Shell*
|
|
This is a very simple-minded scripting language, which doesn't provide
|
|
flow-control instructions (:ref:`The_Shell_language`).
|
|
|
|
Optional components add support for other languages, e.g. Python. Please
|
|
refer to the corresponding component's documentation.
|
|
|
|
|
|
.. _The_Shell_language:
|
|
|
|
The Shell language
|
|
------------------
|
|
|
|
The shell language was initially developed in the context of the GPS
|
|
programming environment, as a way to embed scripting commands in XML
|
|
configuration files.
|
|
|
|
In this language, you can execute any of the commands exported by the
|
|
application, passing any number of arguments they need. Arguments to function
|
|
calls can, but need not, be quoted. Quoting is only mandatory when they
|
|
contain spaces, newline characters, or double-quotes ('"'). To quote an
|
|
argument, surround it by double-quotes, and precede each double-quote it
|
|
contains by a backslash character. Another way of quoting is similar to
|
|
what python provides, which is to triple-quote the argument, i.e. surround it
|
|
by '"""' on each side. In such a case, any special character (in particular
|
|
other double-quotes or backslashes) lose their special meaning and are just
|
|
taken as part of the argument. This is in particular useful when you do not
|
|
know in advance the contents of the argument you are quoting::
|
|
|
|
Shell> function_name arg1 "arg 2" """arg 3"""
|
|
|
|
Commands are executed as if on a stack machine: the result of a command is
|
|
pushed on the stack, and later commands can reference it using `%`
|
|
following by a number. By default, the number of previous results that are
|
|
kept is set to 9, and this can only be changed by modifying the source code
|
|
for GNATColl. The return values are also modified by commands executed
|
|
internally by your application, and that might have no visible output from
|
|
the user's point of view. As a result, you should never assume you know
|
|
what `%1`,... contain unless you just executed a command in the
|
|
same script::
|
|
|
|
Shell> function_name arg1
|
|
Shell> function2_name %1
|
|
|
|
In particular, the `%1` syntax is used when emulating object-oriented
|
|
programming in the shell. A method of a class is just a particular function
|
|
that contains a '.' in its name, and whose first implicit argument is the
|
|
instance on which it applies. This instance is generally the result of
|
|
calling a constructor in an earlier call. Assuming, for instance, that we
|
|
have exported a class "Base" to the shell from our Ada core, we could use
|
|
the following code::
|
|
|
|
Shell> Base arg1 arg2
|
|
Shell> Base.method %1 arg1 arg2
|
|
|
|
to create an instance and call one of its methods.
|
|
Of course, the shell is not the best language for object-oriented programming,
|
|
and better languages should be used instead.
|
|
|
|
When an instance has associated properties (which you can export from Ada
|
|
using `Set_Property`), you access the properties by prefixing its name
|
|
with "@"::
|
|
|
|
Shell> Base arg1 arg2 # Build new instance
|
|
Shell> @id %1 # Access its "id" field
|
|
Shell> @id %1 5 # Set its "id" field
|
|
|
|
|
|
Some commands are automatically added to the shell when this scripting
|
|
language is added to the application. These are
|
|
|
|
.. index:: Function load
|
|
|
|
`Function load (file)`
|
|
Loads the content of `file` from the disk, and execute each of its lines as
|
|
a Shell command. This can for instance be used to load scripts when your
|
|
application is loaded
|
|
|
|
.. index:: Function echo
|
|
|
|
`Function echo (arg...)`
|
|
This function takes any number of argument, and prints them in the console
|
|
associated with the language. By default, when in an interactive console, the
|
|
output of commands is automatically printed to the console. But when you
|
|
execute a script through `load` above, you need to explicitly call
|
|
`echo` to make some output visible.
|
|
|
|
.. index:: Function clear_cache
|
|
|
|
`Function clear_cache`
|
|
This frees the memory used to store the output of previous commands. Calling
|
|
`%1` afterward will not make sense until further commands are executed.
|
|
|
|
|
|
.. _Classes_exported_to_all_languages:
|
|
|
|
Classes exported to all languages
|
|
---------------------------------
|
|
|
|
In addition to the functions exported by each specific scripting language,
|
|
as described above, GNATColl exports the following to all the
|
|
scripting languages. These are exported when your Ada code calls the
|
|
Ada procedure `GNATCOLL.Scripts.Register_Standard_Classes`, which should
|
|
done after you have loaded all the scripting languages.
|
|
|
|
.. index:: Class Console
|
|
|
|
`Class Console`
|
|
`Console` is a name that you can chose yourself when you call the
|
|
above Ada procedure. It will be assumed to be `Console` in the rest
|
|
of this document.
|
|
|
|
This class provides an interface to consoles. A console is an input/output
|
|
area in your application (whether it is a text area in a graphical
|
|
application, or simply standard text I/O in text mode). In particular,
|
|
the python standard output streams `sys.stdin`, `sys.stdout`
|
|
and `sys.stderr` are redirected to an instance of that class. If you
|
|
want to see python's error messages or usual output in your application,
|
|
you must register that class, and define a default console for your
|
|
scripting language through calls to
|
|
`GNATCOLL.Scripts.Set_Default_Console`.
|
|
|
|
You can later add new methods to this class, which would be specific to your
|
|
application. Or you can derive this class into a new class to achieve a
|
|
similar goal.
|
|
|
|
.. index:: Console.write
|
|
|
|
`Console.write(text)`
|
|
This method writes `text` to the console associated with the class
|
|
instance. See the examples delivered with GNATColl for examples on
|
|
how to create a graphical window and make it into a `Console`.
|
|
|
|
.. index:: Console.clear
|
|
|
|
`Console.clear()`
|
|
Clears the contents of the console.
|
|
|
|
.. index:: Console.flush
|
|
|
|
`Console.flush()`
|
|
Does nothing currently, but is needed for compatibility with python.
|
|
Output through `Console` instances is not buffered anyway.
|
|
|
|
.. index:: Console.isatty
|
|
|
|
`Console.isatty(): Boolean`
|
|
Whether the console is a pseudo-terminal. This is always wrong in the
|
|
case of GNATColl.
|
|
|
|
.. index:: Console.read
|
|
|
|
`Console.read([size]): string`
|
|
Reads at most `size` bytes from the console, and returns the resulting
|
|
string.
|
|
|
|
.. index:: Console.readline
|
|
|
|
`Console.readline([size]): string`
|
|
Reads at most `size` lines from the console, and returns them as a single
|
|
string.
|
|
|
|
.. _Scripts_API:
|
|
|
|
Scripts API
|
|
===========
|
|
|
|
This section will give an overview of the API used in the scripts module.
|
|
The reference documentation for this API is in the source files themselves. In
|
|
particular, each :file:`.ads` file fully documents all its public API.
|
|
|
|
As described above, GNATColl contains several levels of API. In
|
|
particular, it provides a low-level interface to python, in the packages
|
|
`GNATCOLL.Python`. This interface is used by the rest of GNATColl,
|
|
but is likely too low-level to really be convenient in your applications,
|
|
since you need to take care of memory management and type conversions by
|
|
yourself.
|
|
|
|
Instead, GNATColl provides a language-neutral Ada API. Using this
|
|
API, it is transparent for your application whether you are talking to the
|
|
Shell, to python, or to another language integrated in GNATColl.
|
|
The code remains exactly the same, and new scripting languages can be added
|
|
in later releases of GNATColl without requiring a change in your
|
|
application. This flexibility is central to the design of GNATColl.
|
|
|
|
In exchange for that flexibility, however, there are language-specific
|
|
features that cannot be performed through the GNATColl API. At
|
|
present, this includes for instance exporting functions that return hash
|
|
tables. But GNATColl doesn't try to export the greatest set of
|
|
features common to all languages. On the contrary, it tries to fully
|
|
support all the languages, and provide reasonable fallback for languages
|
|
that do not support that feature. For instance, named parameters (which
|
|
are a part of the python language) are fully supported, although the
|
|
shell language doesn't support them. But that's an implementation detail
|
|
transparent to your own application.
|
|
|
|
Likewise, your application might decide to always load the python
|
|
scripting language. If GNATColl wasn't compiled with python support,
|
|
the corresponding Ada function still exists (and thus your code still
|
|
compiles), although of course it does nothing. But since the rest of the
|
|
code is independent of python, this is totally transparent for your
|
|
application.
|
|
|
|
|Tip| GNATColl comes with some examples, which you can use
|
|
as a reference when building your own application.
|
|
See the :file:`<prefix>/share/examples/gnatcoll` directory.
|
|
|
|
Interfacing your application with the scripting module is a multistep
|
|
process:
|
|
|
|
* You *must* **initialize** GNATColl and decide which features
|
|
to load
|
|
* You *can* create an **interactive console** for the various
|
|
languages, so that users can perform experiments interactively. This
|
|
is optional, and you could decide to keep the scripting language has a
|
|
hidden implementation detail (or just for automatic testing purposes
|
|
for instance)
|
|
* You *can* **export** some classes and methods.
|
|
This is optional, but it doesn't really make sense to just embed a
|
|
scripting language and export nothing to it. In such a case, you might
|
|
as well spawn a separate executable.
|
|
* You *can* load **start up scripts** or plug-ins that users have
|
|
written to extend your application.
|
|
|
|
.. _Initializing_the_scripting_module:
|
|
|
|
Initializing the scripting module
|
|
---------------------------------
|
|
|
|
GNATColl must be initialized properly in order to provide added
|
|
value to your application. This cannot be done automatically simply by
|
|
depending on the library, since this initialization requires multiple-step
|
|
that must be done at specific moments in the initialization of your whole
|
|
application.
|
|
|
|
This initialization does not depend on whether you have build support
|
|
for python in GNATColl. The same packages and subprograms
|
|
are available in all cases, and therefore you do not need conditional
|
|
compilation in your application to support the various cases.
|
|
|
|
.. _Create_the_scripts_repository:
|
|
|
|
Create the scripts repository
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
The type `GNATCOLL.Scripts.Scripts_Repository` will contain various
|
|
variables common to all the scripting languages, as well as a list of the
|
|
languages that were activated. This is the starting point for all other
|
|
types, since from there you have access to everything. You will have only
|
|
one variable of this type in your application, but it should generally be
|
|
available from all the code that interfaces with the scripting language.
|
|
|
|
Like the rest of GNATColl, this is a tagged type, which you can
|
|
extend in your own code. For instance, the GPS programming environment is
|
|
organized as a kernel and several optional modules. The kernel provides
|
|
the core functionality of GPS, and should be available from most functions
|
|
that interface with the scripting languages. Since these functions have
|
|
very specific profiles, we cannot pass additional arguments to them. One
|
|
way to work around this limitation is to store the additional arguments
|
|
(in this case a pointer to the kernel) in a class derived from
|
|
`Scripts_Repository_Data`.
|
|
|
|
.. highlight:: ada
|
|
|
|
As a result, the code would look like::
|
|
|
|
with GNATCOLL.Scripts;
|
|
Repo : Scripts_Repository := new Scripts_Repository_Record;
|
|
|
|
or, in the more complex case of GPS described above::
|
|
|
|
type Kernel_Scripts_Repository is new
|
|
Scripts_Repository_Data with record
|
|
Kernel : ...;
|
|
end record;
|
|
Repo : Scripts_Repository := new Kernel_Scripts_Repository'
|
|
(Scripts_Repository_Data with Kernel => ...);
|
|
|
|
.. _Loading_the_scripting_language:
|
|
|
|
Loading the scripting language
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
The next step is to decide which scripting languages should be made
|
|
available to users. This must be done before any function is exported,
|
|
since only functions exported after a language has been loaded will be
|
|
made available in that language.
|
|
|
|
|Note| If for instance python support was build into GNATColl, and
|
|
if you decide not to make it available to users, your application will
|
|
still be linked with :file:`libpython`. It is therefore recommended although
|
|
not mandatory to only build those languages that you will use.
|
|
|
|
This is done through a simple call to one or more subprograms. The following
|
|
example registers both the shell and python languages::
|
|
|
|
with GNATCOLL.Scripts.Python;
|
|
with GNATCOLL.Scripts.Shell;
|
|
Register_Shell_Scripting (Repo);
|
|
Register_Python_Scripting (Repo, "MyModule");
|
|
|
|
.. index:: Procedure Register_Shell_Scripting
|
|
|
|
`Procedure Register_Shell_Scripting (Repo)`
|
|
This adds support for the shell language. Any class or function that is
|
|
now exported through GNATColl will be made available in the shell
|
|
|
|
.. index:: Procedure Register_Python_Scripting
|
|
|
|
`Procedure Register_Python_Scripting (Repo, Module_Name)`
|
|
This adds support for the python language. Any class or function exported
|
|
from now on will be made available in python, in the module specified
|
|
by `Module_Name`
|
|
|
|
.. _Exporting_standard_classes:
|
|
|
|
Exporting standard classes
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
To be fully functional, GNATColl requires some predefined classes
|
|
to be exported to all languages (:ref:`Classes_exported_to_all_languages`).
|
|
For instance, the `Console` class is needed for proper interactive with
|
|
the consoles associated with each language.
|
|
|
|
These classes are created with the following code::
|
|
|
|
Register_Standard_Classes (Repo, "Console");
|
|
|
|
This must be done only after all the scripting languages were loaded in the
|
|
previous step, since otherwise the new classes would not be visible in the
|
|
other languages.
|
|
|
|
.. index:: Procedure Register_Standard_Classes
|
|
|
|
`Procedure Register_Standard_Classes(Repo,Console_Class)`
|
|
The second parameter `Console_Class` is the name of the class that
|
|
is bound to a console, and thus provides input/output support. You can chose
|
|
this name so that it matches the classes you intend to export later on from
|
|
your application.
|
|
|
|
.. _Creating_interactive_consoles:
|
|
|
|
Creating interactive consoles
|
|
-----------------------------
|
|
|
|
The goal of the scripting module in GNATColl is to work both in
|
|
text-only applications and graphical applications.
|
|
However, in both cases applications will need a way to capture the output
|
|
of scripting languages and display them to the user (at least for errors, to
|
|
help debugging scripts), and possibly emulate input when a script is waiting
|
|
for such input.
|
|
|
|
GNATColl solved this problem by using an abstract class
|
|
`GNATCOLL.Scripts.Virtual_Console_Record` that defines an API for these
|
|
consoles. This API is used throughout `GNATCOLL.Scripts` whenever input or
|
|
output has to be performed.
|
|
|
|
|Tip| The :file:`examples/` directory in the GNATColl package
|
|
shows how to implement a console in text mode and in graphical mode.
|
|
|
|
If you want to provide feedback or interact with users, you will need to
|
|
provide an actual implementation for these `Virtual_Console`, specific
|
|
to your application. This could be a graphical text window, or based on
|
|
`Ada.Text_IO`. The full API is fully documented in
|
|
:file:`gnatcoll-scripts.ads`, but here is a list of the main subprograms that
|
|
need to be overriden.
|
|
|
|
.. index:: Virtual_Console.Insert_Text
|
|
|
|
`Virtual_Console.Insert_Text (Txt)`
|
|
|
|
.. index:: Virtual_Console.Insert_Log
|
|
|
|
`Virtual_Console.Insert_Log (Txt)`
|
|
|
|
.. index:: Virtual_Console.Insert_Error
|
|
|
|
`Virtual_Console.Insert_Error (Txt)`
|
|
These are the various methods for doing output. Error messages could for
|
|
instance be printed in a different color. Log messages should in general
|
|
be directed elsewhere, and not be made visible to users unless in special
|
|
debugging modes.
|
|
|
|
.. index:: Virtual_Console.Insert_Prompt
|
|
|
|
`Virtual_Console.Insert_Prompt (Txt)`
|
|
This method must display a prompt so that the user knows input is expected.
|
|
Graphical consoles will in general need to remember where the prompt ended
|
|
so that they also know where the user input starts
|
|
|
|
.. index:: Virtual_Console.Set_As_Default_Console
|
|
|
|
`Virtual_Console.Set_As_Default_Console (Script)`
|
|
This method is called when the console becomes the default console for
|
|
a scripting language. They should in general keep a pointer on that
|
|
language, so that when the user presses :kbd:`enter` they know which language
|
|
must execute the command
|
|
|
|
.. index:: Virtual_Console.Read
|
|
|
|
`Virtual_Console.Read (Size, Whole_Line) : String`
|
|
Read either several characters or whole lines from the console. This is
|
|
called when the user scripts read from their stdin.
|
|
|
|
.. index:: Virtual_Console.Set_Data_Primitive
|
|
|
|
`Virtual_Console.Set_Data_Primitive (Instance)`
|
|
|
|
.. index:: Virtual_Console.Get_Instance
|
|
|
|
`Virtual_Console.Get_Instance : Console`
|
|
These two methods are responsible for storing an instance of `Console`
|
|
into a `GNATCOLL.Scripts.Class_Instance`. Such an instance is
|
|
what the user
|
|
manipulates from his scripting language. But when he executes a method, the
|
|
Ada callback must know how to get the associated `Virtual_Console`
|
|
back to perform actual operations on it.
|
|
|
|
These methods are implemented using one of the `GNATCOLL.Scripts.Set_Data`
|
|
and `GNATCOLL.Scripts.Get_Data` operations when in text mode.
|
|
|
|
.. highlight:: ada
|
|
|
|
Once you have created one or more of these console, you can set them as
|
|
the default console for each of the scripting languages. This way, any
|
|
input/output done by scripts in this language will interact with that
|
|
console, instead of being discarded. This is done through code similar
|
|
to::
|
|
|
|
Console := GtkConsole.Create (...);
|
|
Set_Default_Console
|
|
(Lookup_Scripting_Language (Repo, "python"),
|
|
Virtual_Console (Console));
|
|
|
|
Creating a new instance of `Console`, although allowed, will by
|
|
default create an unusable console. Indeed, depending on your application,
|
|
you might want to create a new window, reuse an existing one, or do many
|
|
other things when the user does::
|
|
|
|
c = Console()
|
|
|
|
As a result, GNATColl does not try to guess the correct behavior,
|
|
and thus does not export a constructor for the console. So in the above
|
|
python code, the default python constructor is used. But this constructor
|
|
does not associate `c` with any actual `Virtual_Console`, and
|
|
thus any call to a method of `c` will result in an error.
|
|
|
|
To make it possible for users to create their own consoles, you need to
|
|
export a `Constructor_Method` (see below) for the `Console`
|
|
class. In addition to your own processing, this constructor needs also to
|
|
call::
|
|
|
|
declare
|
|
Inst : constant Class_Instance := Nth_Arg (Data, 1);
|
|
begin
|
|
C := new My_Console_Record; -- or your own type
|
|
GNATCOLL.Scripts.Set_Data (Inst, C);
|
|
end
|
|
|
|
.. _Exporting_classes_and_methods:
|
|
|
|
Exporting classes and methods
|
|
-----------------------------
|
|
|
|
Once all scripting languages have been loaded, you can start exporting
|
|
new classes and functions to all the scripting languages. It is important
|
|
to realize that through a single Ada call, they are exported to all loaded
|
|
scripting languages, without further work required on your part.
|
|
|
|
.. _Classes_diagram:
|
|
|
|
Classes diagram
|
|
^^^^^^^^^^^^^^^
|
|
|
|
The following diagram shows the dependencies between the major data types
|
|
defined in :file:`GNATCOLL.Scripts`. Most of these are abstract classes that
|
|
are implemented by the various scripting languages. Here is a brief description
|
|
of the role of each type:
|
|
|
|
.. index:: class diagram, script module
|
|
|
|
.. image:: classes.png
|
|
|
|
.. index:: Class Scripts_Repository
|
|
|
|
`Class Scripts_Repository`
|
|
As we have seen before, this is a type of which there is a single instance
|
|
in your whole application, and whose main role is to give access to each
|
|
of the scripting languages (`Lookup_Scripting_Language` function), and
|
|
to make it possible to register each exported function only once (it then
|
|
takes care of exporting it to each scripting language).
|
|
|
|
.. index:: Class Scripting_Language
|
|
|
|
`Class Scripting_Language`
|
|
Instances of this type represent a specific language. It provides various
|
|
operations to export subprograms, execute commands, create the other types
|
|
described below,... There should exists a single instance of this class per
|
|
supported language.
|
|
|
|
This class interacts with the script interpreter (for instance python), and
|
|
all code executed in python goes through this type, which then executes your
|
|
Ada callbacks to perform the actual operation.
|
|
|
|
It is also associated with a default console, as described above, so that
|
|
all input and output of the scripts can be made visible to the user.
|
|
|
|
.. index:: Class Callback_Data
|
|
|
|
`Class Callback_Data`
|
|
This type is an opaque tagged type that provides a language-independent
|
|
interface to the scripting language. It gives for instance access to the
|
|
various parameters passed to your subprogram (`Nth_Arg` functions),
|
|
allows you to set the return value (`Set_Return_Value` procedure),
|
|
or raise exceptions (`Set_Error_Msg` procedure),...
|
|
|
|
.. index:: Record Class_Type
|
|
|
|
`Record Class_Type`
|
|
This type is not tagged, and cannot be extended. It basically represents a
|
|
class in any of the scripting languages, and is used to create new instances
|
|
of that class from Ada.
|
|
|
|
.. index:: Class Class_Instance
|
|
|
|
`Class Class_Instance`
|
|
A class instance represents a specific instance of a class. In general,
|
|
such an instance is strongly bound to an instance of an Ada type. For
|
|
instance, if you have a `Foo` type in your application that you wish
|
|
to export, you would create a `Class_Type` called "Foo", and then the
|
|
user can create as many instances as he wants of that class, each of which
|
|
is associated with different values of `Foo` in Ada.
|
|
|
|
Another more specific example is the predefined `Console` class. As
|
|
we have seen before, this is a `Virtual_Console` in Ada. You could
|
|
for instance have two graphical windows in your application, each of which
|
|
is a `Virtual_Console`. In the scripting language, this is exported
|
|
as a class named `Console`. The user can create two
|
|
instances of those, each of which is associated with one of your graphical
|
|
windows. This way, executing `Console.write` on these instances would
|
|
print the string on their respective graphical window.
|
|
|
|
.. highlight:: python
|
|
|
|
Some scripting languages, in particular python, allow you to store any
|
|
data within the class instances. In the example above, the user could for
|
|
instance store the time stamp of the last output in each of the instances.
|
|
It is therefore important that, as much as possible, you always return the
|
|
same `Class_Instance` for a given Ada object. See the following
|
|
python example::
|
|
|
|
myconsole = Console ("title") # Create new console
|
|
myconsole.mydata = "20060619" # Any data, really
|
|
myconsole = Console ("title2") # Create another window
|
|
myconsole = Console ("title") # Must be same as first,
|
|
print myconsole.mydata # so that this prints "20060619"
|
|
|
|
.. index:: Class Instance_Property
|
|
|
|
`Class Instance_Property`
|
|
As we have seen above, a `Class_Instance` is associated in general with
|
|
an Ada object. This `Instance_Property` tagged type should be extended
|
|
for each Ada type you want to be able to store in a `Class_Instance`.
|
|
You can then use the `Set_Data` and `Get_Data` methods of the
|
|
`Class_Instance` to get and retrieve that associated Ada object.
|
|
|
|
.. index:: Class Subprogram_Record
|
|
|
|
`Class Subprogram_Record`
|
|
This class represents a callback in the scripting language, that is some
|
|
code that can be executed when some conditions are met.
|
|
|
|
The exact semantic here depends on each of the programming languages. For
|
|
instance, if you are programming in python, this is the name of a python
|
|
method to execute. If you are programming in shell, this is any shell code.
|
|
|
|
.. highlight:: python
|
|
|
|
The idea here is to blend in as smoothly as possible with the usual constructs
|
|
of each language. For instance, in python one would prefer to write the
|
|
second line rather than the third::
|
|
|
|
def on_exit():
|
|
pass
|
|
set_on_exit_callback(on_exit) # Yes, python style
|
|
set_on_exit_callback("on_exit") # No
|
|
|
|
The last line (using a string as a parameter) would be extremely unusual
|
|
in python, and would for instance force you to qualify the subprogram name
|
|
with the name of its namespace (there would be no implicit namespace
|
|
resolution).
|
|
|
|
To support this special type of parameters, the `Subprogram_Record`
|
|
type was created in Ada.
|
|
|
|
Although the exact way they are all these types are created is largely
|
|
irrelevant to your specific application in general, it might be useful for you
|
|
to override part of the types to provide more advanced features. For instance,
|
|
GPS redefines its own Shell language, that has basically the same behavior as
|
|
the Shell language described above but whose `Subprogram_Record` in fact
|
|
execute internal GPS actions rather than any shell code.
|
|
|
|
.. _Exporting_functions:
|
|
|
|
Exporting functions
|
|
^^^^^^^^^^^^^^^^^^^
|
|
|
|
.. highlight:: ada
|
|
|
|
All functions that you export to the scripting languages will result in a
|
|
call to an Ada subprogram from your own application. This subprogram must
|
|
have the following profile::
|
|
|
|
procedure Handler
|
|
(Data : in out Callback_Data'Class;
|
|
Command : String);
|
|
|
|
The first parameter `Data` gives you access to the parameters of the
|
|
subprogram as passed from the scripting language, and the second parameter
|
|
`Command` is the name of the command to execute. The idea behind this
|
|
second parameter is that a single Ada procedure might handle several
|
|
different script function (for instance because they require common actions
|
|
to be performed).
|
|
|
|
.. index:: Register_Command
|
|
|
|
`Register_Command (Repo,Command,Min_Args,Max_Args,Handler)`
|
|
Each of the shell functions is then exported through a call to
|
|
`Register_Command`. In its simplest form, this procedure takes the
|
|
following arguments. `Repo` is the scripts repository, so that the
|
|
command is exported to all the scripting languages. `Command` is the
|
|
name of the command. `Min_Args` and `Max_Args` are the minimum and
|
|
maximum number of arguments. Most language allow option parameters, and
|
|
this is how you specify them. `Handler` is the Ada procedure to call
|
|
to execute the command.
|
|
|
|
Here is a simple example. It implements a function called `Add`, which
|
|
takes two integers in parameter, and returns their sum::
|
|
|
|
Arg1_C : aliased constant String := "arg1";
|
|
Arg2_C : aliased constant String := "arg2";
|
|
|
|
procedure Sum
|
|
(Data : in out Callback_Data'Class;
|
|
Command : String)
|
|
is
|
|
Arg1, Arg2 : Integer;
|
|
begin
|
|
Name_Parameters ((1 => Arg1_C'Access, 2 => Arg2_C'Access));
|
|
Arg1 := Nth_Arg (Data, 1);
|
|
Arg2 := Nth_Arg (Data, 2);
|
|
Set_Return_Value (Data, Arg1 + Arg2);
|
|
end Sum;
|
|
|
|
Register_Command (Repo, "sum", 2, 2, Sum'Access);
|
|
|
|
This is not the most useful function to export! Still, it illustrates a
|
|
number of important concepts.
|
|
|
|
Automatic parameters types
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
When the command is registered, the number of arguments is specified.
|
|
This means that GNATColl will check on its own whether the right
|
|
number of arguments is provided. But the type of these arguments is not
|
|
specified. Instead, your callback should proceed as if they were correct,
|
|
and try to retrieve them through one of the numerous `Nth_Arg`
|
|
functions. In the example above, we assume they are integer. But if one of
|
|
them was passed as a string, an exception would be raised and sent back to
|
|
the scripting language to display a proper error message to the user. You
|
|
have nothing special to do here.
|
|
|
|
Support for named parameters
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Some languages (especially python) support named parameters, ie parameters
|
|
can be specified in any order on the command line, as long as they are
|
|
properly identified (very similar to Ada's own capabilities). In the example
|
|
above, the call to `Name_Parameters` is really optional, but adds this
|
|
support for your own functions as well. You just have to specify the name
|
|
of the parameters, and GNATColl will then ensure that when you
|
|
call `Nth_Arg` the parameter number 1 is really "arg1".
|
|
For scripting languages that do not support named parameters, this has no
|
|
effect.
|
|
|
|
Your code can then perform as complex a code as needed, and finally
|
|
return a value (or not) to the scripting language, through a call to
|
|
`Set_Return_Value`.
|
|
|
|
.. highlight:: python
|
|
|
|
After the above code has been executed, your users can go to the python
|
|
console and type for instance::
|
|
|
|
from MyModule import * # MyModule is the name we declared above
|
|
print sum (1,2)
|
|
=> 3
|
|
print sum ()
|
|
=> Error: Wrong number of parameters
|
|
print sum ("1", 2)
|
|
=> Error: Parameter 1 should be an integer
|
|
print sum (arg2=2, arg1=1)
|
|
=> 3
|
|
|
|
.. _Exporting_classes:
|
|
|
|
Exporting classes
|
|
^^^^^^^^^^^^^^^^^
|
|
|
|
Whenever you want to make an Ada type accessible through the scripting
|
|
languages, you should export it as a class. For object-oriented languages,
|
|
this would map to the appropriate concept. For other languages, this provides
|
|
a namespace, so that each method of the class now takes an additional first
|
|
parameter which is the instance of the class, and the name of the method is
|
|
prefixed by the class name.
|
|
|
|
.. highlight:: ada
|
|
|
|
Creating a new class is done through a call to `New_Class`, as shown
|
|
in the example below::
|
|
|
|
MyClass : Class_Type;
|
|
MyClass := GNATCOLL.Scripts.New_Class (Repo, "MyClass");
|
|
|
|
At this stage, nothing is visible in the scripting language, but all the
|
|
required setup has been done internally so that you can now add methods to
|
|
this class.
|
|
|
|
You can then register the class methods in the same way that you registered
|
|
functions. An additional parameter `Class` exists for
|
|
`Register_Command`. A method is really just a standard function that
|
|
has an implicit first parameter which is a `Class_Instance`. This
|
|
extra parameter should not be taken into account in `Min_Args` and
|
|
`Max_Args`. You can also declare the method as a static method, ie
|
|
one that doesn't take this extra implicit parameter, and basically just
|
|
uses the class as a namespace.
|
|
|
|
Some special method names are available. In particular,
|
|
`Constructor_Method` should be used for the constructor of a class.
|
|
It is a method that receives, as its first argument, a class instance that
|
|
has just been created. It should associate that instance with the Ada
|
|
object it represents.
|
|
|
|
.. highlight:: python
|
|
|
|
Here is a simple example that exports a class. Each instance of this class
|
|
is associated with a string, passed in parameter to the constructor. The
|
|
class has a single method `print`, which prints its string parameter
|
|
prefixed by the instance's string. To start with, here is a python example
|
|
on what we want to achieve::
|
|
|
|
c1 = MyClass ("prefix1")
|
|
c1.print ("foo")
|
|
=> "prefix1 foo"
|
|
c2 = MyClass () # Using a default prefix
|
|
c2.print ("foo")
|
|
=> "default foo"
|
|
|
|
.. highlight:: ada
|
|
|
|
Here is the corresponding Ada code::
|
|
|
|
with GNATCOLL.Scripts.Impl;
|
|
procedure Handler
|
|
(Data : **in out** Callback_Data'Class; Command : String)
|
|
is
|
|
Inst : Class_Instance := Nth_Arg (Data, 1, MyClass);
|
|
begin
|
|
if Command = Constructor_Method then
|
|
Set_Data (Inst, MyClass, Nth_Arg (Data, 2, "default"));
|
|
elsif Command = "print" then
|
|
Insert_Text
|
|
(Get_Script (Data), null,
|
|
String'(Get_Data (Inst)) & " " & Nth_Arg (Data, 2));
|
|
end if;
|
|
end Handler;
|
|
|
|
Register_Command
|
|
(Repo, Constructor_Method, 0, 1, Handler'Access, MyClass);
|
|
Register_Command
|
|
(Repo, "print", 1, 1, Handler'Access, MyClass);
|
|
|
|
This example also demonstrates a few concepts: the constructor is declared
|
|
as a method that takes one optional argument. The default value is in
|
|
fact passed in the call to `Nth_Arg` and is set to "default".
|
|
In the handler, we know there is always a first argument which is the
|
|
instance on which the method applies. The implementation for the
|
|
constructor stores the prefix in the instance itself, so that several
|
|
instances can have different prefixes (we can't use global variables,
|
|
of course, since we don't know in advance how many instances will exist).
|
|
The implementation for `print` inserts code in the default console
|
|
for the script (we could of course use `Put_Line` or any other way
|
|
to output data), and computes the string to output by concatenating the
|
|
instance's prefix and the parameter to `print`.
|
|
|
|
Note that `Set_Data` and `Get_Data` take the class in parameter,
|
|
in addition to the class instance. This is needed for proper handling of
|
|
multiple inheritance: say we have a class `C` that extends two classes
|
|
`A` and `B`. The Ada code that deals with `A` associates an
|
|
integer with the class instance, whereas the code that deals with `B`
|
|
associates a string. Now, if you have an instance of `C` but call a
|
|
method inherited from `A`, and if `Get_Data` didn't specify the
|
|
class, there would be a risk that a string would be returned instead of the
|
|
expected integer. In fact, the proper solution here is that both `A`
|
|
and `B` store their preferred data at the same time in the instances,
|
|
but only fetch the one they actually need. Therefore instances of `C`
|
|
are associated with two datas.
|
|
|
|
Here is a more advanced example that shows how to export an Ada object. Let's
|
|
assume we have the following Ada type that we want to make available to
|
|
scripts::
|
|
|
|
type MyType is record
|
|
Field : Integer;
|
|
end record;
|
|
|
|
As you can see, this is not a tagged type, but could certainly be. There is
|
|
of course no procedure `Set_Data` in :file:`GNATCOLL.Scripts` that enables
|
|
us to store `MyType` in a `Class_Instance`. This example shows how
|
|
to write such a procedure. The rest of the code would be similar to the
|
|
first example, with a constructor that calls `Set_Data`, and methods
|
|
that call `Get_Data`::
|
|
|
|
type MyPropsR is new Instance_Property_Record with record
|
|
Val : MyType;
|
|
end record;
|
|
type MyProps is access all MyPropsR'Class;
|
|
|
|
procedure Set_Data
|
|
(Inst : Class_Instance; Val : MyType)
|
|
is
|
|
begin
|
|
Set_Data (Inst, Get_Name (MyClass), MyPropsR'(Val => Val));
|
|
end Set_Data;
|
|
|
|
function Get_Data (Inst : Class_Instance) return MyType is
|
|
Data : MyProps := MyProps (Instance_Property'
|
|
(Get_Data (Inst, Get_Name (MyClass))));
|
|
begin
|
|
return Data.Val;
|
|
end Get_Data;
|
|
|
|
Several aspects worth noting in this example. Each data is associated with
|
|
a name, not a class as in the previous example. That's in fact the same
|
|
thing, and mostly for historical reasons. We have to create our own
|
|
instance of `Instance_Property_Record` to store the data, but the
|
|
implementation presents no special difficulty. In fact, we don't absolutely
|
|
need to create `Set_Data` and `Get_Data` and could do everything
|
|
inline in the method implementation, but it is cleaner this way and easier
|
|
to reuse.
|
|
|
|
GNATColl is fully responsible for managing the lifetime of the
|
|
data associated with the class instances and you can override the procedure
|
|
`Destroy` if you need special memory management.
|
|
|
|
.. _Reusing_class_instances:
|
|
|
|
Reusing class instances
|
|
^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
We mentioned above that it is more convenient for users of your exported
|
|
classes if you always return the same class instance for the same Ada
|
|
object (for instance a graphical window should always be associated with
|
|
the same class instance), so that users can associate their own internal
|
|
data with them.
|
|
|
|
GNATColl provides a few types to facilitate this. In passing, it
|
|
is worth noting that in fact the Ada objects will be associated with a
|
|
single instance *per scripting language*, but each language has its
|
|
own instance. Data is not magically transferred from python to shell!
|
|
|
|
You should store the list of associated instances with
|
|
your object. The type `GNATCOLL.Scripts.Instance_List_Access` is meant for
|
|
that purpose, and provides two `Set` and `Get` primitives
|
|
to retrieve existing instances.
|
|
|
|
The final aspect to consider here is how to return existing instances.
|
|
This cannot be done from the constructor method, since when it is called
|
|
it has already received the created instance (this is forced by python, and
|
|
was done the same for other languages for compatibility reasons).
|
|
There are two ways to work around that limitation:
|
|
|
|
* Static `get` methods
|
|
|
|
.. highlight:: python
|
|
|
|
With each of your classes, you can export a static method generally called
|
|
`get` that takes in parameter a way to identify an existing instance,
|
|
and either return it or create a new one. It is also recommended to disable
|
|
the constructor, ie force it to raise an error. Let's examine the python
|
|
code as it would be used::
|
|
|
|
ed = Editor ("file.adb") # constructor
|
|
=> Error, cannot construct instances
|
|
ed = Editor.get ("file.adb")
|
|
=> Create a new instance
|
|
ed2 = Editor.get ("file.adb")
|
|
=> Return existing instance
|
|
ed == ed2
|
|
=> True
|
|
|
|
.. highlight:: ada
|
|
|
|
The corresponding Ada code would be something like::
|
|
|
|
type MyType is record
|
|
Val : Integer;
|
|
Inst : Instance_List_Access;
|
|
end record;
|
|
type MyTypeAccess is access all MyType;
|
|
procedure Handler
|
|
(Data : in out Callback_Data'Class; Cmd : String)
|
|
is
|
|
Inst : Class_Instance;
|
|
Tmp : MyTypeAccess;
|
|
begin
|
|
if Cmd = Constructor_Method then
|
|
Set_Error_Msg (Data, "cannot construct instances");
|
|
elsif Cmd = "get" then
|
|
Tmp := check_if_exists (Nth_Arg (Data, 1));
|
|
if Tmp = null then
|
|
Tmp := create_new_mytype (Nth_Arg (Data, 1));
|
|
Tmp.Inst := new Instance_List;
|
|
end if;
|
|
Inst := Get (Tmp.Inst.all, Get_Script (Data));
|
|
if Inst = No_Class_Instance then
|
|
Inst := New_Instance (Get_Script (Data), MyClass);
|
|
Set (Tmp.Inst.all, Get_Script (Data), Inst);
|
|
Set_Data (Inst, Tmp);
|
|
end if;
|
|
Set_Return_Value (Data, Inst);
|
|
end if;
|
|
end Handler;
|
|
|
|
* Factory classes
|
|
|
|
The standard way to do this in python, which applies to other languages
|
|
as well, is to use the Factory design pattern. For this, we need to
|
|
create one class (`MyClassImpl`) and one factory
|
|
function (`MyClass`).
|
|
|
|
.. highlight:: python
|
|
|
|
The python code now looks like::
|
|
|
|
ed = MyClass ("file.adb") # Create new instance
|
|
=> ed is of type MyClassImpl
|
|
ed = MyClass ("file.adb") # return same instance
|
|
ed.do_something()
|
|
|
|
|
|
It is important to realize that in the call above, we are not calling
|
|
the constructor of a class, but a function. At the Ada level, the function
|
|
has basically the same implementation as the one we gave for `get`
|
|
above. But the python code looks nicer because we do not have these
|
|
additional `.get()` calls. The name of the class `MyClassImpl`
|
|
doesn't appear anywhere in the python code, so this is mostly transparent.
|
|
|
|
However, if you have more than one scripting language, in particular for
|
|
the shell, the code looks less nice in this case::
|
|
|
|
MyClass "file.adb"
|
|
=> <MyClassImpl_Instance_0x12345>
|
|
MyClassImpl.do_something %1
|
|
|
|
and the new name of the class is visible in the method call.
|
|
|
|
|
|
.. _Executing_startup_scripts:
|
|
|
|
Executing startup scripts
|
|
-------------------------
|
|
|
|
The final step in starting up your application is to load extensions or
|
|
plug-ins written in one of the scripting languages.
|
|
|
|
There is not much to be said here, except that you should use the
|
|
`GNATCOLL.Scripts.Execute_File` procedure to do so.
|
|
|
|
.. _Debugging_scripts:
|
|
|
|
Multithreading applications and scripts
|
|
---------------------------------------
|
|
|
|
Python itself is not thread-safe. So a single thread can call the python C API
|
|
at a time. To enforce this, the python interpreter provides a global
|
|
interpreter lock, which you must acquire before calling the C API, and release
|
|
when you are done. To simulate multitasking, the python interpreter will in
|
|
fact release and reacquire the lock every 100 micro-instructions (opcodes in
|
|
the python virtual machine), to give a chance to run to other tasks. So this is
|
|
preemptive multitasking.
|
|
|
|
The threads that are created in Ada that do not need access to python do not
|
|
need any special handling. However, those that need access to python must make
|
|
a special function call before they first call the python C API, so that python
|
|
can create a thread-specific data for them.
|
|
|
|
`GNATCOLL.Scripts.Python` contains a number of subprograms to interact with the
|
|
global interpreter lock of the python engine. The initialization of your
|
|
application needs to do two extra calls::
|
|
|
|
Register_Python_Scripting (...);
|
|
Initialize_Threads_Support; -- Also acquires the lock
|
|
Begin_Allow_Threads; -- Releases the lock
|
|
|
|
Whenever a task needs to execute python commands (or basically use any
|
|
subprogram from `GNATCOLL.Scripts`, it needs to do the following::
|
|
|
|
Ensure_Thread_State; -- Block all python threads
|
|
... access to python C API as usual
|
|
Begin_Allow_Threads; -- Let other python threads run
|
|
|
|
In some cases, the simplest is to get the lock at the beginning of the task,
|
|
and release it when done. This assumes the task executes fast enough. In other
|
|
cases, you will need finer grain control over the lock.
|
|
|
|
Debugging scripts
|
|
-----------------
|
|
|
|
GNATColl provides a convenient hook to debug your script. By default,
|
|
a script (python for instance) will call your Ada callback, which might
|
|
raise errors. Most of the time, the error should indeed be reported to the
|
|
user, and you can thus raise a standard exception, or call
|
|
`Set_Error_Msg`.
|
|
|
|
But if you wish to know which script was executing the command, it is
|
|
generally not doable. You can however activate a trace
|
|
(:ref:`Logging_information`) called `"PYTHON.TB"` (for "traceback"), which will
|
|
output the name of the command that is being executed, as well as the
|
|
full traceback within the python scripts. This will help you locate which
|
|
script is raising an exception.
|
|
|