Modules

Modules are seen as standalone packages which can either function on it’s own as an application, but more likely, be part of a larger application. A module can consist of other modules (which in turn can consist of others etc).

A module is simply a directory that holds one or more files. A module can be contain any of the following:

  • Saffire source files *.sf.
  • Saffire bytecode files *.sfc.
  • A binary module *.so.

The first two are relatively easy to create. The third one makes use of the Saffire API system, that allows you to easily create modules in C. This way, you can create extensions to processing things much faster, or just wrap existing C libraries in a Saffire class.

The saffire extensions repository shows a few of these modules, including a hello-world module that can be imported and used.

Namespacing

Namespacing is implicit within Saffire. Unlike PHP where a namespace is defined per file, in Saffire, the namespace equals the current path of the file given some rules.

First rule: the file that is originally called is the root namespace, also known as \. From that point on, the namespace (sometimes called, the context), will following the files path.

For instance, running /var/www/test.sf, will have the whole test.sf file inside the \ namespace. If that file imports a file /var/www/foo/bar.sf, then the namespace of that file will become \foo.

If the /var/www/foo/bar.sf was called initially, then the namespace of that file would be \ instead of \foo.

So namespaces / context follow the directory structure, with the difference that it uses \ slashes instead of / slashes. This is mainly because of a lexing and parsing issues that do not yet understand the difference between / being a namespace separator or a regular expression or a div operator. As soon as this is resolved, the namespace separator will also change from \ to /.

Loading modules

So how do we load modules?

Let’s assume the following file structure:

 /var/www/
          test.sf
          framework/
                    framework.sf
                    response.sf
                    request.sf

We initially call the test.sf file which looks like this:

import framework\framework;
import framework\request;

f = framework();
req = request.create();
f.handle(req);

The first line will import the framework class from the framework module (both have the same name, which is quite common).

Qualified names

There are different status for names and identifiers:

  • Unqualified class name (UQCN)

    This is a name with any namespace separators. For instance foo.

  • Qualified class name (QCN)

    This is a name with one or more namespace separators, but not starting with one. For instance framework\framework.

  • Fully qualified class name (FQCN)

    This is a QCN but also starts with a namespace seprator. For instance, \framework\framework.

Rules for converting to a FQCN

If a name is not a FQCN, the namespace/context of current file is prepended to the QCN or UQCN.

Name Namespace FQCN
bar \foo \foo\bar
bar \ \bar
bar\baz \foo \foo\bar\baz
bar\baz \foo\bar \foo\bar\bar\baz
\baz \foo\bar \baz
\qux\baz \foo\bar \qux\baz

Unlike directory structures, where you can use .. to reference to a parent directory, you cannot do this with namespaces. If you want to reference to a class in a parent directory, you must use the FQCN of the module in order to do so. Using FQCN’s inside your imports might always be a good idea to begin with.

Loading modules

So, let’s suppose inside a file, Saffire must import the class /foo/bar. How will this work?

There are a few ways saffire looks for files:

  • inside the current directory ..
  • inside the module directory ./modules.
  • inside the global directory /usr/lib/saffire/modules.

So, when we are about to import /foo/bar, we know we need to search for a class named bar inside the foo module. Thus, Saffire looks in the following places:

  1. ./foo/bar.sf
  2. ./modules/foo/bar.sf
  3. /usr/lib/saffire/modules/foo/bar.sf

Whoever, since a module can contain not only saffire source files, but also compiled (.sfc and binary modules .so, it looks for those as well. Thus, the final searches will become (in this order):

  1. ./foo/bar.sf
  2. ./foo/bar.sfc
  3. ./foo/bar.so
  4. ./modules/foo/bar.sf
  5. ./modules/foo/bar.sfc
  6. ./modules/foo/bar.so
  7. /usr/lib/saffire/modules/foo/bar.sf
  8. /usr/lib/saffire/modules/foo/bar.sfc
  9. /usr/lib/saffire/modules/foo/bar.so

Loading internal modules

Some modules are precompiled within saffire. For instance, the \saffire module, and the \saffire\io module (and many others). These modules are located in the builtin table of the current frame / context, and are found before trying to look for external modules. This means that you cannot have a \saffire\io class, as this class is also known internal and will always take presedence over external classes.

The \saffire magic

Some modules are used quite often. For instance, the io class to output something to screen, or the os class for some operating system functionality. These classes are internal modules located in the \saffire module. Thus, the io class can be imported as:

 import \saffire\io;

However, due to historic reasons mostly, but also for some brevity, it’s allowed to omit the \saffire\ part, thus:

 import io;

The reason this works, is because once a module is not found, either internally and externally, it will prepend the module QCN with \saffire. Thus, the import io, after trying to find the io.sf on multiple places, will actually try and locate \saffire\io. This is an internal class, and thus found directly.

The penalty for this is that after all the external checks, Saffire does the same checks again with \saffire prepended, which means 18 calls to figure out that an import could not be found.

Semantic versioning

Saffire packages should adhere the semantic versioning (semver) method of major.minor.build. For more information, see the semver website.

A complete package url could be something like:

	vendor/json@1.2.3

Meaning module json from vendor jaytaph on version 1.2.3.

Versioning

There is an issue with package versioning. Suppose we have three modules, A, B and C.

Our application uses module vendor/A@1.0.0 and vendor/C@1.2.0.

Now, module A itself requires too module C, however, a different version 2.0.0 for instance.

This can cause issues in resolving our dependencies, as Saffire needs to install both C on version 1.2.0, AND C on version 2.0.0.

We are thinking about resolving this issue by actually installing both packages:

./modules/vendor/A-1.0.0
./modules/vendor/C-1.2.0
./modules/vendor/C-2.0.0

Now, all we need to do, is make sure that whenever we import module C inside our code, it uses C-1.2.0. When module A imports module C, it must know that we are looking at module C-2.0.0.

This is a big todo, but relatively easy, as we can figure this out during the IMPORT of a module.