Modules
C3 groups functions, types, variables and macros into namespaces called modules. When doing builds, any C3 file must start with the module
keyword, specifying the module. When compiling single files, the module is not needed and the module name is assumed to be the file name, converted to lower case, with any invalid characters replaced by underscore (_
).
A module can consist of multiple files, e.g.
file_a.c3
file_b.c3
file_c.c3
Here file_a.c3
and file_b.c3
belong to the same module, foo while file_c.c3
belongs to to bar.
Details
Some details about the C3 module system:
- Modules can be arbitrarily nested, e.g.
module foo::bar::baz;
to create the sub module baz in the sub modulebar
of the modulefoo
. - Module names must be alphanumeric lower case letters plus the underscore character:
_
. - Module names are limited to 31 characters.
- Modules may be spread across multiple files.
- A single file may have multiple module declarations.
- Each declaration of a distinct module is called a module section.
Importing Modules
Modules are imported using the import
statement. Imports always recursively import sub-modules. Any module
will automatically import all other modules with the same parent module.
foo.c3
bar.c3
In some cases there may be ambiguities, in which case the full path can be used to resolve the ambiguity:
abc.c3
def.c3
test.c3
Implicit Imports
The module system will also implicitly import:
- The
std::core
module (and sub modules). - Any other module sharing the same top module. E.g. the module
foo::abc
will implicitly also import modulesfoo
andfoo::cde
if they exist.
Visibility
All files in the same module share the same global declaration namespace. By default a symbol is visible to all other modules.
To make a symbol only visible inside the module, use the @private
attribute.
In this example, the other modules can use the init() function after importing foo, but only files in the foo module can use open(), as it is specified as private
.
It’s possible to further restrict visibility: @local
works like @private
except it’s only visible in the
local context.
Overriding Symbol Visibility Rules
By using import <module> @public
, it’s possible to access another module´s private symbols.
Many other module systems have hierarchal visibility rules, but the import @public
feature allows
visibility to be manipulated in a more ad-hoc manner without imposing hard rules.
For example, you may provide a library with two modules: “mylib::net” and “mylib::file” - which both use functions
and types from a common “mylib::internals” module. The two libraries use import mylib::internals @public
to access this module’s private functions and type. To an external user of the library, the “mylib::internals”
does not seem to exist, but inside of your library you use it as a shared dependency.
A simple example:
Note: @local
visibility cannot be overridden using a “@public” import.
Changing The Default Visibility
In a normal module, global declarations will be public by default. If some other
visibility is desired, it’s possible to declare @private
or @local
after the module name.
It will affect all declaration in the same section.
If the default visibility is @private
or @local
, using @public
sets the visibility to public:
Linker Visibility and Exports
A function or global prefixed extern
will be assumed to be linked in later.
An “extern” function may not have a body, and global variables are prohibited
from having an init expression.
The attribute @export
explicitly marks a function as being exported when
creating a (static or dynamic) library. It can also change the linker name of
the function.
Using Functions and Types From Other Modules
As a rule, functions, macros, constants, variables and types in the same module do not need any namespace prefix. For imported modules the following rules hold:
- Functions, macros, constants and variables require at least the (sub-) module name.
- Types do not require the module name unless the name is ambiguous.
- In case of ambiguity, only so many levels of module names are needed as to make the symbol unambiguous.
This means that the rule for the common case can be summarized as
Types are used without prefix; functions, variables, macros and constants are prefixed with the sub module name.
Module Sections
A single file may have multiple module declarations, even for the same module. This allows us to write for example:
Versioning and Dynamic Inclusion
NOTE: This feature may significantly change.
When including dynamic libraries, it is possible to use optional functions and globals. This is done using the
@dynamic
attribute.
An example library could have this:
dynlib.c3i
Importing the dynamic library and setting the base version to 4.5 and minimum version to 3.0, we get the following:
test.c3
In this example the code would run do_something
if available
(that is, when the dynamic library is 4.0 or higher), or
fallback to do_something_else
otherwise.
If we tried to conditionally add something not available in the compilation itself, that is a compile time error:
Versionless dynamic loading is also possible:
maybe_dynlib.c3i
test2.c3
This allows things like optionally loading dynamic libraries on the platforms where this is available.
Textual Includes
$include
It’s sometimes useful to include an entire file, doing so employs the $include
function.
Includes are only valid at the top level.
File Foo.c3
File Foo.x
The result is as if Foo.c3
contained the following:
The include may use an absolute or relative path, the relative path is always relative to the source file in which the include appears.
Note that to use it, the trust level of the compiler must be set to at least 2 with
the —trust option (i.e. use --trust=include
or --trust=full
from the command line).
$exec
An alternative to $include
is $exec
which is similar to include, but instead includes the output of an external
program as the included text.
An example:
Using $exec
requires full trust level, which is enabled with -trust=full
from the command line.
‘$exec’ will by default run from the /scripts
directory for projects, for non-project builds,
the current directory is used as well.
$exec
Scripting
$exec
allows a special scripting mode, where one or more C3 files are compiled on the fly and
run by $exec
.
Non-Recursive Imports
In specific circumstances you only wish to import a module without its submodules. This can be helpful in certain situations where otherwise unnecessary name-collisions would occur, but should not be used in the general case.
The syntax for non-recursive imports is import <module_name> @norecurse;
for example:
For example only importing “mylib” into “my_code” and not wishing to import “submod”.