This section covers the basics of modules and how to import libraries.
A library is a modular program that contains reusable processes, values, and other components frequently used in various applications.
The term "library" can refer to anything from a small memo-like script to a highly sophisticated collection of functionality. But what they all have in common is that they are **called and used as components by other programs.
So far, we've written all our programs in a single file, with all processing self-contained within it. However, when using libraries, programs often span multiple files, with different components working together.
While it is possible to merge all these files into one for execution, doing so may cause conflicts when the same variable or function names are used. To avoid this, each file is typically loaded and executed as an independent module.
That might sound a bit abstract, but in VCSSL, each file automatically corresponds to a single module. So you can simply think of a module as the content of a file. For example, a file named "A.vcssl" defines a module called "module A".
An executable module is one that is directly executed -- as we've done so far -- rather than being used by another module.
In contrast, a library module is not executed directly. Instead, it's used as a component by being loaded from another module.
To use functions provided in a library module from another module (i.e., a program written in a separate file), you need to declare an "import" statement at the beginning of the module:
The "library.path" should be specified using dot notation representing the relative path from the executable module. You do not need to include the file extension ".vcssl."
If the library module is located in the same folder as the executable module, you can just write the filename.
For example, to use "TestLib.vcssl" located in the same folder:
import TestLib;
ImportLib.vcssl
This allows you to use all the functions and constants defined in "lib.vcssl."
To import lib.vcssl located in the bbb folder inside the aaa folder, relative to the executable module:
import aaa.bbb.TestLib;
ImportAaaBbbLib.vcssl
To use functions or variables from another module, if there are no naming conflicts, you can use them just like any other function or variable -- no special syntax is required.
For example, to call a function defined in module B from executable module A:
- Executable Module A (A.vcssl) -
import B; // Import library module B
fun();
A.vcssl
- Library Module B (B.vcssl) -
void fun() {
print("B.fun was called");
}
B.vcssl
When you execute "A.vcssl," the VCSSL console will display:
Even though the fun() function is not defined in module A, calling fun() invokes the one declared in module B.
In the example above, if the function or variable name (and the argument signature, in the case of functions) exists in multiple modules, conflicts can occur.
For instance, try running the following code:
- Executable Module A (A.vcssl) -
import B; // Import library module B
void fun() {
print("A.fun was called");
}
fun(); // Note: Function name 'fun' exists in both A and B
A.vcssl
- Library Module B (B.vcssl) -
void fun() {
print("B.fun was called");
}
B.vcssl
When you execute A.vcssl, the output will be:
In other words, the "fun" function in module A is called, not the one in module B.
As you can see, when calling a function, VCSSL gives priority to functions declared in the current module.
If the function or variable is not found in the current module, then the interpreter searches the modules listed in the import statements. If there are multiple candidates, the one imported later takes precedence.
If it still cannot be found, VCSSL interpreter searches across all modules that have been loaded -- including modules imported by other modules. Again, later-loaded modules take precedence.
Here's an example:
- Executable Module A (A.vcssl) -
import B; // Import library module B
fun();
A.vcssl
- Library Module B (B.vcssl) -
import C; // Import library module C
B.vcssl
- Library Module C (C.vcssl) -
void fun() {
print("C.fun was called");
}
C.vcssl
When you execute A.vcssl, the VCSSL console will display:
Even though module A only imports module B, it can still call the fun function in module C because module B imported module C, and the interpreter is aware of C's existence.
However, this feature can make it difficult to track precedence and may reduce readability when many modules are involved.
Therefore, as a general rule, you should explicitly import the modules you intend to use directly.
In the example above, it is recommended to add "import C;" in module A as well.
The automatic module resolution based on precedence, as discussed earlier, is convenient for small-scale programs.
However, when dealing with many modules -- or when using third-party library modules -- it becomes difficult to fully track which functions (with what names and arguments) are defined in which modules. In such cases, if two functions have the same name and one accidentally calls the wrong one, it can lead to unexpected behavior.
To avoid this, you can explicitly specify the module name before the function or variable name, separated by a dot (.):
- Executable Module A (A.vcssl) -
import B; // Import library module B
import C; // Import library module C
B.fun();
C.fun();
A.vcssl
- Library Module B (B.vcssl) -
void fun() {
println("B.fun was called");
}
B.vcssl
- Library Module C (C.vcssl) -
void fun() {
println("C.fun was called");
}
C.vcssl
When executing A.vcssl, the VCSSL console will display:
By explicitly specifying the module, you can avoid accidentally calling the wrong function due to naming conflicts, while also improving the readability of your code.
Explicitly specifying the module name helps prevent the caller from accidentally accessing the wrong function or variable.
In contrast, the callee -- that is, the module being called -- can also control access using access modifiers. VCSSL provides two access modifiers: private and public.
If there are functions or variables that should not (or need not) be accessed from outside the module, you can mark them as private. This prevents them from being accessed externally -- they will be treated as if they don't exist outside the module.
On the other hand, if you declare a function or variable with public, it becomes accessible from other modules. By default, any function or variable declared without an access modifier is treated as public.
Here's an example:
// This variable cannot be accessed from outside the module
private int a;
// This variable can be freely accessed from outside
public int b;
// This function cannot be called from outside the module
private void funA() {
println("funA was called");
}
// This function can be freely called from outside
public void funB() {
println("funB was called");
}
Access.vcssl
In the example above, variable "a" and function "funA" are marked private, so they cannot be accessed from outside the module.
In contrast, variable "b" and function "funB" are marked public, so they can be freely used externally.
Note that explicitly writing "public" behaves the same as omitting the access modifier. So functionally, there's no difference.
However, using "public" explicitly improves clarity -- it makes clear that the function or variable is intentionally accessible from outside and not just an oversight.
VCSSL guarantees module uniqueness: even if a module is imported multiple times, it exists only once in memory.
For example, consider the following:
- Executable Module A (A.vcssl) -
import B; // Import library module B
import C; // Import library module C
C.x = 2;
B.fun();
A.vcssl
- Library Module B (B.vcssl) -
import C; // Import library module C here again
void fun() {
println(C.x);
}
B.vcssl
- Library Module C (C.vcssl) -
int x = 0;
C.vcssl
When you execute this program, the VCSSL console will output:
This confirms that the C module imported from A and the C module imported from B are the same instance.
When accessing files from within a library module, you must be careful about the path specification.
This is because the relative path must be specified from the perspective of the executable module, not the library module.
For example:
In all of these cases, the paths must be written **relative to the executable module**, not the library's location.
This is important to ensure consistent behavior regardless of where a library module resides in the file structure.
As you've seen throughout this guide, you can write regular statements in the global scope of a VCSSL module.
In such cases, the order in which each module's global area executes becomes important.
VCSSL follows a depth-first, import-based execution order:
Variables declared with the "const" keyword become constants that cannot be changed after initialization:
const int CONSTANT_VALUE = 255;
Const.vcssl
Global constants like these are initialized before the global code execution begins.
Therefore, by the time any global execution starts, all global constants in all modules are guaranteed to be initialized.
VCSSL also provides an "include" directive, similar to import.
Instead of importing a file as a separate module, "include" embeds the contents of another file directly into the current module, as if you had written the contents inline.
So the included file and the including file become part of the same module.
This is useful when:
VCSSL allows function overriding within the same module. If the same function (with the same arguments) is declared multiple times, the one declared later (further down in the file) takes precedence.
This allows you to include a shared function, then override it as needed in the including file.