Modules in Pliant language

Introduction

Pliant compiling engine contains a single dictionary to connect identifiers to definitions. An identifier can be the result of parsing a keyword such as 'a_word', but also any sign such as '+='. A definition can be a constant, or a function, or a meta function.
Several definitions can be associated with each keyword.
Then each definition is associated with a module.

Now, when an identifier is used in a program, the program is part of a module, and the module will provide informations of others module it want to use or ignore the symbols of.

Exporting identifiers

When some new identifiers definitions are provided in a module (functions, methods, meta functions, global variables, constants), they are local to the module.
'export' keyword can be used to make them global so that modules later including this one through 'module' instruction (see bellow) will see them:

gvar Int a_variable
function fact x -> y
  arg Int x y
  ...
export a_variable fact

Please notice that fields in a type definition are also local until they are exported:

type Pair
  field x y
export Pair '. x' '. y'

Sometime a lot symbols are to be exported, so it is more convenient to temporary change the default to 'all new definitions will be automatically exported':

public
type Pair
  field x y
private
gvar Int a_local variable

An alternative way to use 'public' is to use indentation. In this case, no 'private' is needed to restore the default behavior bellow the bloc.

public
  type Pair
     field x y

Exported symbols can be restricted to a subpart of Pliant tree:

scope "/my_corp/"

means that only modules starting with '/my_corp/' can access the symbols exported by this one through using 'module' instruction bellow.
This is very useful to make some symbols locals not to a module, but to a project.

In some complex cases, modules can include each other in a ring. The standard behavior when such a ring is detected is to genenate an error. If no error should be generated because the partially compiled module already exported the symbols the other one requires, you can use:

ring_module

Importing identifiers

'module' instruction provide two things at one. First, if the specified module was not loaded yet, it will be compiled on the fly. Then, the specified module is added to the current module list of viewed module, so that all the symbols it exports can now be used in the current module:

module "/pliant/language/unsafe.pli"

'submodule' does the same as module, but also say that any module that will later use 'module' to view identifiers exported by the current one will automatically also see identifiers in the module passed as a parameter to 'submodule'.

submodule "/pliant/graphic/image/prototype.pli"

Duplicating identifiers

'alias' is an advanced instruction that enables to pick any identifier in any module, and export (duplicate) it to another module under another name:

alias doc document from "/pliant/language/basic/safe.pli" in "/my_corp/my_app/another.pli"

In this sample 'document' identifier defined in /pliant/language/basic/safe.pli is copied as 'doc' identifier in /my_corp/my_app/another.pli module.
'from' and 'in' parameters are optional. If not provided, the current module will be used.

The plugin mechanism

A key advantage of free softwares is to enable power users to modify them according to their particular needs. The huge technical problem it raises is: what to do with these changes. Either they will be contributed back to the common project, and it will grow and become bloated as a result so that new users will loose the initial possibility because mastering the huge thing got impossible for them, or they will remain out of the main tree and it can be a serious issue for the users to upgrade their changes to each new release of the software.
The Pliant plugin notion is just a mechanism to help keep out of the main tree changes that will mostly automatically upgrade to new releases.

Let's assume that a module /a/b.pli contains the following function:

function allowed user -> a
  arg Str user ; arg CBool a
  a := true
  plugin control

Assuming that the computer name is 'demo.fullpliant.org', then you can create a module with name either:
/custom/demo.fullpliant.org/a/b.pli
/custom/fullpliant.org/a/b.pli
/custom/universal/a/b.pli
and with the following content:

custom control
  if user<>"me"
    a := false

So that, in the end, the function executed by Pliant will be:

function allowed user -> a
  arg Str user ; arg CBool a
  a := true
  if user<>"me"
    a := false

Another way to provide a plugin possibility would be:

function allowed user -> a
  arg Str user ; arg CBool a
  plugin control
    a := true

and the way to use it could be:

custom control
  a := user="me"

So that the effective function executed by Pliant would be:

function allowed user -> a
  arg Str user ; arg CBool a
  a := user="me"

As a summary, if custom is provided a single identifier parameter, then the custom code will be added, whereas if custom is provided an identifier and a program bloc, the program bloc will be replaced by the custom one.

The tree possible names for the custom module just make it easy to specify customizations that apply to a single machine, to all the machines in a domain, or to any machine that contains the custom module.

You can get the name of your computer through just entering 'Configure' menu and see the 'Identity' line in the table, or using the following code:

module "/pliant/language/context.pli"
console computer_fullname eol

As a summary, the plugin/custom mechanism is just a mechanism that makes it a little bit easier to maintain some changes out of the main Pliant tree.
The explicit identifier notion makes it a little more adaptable that Unix 'diff' that has to guess the position, but it requires inserting 'plugin' instructions at many places in the source tree (what I see as the proper way to resolve most inclusion disagreements).
Anyway, in the end, it does not solve the main issue which is to check that the changes in the core application did not break the custom part.