Name folding for import is unfinished.
After a program's source code gets big enough, it may become easier to work with when partitioned across multiple files. Once we have done this, we need a way to stitch these fragments together. On this page, we introduce three such mechanisms for use when combining together logic from multiple source files.
include
The include statement incorporates another source file's contents as if were part of the current source file's module. For example, suppose we had a source file called 'useme.cone':
fn inc(a i32) i32: a+1
Another source file can now use its contents:
include useme fn timesnext(a i32): a * inc(a) // uses a function defined by 'useme.cone'
Since the names declared inside any included file(s) are treated as part of the same module's namespace, each of the source files are free to reference each other's names. A source file should not be included more than once in a module. For clarity's sake, it is often best to place all includes near the top of the main source file for the module.
File names and Paths
The include statement specifies a file path. If the file path includes characters that are not legal for a Cone name (such as periods or slashes), it should be enclosed in double quotes (and make use of the appropriate escape sequences where needed). If the file's extension is not specified, .cone is assumed.
In the simplest case, the file path specifies just the name of a source file to be found in the same folder as the source file which includes it. If a file of that name is not found there, the compiler will look for it in a folder of the same name (e.g., "useme/useme.cone"). This behavior allows source files to be organized across nested folders, where related source files are grouped together.
If desired, the file path may explicitly specify folders in front of the source file name. This can be an absolute path name, such as "/libs/math/trigfns". It can also be relative to the source file doing the including, for example "nomnom/useme" or "../lib/useme".
import
Use import to import another module that has its own namespace. For example:
import math3d import opengl
import uses the same file path guidelines as include. However, it is capable of a more robust search for the specified file. If it is not found in the current folder, it will search for it in the list of folders provided to the compiler when invoked. This allows the name of an imported module to correspond to some externally-hosted packaged library.
More than one module may import the same module. The imported module will only be loaded once. Its information is then reused by all modules that imported it.
Module-qualified names
Unlike include, import is bringing in a module with its own namespace. By default, the names of an imported module are kept separate from the names in the module doing the importing. The only name that the importing module automically obtains is the name of the module it is importing.
Since the namespaces of the modules stay separated by default, the importing module normally refers to names inside the imported module using a module-qualified name:
import opengl fn setColor(col Color): opengl::setColor(col)
opengl::SetColor references the setColor function to be found in the imported opengl module. The double-colon operator separates the module name from the name of the function defined in that module.
Module-qualified names can only access public names. Names marked private (beginning with an underscore) are not accessible to any other module.
Namespace folding
By default, an imported module does not pollute the namespace of the module importing it. This is often what you want. However, it can become tedious to read (and type) module-qualified names when they are used a lot by the importing module's code. To address this, the import command provides several ways to fold some or all of its names into the importing module. The names being folded can be for anything: type, function, global variable, macro, etc. However, names folded in may not collide with any name the importing module already uses.
To fold in just a few specific names, list them on the import command, separated by commas:
import math3d::Point3, Mat3 fn move(pos &Point3, delta Point3): pos += delta
Use as to provide an alias for a name that is already in use:
import math3d::Point3 as Point fn move(pos &Point, delta Point): pos += delta
Or one can fold in all public names from the package using *:
import math3d::* fn move(pos &Point3, delta Point3): pos += delta
extern
Sometimes we want to make use of functions and global variables that have been implemented in another language, such as C. Since the Cone compiler cannot process source files written in another language, neither include nor import will suffice. Instead, an extern block must be used to declare these entities and their types so that Cone logic may make use of them.
extern: // Functions fn printFloat(nbr f32) fn printInt(nbr i32) fn printString(str *u8) trust // Global variables mut indentation
The printString function has been marked as trustable. This indicates that we trust it to safely make use of the pointer it is given. If we had not done so, any attempt to call such a potentially unsafe extern function would require use of trust.
Although these extern names are effectively owned by some other module, this folds these names into the current module, so that they can be used by any logic in this module. Since they are owned by another module, that module is responsible for initializing its global variables and implementing its functions. Thus, no functions in an extern block should be given implementation code, nor should variables be initialized.
Associated types and methods
Not uncommonly, external functions and variables depend on custom types and constants defined by the external module. If so, these types should also be declared in the extern block. Constants can be defined using an enum. If the internal field structure of a type is unknown, an opaque struct may be used.
When custom types are defined, any externally defined function that take a value of that type as the first parameter may be declared as methods on that type. This allows the more convenient use of the dot operator when invoking functions on some data value.
extern as include file
It can be helpful to package the extern block for some C-compatible library (e.g., libcurl or sdl2) into its own file. This allows every module that needs these declarations to obtain them via a simple include.